From 3fbacd0f495f087af5a6316da5abe2b4595d8c9d Mon Sep 17 00:00:00 2001 From: Ac_K Date: Fri, 25 Nov 2022 12:41:34 +0100 Subject: [PATCH] ava: Rework DLC Manager, Add various fixes and cleanup (#3896) * Fixes Everything Part.2 * Change sorting, fix remove and heading --- Ryujinx.Ava/Assets/Locales/en_US.json | 4 +- Ryujinx.Ava/Program.cs | 42 ++- .../Ui/Models/DownloadableContentModel.cs | 20 +- .../Ui/ViewModels/MainWindowViewModel.cs | 286 ++++++++---------- .../DownloadableContentManagerWindow.axaml | 147 +++++---- .../DownloadableContentManagerWindow.axaml.cs | 157 ++++++---- Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs | 23 +- Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml | 2 +- .../Ui/Windows/SettingsWindow.axaml.cs | 62 ++-- .../Ui/Windows/TitleUpdateWindow.axaml.cs | 7 + 10 files changed, 411 insertions(+), 339 deletions(-) diff --git a/Ryujinx.Ava/Assets/Locales/en_US.json b/Ryujinx.Ava/Assets/Locales/en_US.json index 5e3ddb8ae..e98988b7c 100644 --- a/Ryujinx.Ava/Assets/Locales/en_US.json +++ b/Ryujinx.Ava/Assets/Locales/en_US.json @@ -410,6 +410,8 @@ "DlcManagerTableHeadingContainerPathLabel": "Container Path", "DlcManagerTableHeadingFullPathLabel": "Full Path", "DlcManagerRemoveAllButton": "Remove All", + "DlcManagerEnableAllButton": "Enable All", + "DlcManagerDisableAllButton": "Disable All", "MenuBarOptionsChangeLanguage": "Change Language", "CommonSort": "Sort", "CommonShowNames": "Show Names", @@ -567,7 +569,7 @@ "DlcWindowTitle": "Manage Game DLC", "UpdateWindowTitle": "Manage Game Updates", "CheatWindowHeading": "Cheats Available for {0} [{1}]", - "DlcWindowHeading": "DLC Available for {0} [{1}]", + "DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})", "UserProfilesEditProfile": "Edit Selected", "Cancel": "Cancel", "Save": "Save", diff --git a/Ryujinx.Ava/Program.cs b/Ryujinx.Ava/Program.cs index 053bccab6..a941302b2 100644 --- a/Ryujinx.Ava/Program.cs +++ b/Ryujinx.Ava/Program.cs @@ -23,19 +23,18 @@ namespace Ryujinx.Ava { internal class Program { - public static double WindowScaleFactor { get; set; } - public static double ActualScaleFactor { get; set; } - public static string Version { get; private set; } - public static string ConfigurationPath { get; private set; } - public static bool PreviewerDetached { get; private set; } - - public static RenderTimer RenderTimer { get; private set; } + public static double WindowScaleFactor { get; set; } + public static double ActualScaleFactor { get; set; } + public static string Version { get; private set; } + public static string ConfigurationPath { get; private set; } + public static bool PreviewerDetached { get; private set; } + public static RenderTimer RenderTimer { get; private set; } [DllImport("user32.dll", SetLastError = true)] public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type); private const uint MB_ICONWARNING = 0x30; - private const int BaseDpi = 96; + private const int BaseDpi = 96; public static void Main(string[] args) { @@ -43,7 +42,7 @@ namespace Ryujinx.Ava if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134)) { - MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MB_ICONWARNING); + _ = MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MB_ICONWARNING); } PreviewerDetached = true; @@ -64,16 +63,16 @@ namespace Ryujinx.Ava .With(new X11PlatformOptions { EnableMultiTouch = true, - EnableIme = true, - UseEGL = false, - UseGpu = false + EnableIme = true, + UseEGL = false, + UseGpu = false }) .With(new Win32PlatformOptions { - EnableMultitouch = true, - UseWgl = false, - AllowEglInitialization = false, - CompositionBackdropCornerRadius = 8f, + EnableMultitouch = true, + UseWgl = false, + AllowEglInitialization = false, + CompositionBackdropCornerRadius = 8.0f, }) .UseSkia() .AfterSetup(_ => @@ -122,12 +121,10 @@ namespace Ryujinx.Ava PrintSystemInfo(); // Enable OGL multithreading on the driver, when available. - BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; - DriverUtilities.ToggleOGLThreading(threadingMode == BackendThreading.Off); + DriverUtilities.ToggleOGLThreading(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off); // Check if keys exists. - bool hasSystemProdKeys = File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")); - if (!hasSystemProdKeys) + if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys"))) { if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys")))) { @@ -143,7 +140,7 @@ namespace Ryujinx.Ava public static void ReloadConfig() { - string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"); + string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"); string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json"); // Now load the configuration as the other subsystems are now registered @@ -197,8 +194,7 @@ namespace Ryujinx.Ava Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}"); SystemInfo.Gather().Print(); - var enabledLogs = Logger.GetEnabledLevels(); - Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(enabledLogs.Count == 0 ? "" : string.Join(", ", enabledLogs))}"); + Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(Logger.GetEnabledLevels().Count == 0 ? "" : string.Join(", ", Logger.GetEnabledLevels()))}"); if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom) { diff --git a/Ryujinx.Ava/Ui/Models/DownloadableContentModel.cs b/Ryujinx.Ava/Ui/Models/DownloadableContentModel.cs index 67530f62c..5f3ca0317 100644 --- a/Ryujinx.Ava/Ui/Models/DownloadableContentModel.cs +++ b/Ryujinx.Ava/Ui/Models/DownloadableContentModel.cs @@ -1,8 +1,22 @@ -namespace Ryujinx.Ava.Ui.Models +using Ryujinx.Ava.Ui.ViewModels; + +namespace Ryujinx.Ava.Ui.Models { - public class DownloadableContentModel + public class DownloadableContentModel : BaseModel { - public bool Enabled { get; set; } + private bool _enabled; + + public bool Enabled + { + get => _enabled; + set + { + _enabled = value; + + OnPropertyChanged(); + } + } + public string TitleId { get; } public string ContainerPath { get; } public string FullPath { get; } diff --git a/Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs b/Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs index 3672f9633..d5d3b7609 100644 --- a/Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs +++ b/Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs @@ -19,6 +19,7 @@ using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.HLE; using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; using Ryujinx.Modules; using Ryujinx.Ui.App.Common; using Ryujinx.Ui.Common; @@ -47,6 +48,7 @@ namespace Ryujinx.Ava.Ui.ViewModels private string _loadHeading; private string _cacheLoadStatus; private string _searchText; + private Timer _searchTimer; private string _dockedStatusText; private string _fifoStatusText; private string _gameStatusText; @@ -115,10 +117,20 @@ namespace Ryujinx.Ava.Ui.ViewModels { _searchText = value; - RefreshView(); + _searchTimer?.Dispose(); + + _searchTimer = new Timer(TimerCallback, null, 1000, 0); } } + private void TimerCallback(object obj) + { + RefreshView(); + + _searchTimer.Dispose(); + _searchTimer = null; + } + public ReadOnlyObservableCollection AppsObservableList { get => _appsObservableList; @@ -200,22 +212,19 @@ namespace Ryujinx.Ava.Ui.ViewModels private string _showUikey = "F4"; private string _pauseKey = "F5"; private string _screenshotkey = "F8"; - private float _volume; + private float _volume; private string _backendText; public ApplicationData SelectedApplication { get { - switch (Glyph) + return Glyph switch { - case Glyph.List: - return _owner.GameList.SelectedApplication; - case Glyph.Grid: - return _owner.GameGrid.SelectedApplication; - default: - return null; - } + Glyph.List => _owner.GameList.SelectedApplication, + Glyph.Grid => _owner.GameGrid.SelectedApplication, + _ => null, + }; } } @@ -408,6 +417,7 @@ namespace Ryujinx.Ava.Ui.ViewModels { _owner.AppHost.Device.SetVolume(_volume); } + OnPropertyChanged(nameof(VolumeStatusText)); OnPropertyChanged(nameof(VolumeMuted)); OnPropertyChanged(); @@ -477,38 +487,36 @@ namespace Ryujinx.Ava.Ui.ViewModels internal void Sort(bool isAscending) { IsAscending = isAscending; + RefreshView(); } internal void Sort(ApplicationSort sort) { SortMode = sort; + RefreshView(); } private IComparer GetComparer() { - switch (SortMode) + return SortMode switch { - case ApplicationSort.LastPlayed: - return new Models.Generic.LastPlayedSortComparer(IsAscending); - case ApplicationSort.FileSize: - return new Models.Generic.FileSizeSortComparer(IsAscending); - case ApplicationSort.TotalTimePlayed: - return new Models.Generic.TimePlayedSortComparer(IsAscending); - case ApplicationSort.Title: - return IsAscending ? SortExpressionComparer.Ascending(app => app.TitleName) : SortExpressionComparer.Descending(app => app.TitleName); - case ApplicationSort.Favorite: - return !IsAscending ? SortExpressionComparer.Ascending(app => app.Favorite) : SortExpressionComparer.Descending(app => app.Favorite); - case ApplicationSort.Developer: - return IsAscending ? SortExpressionComparer.Ascending(app => app.Developer) : SortExpressionComparer.Descending(app => app.Developer); - case ApplicationSort.FileType: - return IsAscending ? SortExpressionComparer.Ascending(app => app.FileExtension) : SortExpressionComparer.Descending(app => app.FileExtension); - case ApplicationSort.Path: - return IsAscending ? SortExpressionComparer.Ascending(app => app.Path) : SortExpressionComparer.Descending(app => app.Path); - default: - return null; - } + ApplicationSort.LastPlayed => new Models.Generic.LastPlayedSortComparer(IsAscending), + ApplicationSort.FileSize => new Models.Generic.FileSizeSortComparer(IsAscending), + ApplicationSort.TotalTimePlayed => new Models.Generic.TimePlayedSortComparer(IsAscending), + ApplicationSort.Title => IsAscending ? SortExpressionComparer.Ascending(app => app.TitleName) + : SortExpressionComparer.Descending(app => app.TitleName), + ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer.Ascending(app => app.Favorite) + : SortExpressionComparer.Descending(app => app.Favorite), + ApplicationSort.Developer => IsAscending ? SortExpressionComparer.Ascending(app => app.Developer) + : SortExpressionComparer.Descending(app => app.Developer), + ApplicationSort.FileType => IsAscending ? SortExpressionComparer.Ascending(app => app.FileExtension) + : SortExpressionComparer.Descending(app => app.FileExtension), + ApplicationSort.Path => IsAscending ? SortExpressionComparer.Ascending(app => app.Path) + : SortExpressionComparer.Descending(app => app.Path), + _ => null, + }; } private void RefreshView() @@ -611,40 +619,31 @@ namespace Ryujinx.Ava.Ui.ViewModels } } - public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite; - public bool IsSortedByTitle => SortMode == ApplicationSort.Title; - public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer; + public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite; + public bool IsSortedByTitle => SortMode == ApplicationSort.Title; + public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer; public bool IsSortedByLastPlayed => SortMode == ApplicationSort.LastPlayed; public bool IsSortedByTimePlayed => SortMode == ApplicationSort.TotalTimePlayed; - public bool IsSortedByType => SortMode == ApplicationSort.FileType; - public bool IsSortedBySize => SortMode == ApplicationSort.FileSize; - public bool IsSortedByPath => SortMode == ApplicationSort.Path; + public bool IsSortedByType => SortMode == ApplicationSort.FileType; + public bool IsSortedBySize => SortMode == ApplicationSort.FileSize; + public bool IsSortedByPath => SortMode == ApplicationSort.Path; public string SortName { get { - switch (SortMode) + return SortMode switch { - case ApplicationSort.Title: - return LocaleManager.Instance["GameListHeaderApplication"]; - case ApplicationSort.Developer: - return LocaleManager.Instance["GameListHeaderDeveloper"]; - case ApplicationSort.LastPlayed: - return LocaleManager.Instance["GameListHeaderLastPlayed"]; - case ApplicationSort.TotalTimePlayed: - return LocaleManager.Instance["GameListHeaderTimePlayed"]; - case ApplicationSort.FileType: - return LocaleManager.Instance["GameListHeaderFileExtension"]; - case ApplicationSort.FileSize: - return LocaleManager.Instance["GameListHeaderFileSize"]; - case ApplicationSort.Path: - return LocaleManager.Instance["GameListHeaderPath"]; - case ApplicationSort.Favorite: - return LocaleManager.Instance["CommonFavorite"]; - } - - return string.Empty; + ApplicationSort.Title => LocaleManager.Instance["GameListHeaderApplication"], + ApplicationSort.Developer => LocaleManager.Instance["GameListHeaderDeveloper"], + ApplicationSort.LastPlayed => LocaleManager.Instance["GameListHeaderLastPlayed"], + ApplicationSort.TotalTimePlayed => LocaleManager.Instance["GameListHeaderTimePlayed"], + ApplicationSort.FileType => LocaleManager.Instance["GameListHeaderFileExtension"], + ApplicationSort.FileSize => LocaleManager.Instance["GameListHeaderFileSize"], + ApplicationSort.Path => LocaleManager.Instance["GameListHeaderPath"], + ApplicationSort.Favorite => LocaleManager.Instance["CommonFavorite"], + _ => string.Empty, + }; } } @@ -668,6 +667,7 @@ namespace Ryujinx.Ava.Ui.ViewModels get => KeyGesture.Parse(_showUikey); set { _showUikey = value.ToString(); + OnPropertyChanged(); } } @@ -677,6 +677,7 @@ namespace Ryujinx.Ava.Ui.ViewModels get => KeyGesture.Parse(_screenshotkey); set { _screenshotkey = value.ToString(); + OnPropertyChanged(); } } @@ -686,14 +687,15 @@ namespace Ryujinx.Ava.Ui.ViewModels get => KeyGesture.Parse(_pauseKey); set { _pauseKey = value.ToString(); + OnPropertyChanged(); } } - public bool IsGridSmall => ConfigurationState.Instance.Ui.GridSize == 1; + public bool IsGridSmall => ConfigurationState.Instance.Ui.GridSize == 1; public bool IsGridMedium => ConfigurationState.Instance.Ui.GridSize == 2; - public bool IsGridLarge => ConfigurationState.Instance.Ui.GridSize == 3; - public bool IsGridHuge => ConfigurationState.Instance.Ui.GridSize == 4; + public bool IsGridLarge => ConfigurationState.Instance.Ui.GridSize == 3; + public bool IsGridHuge => ConfigurationState.Instance.Ui.GridSize == 4; public int GridSizeScale { @@ -728,14 +730,14 @@ namespace Ryujinx.Ava.Ui.ViewModels if (_owner.AppHost.Device.System.SearchingForAmiibo(out int deviceId)) { - string titleId = _owner.AppHost.Device.Application.TitleIdText.ToUpper(); + string titleId = _owner.AppHost.Device.Application.TitleIdText.ToUpper(); AmiiboWindow window = new(_showAll, _lastScannedAmiiboId, titleId); await window.ShowDialog(_owner); if (window.IsScanned) { - _showAll = window.ViewModel.ShowAllAmiibo; + _showAll = window.ViewModel.ShowAllAmiibo; _lastScannedAmiiboId = window.ScannedAmiibo.GetId(); _owner.AppHost.Device.System.ScanAmiibo(deviceId, _lastScannedAmiiboId, window.ViewModel.UseRandomUuid); @@ -766,8 +768,9 @@ namespace Ryujinx.Ava.Ui.ViewModels private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e) { - StatusBarProgressValue = e.NumAppsLoaded; + StatusBarProgressValue = e.NumAppsLoaded; StatusBarProgressMaximum = e.NumAppsFound; + LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", StatusBarProgressValue, StatusBarProgressMaximum); Dispatcher.UIThread.Post(() => @@ -792,9 +795,11 @@ namespace Ryujinx.Ava.Ui.ViewModels await Dispatcher.UIThread.InvokeAsync(() => { Applications.Clear(); + _owner.LoadProgressBar.IsVisible = true; - StatusBarProgressMaximum = 0; - StatusBarProgressValue = 0; + StatusBarProgressMaximum = 0; + StatusBarProgressValue = 0; + LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", 0, 0); }); @@ -842,12 +847,12 @@ namespace Ryujinx.Ava.Ui.ViewModels } }); - dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } }); + dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } }); dialog.Filters.Add(new FileDialogFilter { Name = "PFS0", Extensions = { "pfs0" } }); - dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } }); - dialog.Filters.Add(new FileDialogFilter { Name = "NCA", Extensions = { "nca" } }); - dialog.Filters.Add(new FileDialogFilter { Name = "NRO", Extensions = { "nro" } }); - dialog.Filters.Add(new FileDialogFilter { Name = "NSO", Extensions = { "nso" } }); + dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } }); + dialog.Filters.Add(new FileDialogFilter { Name = "NCA", Extensions = { "nca" } }); + dialog.Filters.Add(new FileDialogFilter { Name = "NRO", Extensions = { "nro" } }); + dialog.Filters.Add(new FileDialogFilter { Name = "NSO", Extensions = { "nso" } }); string[] files = await dialog.ShowAsync(_owner); @@ -878,10 +883,12 @@ namespace Ryujinx.Ava.Ui.ViewModels { ShowUiKey = new KeyGesture(showUiKey, KeyModifiers.None); } + if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey)) { ScreenshotKey = new KeyGesture(screenshotKey, KeyModifiers.None); } + if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey)) { PauseKey = new KeyGesture(pauseKey, KeyModifiers.None); @@ -941,9 +948,7 @@ namespace Ryujinx.Ava.Ui.ViewModels _lastFullscreenToggle = Environment.TickCount64; - WindowState state = _owner.WindowState; - - if (state == WindowState.FullScreen) + if (_owner.WindowState == WindowState.FullScreen) { _owner.WindowState = WindowState.Normal; @@ -971,8 +976,7 @@ namespace Ryujinx.Ava.Ui.ViewModels { if (IsGameRunning) { - ConfigurationState.Instance.System.EnableDockedMode.Value = - !ConfigurationState.Instance.System.EnableDockedMode.Value; + ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value; } } @@ -985,6 +989,7 @@ namespace Ryujinx.Ava.Ui.ViewModels else if (IsGameRunning) { await Task.Delay(100); + _owner.AppHost?.ShowExitPrompt(); } } @@ -994,6 +999,7 @@ namespace Ryujinx.Ava.Ui.ViewModels _owner.SettingsWindow = new(_owner.VirtualFileSystem, _owner.ContentManager); await _owner.SettingsWindow.ShowDialog(_owner); + LoadConfigurableHotKeys(); } @@ -1004,9 +1010,7 @@ namespace Ryujinx.Ava.Ui.ViewModels public async void OpenAboutWindow() { - AboutWindow window = new(); - - await window.ShowDialog(_owner); + await new AboutWindow().ShowDialog(_owner); } public void ChangeLanguage(object obj) @@ -1020,7 +1024,7 @@ namespace Ryujinx.Ava.Ui.ViewModels try { ProgressMaximum = total; - ProgressValue = current; + ProgressValue = current; switch (state) { @@ -1030,13 +1034,13 @@ namespace Ryujinx.Ava.Ui.ViewModels { case PtcLoadingState.Start: case PtcLoadingState.Loading: - LoadHeading = LocaleManager.Instance["CompilingPPTC"]; + LoadHeading = LocaleManager.Instance["CompilingPPTC"]; IsLoadingIndeterminate = false; break; case PtcLoadingState.Loaded: - LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName); + LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName); IsLoadingIndeterminate = true; - CacheLoadStatus = ""; + CacheLoadStatus = ""; break; } break; @@ -1046,13 +1050,13 @@ namespace Ryujinx.Ava.Ui.ViewModels { case ShaderCacheLoadingState.Start: case ShaderCacheLoadingState.Loading: - LoadHeading = LocaleManager.Instance["CompilingShaders"]; + LoadHeading = LocaleManager.Instance["CompilingShaders"]; IsLoadingIndeterminate = false; break; case ShaderCacheLoadingState.Loaded: - LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName); + LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName); IsLoadingIndeterminate = true; - CacheLoadStatus = ""; + CacheLoadStatus = ""; break; } break; @@ -1065,14 +1069,12 @@ namespace Ryujinx.Ava.Ui.ViewModels public void OpenUserSaveDirectory() { - var selection = SelectedApplication; - + ApplicationData selection = SelectedApplication; if (selection != null) { Task.Run(() => { - if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, - out ulong titleIdNumber)) + if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber)) { Dispatcher.UIThread.Post(async () => { @@ -1082,8 +1084,8 @@ namespace Ryujinx.Ava.Ui.ViewModels return; } - var userId = new LibHac.Fs.UserId((ulong)_owner.AccountManager.LastOpenedUser.UserId.High, (ulong)_owner.AccountManager.LastOpenedUser.UserId.Low); - var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveType: default, userId, saveDataId: default, index: default); + UserId userId = new((ulong)_owner.AccountManager.LastOpenedUser.UserId.High, (ulong)_owner.AccountManager.LastOpenedUser.UserId.Low); + SaveDataFilter saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveType: default, userId, saveDataId: default, index: default); OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber); }); } @@ -1091,8 +1093,7 @@ namespace Ryujinx.Ava.Ui.ViewModels public void ToggleFavorite() { - var selection = SelectedApplication; - + ApplicationData selection = SelectedApplication; if (selection != null) { selection.Favorite = !selection.Favorite; @@ -1108,11 +1109,10 @@ namespace Ryujinx.Ava.Ui.ViewModels public void OpenModsDirectory() { - var selection = SelectedApplication; - + ApplicationData selection = SelectedApplication; if (selection != null) { - string modsBasePath = _owner.VirtualFileSystem.ModLoader.GetModsBasePath(); + string modsBasePath = _owner.VirtualFileSystem.ModLoader.GetModsBasePath(); string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(modsBasePath, selection.TitleId); OpenHelper.OpenFolder(titleModsPath); @@ -1121,12 +1121,12 @@ namespace Ryujinx.Ava.Ui.ViewModels public void OpenSdModsDirectory() { - var selection = SelectedApplication; + ApplicationData selection = SelectedApplication; if (selection != null) { string sdModsBasePath = _owner.VirtualFileSystem.ModLoader.GetSdModsBasePath(); - string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, selection.TitleId); + string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, selection.TitleId); OpenHelper.OpenFolder(titleModsPath); } @@ -1134,13 +1134,11 @@ namespace Ryujinx.Ava.Ui.ViewModels public void OpenPtcDirectory() { - var selection = SelectedApplication; - + ApplicationData selection = SelectedApplication; if (selection != null) { - string ptcDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu"); - - string mainPath = Path.Combine(ptcDir, "0"); + string ptcDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu"); + string mainPath = Path.Combine(ptcDir, "0"); string backupPath = Path.Combine(ptcDir, "1"); if (!Directory.Exists(ptcDir)) @@ -1156,16 +1154,18 @@ namespace Ryujinx.Ava.Ui.ViewModels public async void PurgePtcCache() { - var selection = SelectedApplication; - + ApplicationData selection = SelectedApplication; if (selection != null) { - DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "0")); + DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "0")); DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "1")); // FIXME: Found a way to reproduce the bold effect on the title name (fork?). - UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"], - string.Format(LocaleManager.Instance["DialogPPTCDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]); + UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"], + string.Format(LocaleManager.Instance["DialogPPTCDeletionMessage"], selection.TitleName), + LocaleManager.Instance["InputDialogYes"], + LocaleManager.Instance["InputDialogNo"], + LocaleManager.Instance["RyujinxConfirm"]); List cacheFiles = new(); @@ -1198,8 +1198,7 @@ namespace Ryujinx.Ava.Ui.ViewModels public void OpenShaderCacheDirectory() { - var selection = SelectedApplication; - + ApplicationData selection = SelectedApplication; if (selection != null) { string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader"); @@ -1220,18 +1219,20 @@ namespace Ryujinx.Ava.Ui.ViewModels public async void PurgeShaderCache() { - var selection = SelectedApplication; - + ApplicationData selection = SelectedApplication; if (selection != null) { DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader")); // FIXME: Found a way to reproduce the bold effect on the title name (fork?). UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"], - string.Format(LocaleManager.Instance["DialogShaderDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]); + string.Format(LocaleManager.Instance["DialogShaderDeletionMessage"], selection.TitleName), + LocaleManager.Instance["InputDialogYes"], + LocaleManager.Instance["InputDialogNo"], + LocaleManager.Instance["RyujinxConfirm"]); - List oldCacheDirectories = new List(); - List newCacheFiles = new List(); + List oldCacheDirectories = new(); + List newCacheFiles = new(); if (shaderCacheDir.Exists) { @@ -1279,38 +1280,28 @@ namespace Ryujinx.Ava.Ui.ViewModels public async void OpenTitleUpdateManager() { - var selection = SelectedApplication; - + ApplicationData selection = SelectedApplication; if (selection != null) { - TitleUpdateWindow titleUpdateManager = - new(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName); - - await titleUpdateManager.ShowDialog(_owner); + await new TitleUpdateWindow(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName).ShowDialog(_owner); } } public async void OpenDownloadableContentManager() { - var selection = SelectedApplication; - + ApplicationData selection = SelectedApplication; if (selection != null) { - DownloadableContentManagerWindow downloadableContentManager = new(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName); - - await downloadableContentManager.ShowDialog(_owner); + await new DownloadableContentManagerWindow(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName).ShowDialog(_owner); } } public async void OpenCheatManager() { - var selection = SelectedApplication; - + ApplicationData selection = SelectedApplication; if (selection != null) { - CheatWindow cheatManager = new(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName); - - await cheatManager.ShowDialog(_owner); + await new CheatWindow(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName).ShowDialog(_owner); } } @@ -1321,13 +1312,10 @@ namespace Ryujinx.Ava.Ui.ViewModels return; } - var application = _owner.AppHost.Device.Application; - + ApplicationLoader application = _owner.AppHost.Device.Application; if (application != null) { - CheatWindow cheatManager = new(_owner.VirtualFileSystem, application.TitleIdText, application.TitleName); - - await cheatManager.ShowDialog(_owner); + await new CheatWindow(_owner.VirtualFileSystem, application.TitleIdText, application.TitleName).ShowDialog(_owner); _owner.AppHost.Device.EnableCheats(); } @@ -1335,14 +1323,12 @@ namespace Ryujinx.Ava.Ui.ViewModels public void OpenDeviceSaveDirectory() { - var selection = SelectedApplication; - + ApplicationData selection = SelectedApplication; if (selection != null) { Task.Run(() => { - if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, - out ulong titleIdNumber)) + if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber)) { Dispatcher.UIThread.Post(async () => { @@ -1360,14 +1346,12 @@ namespace Ryujinx.Ava.Ui.ViewModels public void OpenBcatSaveDirectory() { - var selection = SelectedApplication; - + ApplicationData selection = SelectedApplication; if (selection != null) { Task.Run(() => { - if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, - out ulong titleIdNumber)) + if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber)) { Dispatcher.UIThread.Post(async () => { @@ -1420,12 +1404,10 @@ namespace Ryujinx.Ava.Ui.ViewModels _owner.Close(); } - private async Task HandleFirmwareInstallation(string path) + private async Task HandleFirmwareInstallation(string filename) { try { - string filename = path; - SystemVersion firmwareVersion = _owner.ContentManager.VerifyFirmwarePackage(filename); if (firmwareVersion == null) @@ -1437,7 +1419,6 @@ namespace Ryujinx.Ava.Ui.ViewModels string dialogTitle = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallTitle"], firmwareVersion.VersionString); - SystemVersion currentVersion = _owner.ContentManager.GetCurrentFirmwareVersion(); string dialogMessage = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallMessage"], firmwareVersion.VersionString); @@ -1480,11 +1461,12 @@ namespace Ryujinx.Ava.Ui.ViewModels string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallSuccessMessage"], firmwareVersion.VersionString); await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance["InputDialogOk"], "", LocaleManager.Instance["RyujinxInfo"]); + Logger.Info?.Print(LogClass.Application, message); // Purge Applet Cache. - DirectoryInfo miiEditorCacheFolder = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache")); + DirectoryInfo miiEditorCacheFolder = new DirectoryInfo(Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache")); if (miiEditorCacheFolder.Exists) { @@ -1514,8 +1496,8 @@ namespace Ryujinx.Ava.Ui.ViewModels catch (LibHac.Common.Keys.MissingKeyException ex) { Logger.Error?.Print(LogClass.Application, ex.ToString()); - Dispatcher.UIThread.Post(async () => await - UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, _owner)); + + Dispatcher.UIThread.Post(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, _owner)); } catch (Exception ex) { @@ -1527,8 +1509,8 @@ namespace Ryujinx.Ava.Ui.ViewModels { OpenFileDialog dialog = new() { AllowMultiple = false }; dialog.Filters.Add(new FileDialogFilter { Name = LocaleManager.Instance["FileDialogAllTypes"], Extensions = { "xci", "zip" } }); - dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } }); - dialog.Filters.Add(new FileDialogFilter { Name = "ZIP", Extensions = { "zip" } }); + dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } }); + dialog.Filters.Add(new FileDialogFilter { Name = "ZIP", Extensions = { "zip" } }); string[] file = await dialog.ShowAsync(_owner); diff --git a/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml b/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml index 068ea826a..0189c505e 100644 --- a/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml +++ b/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml @@ -3,89 +3,126 @@ xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" - SizeToContent="Height" - Width="600" MinHeight="500" Height="500" - WindowStartupLocation="CenterOwner" + Width="800" + Height="500" MinWidth="600" + MinHeight="500" + SizeToContent="Height" + WindowStartupLocation="CenterOwner" mc:Ignorable="d"> + - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml.cs index 972ffbc3e..b1c86afcb 100644 --- a/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml.cs @@ -8,6 +8,7 @@ using LibHac.FsSystem; using LibHac.Tools.Fs; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; +using LibHac.Tools.FsSystem.Save; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Ui.Controls; using Ryujinx.Ava.Ui.Models; @@ -16,8 +17,11 @@ using Ryujinx.Common.Utilities; using Ryujinx.HLE.FileSystem; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; using System.IO; using System.Linq; +using System.Reactive.Linq; using System.Text; using System.Threading.Tasks; using Path = System.IO.Path; @@ -27,14 +31,13 @@ namespace Ryujinx.Ava.Ui.Windows public partial class DownloadableContentManagerWindow : StyleableWindow { private readonly List _downloadableContentContainerList; - private readonly string _downloadableContentJsonPath; + private readonly string _downloadableContentJsonPath; - public VirtualFileSystem VirtualFileSystem { get; } - public AvaloniaList DownloadableContents { get; set; } = new AvaloniaList(); - public ulong TitleId { get; } - public string TitleName { get; } + private VirtualFileSystem _virtualFileSystem { get; } + private AvaloniaList _downloadableContents { get; set; } - public string Heading => string.Format(LocaleManager.Instance["DlcWindowHeading"], TitleName, TitleId.ToString("X16")); + private ulong TitleId { get; } + private string TitleName { get; } public DownloadableContentManagerWindow() { @@ -42,14 +45,15 @@ namespace Ryujinx.Ava.Ui.Windows InitializeComponent(); - Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"]; + Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["DlcWindowTitle"]} - {TitleName} ({TitleId:X16})"; } public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) { - VirtualFileSystem = virtualFileSystem; - TitleId = titleId; - TitleName = titleName; + _virtualFileSystem = virtualFileSystem; + _downloadableContents = new AvaloniaList(); + TitleId = titleId; + TitleName = titleName; _downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json"); @@ -66,9 +70,24 @@ namespace Ryujinx.Ava.Ui.Windows InitializeComponent(); - Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"]; + RemoveButton.IsEnabled = false; + + DlcDataGrid.SelectionChanged += DlcDataGrid_SelectionChanged; + + Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["DlcWindowTitle"]} - {TitleName} ({TitleId:X16})"; LoadDownloadableContents(); + PrintHeading(); + } + + private void DlcDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + RemoveButton.IsEnabled = (DlcDataGrid.SelectedItems.Count > 0); + } + + private void PrintHeading() + { + Heading.Text = string.Format(LocaleManager.Instance["DlcWindowHeading"], _downloadableContents.Count, TitleName, TitleId.ToString("X16")); } private void LoadDownloadableContents() @@ -79,23 +98,23 @@ namespace Ryujinx.Ava.Ui.Windows { using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath); - PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage()); + PartitionFileSystem pfs = new(containerFile.AsStorage()); - VirtualFileSystem.ImportTickets(pfs); + _virtualFileSystem.ImportTickets(pfs); foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList) { - using var ncaFile = new UniqueRef(); + using UniqueRef ncaFile = new(); pfs.OpenFile(ref ncaFile.Ref(), downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath); + Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath); if (nca != null) { - DownloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), - downloadableContentContainer.ContainerPath, - downloadableContentNca.FullPath, - downloadableContentNca.Enabled)); + _downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), + downloadableContentContainer.ContainerPath, + downloadableContentNca.FullPath, + downloadableContentNca.Enabled)); } } } @@ -105,11 +124,11 @@ namespace Ryujinx.Ava.Ui.Windows Save(); } - private Nca TryCreateNca(IStorage ncaStorage, string containerPath) + private Nca TryOpenNca(IStorage ncaStorage, string containerPath) { try { - return new Nca(VirtualFileSystem.KeySet, ncaStorage); + return new Nca(_virtualFileSystem.KeySet, ncaStorage); } catch (Exception ex) { @@ -124,61 +143,73 @@ namespace Ryujinx.Ava.Ui.Windows private async Task AddDownloadableContent(string path) { - if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null) + if (!File.Exists(path) || _downloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null) { return; } - using (FileStream containerFile = File.OpenRead(path)) + using FileStream containerFile = File.OpenRead(path); + + PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage()); + bool containsDownloadableContent = false; + + _virtualFileSystem.ImportTickets(partitionFileSystem); + + foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca")) { - PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage()); - bool containsDownloadableContent = false; + using var ncaFile = new UniqueRef(); - VirtualFileSystem.ImportTickets(pfs); + partitionFileSystem.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) + Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path); + if (nca == null) { - using var ncaFile = new UniqueRef(); - - pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), path); - - if (nca == null) - { - continue; - } - - if (nca.Header.ContentType == NcaContentType.PublicData) - { - if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != TitleId) - { - break; - } - - DownloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true)); - - containsDownloadableContent = true; - } + continue; } - if (!containsDownloadableContent) + if (nca.Header.ContentType == NcaContentType.PublicData) { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]); + if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != TitleId) + { + break; + } + + _downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true)); + + containsDownloadableContent = true; } } + + if (!containsDownloadableContent) + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]); + } } private void RemoveDownloadableContents(bool removeSelectedOnly = false) { if (removeSelectedOnly) { - DownloadableContents.RemoveAll(DownloadableContents.Where(x => x.Enabled).ToList()); + AvaloniaList removedItems = new(); + + foreach (var item in DlcDataGrid.SelectedItems) + { + removedItems.Add(item as DownloadableContentModel); + } + + DlcDataGrid.SelectedItems.Clear(); + + foreach (var item in removedItems) + { + _downloadableContents.RemoveAll(_downloadableContents.Where(x => x.TitleId == item.TitleId).ToList()); + } } else { - DownloadableContents.Clear(); + _downloadableContents.Clear(); } + + PrintHeading(); } public void RemoveSelected() @@ -191,6 +222,22 @@ namespace Ryujinx.Ava.Ui.Windows RemoveDownloadableContents(); } + public void EnableAll() + { + foreach(var item in _downloadableContents) + { + item.Enabled = true; + } + } + + public void DisableAll() + { + foreach (var item in _downloadableContents) + { + item.Enabled = false; + } + } + public async void Add() { OpenFileDialog dialog = new OpenFileDialog() @@ -214,6 +261,8 @@ namespace Ryujinx.Ava.Ui.Windows await AddDownloadableContent(file); } } + + PrintHeading(); } public void Save() @@ -222,7 +271,7 @@ namespace Ryujinx.Ava.Ui.Windows DownloadableContentContainer container = default; - foreach (DownloadableContentModel downloadableContent in DownloadableContents) + foreach (DownloadableContentModel downloadableContent in _downloadableContents) { if (container.ContainerPath != downloadableContent.ContainerPath) { diff --git a/Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs index be4517c86..e874982ab 100644 --- a/Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs @@ -90,8 +90,8 @@ namespace Ryujinx.Ava.Ui.Windows Title = $"Ryujinx {Program.Version}"; - Height = Height / Program.WindowScaleFactor; - Width = Width / Program.WindowScaleFactor; + Height /= Program.WindowScaleFactor; + Width /= Program.WindowScaleFactor; if (Program.PreviewerDetached) { @@ -523,23 +523,20 @@ namespace Ryujinx.Ava.Ui.Windows public static void UpdateGraphicsConfig() { - int resScale = ConfigurationState.Instance.Graphics.ResScale; - float resScaleCustom = ConfigurationState.Instance.Graphics.ResScaleCustom; - - GraphicsConfig.ResScale = resScale == -1 ? resScaleCustom : resScale; - GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy; - GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath; - GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache; + GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1 ? ConfigurationState.Instance.Graphics.ResScaleCustom : ConfigurationState.Instance.Graphics.ResScale; + GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy; + GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath; + GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache; GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression; - GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE; + GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE; } public void LoadHotKeys() { - HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt)); + HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt)); HotKeyManager.SetHotKey(FullscreenHotKey2, new KeyGesture(Key.F11)); - HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9)); - HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape)); + HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9)); + HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape)); } public static void SaveConfig() diff --git a/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml b/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml index 0946a2eb9..1791d8ea5 100644 --- a/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml +++ b/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml @@ -75,7 +75,7 @@ Spacing="10"> diff --git a/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml.cs index 810beb3e0..0e610d77b 100644 --- a/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml.cs @@ -16,7 +16,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading.Tasks; using TimeZone = Ryujinx.Ava.Ui.Models.TimeZone; namespace Ryujinx.Ava.Ui.Windows @@ -31,7 +30,7 @@ namespace Ryujinx.Ava.Ui.Windows { Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["Settings"]}"; - ViewModel = new SettingsViewModel(virtualFileSystem, contentManager, this); + ViewModel = new SettingsViewModel(virtualFileSystem, contentManager, this); DataContext = ViewModel; InitializeComponent(); @@ -48,7 +47,7 @@ namespace Ryujinx.Ava.Ui.Windows public SettingsWindow() { - ViewModel = new SettingsViewModel(); + ViewModel = new SettingsViewModel(); DataContext = ViewModel; InitializeComponent(); @@ -79,7 +78,7 @@ namespace Ryujinx.Ava.Ui.Windows PointerPressed += MouseClick; - IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad(ViewModel.AvaloniaKeyboardDriver.GamepadsIds[0]); + IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad(ViewModel.AvaloniaKeyboardDriver.GamepadsIds[0]); IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard); _currentAssigner.GetInputAndAssign(assigner); @@ -92,6 +91,7 @@ namespace Ryujinx.Ava.Ui.Windows _currentAssigner.Cancel(); _currentAssigner = null; + button.IsChecked = false; } } @@ -122,36 +122,19 @@ namespace Ryujinx.Ava.Ui.Windows { if (e.SelectedItem is NavigationViewItem navitem) { - switch (navitem.Tag.ToString()) + NavPanel.Content = navitem.Tag.ToString() switch { - case "UiPage": - NavPanel.Content = UiPage; - break; - case "InputPage": - NavPanel.Content = InputPage; - break; - case "HotkeysPage": - NavPanel.Content = HotkeysPage; - break; - case "SystemPage": - NavPanel.Content = SystemPage; - break; - case "CpuPage": - NavPanel.Content = CpuPage; - break; - case "GraphicsPage": - NavPanel.Content = GraphicsPage; - break; - case "AudioPage": - NavPanel.Content = AudioPage; - break; - case "NetworkPage": - NavPanel.Content = NetworkPage; - break; - case "LoggingPage": - NavPanel.Content = LoggingPage; - break; - } + "UiPage" => UiPage, + "InputPage" => InputPage, + "HotkeysPage" => HotkeysPage, + "SystemPage" => SystemPage, + "CpuPage" => CpuPage, + "GraphicsPage" => GraphicsPage, + "AudioPage" => AudioPage, + "NetworkPage" => NetworkPage, + "LoggingPage" => LoggingPage, + _ => throw new NotImplementedException() + }; } } @@ -178,13 +161,18 @@ namespace Ryujinx.Ava.Ui.Windows private void RemoveButton_OnClick(object sender, RoutedEventArgs e) { - List selected = new(GameList.SelectedItems.Cast()); + int oldIndex = GameList.SelectedIndex; - foreach (string path in selected) + foreach (string path in new List(GameList.SelectedItems.Cast())) { ViewModel.GameDirectories.Remove(path); ViewModel.DirectoryChanged = true; } + + if (GameList.ItemCount > 0) + { + GameList.SelectedIndex = oldIndex < GameList.ItemCount ? oldIndex : 0; + } } private void TimeZoneBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) @@ -214,7 +202,6 @@ namespace Ryujinx.Ava.Ui.Windows private void SaveButton_Clicked(object sender, RoutedEventArgs e) { SaveSettings(); - Close(); } @@ -232,7 +219,6 @@ namespace Ryujinx.Ava.Ui.Windows private void SaveSettings() { ViewModel.SaveSettings(); - ControllerSettings?.SaveCurrentProfile(); if (Owner is MainWindow window && ViewModel.DirectoryChanged) @@ -246,8 +232,10 @@ namespace Ryujinx.Ava.Ui.Windows protected override void OnClosed(EventArgs e) { ControllerSettings.Dispose(); + _currentAssigner?.Cancel(); _currentAssigner = null; + base.OnClosed(e); } } diff --git a/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs index b4d5d5b5d..751c7afc6 100644 --- a/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs @@ -131,6 +131,13 @@ namespace Ryujinx.Ava.Ui.Windows nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure(); TitleUpdates.Add(new TitleUpdateModel(controlData, path)); + + foreach (var update in TitleUpdates) + { + update.IsEnabled = false; + } + + TitleUpdates.Last().IsEnabled = true; } else {