mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-01 23:49:44 +08:00
[GUI] Update library page layout (#690)
* [GUI] Add horizontal game tiles * [GUI] Update launching elements layout * [GUI] Change cards format to squares
This commit is contained in:
+128
-133
@@ -6,7 +6,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:SharpEmu.GUI"
|
||||
xmlns:primitives="clr-namespace:Avalonia.Controls.Primitives;assembly=Avalonia.Controls"
|
||||
x:Class="SharpEmu.GUI.MainWindow"
|
||||
Title="SharpEmu"
|
||||
Width="1280" Height="820"
|
||||
@@ -91,7 +90,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
</Grid>
|
||||
|
||||
<!-- Main content -->
|
||||
<Grid x:Name="MainContent" Grid.Row="1" Margin="32,24,32,20" RowDefinitions="Auto,*,Auto,Auto">
|
||||
<Grid x:Name="MainContent" Grid.Row="1" Margin="32,24,32,20" RowDefinitions="Auto,*,Auto">
|
||||
|
||||
<!-- Library / Options page switcher, with the library toolbar sharing
|
||||
the same row on the right. Plain buttons (not TabItem) so there is
|
||||
@@ -119,10 +118,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
PlaceholderText="{Binding [Library.SearchWatermark], Source={x:Static local:Localization.Instance}}"
|
||||
Width="240"
|
||||
VerticalAlignment="Center" />
|
||||
<Button x:Name="AddFolderButton"
|
||||
Classes="ghost"
|
||||
Content="{Binding [Library.AddFolder], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
<Button x:Name="OpenFileButton"
|
||||
Classes="ghost"
|
||||
Content="{Binding [Library.OpenFile], Source={x:Static local:Localization.Instance}}"
|
||||
@@ -132,12 +127,12 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
<Panel Grid.Row="1" x:Name="PagesHost">
|
||||
|
||||
<!-- Library page. The tile row gets extra top margin so it sits
|
||||
closer to eye level (PS5 home-screen style) instead of hugging
|
||||
the Library/Options switcher above it. -->
|
||||
<Panel x:Name="LibraryPage" Margin="0,64,0,0">
|
||||
<!-- Library page. Covers use a horizontal virtualized rail so the
|
||||
number of realized images stays bounded for large libraries. -->
|
||||
<Grid x:Name="LibraryPage" Margin="0,46,0,0" RowDefinitions="188,*">
|
||||
<ListBox x:Name="GameList" Classes="tileGrid" Background="Transparent"
|
||||
SelectionMode="Single" Padding="0">
|
||||
Grid.Row="0"
|
||||
SelectionMode="Single" Padding="4,0,28,0">
|
||||
<ListBox.ContextMenu>
|
||||
<ContextMenu x:Name="GameContextMenu" Placement="Pointer">
|
||||
<MenuItem x:Name="CtxLaunch"
|
||||
@@ -190,49 +185,135 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
</ListBox.ContextMenu>
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel />
|
||||
<VirtualizingStackPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.ItemTemplate>
|
||||
<ListBox.DataTemplates>
|
||||
<DataTemplate x:DataType="local:GameEntry" x:CompileBindings="True">
|
||||
<StackPanel Width="128" Height="172" Spacing="7">
|
||||
<Border Classes="coverShadow" Width="128" Height="128">
|
||||
<Border Classes="coverClip">
|
||||
<Panel>
|
||||
<Border Background="{Binding PlaceholderBrush}" IsVisible="{Binding !HasCover}">
|
||||
<TextBlock Text="{Binding Initials}" FontSize="36" FontWeight="Bold"
|
||||
Foreground="#E8ECF4" Opacity="0.85"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<Image Source="{Binding Cover}" Stretch="UniformToFill" IsVisible="{Binding HasCover}" />
|
||||
</Panel>
|
||||
</Border>
|
||||
<Border Classes="coverShadow libraryGameCard"
|
||||
Width="148"
|
||||
Height="148"
|
||||
ToolTip.Tip="{Binding Name}"
|
||||
AutomationProperties.Name="{Binding Name}">
|
||||
<Border Classes="coverClip">
|
||||
<Panel>
|
||||
<Border Background="{Binding PlaceholderBrush}" IsVisible="{Binding !HasCover}">
|
||||
<TextBlock Text="{Binding Initials}" FontSize="38" FontWeight="Light"
|
||||
Foreground="#C4E8ECF4"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<Image Source="{Binding Cover}"
|
||||
Stretch="UniformToFill"
|
||||
RenderOptions.BitmapInterpolationMode="LowQuality"
|
||||
IsVisible="{Binding HasCover}" />
|
||||
</Panel>
|
||||
</Border>
|
||||
<TextBlock Text="{Binding Name}" FontSize="13" FontWeight="SemiBold"
|
||||
TextWrapping="Wrap" MaxLines="2" TextTrimming="CharacterEllipsis"
|
||||
TextAlignment="Center" HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:AddFolderTile">
|
||||
<Border Classes="addFolderTile"
|
||||
Width="148"
|
||||
Height="148"
|
||||
ToolTip.Tip="{Binding [Library.AddFolder],
|
||||
Source={x:Static local:Localization.Instance},
|
||||
x:CompileBindings=False}"
|
||||
AutomationProperties.Name="{Binding [Library.AddFolder],
|
||||
Source={x:Static local:Localization.Instance},
|
||||
x:CompileBindings=False}">
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="12">
|
||||
<Border Classes="addFolderTileIcon">
|
||||
<TextBlock Classes="addFolderGlyph addFolderIconGlyph"
|
||||
Text="+"
|
||||
FontSize="34"
|
||||
FontWeight="Light"
|
||||
LineHeight="34"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<TextBlock Classes="addFolderGlyph"
|
||||
Text="{Binding [Library.AddFolder],
|
||||
Source={x:Static local:Localization.Instance},
|
||||
x:CompileBindings=False}"
|
||||
FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.DataTemplates>
|
||||
</ListBox>
|
||||
|
||||
<!-- Selected-game identity and actions share one detail surface so
|
||||
the selected title and metadata are never rendered twice. -->
|
||||
<StackPanel x:Name="LibrarySelectedDetails"
|
||||
Grid.Row="1"
|
||||
x:DataType="local:GameEntry"
|
||||
x:CompileBindings="True"
|
||||
IsVisible="False"
|
||||
MaxWidth="980"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Margin="4,8,0,0"
|
||||
Spacing="18">
|
||||
<TextBlock Text="{Binding Name}"
|
||||
FontSize="42"
|
||||
FontWeight="SemiBold"
|
||||
TextWrapping="Wrap"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
MaxLines="2" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Border Classes="pill" IsVisible="{Binding HasTitleId}">
|
||||
<TextBlock Text="{Binding TitleId}" FontSize="11" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource MutedBrush}" />
|
||||
</Border>
|
||||
<Border Classes="pill" IsVisible="{Binding HasVersion}">
|
||||
<TextBlock Text="{Binding VersionText}" FontSize="11" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource MutedBrush}" />
|
||||
</Border>
|
||||
<Border Classes="pill">
|
||||
<TextBlock Text="{Binding SizeText}" FontSize="11" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource MutedBrush}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<Grid Width="360"
|
||||
ColumnDefinitions="*,*"
|
||||
ColumnSpacing="8"
|
||||
HorizontalAlignment="Left">
|
||||
<Button x:Name="LaunchButton"
|
||||
Grid.Column="0"
|
||||
Classes="accent"
|
||||
Content="Launch"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsEnabled="False" />
|
||||
<ToggleButton x:Name="ConsoleToggle"
|
||||
Grid.Column="1"
|
||||
Classes="ghost"
|
||||
Padding="22,10"
|
||||
HorizontalAlignment="Stretch"
|
||||
Content="{Binding [Launch.Console],
|
||||
Source={x:Static local:Localization.Instance},
|
||||
x:CompileBindings=False}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Empty state -->
|
||||
<StackPanel x:Name="EmptyState" Spacing="10" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
<StackPanel x:Name="EmptyState" Grid.RowSpan="2"
|
||||
Spacing="10" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
IsVisible="False">
|
||||
<TextBlock Text="🎮" FontSize="44" HorizontalAlignment="Center" Opacity="0.7" />
|
||||
<TextBlock x:Name="EmptyStateTitle" Text="Your library is empty" FontSize="18" FontWeight="SemiBold"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock x:Name="EmptyStateHint" Text="Add a folder containing your games to get started."
|
||||
FontSize="13" Foreground="{StaticResource MutedBrush}" HorizontalAlignment="Center" />
|
||||
<Button x:Name="EmptyAddFolderButton" Classes="accent"
|
||||
Content="{Binding [Library.Empty.AddFolder], Source={x:Static local:Localization.Instance}}"
|
||||
HorizontalAlignment="Center" Margin="0,8,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Loading state: covers the grid while a scan (initial load,
|
||||
rescan, or a just-added folder) is in flight, so the screen
|
||||
never looks blank mid-scan. -->
|
||||
<StackPanel x:Name="LoadingState" Spacing="14" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
<StackPanel x:Name="LoadingState" Grid.RowSpan="2"
|
||||
Spacing="14" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
IsVisible="False">
|
||||
<ProgressBar IsIndeterminate="True" Width="180" Height="3" />
|
||||
<TextBlock x:Name="LoadingStateText"
|
||||
@@ -240,11 +321,11 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
FontSize="13"
|
||||
Foreground="{StaticResource MutedBrush}" HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</Grid>
|
||||
|
||||
<!-- Options page: sub-tabs so future categories (Graphics, …) slot
|
||||
in next to General. Card-grouped sections match the visual
|
||||
language used by the console panel and launch bar below. -->
|
||||
language used by the console panel below. -->
|
||||
<Grid x:Name="OptionsPage" IsVisible="False">
|
||||
<TabControl>
|
||||
<TabItem x:Name="GeneralTabItem"
|
||||
@@ -670,7 +751,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<Border Grid.Row="2" x:Name="ConsolePanel" Classes="card" Padding="0" Height="240"
|
||||
Margin="0,12,0,0" IsVisible="False">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Grid Grid.Row="0" ColumnDefinitions="*,Auto,Auto,Auto,Auto,Auto" Margin="16,12,16,8">
|
||||
<Grid Grid.Row="0" ColumnDefinitions="*,Auto,Auto,Auto,Auto,Auto,Auto" Margin="16,12,16,8">
|
||||
<TextBlock x:Name="ConsoleSectionTitle" Classes="sectionTitle"
|
||||
Text="{Binding [Console.Title], Source={x:Static local:Localization.Instance}}"
|
||||
VerticalAlignment="Center" />
|
||||
@@ -692,7 +773,15 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<Button Grid.Column="5" x:Name="ClearLogButton" Classes="ghost"
|
||||
Content="{Binding [Console.Clear], Source={x:Static local:Localization.Instance}}"
|
||||
FontSize="12"
|
||||
Padding="10,4" />
|
||||
Padding="10,4" Margin="0,0,8,0" />
|
||||
<Button Grid.Column="6"
|
||||
x:Name="CloseConsoleButton"
|
||||
Classes="ghost"
|
||||
Content="×"
|
||||
AutomationProperties.Name="{Binding [Launch.Console],
|
||||
Source={x:Static local:Localization.Instance}}"
|
||||
FontSize="16"
|
||||
Padding="10,2" />
|
||||
</Grid>
|
||||
<ListBox Grid.Row="1" x:Name="ConsoleList" Classes="console" BorderThickness="0,1,0,0"
|
||||
BorderBrush="{StaticResource CardBorderBrush}" CornerRadius="0,0,12,12">
|
||||
@@ -705,102 +794,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Launch bar; capped so it does not sprawl on a maximized window. -->
|
||||
<Border Grid.Row="3" x:Name="LaunchBar" Classes="card" Margin="0,12,0,0" Padding="14" MaxWidth="1280">
|
||||
<StackPanel Spacing="14">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
|
||||
<!-- Selected game cover thumbnail -->
|
||||
<Border Grid.Column="0" Classes="coverClip" Width="56" Height="56" CornerRadius="8"
|
||||
VerticalAlignment="Center">
|
||||
<Panel x:Name="SelectedCoverPanel"
|
||||
x:DataType="local:GameEntry"
|
||||
x:CompileBindings="True">
|
||||
<Border Background="{Binding PlaceholderBrush, FallbackValue={x:Null}}"
|
||||
IsVisible="{Binding !HasCover, FallbackValue=False}">
|
||||
<TextBlock Text="{Binding Initials}" FontSize="20" FontWeight="Bold"
|
||||
Foreground="#E8ECF4" Opacity="0.85"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<Image Source="{Binding Cover}" Stretch="UniformToFill"
|
||||
IsVisible="{Binding HasCover, FallbackValue=False}" />
|
||||
</Panel>
|
||||
</Border>
|
||||
|
||||
<StackPanel Grid.Column="1" Spacing="4" VerticalAlignment="Center" Margin="14,0,14,0">
|
||||
<Grid ColumnDefinitions="Auto,Auto,*">
|
||||
<TextBlock Grid.Column="0" x:Name="SelectedGameTitle" Text="No game selected" FontSize="16"
|
||||
FontWeight="Bold" TextTrimming="CharacterEllipsis" VerticalAlignment="Center"
|
||||
MaxWidth="340" Margin="0,0,10,0" />
|
||||
|
||||
<!-- Title id / version / size badges, right next to the
|
||||
title. The title's own MaxWidth (not a "*" column) is
|
||||
what keeps them from drifting to the far right. -->
|
||||
<StackPanel Grid.Column="1" x:Name="SelectedBadgesRow"
|
||||
x:DataType="local:GameEntry"
|
||||
x:CompileBindings="True"
|
||||
Orientation="Horizontal" Spacing="6"
|
||||
IsVisible="False" VerticalAlignment="Center">
|
||||
<Border Classes="pill" IsVisible="{Binding HasTitleId, FallbackValue=False}">
|
||||
<TextBlock Text="{Binding TitleId}" FontSize="10" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource MutedBrush}" />
|
||||
</Border>
|
||||
<Border Classes="pill" IsVisible="{Binding HasVersion, FallbackValue=False}">
|
||||
<TextBlock Text="{Binding VersionText}" FontSize="10" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource MutedBrush}" />
|
||||
</Border>
|
||||
<Border Classes="pill">
|
||||
<TextBlock Text="{Binding SizeText, FallbackValue=''}" FontSize="10" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource MutedBrush}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<TextBlock x:Name="SelectedGamePath" Text="Pick a game from the library, or open an eboot.bin directly."
|
||||
FontSize="11" Foreground="{StaticResource MutedBrush}" TextTrimming="CharacterEllipsis" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="7">
|
||||
<Ellipse x:Name="StatusDot" Width="8" Height="8" Fill="{StaticResource FaintBrush}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock x:Name="StatusText" Text="Idle" FontSize="11" Foreground="{StaticResource MutedBrush}"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
||||
<ToggleButton x:Name="ConsoleToggle" Classes="ghost"
|
||||
Content="{Binding [Launch.Console], Source={x:Static local:Localization.Instance}}" />
|
||||
<Button x:Name="LaunchButton" Classes="accent"
|
||||
Content="{Binding [Launch.Launch], Source={x:Static local:Localization.Instance}}"
|
||||
IsEnabled="False" />
|
||||
<Button x:Name="StopButton" Classes="danger"
|
||||
Content="{Binding [Launch.Stop], Source={x:Static local:Localization.Instance}}"
|
||||
IsEnabled="False" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- Keep launch progress above the blurred library while the SDL game
|
||||
process owns its independent top-level window. -->
|
||||
<primitives:Popup x:Name="SessionLoadingPopup"
|
||||
IsOpen="False"
|
||||
PlacementTarget="{Binding #MainContent}"
|
||||
Placement="Center"
|
||||
Topmost="True"
|
||||
ShouldUseOverlayLayer="False"
|
||||
TakesFocusFromNativeControl="False"
|
||||
IsLightDismissEnabled="False">
|
||||
<Border Classes="card" Width="380" CornerRadius="18" Padding="22,18">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock x:Name="SessionLoadingTitle" Text="Loading game" FontSize="16" FontWeight="SemiBold" />
|
||||
<TextBlock x:Name="SessionLoadingDetail" Text="Preparing the emulation session..." FontSize="12"
|
||||
Foreground="{StaticResource MutedBrush}" TextTrimming="CharacterEllipsis" />
|
||||
<ProgressBar x:Name="SessionLoadingProgress" IsIndeterminate="True" Height="5" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</primitives:Popup>
|
||||
|
||||
<!-- Status bar -->
|
||||
<Grid x:Name="StatusBar" Grid.Row="2" Height="32" Background="{StaticResource ChromeBrush}"
|
||||
ColumnDefinitions="*,Auto">
|
||||
|
||||
Reference in New Issue
Block a user