From e28069c7e3bc0b9858b73b6f9a5aeab70bd2ded4 Mon Sep 17 00:00:00 2001 From: ParantezTech <12572227+par274@users.noreply.github.com> Date: Fri, 31 Jul 2026 13:20:14 +0300 Subject: [PATCH] [GUI] Add grid settings to GUI and implement grid snapping --- src/SharpEmu.GUI/GuiSettings.cs | 3 + src/SharpEmu.GUI/Languages/en.json | 2 + src/SharpEmu.GUI/Languages/tr.json | 2 + src/SharpEmu.GUI/MainWindow.GameOptions.cs | 16 +-- src/SharpEmu.GUI/MainWindow.axaml | 65 +++++----- src/SharpEmu.GUI/MainWindow.axaml.cs | 111 +++++++++++++++++- src/SharpEmu.GUI/Themes/Styles/Buttons.axaml | 17 +++ .../Themes/Styles/GameOptions.axaml | 30 +++++ src/SharpEmu.GUI/Themes/Styles/Library.axaml | 36 ++++++ .../GUI/GuiSettingsTests.cs | 13 ++ 10 files changed, 249 insertions(+), 46 deletions(-) diff --git a/src/SharpEmu.GUI/GuiSettings.cs b/src/SharpEmu.GUI/GuiSettings.cs index 2634830c..f365613b 100644 --- a/src/SharpEmu.GUI/GuiSettings.cs +++ b/src/SharpEmu.GUI/GuiSettings.cs @@ -40,6 +40,8 @@ public sealed class GuiSettings /// Loop the selected game's sce_sys/snd0.at9 preview music. public bool PlayTitleMusic { get; set; } = true; + public string LibraryLayout { get; set; } = "Carousel"; + public string? EmulatorPath { get; set; } /// UI language, matching a file code under Languages/ (e.g. "en", "tr"). @@ -132,6 +134,7 @@ public sealed class GuiSettings { settings.RenderResolutionScale = 1.0; } + settings.LibraryLayout = NormalizeChoice(settings.LibraryLayout, "Carousel", "Grid"); settings.WindowMode = NormalizeChoice(settings.WindowMode, "Windowed", "Borderless", "Exclusive"); settings.Resolution = NormalizeResolution(settings.Resolution); settings.ScalingMode = NormalizeChoice(settings.ScalingMode, "Fit", "Cover", "Stretch", "Integer"); diff --git a/src/SharpEmu.GUI/Languages/en.json b/src/SharpEmu.GUI/Languages/en.json index 30bc6734..f0b8af9f 100644 --- a/src/SharpEmu.GUI/Languages/en.json +++ b/src/SharpEmu.GUI/Languages/en.json @@ -9,6 +9,8 @@ "Library.SearchWatermark": "Search library…", "Library.AddFolder": "Add folder", "Library.OpenFile": "Open file…", + "Library.View.Grid": "Stacked view", + "Library.View.Carousel": "Row view", "Library.Context.Launch": "Launch", "Library.Context.OpenFolder": "Open game folder", diff --git a/src/SharpEmu.GUI/Languages/tr.json b/src/SharpEmu.GUI/Languages/tr.json index ad074051..ab7fdc09 100644 --- a/src/SharpEmu.GUI/Languages/tr.json +++ b/src/SharpEmu.GUI/Languages/tr.json @@ -9,6 +9,8 @@ "Library.SearchWatermark": "Kütüphanede ara…", "Library.AddFolder": "Klasör ekle", "Library.OpenFile": "Dosya aç…", + "Library.View.Grid": "Alt alta görünüm", + "Library.View.Carousel": "Tek sıra görünüm", "Library.Context.Launch": "Başlat", "Library.Context.OpenFolder": "Oyun klasörünü aç", diff --git a/src/SharpEmu.GUI/MainWindow.GameOptions.cs b/src/SharpEmu.GUI/MainWindow.GameOptions.cs index ac0f6e95..a2bb69d2 100644 --- a/src/SharpEmu.GUI/MainWindow.GameOptions.cs +++ b/src/SharpEmu.GUI/MainWindow.GameOptions.cs @@ -476,18 +476,6 @@ public partial class MainWindow ("SHARPEMU_GUEST_IMAGE_CPU_SYNC", GameEnvGuestImageCpuSyncToggle), ]; - private static void SetGameOptionsOpenClass(Control control, bool active) - { - if (active) - { - if (!control.Classes.Contains("gameOptionsOpen")) - { - control.Classes.Add("gameOptionsOpen"); - } - } - else - { - control.Classes.Remove("gameOptionsOpen"); - } - } + private static void SetGameOptionsOpenClass(Control control, bool active) => + SetClass(control, "gameOptionsOpen", active); } diff --git a/src/SharpEmu.GUI/MainWindow.axaml b/src/SharpEmu.GUI/MainWindow.axaml index 9ba5ee1e..c56abd09 100644 --- a/src/SharpEmu.GUI/MainWindow.axaml +++ b/src/SharpEmu.GUI/MainWindow.axaml @@ -136,6 +136,13 @@ SPDX-License-Identifier: GPL-2.0-or-later + + SelectionMode="Single"> - - - - - - - - - - - - - + + + + + + + + + + - + + + Margin="4,8,0,0"> - ConsoleToggle.IsChecked = false; LibraryTabButton.Click += (_, _) => SetActivePage(0); OptionsTabButton.Click += (_, _) => SetActivePage(1); + LibraryLayoutButton.Click += (_, _) => ToggleLibraryLayout(); + LibraryPage.SizeChanged += (_, _) => UpdateLibraryGridHeight(); + LibrarySelectedDetails.SizeChanged += (_, _) => UpdateLibraryGridHeight(); ConsoleToggle.IsCheckedChanged += (_, _) => ConsolePanel.IsVisible = ConsoleToggle.IsChecked == true && _consoleWindow is null; WireOptionsNavigation(); WireGameOptions(); @@ -379,18 +385,73 @@ public partial class MainWindow : Window } } - private static void SetActiveClass(Button button, bool active) + private void SetLibraryLayout(bool grid) + { + _isLibraryGridLayout = grid; + SetClass(GameList, "gridLayout", grid); + SetClass(LibrarySelectedDetails, "gridLayout", grid); + LibraryPage.RowDefinitions[0].Height = grid + ? GridLength.Auto + : new GridLength(188); + LibraryPage.Margin = grid + ? new Thickness(0, 6, 0, 0) + : new Thickness(0, 46, 0, 0); + UpdateLibraryGridHeight(); + UpdateLibraryLayoutButton(); + + if (GameList.SelectedItem is { } selected) + { + Dispatcher.UIThread.Post( + () => GameList.ScrollIntoView(selected), + DispatcherPriority.Loaded); + } + } + + private void UpdateLibraryGridHeight() + { + var pageHeight = LibraryPage.Bounds.Height; + if (!_isLibraryGridLayout || pageHeight <= 0) + { + GameList.MaxHeight = double.PositiveInfinity; + return; + } + + GameList.MaxHeight = Math.Max( + 0, + pageHeight - LibrarySelectedDetails.DesiredSize.Height); + } + + private void ToggleLibraryLayout() + { + SetLibraryLayout(!_isLibraryGridLayout); + _settings.LibraryLayout = _isLibraryGridLayout ? "Grid" : "Carousel"; + _settings.Save(); + } + + private void UpdateLibraryLayoutButton() + { + LibraryLayoutGlyph.Text = _isLibraryGridLayout ? "view_carousel" : "grid_view"; + var label = Localization.Instance.Get( + _isLibraryGridLayout ? "Library.View.Carousel" : "Library.View.Grid"); + ToolTip.SetTip(LibraryLayoutButton, label); + AutomationProperties.SetName(LibraryLayoutButton, label); + } + + private static void SetActiveClass(Button button, bool active) => + SetClass(button, "active", active); + + private static void SetClass(Control control, string className, bool active) { if (active) { - if (!button.Classes.Contains("active")) + if (!control.Classes.Contains(className)) { - button.Classes.Add("active"); + control.Classes.Add(className); } } else { - button.Classes.Remove("active"); + control.Classes.Remove(className); } } @@ -676,6 +737,23 @@ public partial class MainWindow : Window MoveSelection(1); } + if (_isLibraryGridLayout) + { + var up = (pad.Buttons & HostGamepadButtons.Up) != 0 || pad.LeftY < 64; + var down = (pad.Buttons & HostGamepadButtons.Down) != 0 || pad.LeftY > 192; + var rowStep = LibraryRowStep(); + + if (ShouldNavigate(up, ref _navUpNextAt, now)) + { + MoveSelection(-rowStep); + } + + if (ShouldNavigate(down, ref _navDownNextAt, now)) + { + MoveSelection(rowStep); + } + } + var pressed = pad.Buttons & ~_previousPadButtons; if ((pressed & HostGamepadButtons.Cross) != 0) { @@ -712,6 +790,29 @@ public partial class MainWindow : Window return false; } + private int LibraryRowStep() + { + if (GameList.ContainerFromIndex(0) is not { } first) + { + return 1; + } + + var top = first.Bounds.Top; + var columns = 1; + for (var index = 1; index < _libraryTiles.Count; index++) + { + if (GameList.ContainerFromIndex(index) is not { } container || + Math.Abs(container.Bounds.Top - top) > 0.5) + { + break; + } + + columns++; + } + + return columns; + } + private void MoveSelection(int delta) { var index = GameList.SelectedIndex < 0 @@ -783,6 +884,7 @@ public partial class MainWindow : Window RefreshHostRefreshRates(_settings.RefreshRate); RefreshUpdateText(); UpdateEmptyStateTexts(); + UpdateLibraryLayoutButton(); UpdateRunButtons(); } @@ -1067,6 +1169,7 @@ public partial class MainWindow : Window LogToFileToggle.IsChecked = _settings.LogToFile; OverrideLogFileToggle.IsChecked = _settings.OverrideLogFile; TitleMusicToggle.IsChecked = _settings.PlayTitleMusic; + SetLibraryLayout(string.Equals(_settings.LibraryLayout, "Grid", StringComparison.OrdinalIgnoreCase)); DiscordToggle.IsChecked = _settings.DiscordRichPresence; AutoUpdateToggle.IsChecked = _settings.CheckForUpdatesOnStartup; EnvBthidToggle.IsChecked = _settings.EnvironmentToggles.Contains("SHARPEMU_BTHID_UNAVAILABLE"); diff --git a/src/SharpEmu.GUI/Themes/Styles/Buttons.axaml b/src/SharpEmu.GUI/Themes/Styles/Buttons.axaml index 914a41f0..9f632575 100644 --- a/src/SharpEmu.GUI/Themes/Styles/Buttons.axaml +++ b/src/SharpEmu.GUI/Themes/Styles/Buttons.axaml @@ -43,6 +43,23 @@ Shared launcher button variants and page switcher styles. + + + + + + + + + + + + + + + + + + + + + +