Public Access
99 lines
3.5 KiB
C#
99 lines
3.5 KiB
C#
using Avalonia.Controls;
|
|
using Avalonia.Input;
|
|
using Avalonia.Interactivity;
|
|
using DodoSSH.Client.App.Platform;
|
|
|
|
namespace DodoSSH.Client.App.Views;
|
|
|
|
/// <summary>
|
|
/// The window's own titlebar, drawn because the design draws one.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Everything here is a window operation, which is why it is code-behind and not a command on a view model:
|
|
/// dragging, maximising and closing are properties of the <see cref="Window"/> this control happens to be
|
|
/// inside, and a view model that knew about them would be a view model that could not be tested without one.
|
|
/// </para>
|
|
/// <para>
|
|
/// The window is found by walking up rather than injected, so this control drops into any window — including
|
|
/// the bare one the layout harness hosts it in, where <see cref="Host"/> is simply a different window and
|
|
/// every handler still has something to act on.
|
|
/// </para>
|
|
/// </remarks>
|
|
internal sealed partial class TitleBar : UserControl
|
|
{
|
|
public TitleBar()
|
|
{
|
|
InitializeComponent();
|
|
|
|
// ◆ A nightly says which one it is, in the one place a person is always looking at.
|
|
//
|
|
// The two channels install side by side on purpose, so both windows are on screen at once, both
|
|
// are called DodoSSH, and both ask for a passphrase the same way. Everything else that
|
|
// distinguishes them — the Start menu entry, the install directory, the version on the
|
|
// preferences screen — is somewhere nobody is looking while typing into one of them.
|
|
//
|
|
// Set here rather than in the markup so the release build is exactly what the XAML says, and so
|
|
// the layout harness, which hosts this control directly, measures the shipping string.
|
|
if (DesktopChannel.IsNightly)
|
|
{
|
|
ProductName.Text = "DodoSSH Nightly";
|
|
}
|
|
}
|
|
|
|
private readonly CaptionPresses presses = new();
|
|
|
|
private Window? Host => TopLevel.GetTopLevel(this) as Window;
|
|
|
|
/// <remarks>
|
|
/// Left button only, and only on a press that has not already been handled by something inside the bar —
|
|
/// otherwise dragging would start from the close button and swallow the click that was meant to close
|
|
/// the window.
|
|
/// </remarks>
|
|
private void OnDrag(object? sender, PointerPressedEventArgs e)
|
|
{
|
|
if (e.Handled || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Decided on the press, before the drag would take it: see CaptionPresses.
|
|
if (presses.IsSecond(e, this))
|
|
{
|
|
OnToggleMaximised(sender, e);
|
|
e.Handled = true;
|
|
|
|
return;
|
|
}
|
|
|
|
Host?.BeginMoveDrag(e);
|
|
}
|
|
|
|
private void OnMinimise(object? sender, RoutedEventArgs e)
|
|
{
|
|
if (Host is { } window)
|
|
{
|
|
window.WindowState = WindowState.Minimized;
|
|
}
|
|
}
|
|
|
|
/// <remarks>
|
|
/// Both the button and a double-click on the bar arrive here, which is the convention Windows sets and
|
|
/// the one people reach for without thinking about it. The double-click comes from <see cref="OnDrag"/>
|
|
/// rather than <c>DoubleTapped</c>; see <see cref="CaptionPresses"/>.
|
|
/// </remarks>
|
|
private void OnToggleMaximised(object? sender, RoutedEventArgs e)
|
|
{
|
|
if (Host is not { } window)
|
|
{
|
|
return;
|
|
}
|
|
|
|
window.WindowState = window.WindowState == WindowState.Maximized
|
|
? WindowState.Normal
|
|
: WindowState.Maximized;
|
|
}
|
|
|
|
private void OnClose(object? sender, RoutedEventArgs e) => Host?.Close();
|
|
}
|