mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-30 22:49:53 +08:00
[GUI] Update window chrome controls (#700)
This commit is contained in:
@@ -18,6 +18,7 @@ public partial class App : Application
|
|||||||
{
|
{
|
||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
|
desktop.ShutdownMode = Avalonia.Controls.ShutdownMode.OnMainWindowClose;
|
||||||
desktop.MainWindow = new MainWindow();
|
desktop.MainWindow = new MainWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,19 +73,23 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
Classes="windowChrome"
|
Classes="windowChrome"
|
||||||
ToolTip.Tip="Minimize"
|
ToolTip.Tip="Minimize"
|
||||||
AutomationProperties.Name="Minimize window">
|
AutomationProperties.Name="Minimize window">
|
||||||
<TextBlock Text="—" FontSize="16" />
|
<TextBlock Classes="materialSymbol compact windowChromeGlyph"
|
||||||
|
Text="minimize" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button x:Name="MaximizeButton"
|
<Button x:Name="MaximizeButton"
|
||||||
Classes="windowChrome"
|
Classes="windowChrome"
|
||||||
ToolTip.Tip="Restore"
|
ToolTip.Tip="Restore"
|
||||||
AutomationProperties.Name="Restore window">
|
AutomationProperties.Name="Restore window">
|
||||||
<TextBlock x:Name="MaximizeGlyph" Text="❐" FontSize="13" />
|
<TextBlock x:Name="MaximizeGlyph"
|
||||||
|
Classes="materialSymbol compact windowChromeGlyph"
|
||||||
|
Text="filter_none" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button x:Name="CloseButton"
|
<Button x:Name="CloseButton"
|
||||||
Classes="windowChrome windowClose"
|
Classes="windowChrome windowClose"
|
||||||
ToolTip.Tip="Close"
|
ToolTip.Tip="Close"
|
||||||
AutomationProperties.Name="Close window">
|
AutomationProperties.Name="Close window">
|
||||||
<TextBlock Text="×" FontSize="20" />
|
<TextBlock Classes="materialSymbol windowChromeGlyph windowCloseGlyph"
|
||||||
|
Text="close" />
|
||||||
</Button>
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -165,8 +165,6 @@ public partial class MainWindow : Window
|
|||||||
ConsoleList.ItemsSource = _consoleLines;
|
ConsoleList.ItemsSource = _consoleLines;
|
||||||
_consoleMirror = GuiConsoleMirror.Install((line, isError) =>
|
_consoleMirror = GuiConsoleMirror.Install((line, isError) =>
|
||||||
_pendingLines.Enqueue((line, isError)));
|
_pendingLines.Enqueue((line, isError)));
|
||||||
Closed += (_, _) => _emulator?.Stop();
|
|
||||||
|
|
||||||
_consoleFlushTimer = new DispatcherTimer
|
_consoleFlushTimer = new DispatcherTimer
|
||||||
{
|
{
|
||||||
Interval = TimeSpan.FromMilliseconds(80),
|
Interval = TimeSpan.FromMilliseconds(80),
|
||||||
@@ -297,7 +295,8 @@ public partial class MainWindow : Window
|
|||||||
CtxRemove.Click += (_, _) => RemoveSelectedFromLibrary();
|
CtxRemove.Click += (_, _) => RemoveSelectedFromLibrary();
|
||||||
|
|
||||||
Opened += async (_, _) => await OnOpenedAsync();
|
Opened += async (_, _) => await OnOpenedAsync();
|
||||||
Closing += (_, _) => OnWindowClosing();
|
Closing += (_, _) => BeginWindowClosing();
|
||||||
|
Closed += (_, _) => CompleteWindowClosing();
|
||||||
|
|
||||||
SdlLauncherGamepad.EnsureStarted();
|
SdlLauncherGamepad.EnsureStarted();
|
||||||
_gamepadTimer = new DispatcherTimer
|
_gamepadTimer = new DispatcherTimer
|
||||||
@@ -816,22 +815,44 @@ public partial class MainWindow : Window
|
|||||||
// still needs a preview hook for its own shortcuts.
|
// still needs a preview hook for its own shortcuts.
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnWindowClosing()
|
private void BeginWindowClosing()
|
||||||
{
|
{
|
||||||
|
if (_isClosing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_isClosing = true;
|
_isClosing = true;
|
||||||
Interlocked.Increment(ref _libraryScanGeneration);
|
Interlocked.Increment(ref _libraryScanGeneration);
|
||||||
Interlocked.Increment(ref _detailLoadGeneration);
|
Interlocked.Increment(ref _detailLoadGeneration);
|
||||||
_libraryWatcher.Dispose();
|
|
||||||
_settings.Save();
|
|
||||||
_consoleFlushTimer.Stop();
|
_consoleFlushTimer.Stop();
|
||||||
_gamepadTimer.Stop();
|
_gamepadTimer.Stop();
|
||||||
SdlLauncherGamepad.Shutdown();
|
}
|
||||||
_sndPreview.Stop();
|
|
||||||
_discord?.Dispose();
|
private void CompleteWindowClosing()
|
||||||
_consoleWindow?.Close();
|
{
|
||||||
_emulator?.Dispose();
|
RunShutdownStep("library watcher", _libraryWatcher.Dispose);
|
||||||
_consoleMirror?.Dispose();
|
RunShutdownStep("settings", _settings.Save);
|
||||||
DropFileLog();
|
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)
|
private void OnTitleBarPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
@@ -868,14 +889,17 @@ public partial class MainWindow : Window
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var isMaximized = WindowState == WindowState.Maximized;
|
var state = GetMaximizeButtonState(WindowState);
|
||||||
glyph.Text = isMaximized ? "❐" : "□";
|
glyph.Text = state.Glyph;
|
||||||
ToolTip.SetTip(button, isMaximized ? "Restore" : "Maximize");
|
ToolTip.SetTip(button, state.ToolTip);
|
||||||
AutomationProperties.SetName(
|
AutomationProperties.SetName(button, state.AutomationName);
|
||||||
button,
|
|
||||||
isMaximized ? "Restore window" : "Maximize window");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static WindowMaximizeButtonState GetMaximizeButtonState(WindowState windowState) =>
|
||||||
|
windowState == WindowState.Maximized
|
||||||
|
? new("filter_none", "Restore", "Restore window")
|
||||||
|
: new("crop_square", "Maximize", "Maximize window");
|
||||||
|
|
||||||
private void UpdateWindowChromeState()
|
private void UpdateWindowChromeState()
|
||||||
{
|
{
|
||||||
UpdateMaximizeButton();
|
UpdateMaximizeButton();
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ Window chrome button sizing and interaction states.
|
|||||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
</Style>
|
</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">
|
<Style Selector="Button.windowChrome:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
<Setter Property="Background" Value="{StaticResource ElevatedBrush}" />
|
<Setter Property="Background" Value="{StaticResource ElevatedBrush}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
<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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user