mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-30 22:49:53 +08:00
[GUI] add custom top-bar across al platforms (#682)
This commit is contained in:
@@ -25,6 +25,7 @@ cascade order so individual launcher views do not redefine global visuals.
|
|||||||
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Base.axaml" />
|
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Base.axaml" />
|
||||||
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Surfaces.axaml" />
|
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Surfaces.axaml" />
|
||||||
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Buttons.axaml" />
|
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Buttons.axaml" />
|
||||||
|
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Chrome.axaml" />
|
||||||
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Inputs.axaml" />
|
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Inputs.axaml" />
|
||||||
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Console.axaml" />
|
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Console.axaml" />
|
||||||
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Library.axaml" />
|
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Library.axaml" />
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
WindowState="Maximized"
|
WindowState="Maximized"
|
||||||
WindowStartupLocation="CenterScreen"
|
WindowStartupLocation="CenterScreen"
|
||||||
Background="{StaticResource BgBrush}"
|
Background="{StaticResource BgBrush}"
|
||||||
ExtendClientAreaToDecorationsHint="True"
|
WindowDecorations="None"
|
||||||
WindowDecorations="Full"
|
|
||||||
ExtendClientAreaTitleBarHeightHint="44"
|
|
||||||
Icon="avares://SharpEmu.GUI/Assets/SharpEmu.ico"
|
Icon="avares://SharpEmu.GUI/Assets/SharpEmu.ico"
|
||||||
KeyDown="OnKeyDown">
|
KeyDown="OnKeyDown">
|
||||||
|
|
||||||
@@ -46,7 +44,11 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
|
|
||||||
<!-- Title bar; hidden in fullscreen (F11) along with the status bar, so
|
<!-- Title bar; hidden in fullscreen (F11) along with the status bar, so
|
||||||
the game gets the whole screen. -->
|
the game gets the whole screen. -->
|
||||||
<Grid x:Name="TitleBar" Grid.Row="0" Height="44" Background="{StaticResource ChromeBrush}">
|
<Grid x:Name="TitleBar"
|
||||||
|
Grid.Row="0"
|
||||||
|
Height="44"
|
||||||
|
ColumnDefinitions="*,Auto"
|
||||||
|
Background="{StaticResource ChromeBrush}">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="10" Margin="16,0" VerticalAlignment="Center">
|
<StackPanel Orientation="Horizontal" Spacing="10" Margin="16,0" VerticalAlignment="Center">
|
||||||
<Image Source="avares://SharpEmu.GUI/Assets/SharpEmu.ico" Width="20" Height="20"
|
<Image Source="avares://SharpEmu.GUI/Assets/SharpEmu.ico" Width="20" Height="20"
|
||||||
RenderOptions.BitmapInterpolationMode="HighQuality" />
|
RenderOptions.BitmapInterpolationMode="HighQuality" />
|
||||||
@@ -55,6 +57,29 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
<TextBlock x:Name="VersionText" Text="v0.0.1" FontSize="11" Foreground="{StaticResource MutedBrush}" />
|
<TextBlock x:Name="VersionText" Text="v0.0.1" FontSize="11" Foreground="{StaticResource MutedBrush}" />
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
<StackPanel x:Name="WindowChromeButtons"
|
||||||
|
Grid.Column="1"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right">
|
||||||
|
<Button x:Name="MinimizeButton"
|
||||||
|
Classes="windowChrome"
|
||||||
|
ToolTip.Tip="Minimize"
|
||||||
|
AutomationProperties.Name="Minimize window">
|
||||||
|
<TextBlock Text="—" FontSize="16" />
|
||||||
|
</Button>
|
||||||
|
<Button x:Name="MaximizeButton"
|
||||||
|
Classes="windowChrome"
|
||||||
|
ToolTip.Tip="Restore"
|
||||||
|
AutomationProperties.Name="Restore window">
|
||||||
|
<TextBlock x:Name="MaximizeGlyph" Text="❐" FontSize="13" />
|
||||||
|
</Button>
|
||||||
|
<Button x:Name="CloseButton"
|
||||||
|
Classes="windowChrome windowClose"
|
||||||
|
ToolTip.Tip="Close"
|
||||||
|
AutomationProperties.Name="Close window">
|
||||||
|
<TextBlock Text="×" FontSize="20" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Main content -->
|
<!-- Main content -->
|
||||||
@@ -662,5 +687,73 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
<TextBlock x:Name="StatusBarRight" Grid.Column="1" Text="" FontSize="11"
|
<TextBlock x:Name="StatusBarRight" Grid.Column="1" Text="" FontSize="11"
|
||||||
Foreground="{StaticResource FaintBrush}" VerticalAlignment="Center" Margin="16,0" />
|
Foreground="{StaticResource FaintBrush}" VerticalAlignment="Center" Margin="16,0" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Portable resize hit targets for the frameless desktop window. They
|
||||||
|
are disabled while maximized or fullscreen in code-behind. -->
|
||||||
|
<Panel x:Name="ResizeHandles"
|
||||||
|
Grid.RowSpan="3"
|
||||||
|
ZIndex="1000"
|
||||||
|
IsVisible="False">
|
||||||
|
<Border Tag="North"
|
||||||
|
Height="6"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Background="#01000000"
|
||||||
|
Cursor="TopSide"
|
||||||
|
PointerPressed="OnResizeHandlePointerPressed" />
|
||||||
|
<Border Tag="South"
|
||||||
|
Height="6"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Background="#01000000"
|
||||||
|
Cursor="BottomSide"
|
||||||
|
PointerPressed="OnResizeHandlePointerPressed" />
|
||||||
|
<Border Tag="West"
|
||||||
|
Width="6"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Background="#01000000"
|
||||||
|
Cursor="LeftSide"
|
||||||
|
PointerPressed="OnResizeHandlePointerPressed" />
|
||||||
|
<Border Tag="East"
|
||||||
|
Width="6"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Background="#01000000"
|
||||||
|
Cursor="RightSide"
|
||||||
|
PointerPressed="OnResizeHandlePointerPressed" />
|
||||||
|
<Border Tag="NorthWest"
|
||||||
|
Width="12"
|
||||||
|
Height="12"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Background="#01000000"
|
||||||
|
Cursor="TopLeftCorner"
|
||||||
|
PointerPressed="OnResizeHandlePointerPressed" />
|
||||||
|
<Border Tag="NorthEast"
|
||||||
|
Width="12"
|
||||||
|
Height="12"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Background="#01000000"
|
||||||
|
Cursor="TopRightCorner"
|
||||||
|
PointerPressed="OnResizeHandlePointerPressed" />
|
||||||
|
<Border Tag="SouthWest"
|
||||||
|
Width="12"
|
||||||
|
Height="12"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Background="#01000000"
|
||||||
|
Cursor="BottomLeftCorner"
|
||||||
|
PointerPressed="OnResizeHandlePointerPressed" />
|
||||||
|
<Border Tag="SouthEast"
|
||||||
|
Width="12"
|
||||||
|
Height="12"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Background="#01000000"
|
||||||
|
Cursor="BottomRightCorner"
|
||||||
|
PointerPressed="OnResizeHandlePointerPressed" />
|
||||||
|
</Panel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using Avalonia.Automation;
|
||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
@@ -164,6 +165,22 @@ public partial class MainWindow : Window
|
|||||||
};
|
};
|
||||||
|
|
||||||
TitleBar.PointerPressed += OnTitleBarPointerPressed;
|
TitleBar.PointerPressed += OnTitleBarPointerPressed;
|
||||||
|
TitleBar.DoubleTapped += OnTitleBarDoubleTapped;
|
||||||
|
MinimizeButton.Click += (_, _) => WindowState = WindowState.Minimized;
|
||||||
|
MaximizeButton.Click += (_, _) => ToggleMaximized();
|
||||||
|
CloseButton.Click += (_, _) => Close();
|
||||||
|
Opened += (_, _) =>
|
||||||
|
{
|
||||||
|
// Some compositors ignore the initial maximized state while a
|
||||||
|
// frameless native window is still being created.
|
||||||
|
if (WindowState == WindowState.Normal)
|
||||||
|
{
|
||||||
|
WindowState = WindowState.Maximized;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateWindowChromeState();
|
||||||
|
};
|
||||||
|
UpdateWindowChromeState();
|
||||||
GameList.SelectionChanged += (_, _) => UpdateSelectedGame();
|
GameList.SelectionChanged += (_, _) => UpdateSelectedGame();
|
||||||
GameList.DoubleTapped += (_, _) => LaunchSelected();
|
GameList.DoubleTapped += (_, _) => LaunchSelected();
|
||||||
SearchBox.TextChanged += (_, _) => RefreshVisibleGames();
|
SearchBox.TextChanged += (_, _) => RefreshVisibleGames();
|
||||||
@@ -813,12 +830,81 @@ public partial class MainWindow : Window
|
|||||||
|
|
||||||
private void OnTitleBarPointerPressed(object? sender, PointerPressedEventArgs e)
|
private void OnTitleBarPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
if (e.Source is Visual source &&
|
||||||
|
source.FindAncestorOfType<Button>(includeSelf: true) is null &&
|
||||||
|
e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||||
{
|
{
|
||||||
BeginMoveDrag(e);
|
BeginMoveDrag(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnTitleBarDoubleTapped(object? sender, TappedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Source is Visual source &&
|
||||||
|
source.FindAncestorOfType<Button>(includeSelf: true) is null)
|
||||||
|
{
|
||||||
|
ToggleMaximized();
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleMaximized()
|
||||||
|
{
|
||||||
|
WindowState = WindowState == WindowState.Maximized
|
||||||
|
? WindowState.Normal
|
||||||
|
: WindowState.Maximized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateMaximizeButton()
|
||||||
|
{
|
||||||
|
if (MaximizeGlyph is not { } glyph || MaximizeButton is not { } button)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var isMaximized = WindowState == WindowState.Maximized;
|
||||||
|
glyph.Text = isMaximized ? "❐" : "□";
|
||||||
|
ToolTip.SetTip(button, isMaximized ? "Restore" : "Maximize");
|
||||||
|
AutomationProperties.SetName(
|
||||||
|
button,
|
||||||
|
isMaximized ? "Restore window" : "Maximize window");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateWindowChromeState()
|
||||||
|
{
|
||||||
|
UpdateMaximizeButton();
|
||||||
|
var isFullscreen = WindowState == WindowState.FullScreen;
|
||||||
|
|
||||||
|
if (TitleBar is { } titleBar)
|
||||||
|
{
|
||||||
|
titleBar.IsVisible = !isFullscreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StatusBar is { } statusBar)
|
||||||
|
{
|
||||||
|
statusBar.IsVisible = !isFullscreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ResizeHandles is { } handles)
|
||||||
|
{
|
||||||
|
handles.IsVisible = CanResize && WindowState == WindowState.Normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnResizeHandlePointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
if (WindowState != WindowState.Normal ||
|
||||||
|
!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed ||
|
||||||
|
sender is not Control { Tag: string edgeName } ||
|
||||||
|
!Enum.TryParse<WindowEdge>(edgeName, out var edge))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BeginResizeDrag(edge, e);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Settings ----
|
// ---- Settings ----
|
||||||
|
|
||||||
private void ApplySettingsToControls()
|
private void ApplySettingsToControls()
|
||||||
@@ -1831,6 +1917,8 @@ public partial class MainWindow : Window
|
|||||||
base.OnPropertyChanged(change);
|
base.OnPropertyChanged(change);
|
||||||
if (change.Property == WindowStateProperty)
|
if (change.Property == WindowStateProperty)
|
||||||
{
|
{
|
||||||
|
UpdateWindowChromeState();
|
||||||
|
|
||||||
// The XAML WindowState="Maximized" assignment raises this change
|
// The XAML WindowState="Maximized" assignment raises this change
|
||||||
// during InitializeComponent, before named controls are wired up.
|
// during InitializeComponent, before named controls are wired up.
|
||||||
if (WindowState == WindowState.Minimized)
|
if (WindowState == WindowState.Minimized)
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<!--
|
||||||
|
Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
Window chrome button sizing and interaction states.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Style Selector="Button.windowChrome">
|
||||||
|
<Setter Property="Width" Value="46" />
|
||||||
|
<Setter Property="Height" Value="44" />
|
||||||
|
<Setter Property="MinWidth" Value="0" />
|
||||||
|
<Setter Property="MinHeight" Value="0" />
|
||||||
|
<Setter Property="Padding" Value="0" />
|
||||||
|
<Setter Property="CornerRadius" Value="0" />
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.windowChrome:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource ElevatedBrush}" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.windowClose:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource DangerBrush}" />
|
||||||
|
<Setter Property="Foreground" Value="White" />
|
||||||
|
</Style>
|
||||||
|
</Styles>
|
||||||
Reference in New Issue
Block a user