From 045f9a39bb740551f1dd163f92ac2ee03bfd821a Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Thu, 17 Oct 2024 01:24:16 -0500 Subject: [PATCH] UI: Remove title bar, put icon next to File button. Added icons to most dropdown menu actions. Title bar content is now displayed when hovering the logo in the title bar. --- src/Ryujinx.UI.Common/App/ApplicationData.cs | 4 +- .../Configuration/ConfigurationState.cs | 3 + .../Helper/ValueFormatUtils.cs | 8 +- src/Ryujinx/Common/Icon/IconExtension.cs | 26 ++ src/Ryujinx/Program.cs | 46 +-- src/Ryujinx/Ryujinx.csproj | 7 +- src/Ryujinx/UI/Applet/AvaHostUIHandler.cs | 6 +- .../Applet/AvaloniaDynamicTextInputHandler.cs | 2 +- .../UI/Controls/ApplicationContextMenu.axaml | 29 +- .../Controls/ApplicationContextMenu.axaml.cs | 321 ++++++++---------- .../UI/Controls/ApplicationGridView.axaml.cs | 29 +- .../UI/Controls/ApplicationListView.axaml | 4 +- .../UI/Controls/ApplicationListView.axaml.cs | 29 +- .../UI/ViewModels/Input/InputViewModel.cs | 6 +- .../UI/Views/Main/MainMenuBarView.axaml | 107 +++++- .../UI/Views/Main/MainMenuBarView.axaml.cs | 86 ++--- .../UI/Views/Main/MainStatusBarView.axaml.cs | 10 +- .../UI/Views/Main/MainViewControls.axaml | 2 +- .../UI/Views/Main/MainViewControls.axaml.cs | 16 +- src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 19 +- src/Ryujinx/UI/Windows/StyleableWindow.cs | 11 +- 21 files changed, 381 insertions(+), 390 deletions(-) create mode 100644 src/Ryujinx/Common/Icon/IconExtension.cs diff --git a/src/Ryujinx.UI.Common/App/ApplicationData.cs b/src/Ryujinx.UI.Common/App/ApplicationData.cs index 08bd2677d..c2c0d0b4c 100644 --- a/src/Ryujinx.UI.Common/App/ApplicationData.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationData.cs @@ -19,6 +19,8 @@ namespace Ryujinx.UI.App.Common { public class ApplicationData { + public static Func LocalizedNever = () => "Never"; + public bool Favorite { get; set; } public byte[] Icon { get; set; } public string Name { get; set; } = "Unknown"; @@ -34,7 +36,7 @@ namespace Ryujinx.UI.App.Common public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed); - public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed); + public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed) ?? LocalizedNever(); public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize); diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs index de33595ea..e6c5c065c 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs @@ -1,3 +1,4 @@ +using ARMeilleure; using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; @@ -1645,6 +1646,8 @@ namespace Ryujinx.UI.Common.Configuration } Instance = new ConfigurationState(); + + Instance.System.EnableLowPowerPtc.Event += (_, evnt) => Optimizations.LowPower = evnt.NewValue; } } } diff --git a/src/Ryujinx.UI.Common/Helper/ValueFormatUtils.cs b/src/Ryujinx.UI.Common/Helper/ValueFormatUtils.cs index 8ea3e721f..c203834f5 100644 --- a/src/Ryujinx.UI.Common/Helper/ValueFormatUtils.cs +++ b/src/Ryujinx.UI.Common/Helper/ValueFormatUtils.cs @@ -75,13 +75,7 @@ namespace Ryujinx.UI.Common.Helper { culture ??= CultureInfo.CurrentCulture; - if (!utcDateTime.HasValue) - { - // In the Avalonia UI, this is turned into a localized version of "Never" by LocalizedNeverConverter. - return "Never"; - } - - return utcDateTime.Value.ToLocalTime().ToString(culture); + return utcDateTime?.ToLocalTime().ToString(culture); } /// diff --git a/src/Ryujinx/Common/Icon/IconExtension.cs b/src/Ryujinx/Common/Icon/IconExtension.cs new file mode 100644 index 000000000..41bdf5b02 --- /dev/null +++ b/src/Ryujinx/Common/Icon/IconExtension.cs @@ -0,0 +1,26 @@ +using Avalonia.Data.Core; +using Avalonia.Markup.Xaml; +using Avalonia.Markup.Xaml.MarkupExtensions; +using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings; +using System; + +namespace Ryujinx.Ava.Common.Icon +{ + internal class IconExtension(string iconString) : MarkupExtension + { + private ClrPropertyInfo PropertyInfo + => new( + "Item", + _ => new Projektanker.Icons.Avalonia.Icon { Value = iconString }, + null, + typeof(Projektanker.Icons.Avalonia.Icon) + ); + + public override object ProvideValue(IServiceProvider serviceProvider) => + new CompiledBindingExtension( + new CompiledBindingPathBuilder() + .Property(PropertyInfo, PropertyInfoAccessorFactory.CreateInpcPropertyAccessor) + .Build() + ).ProvideValue(serviceProvider); + } +} diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index 3d5fadd41..562530b2b 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -1,6 +1,11 @@ +using ARMeilleure; using Avalonia; using Avalonia.Threading; using DiscordRPC; +using Projektanker.Icons.Avalonia; +using Projektanker.Icons.Avalonia.FontAwesome; +using Projektanker.Icons.Avalonia.MaterialDesign; +using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Windows; using Ryujinx.Common; @@ -11,6 +16,7 @@ using Ryujinx.Common.SystemInterop; using Ryujinx.Graphics.Vulkan.MoltenVK; using Ryujinx.Modules; using Ryujinx.SDL2.Common; +using Ryujinx.UI.App.Common; using Ryujinx.UI.Common; using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Helper; @@ -51,31 +57,32 @@ namespace Ryujinx.Ava LoggerAdapter.Register(); + IconProvider.Current + .Register() + .Register(); + return BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); } - public static AppBuilder BuildAvaloniaApp() - { - return AppBuilder.Configure() + public static AppBuilder BuildAvaloniaApp() => + AppBuilder.Configure() .UsePlatformDetect() .With(new X11PlatformOptions { EnableMultiTouch = true, EnableIme = true, EnableInputFocusProxy = Environment.GetEnvironmentVariable("XDG_CURRENT_DESKTOP") == "gamescope", - RenderingMode = UseHardwareAcceleration ? - new[] { X11RenderingMode.Glx, X11RenderingMode.Software } : - new[] { X11RenderingMode.Software }, + RenderingMode = UseHardwareAcceleration + ? [ X11RenderingMode.Glx, X11RenderingMode.Software ] + : [ X11RenderingMode.Software ], }) .With(new Win32PlatformOptions { WinUICompositionBackdropCornerRadius = 8.0f, - RenderingMode = UseHardwareAcceleration ? - new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software } : - new[] { Win32RenderingMode.Software }, - }) - .UseSkia(); - } + RenderingMode = UseHardwareAcceleration + ? [ Win32RenderingMode.AngleEgl, Win32RenderingMode.Software ] + : [ Win32RenderingMode.Software ], + }); private static void Initialize(string[] args) { @@ -102,6 +109,9 @@ namespace Ryujinx.Ava // Setup base data directory. AppDataManager.Initialize(CommandLineState.BaseDirPathArg); + // Set the delegate for localizing the word "never" in the UI + ApplicationData.LocalizedNever = () => LocaleManager.Instance[LocaleKeys.Never]; + // Initialize the configuration. ConfigurationState.Initialize(); @@ -218,14 +228,10 @@ namespace Ryujinx.Ava Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(Logger.GetEnabledLevels().Count == 0 ? "" : string.Join(", ", Logger.GetEnabledLevels()))}"); - if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom) - { - Logger.Notice.Print(LogClass.Application, $"Launch Mode: Custom Path {AppDataManager.BaseDirPath}"); - } - else - { - Logger.Notice.Print(LogClass.Application, $"Launch Mode: {AppDataManager.Mode}"); - } + Logger.Notice.Print(LogClass.Application, + AppDataManager.Mode == AppDataManager.LaunchMode.Custom + ? $"Launch Mode: Custom Path {AppDataManager.BaseDirPath}" + : $"Launch Mode: {AppDataManager.Mode}"); } private static void ProcessUnhandledException(Exception ex, bool isTerminating) diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index 6718b7fcc..c1c238926 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -44,7 +44,9 @@ - + + + @@ -69,8 +71,7 @@ - + diff --git a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs index 4bcf8eb94..d72f75cff 100644 --- a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs +++ b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs @@ -122,7 +122,7 @@ namespace Ryujinx.Ava.UI.Applet { try { - _parent.ViewModel.AppHost.NpadManager.BlockInputUpdates(); + MainWindow.ViewModel.AppHost.NpadManager.BlockInputUpdates(); var response = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.SoftwareKeyboard], args); if (response.Result == UserResult.Ok) @@ -144,7 +144,7 @@ namespace Ryujinx.Ava.UI.Applet }); dialogCloseEvent.WaitOne(); - _parent.ViewModel.AppHost.NpadManager.UnblockInputUpdates(); + MainWindow.ViewModel.AppHost.NpadManager.UnblockInputUpdates(); userText = error ? null : inputText; @@ -154,7 +154,7 @@ namespace Ryujinx.Ava.UI.Applet public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value) { device.Configuration.UserChannelPersistence.ExecuteProgram(kind, value); - _parent.ViewModel.AppHost?.Stop(); + MainWindow.ViewModel.AppHost?.Stop(); } public bool DisplayErrorAppletDialog(string title, string message, string[] buttons) diff --git a/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs b/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs index 0e7cfb8e6..89bdfe748 100644 --- a/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs +++ b/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs @@ -129,7 +129,7 @@ namespace Ryujinx.Ava.UI.Applet Dispatcher.UIThread.Post(() => { _hiddenTextBox.Clear(); - _parent.ViewModel.RendererHostControl.Focus(); + MainWindow.ViewModel.RendererHostControl.Focus(); _parent = null; }); diff --git a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml index dd0926fc9..4a6c99d1d 100644 --- a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml +++ b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml @@ -3,24 +3,29 @@ xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:icon="clr-namespace:Ryujinx.Ava.Common.Icon" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" x:DataType="viewModels:MainWindowViewModel"> - + Header="{locale:Locale GameListContextMenuRunApplication}" + Icon="{icon:Icon fa-solid fa-play}"/> - + Icon="{icon:Icon fa-solid fa-bookmark}" + ToolTip.Tip="{OnPlatform Default={locale:Locale GameListContextMenuCreateShortcutToolTip}, macOS={locale:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" /> - + diff --git a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs index b84015d6e..ea061c5dc 100644 --- a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs +++ b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs @@ -13,6 +13,7 @@ using Ryujinx.Common.Configuration; using Ryujinx.HLE.HOS; using Ryujinx.UI.App.Common; using Ryujinx.UI.Common.Helper; +using SkiaSharp; using System; using System.Collections.Generic; using System.IO; @@ -34,165 +35,132 @@ namespace Ryujinx.Ava.UI.Controls public void ToggleFavorite_Click(object sender, RoutedEventArgs args) { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) + return; - if (viewModel?.SelectedApplication != null) + viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite; + + ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.IdString, appMetadata => { - viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite; + appMetadata.Favorite = viewModel.SelectedApplication.Favorite; + }); - ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.IdString, appMetadata => - { - appMetadata.Favorite = viewModel.SelectedApplication.Favorite; - }); - - viewModel.RefreshView(); - } + viewModel.RefreshView(); } public void OpenUserSaveDirectory_Click(object sender, RoutedEventArgs args) { - if (sender is MenuItem { DataContext: MainWindowViewModel viewModel }) - { + if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) OpenSaveDirectory(viewModel, SaveDataType.Account, new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low)); - } } public void OpenDeviceSaveDirectory_Click(object sender, RoutedEventArgs args) { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - OpenSaveDirectory(viewModel, SaveDataType.Device, default); + if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) + OpenSaveDirectory(viewModel, SaveDataType.Device, default); } public void OpenBcatSaveDirectory_Click(object sender, RoutedEventArgs args) { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - OpenSaveDirectory(viewModel, SaveDataType.Bcat, default); + if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) + OpenSaveDirectory(viewModel, SaveDataType.Bcat, default); } private static void OpenSaveDirectory(MainWindowViewModel viewModel, SaveDataType saveDataType, UserId userId) { - if (viewModel?.SelectedApplication != null) - { - var saveDataFilter = SaveDataFilter.Make(viewModel.SelectedApplication.Id, saveDataType, userId, saveDataId: default, index: default); + var saveDataFilter = SaveDataFilter.Make(viewModel.SelectedApplication.Id, saveDataType, userId, saveDataId: default, index: default); - ApplicationHelper.OpenSaveDir(in saveDataFilter, viewModel.SelectedApplication.Id, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.Name); - } + ApplicationHelper.OpenSaveDir(in saveDataFilter, viewModel.SelectedApplication.Id, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.Name); } public async void OpenTitleUpdateManager_Click(object sender, RoutedEventArgs args) { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { + if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) await TitleUpdateWindow.Show(viewModel.ApplicationLibrary, viewModel.SelectedApplication); - } } public async void OpenDownloadableContentManager_Click(object sender, RoutedEventArgs args) { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { + if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) await DownloadableContentManagerWindow.Show(viewModel.ApplicationLibrary, viewModel.SelectedApplication); - } } public async void OpenCheatManager_Click(object sender, RoutedEventArgs args) { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { + if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) await new CheatWindow( viewModel.VirtualFileSystem, viewModel.SelectedApplication.IdString, viewModel.SelectedApplication.Name, - viewModel.SelectedApplication.Path).ShowDialog(viewModel.TopLevel as Window); - } + viewModel.SelectedApplication.Path).ShowDialog((Window)viewModel.TopLevel); } public void OpenModsDirectory_Click(object sender, RoutedEventArgs args) { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) + return; - if (viewModel?.SelectedApplication != null) - { - string modsBasePath = ModLoader.GetModsBasePath(); - string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.IdString); + string modsBasePath = ModLoader.GetModsBasePath(); + string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.IdString); - OpenHelper.OpenFolder(titleModsPath); - } + OpenHelper.OpenFolder(titleModsPath); } public void OpenSdModsDirectory_Click(object sender, RoutedEventArgs args) { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) + return; - if (viewModel?.SelectedApplication != null) - { - string sdModsBasePath = ModLoader.GetSdModsBasePath(); - string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.IdString); + string sdModsBasePath = ModLoader.GetSdModsBasePath(); + string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.IdString); - OpenHelper.OpenFolder(titleModsPath); - } + OpenHelper.OpenFolder(titleModsPath); } public async void OpenModManager_Click(object sender, RoutedEventArgs args) { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { + if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) await ModManagerWindow.Show(viewModel.SelectedApplication.Id, viewModel.SelectedApplication.Name); - } } public async void PurgePtcCache_Click(object sender, RoutedEventArgs args) { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) + return; - if (viewModel?.SelectedApplication != null) + UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogWarning], + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.Name) + ); + + if (result == UserResult.Yes) { - UserResult result = await ContentDialogHelper.CreateConfirmationDialog( - LocaleManager.Instance[LocaleKeys.DialogWarning], - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.Name), - LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.InputDialogNo], - LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0")); + DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1")); - if (result == UserResult.Yes) + List cacheFiles = new(); + + if (mainDir.Exists) { - DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0")); - DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1")); + cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache")); + } - List cacheFiles = new(); + if (backupDir.Exists) + { + cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache")); + } - if (mainDir.Exists) + if (cacheFiles.Count > 0) + { + foreach (FileInfo file in cacheFiles) { - cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache")); - } - - if (backupDir.Exists) - { - cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache")); - } - - if (cacheFiles.Count > 0) - { - foreach (FileInfo file in cacheFiles) + try { - try - { - file.Delete(); - } - catch (Exception ex) - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, ex)); - } + file.Delete(); + } + catch (Exception ex) + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, ex)); } } } @@ -201,55 +169,51 @@ namespace Ryujinx.Ava.UI.Controls public async void PurgeShaderCache_Click(object sender, RoutedEventArgs args) { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) + return; - if (viewModel?.SelectedApplication != null) + UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogWarning], + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.Name) + ); + + if (result == UserResult.Yes) { - UserResult result = await ContentDialogHelper.CreateConfirmationDialog( - LocaleManager.Instance[LocaleKeys.DialogWarning], - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.Name), - LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.InputDialogNo], - LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader")); - if (result == UserResult.Yes) + List oldCacheDirectories = new(); + List newCacheFiles = new(); + + if (shaderCacheDir.Exists) { - DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader")); + oldCacheDirectories.AddRange(shaderCacheDir.EnumerateDirectories("*")); + newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.toc")); + newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.data")); + } - List oldCacheDirectories = new(); - List newCacheFiles = new(); - - if (shaderCacheDir.Exists) + if ((oldCacheDirectories.Count > 0 || newCacheFiles.Count > 0)) + { + foreach (DirectoryInfo directory in oldCacheDirectories) { - oldCacheDirectories.AddRange(shaderCacheDir.EnumerateDirectories("*")); - newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.toc")); - newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.data")); + try + { + directory.Delete(true); + } + catch (Exception ex) + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, directory.Name, ex)); + } } - if ((oldCacheDirectories.Count > 0 || newCacheFiles.Count > 0)) + foreach (FileInfo file in newCacheFiles) { - foreach (DirectoryInfo directory in oldCacheDirectories) + try { - try - { - directory.Delete(true); - } - catch (Exception ex) - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, directory.Name, ex)); - } + file.Delete(); } - - foreach (FileInfo file in newCacheFiles) + catch (Exception ex) { - try - { - file.Delete(); - } - catch (Exception ex) - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.ShaderCachePurgeError, file.Name, ex)); - } + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.ShaderCachePurgeError, file.Name, ex)); } } } @@ -258,30 +222,26 @@ namespace Ryujinx.Ava.UI.Controls public void OpenPtcDirectory_Click(object sender, RoutedEventArgs args) { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) + return; + + string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu"); + string mainDir = Path.Combine(ptcDir, "0"); + string backupDir = Path.Combine(ptcDir, "1"); - if (viewModel?.SelectedApplication != null) + if (!Directory.Exists(ptcDir)) { - string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu"); - string mainDir = Path.Combine(ptcDir, "0"); - string backupDir = Path.Combine(ptcDir, "1"); - - if (!Directory.Exists(ptcDir)) - { - Directory.CreateDirectory(ptcDir); - Directory.CreateDirectory(mainDir); - Directory.CreateDirectory(backupDir); - } - - OpenHelper.OpenFolder(ptcDir); + Directory.CreateDirectory(ptcDir); + Directory.CreateDirectory(mainDir); + Directory.CreateDirectory(backupDir); } + + OpenHelper.OpenFolder(ptcDir); } public void OpenShaderCacheDirectory_Click(object sender, RoutedEventArgs args) { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) + if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) { string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader"); @@ -296,9 +256,7 @@ namespace Ryujinx.Ava.UI.Controls public async void ExtractApplicationExeFs_Click(object sender, RoutedEventArgs args) { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) + if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) { await ApplicationHelper.ExtractSection( viewModel.StorageProvider, @@ -310,67 +268,60 @@ namespace Ryujinx.Ava.UI.Controls public async void ExtractApplicationRomFs_Click(object sender, RoutedEventArgs args) { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { + if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) await ApplicationHelper.ExtractSection( viewModel.StorageProvider, NcaSectionType.Data, viewModel.SelectedApplication.Path, viewModel.SelectedApplication.Name); - } } public async void ExtractApplicationLogo_Click(object sender, RoutedEventArgs args) { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) + return; - if (viewModel?.SelectedApplication is { } selectedApp) + var result = await viewModel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions { - var result = await viewModel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions - { - Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle], - AllowMultiple = false, - }); + Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle], + AllowMultiple = false, + }); - if (result.Count == 0) - { - return; - } + if (result.Count == 0) + return; - ApplicationHelper.ExtractSection( - result[0].Path.LocalPath, - NcaSectionType.Logo, - viewModel.SelectedApplication.Path, - viewModel.SelectedApplication.Name); + ApplicationHelper.ExtractSection( + result[0].Path.LocalPath, + NcaSectionType.Logo, + viewModel.SelectedApplication.Path, + viewModel.SelectedApplication.Name); - var iconFile = await result[0].CreateFileAsync(selectedApp.IdString + ".png"); - await using var fileStream = await iconFile.OpenWriteAsync(); + var iconFile = await result[0].CreateFileAsync($"{viewModel.SelectedApplication.IdString}.png"); + await using var fileStream = await iconFile.OpenWriteAsync(); - fileStream.Write(selectedApp.Icon); - } + using var bitmap = SKBitmap.Decode(viewModel.SelectedApplication.Icon) + .Resize(new SKSizeI(512, 512), SKFilterQuality.High); + + using var png = bitmap.Encode(SKEncodedImageFormat.Png, 100); + + png.SaveTo(fileStream); } public void CreateApplicationShortcut_Click(object sender, RoutedEventArgs args) { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { - ApplicationData selectedApplication = viewModel.SelectedApplication; - ShortcutHelper.CreateAppShortcut(selectedApplication.Path, selectedApplication.Name, selectedApplication.IdString, selectedApplication.Icon); - } + if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) + ShortcutHelper.CreateAppShortcut( + viewModel.SelectedApplication.Path, + viewModel.SelectedApplication.Name, + viewModel.SelectedApplication.IdString, + viewModel.SelectedApplication.Icon + ); } public async void RunApplication_Click(object sender, RoutedEventArgs args) { - var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - - if (viewModel?.SelectedApplication != null) - { + if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) await viewModel.LoadApplication(viewModel.SelectedApplication); - } } } } diff --git a/src/Ryujinx/UI/Controls/ApplicationGridView.axaml.cs b/src/Ryujinx/UI/Controls/ApplicationGridView.axaml.cs index ee15bc8d5..25a34b423 100644 --- a/src/Ryujinx/UI/Controls/ApplicationGridView.axaml.cs +++ b/src/Ryujinx/UI/Controls/ApplicationGridView.axaml.cs @@ -15,37 +15,22 @@ namespace Ryujinx.Ava.UI.Controls public event EventHandler ApplicationOpened { - add { AddHandler(ApplicationOpenedEvent, value); } - remove { RemoveHandler(ApplicationOpenedEvent, value); } + add => AddHandler(ApplicationOpenedEvent, value); + remove => RemoveHandler(ApplicationOpenedEvent, value); } - public ApplicationGridView() - { - InitializeComponent(); - } + public ApplicationGridView() => InitializeComponent(); public void GameList_DoubleTapped(object sender, TappedEventArgs args) { - if (sender is ListBox listBox) - { - if (listBox.SelectedItem is ApplicationData selected) - { - RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent)); - } - } + if (sender is ListBox { SelectedItem: ApplicationData selected }) + RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent)); } public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs args) { - if (sender is ListBox listBox) - { - (DataContext as MainWindowViewModel).GridSelectedApplication = listBox.SelectedItem as ApplicationData; - } - } - - private void SearchBox_OnKeyUp(object sender, KeyEventArgs args) - { - (DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text; + if (DataContext is MainWindowViewModel viewModel && sender is ListBox { SelectedItem: ApplicationData selected }) + viewModel.GridSelectedApplication = selected; } } } diff --git a/src/Ryujinx/UI/Controls/ApplicationListView.axaml b/src/Ryujinx/UI/Controls/ApplicationListView.axaml index f99cf316e..6c7a080d1 100644 --- a/src/Ryujinx/UI/Controls/ApplicationListView.axaml +++ b/src/Ryujinx/UI/Controls/ApplicationListView.axaml @@ -109,7 +109,7 @@ Spacing="5"> ApplicationOpened { - add { AddHandler(ApplicationOpenedEvent, value); } - remove { RemoveHandler(ApplicationOpenedEvent, value); } + add => AddHandler(ApplicationOpenedEvent, value); + remove => RemoveHandler(ApplicationOpenedEvent, value); } - public ApplicationListView() - { - InitializeComponent(); - } + public ApplicationListView() => InitializeComponent(); public void GameList_DoubleTapped(object sender, TappedEventArgs args) { - if (sender is ListBox listBox) - { - if (listBox.SelectedItem is ApplicationData selected) - { - RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent)); - } - } + if (sender is ListBox { SelectedItem: ApplicationData selected }) + RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent)); } public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs args) { - if (sender is ListBox listBox) - { - (DataContext as MainWindowViewModel).ListSelectedApplication = listBox.SelectedItem as ApplicationData; - } - } - - private void SearchBox_OnKeyUp(object sender, KeyEventArgs args) - { - (DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text; + if (DataContext is MainWindowViewModel viewModel && sender is ListBox { SelectedItem: ApplicationData selected }) + viewModel.ListSelectedApplication = selected; } } } diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index 89cc6496d..d1c29f15d 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -244,7 +244,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input _mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; - _mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates(); + MainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates(); _isLoaded = false; @@ -847,7 +847,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input } } - _mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); + MainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); // Atomically replace and signal input change. // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event. @@ -879,7 +879,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; - _mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates(); + MainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates(); SelectedGamepad?.Dispose(); diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml index e7815bba8..1c13440d8 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml @@ -4,6 +4,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:icon="clr-namespace:Ryujinx.Ava.Common.Icon" mc:Ignorable="d" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" x:DataType="viewModels:MainWindowViewModel" @@ -12,6 +13,13 @@ + + + - + @@ -63,14 +76,28 @@ + Padding="0" + Icon="{icon:Icon fa-solid fa-expand}" + InputGesture="F11"> + + + + + - - + + Padding="0" + Header="{locale:Locale MenuBarOptionsChangeLanguage}" + Icon="{icon:Icon fa-solid fa-language}"> + + + + + + Icon="{icon:Icon fa-solid fa-gear}" + ToolTip.Tip="{locale:Locale OpenSettingsTooltip}"> + + + + + + ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}"> + + + + + @@ -170,26 +239,30 @@ AttachedToVisualTree="ScanAmiiboMenuItem_AttachedToVisualTree" Click="OpenAmiiboWindow" Header="{locale:Locale MenuBarActionsScanAmiibo}" + Icon="{icon:Icon mdi-cube-scan}" IsEnabled="{Binding IsAmiiboRequested}" /> - - - + + + @@ -198,8 +271,8 @@ - - + + @@ -208,11 +281,13 @@ IsEnabled="{Binding CanUpdate}" Click="CheckForUpdates" Header="{locale:Locale MenuBarHelpCheckForUpdates}" + Icon="{icon:Icon mdi-update}" ToolTip.Tip="{locale:Locale CheckUpdatesTooltip}" /> diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs index 73ae0df14..f6fe253a5 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs @@ -2,6 +2,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Threading; +using Gommon; using LibHac.Ncm; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Ava.Common.Locale; @@ -77,11 +78,9 @@ namespace Ryujinx.Ava.UI.Views.Main MenuItem menuItem = new() { - Header = languageName, - Command = MiniCommand.Create(() => - { - MainWindowViewModel.ChangeLanguage(languageCode); - }), + Padding = new Thickness(10, 0, 0, 0), + Header = " " + languageName, + Command = MiniCommand.Create(() => MainWindowViewModel.ChangeLanguage(languageCode)), }; menuItems.Add(menuItem); @@ -99,23 +98,23 @@ namespace Ryujinx.Ava.UI.Views.Main Window = window; } - ViewModel = Window.ViewModel; + ViewModel = MainWindow.ViewModel; DataContext = ViewModel; } private async void StopEmulation_Click(object sender, RoutedEventArgs e) { - await Window.ViewModel.AppHost?.ShowExitPrompt(); + await MainWindow.ViewModel.AppHost?.ShowExitPrompt().OrCompleted()!; } private void PauseEmulation_Click(object sender, RoutedEventArgs e) { - Window.ViewModel.AppHost?.Pause(); + MainWindow.ViewModel.AppHost?.Pause(); } private void ResumeEmulation_Click(object sender, RoutedEventArgs e) { - Window.ViewModel.AppHost?.Resume(); + MainWindow.ViewModel.AppHost?.Resume(); } public async void OpenSettings(object sender, RoutedEventArgs e) @@ -149,9 +148,7 @@ namespace Ryujinx.Ava.UI.Views.Main public async void OpenAmiiboWindow(object sender, RoutedEventArgs e) { if (!ViewModel.IsAmiiboRequested) - { return; - } if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId)) { @@ -173,17 +170,15 @@ namespace Ryujinx.Ava.UI.Views.Main public async void OpenCheatManagerForCurrentApp(object sender, RoutedEventArgs e) { if (!ViewModel.IsGameRunning) - { return; - } - + string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString(); await new CheatWindow( Window.VirtualFileSystem, ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText, name, - Window.ViewModel.SelectedApplication.Path).ShowDialog(Window); + MainWindow.ViewModel.SelectedApplication.Path).ShowDialog(Window); ViewModel.AppHost.Device.EnableCheats(); } @@ -191,85 +186,50 @@ namespace Ryujinx.Ava.UI.Views.Main private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) { if (sender is MenuItem) - { - ViewModel.IsAmiiboRequested = Window.ViewModel.AppHost.Device.System.SearchingForAmiibo(out _); - } + ViewModel.IsAmiiboRequested = MainWindow.ViewModel.AppHost.Device.System.SearchingForAmiibo(out _); } private async void InstallFileTypes_Click(object sender, RoutedEventArgs e) { if (FileAssociationHelper.Install()) - { await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); - } else - { await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesErrorMessage]); - } } private async void UninstallFileTypes_Click(object sender, RoutedEventArgs e) { if (FileAssociationHelper.Uninstall()) - { await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); - } else - { await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesErrorMessage]); - } } private async void ChangeWindowSize_Click(object sender, RoutedEventArgs e) { - if (sender is MenuItem item) + if (sender is not MenuItem { Tag: string resolution }) return; + + (int height, int width) = resolution.Split(' ') + .Into(parts => (int.Parse(parts[0]), int.Parse(parts[1]))); + + await Dispatcher.UIThread.InvokeAsync(() => { - int height; - int width; + ViewModel.WindowState = WindowState.Normal; - switch (item.Tag) - { - case "720": - height = 720; - width = 1280; - break; + height += (int)Window.StatusBarHeight + (int)Window.MenuBarHeight; - case "1080": - height = 1080; - width = 1920; - break; - - default: - throw new ArgumentNullException($"Invalid Tag for {item}"); - } - - await Dispatcher.UIThread.InvokeAsync(() => - { - ViewModel.WindowState = WindowState.Normal; - - height += (int)Window.StatusBarHeight + (int)Window.MenuBarHeight; - - Window.Arrange(new Rect(Window.Position.X, Window.Position.Y, width, height)); - }); - } + Window.Arrange(new Rect(Window.Position.X, Window.Position.Y, width, height)); + }); } public async void CheckForUpdates(object sender, RoutedEventArgs e) { if (Updater.CanUpdate(true)) - { await Updater.BeginParse(Window, true); - } } - public async void OpenAboutWindow(object sender, RoutedEventArgs e) - { - await AboutWindow.Show(); - } + public async void OpenAboutWindow(object sender, RoutedEventArgs e) => await AboutWindow.Show(); - public void CloseWindow(object sender, RoutedEventArgs e) - { - Window.Close(); - } + public void CloseWindow(object sender, RoutedEventArgs e) => Window.Close(); } } diff --git a/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs index 528495713..39b00ea33 100644 --- a/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs +++ b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs @@ -28,14 +28,14 @@ namespace Ryujinx.Ava.UI.Views.Main Window = window; } - DataContext = Window.ViewModel; + DataContext = MainWindow.ViewModel; } private void VsyncStatus_PointerReleased(object sender, PointerReleasedEventArgs e) { - Window.ViewModel.AppHost.ToggleVSync(); + MainWindow.ViewModel.AppHost.ToggleVSync(); - Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {Window.ViewModel.AppHost.Device.EnableDeviceVsync}"); + Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {MainWindow.ViewModel.AppHost.Device.EnableDeviceVsync}"); } private void DockedStatus_PointerReleased(object sender, PointerReleasedEventArgs e) @@ -57,9 +57,9 @@ namespace Ryujinx.Ava.UI.Views.Main private void VolumeStatus_OnPointerWheelChanged(object sender, PointerWheelEventArgs e) { // Change the volume by 5% at a time - float newValue = Window.ViewModel.Volume + (float)e.Delta.Y * 0.05f; + float newValue = MainWindow.ViewModel.Volume + (float)e.Delta.Y * 0.05f; - Window.ViewModel.Volume = newValue switch + MainWindow.ViewModel.Volume = newValue switch { < 0 => 0, > 1 => 1, diff --git a/src/Ryujinx/UI/Views/Main/MainViewControls.axaml b/src/Ryujinx/UI/Views/Main/MainViewControls.axaml index cc21b5c60..485d6de01 100644 --- a/src/Ryujinx/UI/Views/Main/MainViewControls.axaml +++ b/src/Ryujinx/UI/Views/Main/MainViewControls.axaml @@ -1,4 +1,4 @@ -(button.Tag.ToString())); - } + if (sender is RadioButton { Tag: string sortStrategy }) + ViewModel.Sort(Enum.Parse(sortStrategy)); } public void Order_Checked(object sender, RoutedEventArgs args) { - if (sender is RadioButton button) - { - ViewModel.Sort(button.Tag.ToString() != "Descending"); - } + if (sender is RadioButton { Tag: string sortOrder }) + ViewModel.Sort(sortOrder is not "Descending"); } private void SearchBox_OnKeyUp(object sender, KeyEventArgs e) diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index 8a155b3cb..f151e69af 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -6,6 +6,7 @@ using Avalonia.Platform; using Avalonia.Threading; using DynamicData; using FluentAvalonia.UI.Controls; +using FluentAvalonia.UI.Windowing; using LibHac.Tools.FsSystem; using Ryujinx.Ava.Common; using Ryujinx.Ava.Common.Locale; @@ -36,7 +37,9 @@ namespace Ryujinx.Ava.UI.Windows { public partial class MainWindow : StyleableWindow { - internal static MainWindowViewModel MainWindowViewModel { get; private set; } + internal static MainWindowViewModel ViewModel { get; private set; } + + internal readonly AvaHostUIHandler UiHandler; private bool _isLoading; private bool _applicationsLoadedOnce; @@ -46,7 +49,6 @@ namespace Ryujinx.Ava.UI.Windows private static string _launchPath; private static string _launchApplicationId; private static bool _startFullscreen; - internal readonly AvaHostUIHandler UiHandler; private IDisposable _appLibraryAppsSubscription; public VirtualFileSystem VirtualFileSystem { get; private set; } @@ -57,7 +59,6 @@ namespace Ryujinx.Ava.UI.Windows public InputManager InputManager { get; private set; } - internal MainWindowViewModel ViewModel { get; private set; } public SettingsWindow SettingsWindow { get; set; } public static bool ShowKeyErrorOnLoad { get; set; } @@ -68,11 +69,7 @@ namespace Ryujinx.Ava.UI.Windows public MainWindow() { - ViewModel = new MainWindowViewModel(); - - MainWindowViewModel = ViewModel; - - DataContext = ViewModel; + DataContext = ViewModel = new MainWindowViewModel(); InitializeComponent(); Load(); @@ -81,6 +78,10 @@ namespace Ryujinx.Ava.UI.Windows ViewModel.Title = App.FormatTitle(); + TitleBar.ExtendsContentIntoTitleBar = true; + TitleBar.TitleBarHitTestType = TitleBarHitTestType.Complex; + + // NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point. StatusBarHeight = StatusBarView.StatusBar.MinHeight; MenuBarHeight = MenuBar.MinHeight; @@ -105,9 +106,7 @@ namespace Ryujinx.Ava.UI.Windows private void OnPlatformColorValuesChanged(object sender, PlatformColorValues e) { if (Application.Current is App app) - { app.ApplyConfiguredTheme(); - } } protected override void OnClosed(EventArgs e) diff --git a/src/Ryujinx/UI/Windows/StyleableWindow.cs b/src/Ryujinx/UI/Windows/StyleableWindow.cs index 94efb764e..b62af9824 100644 --- a/src/Ryujinx/UI/Windows/StyleableWindow.cs +++ b/src/Ryujinx/UI/Windows/StyleableWindow.cs @@ -3,6 +3,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Media; using Avalonia.Media.Imaging; using Avalonia.Platform; +using FluentAvalonia.UI.Windowing; using Ryujinx.Ava.Common.Locale; using Ryujinx.UI.Common.Configuration; using System.IO; @@ -10,21 +11,13 @@ using System.Reflection; namespace Ryujinx.Ava.UI.Windows { - public class StyleableWindow : Window + public class StyleableWindow : AppWindow { - public Bitmap IconImage { get; set; } - public StyleableWindow() { WindowStartupLocation = WindowStartupLocation.CenterOwner; TransparencyLevelHint = [WindowTransparencyLevel.None]; - using Stream stream = Assembly.GetAssembly(typeof(ConfigurationState))!.GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Ryujinx.png")!; - - Icon = new WindowIcon(stream); - stream.Position = 0; - IconImage = new Bitmap(stream); - LocaleManager.Instance.LocaleChanged += LocaleChanged; LocaleChanged(); }