[GUI] Update window chrome controls (#700)

This commit is contained in:
Daniel Freak
2026-07-30 17:03:57 +03:00
committed by GitHub
parent 5f1bd5a77c
commit 753ddf93be
6 changed files with 93 additions and 22 deletions
+1
View File
@@ -18,6 +18,7 @@ public partial class App : Application
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.ShutdownMode = Avalonia.Controls.ShutdownMode.OnMainWindowClose;
desktop.MainWindow = new MainWindow();
}
+7 -3
View File
@@ -73,19 +73,23 @@ SPDX-License-Identifier: GPL-2.0-or-later
Classes="windowChrome"
ToolTip.Tip="Minimize"
AutomationProperties.Name="Minimize window">
<TextBlock Text="—" FontSize="16" />
<TextBlock Classes="materialSymbol compact windowChromeGlyph"
Text="minimize" />
</Button>
<Button x:Name="MaximizeButton"
Classes="windowChrome"
ToolTip.Tip="Restore"
AutomationProperties.Name="Restore window">
<TextBlock x:Name="MaximizeGlyph" Text="❐" FontSize="13" />
<TextBlock x:Name="MaximizeGlyph"
Classes="materialSymbol compact windowChromeGlyph"
Text="filter_none" />
</Button>
<Button x:Name="CloseButton"
Classes="windowChrome windowClose"
ToolTip.Tip="Close"
AutomationProperties.Name="Close window">
<TextBlock Text="×" FontSize="20" />
<TextBlock Classes="materialSymbol windowChromeGlyph windowCloseGlyph"
Text="close" />
</Button>
</StackPanel>
</Grid>
+43 -19
View File
@@ -165,8 +165,6 @@ public partial class MainWindow : Window
ConsoleList.ItemsSource = _consoleLines;
_consoleMirror = GuiConsoleMirror.Install((line, isError) =>
_pendingLines.Enqueue((line, isError)));
Closed += (_, _) => _emulator?.Stop();
_consoleFlushTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(80),
@@ -297,7 +295,8 @@ public partial class MainWindow : Window
CtxRemove.Click += (_, _) => RemoveSelectedFromLibrary();
Opened += async (_, _) => await OnOpenedAsync();
Closing += (_, _) => OnWindowClosing();
Closing += (_, _) => BeginWindowClosing();
Closed += (_, _) => CompleteWindowClosing();
SdlLauncherGamepad.EnsureStarted();
_gamepadTimer = new DispatcherTimer
@@ -816,22 +815,44 @@ public partial class MainWindow : Window
// still needs a preview hook for its own shortcuts.
}
private void OnWindowClosing()
private void BeginWindowClosing()
{
if (_isClosing)
{
return;
}
_isClosing = true;
Interlocked.Increment(ref _libraryScanGeneration);
Interlocked.Increment(ref _detailLoadGeneration);
_libraryWatcher.Dispose();
_settings.Save();
_consoleFlushTimer.Stop();
_gamepadTimer.Stop();
SdlLauncherGamepad.Shutdown();
_sndPreview.Stop();
_discord?.Dispose();
_consoleWindow?.Close();
_emulator?.Dispose();
_consoleMirror?.Dispose();
DropFileLog();
}
private void CompleteWindowClosing()
{
RunShutdownStep("library watcher", _libraryWatcher.Dispose);
RunShutdownStep("settings", _settings.Save);
RunShutdownStep("SDL gamepad", SdlLauncherGamepad.Shutdown);
RunShutdownStep("title music", _sndPreview.Stop);
RunShutdownStep("Discord Rich Presence", () => _discord?.Dispose());
RunShutdownStep("console window", () => _consoleWindow?.Close());
RunShutdownStep("emulator process", () => _emulator?.Dispose());
RunShutdownStep("console mirror", () => _consoleMirror?.Dispose());
RunShutdownStep("file log", DropFileLog);
}
private static void RunShutdownStep(string component, Action action)
{
try
{
action();
}
catch (Exception exception)
{
Console.Error.WriteLine(
$"[GUI][WARN] Failed to clean up {component}: {exception}");
}
}
private void OnTitleBarPointerPressed(object? sender, PointerPressedEventArgs e)
@@ -868,14 +889,17 @@ public partial class MainWindow : Window
return;
}
var isMaximized = WindowState == WindowState.Maximized;
glyph.Text = isMaximized ? "❐" : "□";
ToolTip.SetTip(button, isMaximized ? "Restore" : "Maximize");
AutomationProperties.SetName(
button,
isMaximized ? "Restore window" : "Maximize window");
var state = GetMaximizeButtonState(WindowState);
glyph.Text = state.Glyph;
ToolTip.SetTip(button, state.ToolTip);
AutomationProperties.SetName(button, state.AutomationName);
}
internal static WindowMaximizeButtonState GetMaximizeButtonState(WindowState windowState) =>
windowState == WindowState.Maximized
? new("filter_none", "Restore", "Restore window")
: new("crop_square", "Maximize", "Maximize window");
private void UpdateWindowChromeState()
{
UpdateMaximizeButton();
@@ -19,6 +19,12 @@ Window chrome button sizing and interaction states.
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
</Style>
<Style Selector="TextBlock.windowChromeGlyph">
<Setter Property="IsHitTestVisible" Value="False" />
</Style>
<Style Selector="TextBlock.windowCloseGlyph">
<Setter Property="Margin" Value="0,-1,0,1" />
</Style>
<Style Selector="Button.windowChrome:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource ElevatedBrush}" />
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
@@ -0,0 +1,9 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.GUI;
internal readonly record struct WindowMaximizeButtonState(
string Glyph,
string ToolTip,
string AutomationName);
@@ -0,0 +1,27 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using Avalonia.Controls;
using SharpEmu.GUI;
using Xunit;
namespace SharpEmu.Libs.Tests.GUI;
public sealed class WindowChromeTests
{
[Theory]
[InlineData(WindowState.Normal, "crop_square", "Maximize", "Maximize window")]
[InlineData(WindowState.Maximized, "filter_none", "Restore", "Restore window")]
public void GetMaximizeButtonState_ReturnsConsistentVisualAndAccessibleState(
WindowState windowState,
string expectedGlyph,
string expectedToolTip,
string expectedAutomationName)
{
var state = MainWindow.GetMaximizeButtonState(windowState);
Assert.Equal(expectedGlyph, state.Glyph);
Assert.Equal(expectedToolTip, state.ToolTip);
Assert.Equal(expectedAutomationName, state.AutomationName);
}
}