language feature: field keyword & partial properties for observable properties in main & settings window view models

This commit is contained in:
GreemDev
2025-09-12 14:10:52 -05:00
parent a5a13271d5
commit 0e26e35ec0
2 changed files with 339 additions and 251 deletions

View File

@ -64,55 +64,101 @@ namespace Ryujinx.Ava.UI.ViewModels
public partial class MainWindowViewModel : BaseModel public partial class MainWindowViewModel : BaseModel
{ {
private const int HotKeyPressDelayMs = 500; private const int HotKeyPressDelayMs = 500;
private delegate int LoadContentFromFolderDelegate(List<string> dirs, out int numRemoved); private delegate int LoadContentFromFolderDelegate(List<string> dirs, out int numRemoved);
[ObservableProperty] private ObservableCollectionExtended<ApplicationData> _applications; [ObservableProperty] public partial ObservableCollectionExtended<ApplicationData> Applications { get; set; }
[ObservableProperty] private string _aspectRatioStatusText;
[ObservableProperty] private string _loadHeading; [ObservableProperty] public partial string AspectRatioStatusText { get; set; }
[ObservableProperty] private string _cacheLoadStatus;
[ObservableProperty] private string _dockedStatusText; [ObservableProperty] public partial string LoadHeading { get; set; }
[ObservableProperty] private string _fifoStatusText;
[ObservableProperty] private string _gameStatusText; [ObservableProperty] public partial string CacheLoadStatus { get; set; }
[ObservableProperty] private string _volumeStatusText;
[ObservableProperty] private string _gpuNameText; [ObservableProperty] public partial string DockedStatusText { get; set; }
[ObservableProperty] private string _backendText;
[ObservableProperty] private string _shaderCountText; [ObservableProperty] public partial string FifoStatusText { get; set; }
[ObservableProperty] private bool _showShaderCompilationHint;
[ObservableProperty] private bool _isFullScreen; [ObservableProperty] public partial string GameStatusText { get; set; }
[ObservableProperty] private int _progressMaximum;
[ObservableProperty] private int _progressValue; [ObservableProperty] public partial string VolumeStatusText { get; set; }
[ObservableProperty] private bool _showMenuAndStatusBar = true;
[ObservableProperty] private bool _showStatusSeparator; [ObservableProperty] public partial string GpuNameText { get; set; }
[ObservableProperty] private Brush _progressBarForegroundColor;
[ObservableProperty] private Brush _progressBarBackgroundColor; [ObservableProperty] public partial string BackendText { get; set; }
[ObservableProperty] private Brush _vSyncModeColor;
#nullable enable [ObservableProperty] public partial string ShaderCountText { get; set; }
[ObservableProperty] private byte[]? _selectedIcon;
#nullable disable [ObservableProperty] public partial bool ShowShaderCompilationHint { get; set; }
[ObservableProperty] private int _statusBarProgressMaximum;
[ObservableProperty] private int _statusBarProgressValue; [ObservableProperty] public partial bool IsFullScreen { get; set; }
[ObservableProperty] private string _statusBarProgressStatusText;
[ObservableProperty] private bool _statusBarProgressStatusVisible; [ObservableProperty] public partial int ProgressMaximum { get; set; }
[ObservableProperty] private bool _isPaused;
[ObservableProperty] private bool _isLoadingIndeterminate = true; [ObservableProperty] public partial int ProgressValue { get; set; }
[ObservableProperty] private bool _showAll;
[ObservableProperty] private string _lastScannedAmiiboId; [ObservableProperty] public partial bool ShowMenuAndStatusBar { get; set; } = true;
[ObservableProperty] public partial bool ShowStatusSeparator { get; set; }
[ObservableProperty] public partial Brush ProgressBarForegroundColor { get; set; }
[ObservableProperty] public partial Brush ProgressBarBackgroundColor { get; set; }
#pragma warning disable MVVMTK0042 // Must stay a normal observable field declaration since this is used as an out parameter target
[ObservableProperty] private ReadOnlyObservableCollection<ApplicationData> _appsObservableList; [ObservableProperty] private ReadOnlyObservableCollection<ApplicationData> _appsObservableList;
[ObservableProperty] private long _lastFullscreenToggle = Environment.TickCount64; #pragma warning restore MVVMTK0042
[ObservableProperty] private bool _showContent = true;
[ObservableProperty] private float _volumeBeforeMute; [ObservableProperty] public partial Brush VSyncModeColor { get; set; }
[ObservableProperty] private bool _areMimeTypesRegistered = FileAssociationHelper.AreMimeTypesRegistered; #nullable enable
[ObservableProperty] private Cursor _cursor; [ObservableProperty] public partial byte[]? SelectedIcon { get; set; }
[ObservableProperty] private string _title; #nullable disable
[ObservableProperty] private WindowState _windowState; [ObservableProperty] public partial int StatusBarProgressMaximum { get; set; }
[ObservableProperty] private double _windowWidth;
[ObservableProperty] private double _windowHeight; [ObservableProperty] public partial int StatusBarProgressValue { get; set; }
[ObservableProperty] private bool _isActive;
[ObservableProperty] private bool _isSubMenuOpen; [ObservableProperty] public partial string StatusBarProgressStatusText { get; set; }
[ObservableProperty] private ApplicationContextMenu _listAppContextMenu;
[ObservableProperty] private ApplicationContextMenu _gridAppContextMenu; [ObservableProperty] public partial bool StatusBarProgressStatusVisible { get; set; }
[ObservableProperty] private bool _isRyuLdnEnabled;
[ObservableProperty] private bool _updateAvailable; [ObservableProperty] public partial bool IsPaused { get; set; }
[ObservableProperty] public partial bool IsLoadingIndeterminate { get; set; } = true;
[ObservableProperty] public partial bool ShowAll { get; set; }
[ObservableProperty] public partial string LastScannedAmiiboId { get; set; }
[ObservableProperty]
public partial long LastFullscreenToggle { get; set; } = Environment.TickCount64;
[ObservableProperty] public partial bool ShowContent { get; set; } = true;
[ObservableProperty] public partial float VolumeBeforeMute { get; set; }
[ObservableProperty]
public partial bool AreMimeTypesRegistered { get; set; } = FileAssociationHelper.AreMimeTypesRegistered;
[ObservableProperty] public partial Cursor Cursor { get; set; }
[ObservableProperty] public partial string Title { get; set; }
[ObservableProperty] public partial WindowState WindowState { get; set; }
[ObservableProperty] public partial double WindowWidth { get; set; }
[ObservableProperty] public partial double WindowHeight { get; set; }
[ObservableProperty] public partial bool IsActive { get; set; }
[ObservableProperty] public partial bool IsSubMenuOpen { get; set; }
[ObservableProperty] public partial ApplicationContextMenu ListAppContextMenu { get; set; }
[ObservableProperty] public partial ApplicationContextMenu GridAppContextMenu { get; set; }
[ObservableProperty] public partial bool IsRyuLdnEnabled { get; set; }
[ObservableProperty] public partial bool UpdateAvailable { get; set; }
public static AsyncRelayCommand UpdateCommand { get; } = Commands.Create(async () => public static AsyncRelayCommand UpdateCommand { get; } = Commands.Create(async () =>
{ {
@ -120,27 +166,17 @@ namespace Ryujinx.Ava.UI.ViewModels
await Updater.BeginUpdateAsync(true); await Updater.BeginUpdateAsync(true);
}); });
private bool _showTotalTimePlayed;
private bool _showLoadProgress;
private bool _isGameRunning; private bool _isGameRunning;
private bool _isAmiiboRequested;
private bool _isAmiiboBinRequested;
private string _searchText; private string _searchText;
private Timer _searchTimer; private Timer _searchTimer;
private string _vSyncModeText;
private string _showUiKey = "F4"; private string _showUiKey = "F4";
private string _pauseKey = "F5"; private string _pauseKey = "F5";
private string _screenshotKey = "F8"; private string _screenshotKey = "F8";
private float _volume; private float _volume;
private bool _isAppletMenuActive;
private bool _statusBarVisible;
private bool _canUpdate = true;
private ApplicationData _currentApplicationData; private ApplicationData _currentApplicationData;
private readonly AutoResetEvent _rendererWaitEvent; private readonly AutoResetEvent _rendererWaitEvent;
private int _customVSyncInterval; private int _customVSyncInterval;
private int _customVSyncIntervalPercentageProxy; private int _customVSyncIntervalPercentageProxy;
private ApplicationData _listSelectedApplication;
private ApplicationData _gridSelectedApplication;
// Key is Title ID // Key is Title ID
/// <summary> /// <summary>
@ -266,20 +302,20 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool CanUpdate public bool CanUpdate
{ {
get => _canUpdate && EnableNonGameRunningControls && Updater.CanUpdate(); get => field && EnableNonGameRunningControls && Updater.CanUpdate();
set set
{ {
_canUpdate = value; field = value;
OnPropertyChanged(); OnPropertyChanged();
} }
} } = true;
public bool StatusBarVisible public bool StatusBarVisible
{ {
get => _statusBarVisible && EnableNonGameRunningControls; get => field && EnableNonGameRunningControls;
set set
{ {
_statusBarVisible = value; field = value;
OnPropertyChanged(); OnPropertyChanged();
} }
@ -311,20 +347,21 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsAmiiboRequested public bool IsAmiiboRequested
{ {
get => _isAmiiboRequested && _isGameRunning; get => field && _isGameRunning;
set set
{ {
_isAmiiboRequested = value; field = value;
OnPropertyChanged(); OnPropertyChanged();
} }
} }
public bool IsAmiiboBinRequested public bool IsAmiiboBinRequested
{ {
get => _isAmiiboBinRequested && _isGameRunning; get => field && _isGameRunning;
set set
{ {
_isAmiiboBinRequested = value; field = value;
OnPropertyChanged(); OnPropertyChanged();
} }
@ -334,10 +371,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool ShowLoadProgress public bool ShowLoadProgress
{ {
get => _showLoadProgress; get;
set set
{ {
_showLoadProgress = value; field = value;
OnPropertyChanged(); OnPropertyChanged();
OnPropertyChanged(nameof(ShowFirmwareStatus)); OnPropertyChanged(nameof(ShowFirmwareStatus));
@ -359,24 +396,24 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool ShowTotalTimePlayed public bool ShowTotalTimePlayed
{ {
get => _showTotalTimePlayed && EnableNonGameRunningControls; get => field && EnableNonGameRunningControls;
set set
{ {
_showTotalTimePlayed = value; field = value;
OnPropertyChanged(); OnPropertyChanged();
} }
} }
public ApplicationData ListSelectedApplication public ApplicationData ListSelectedApplication
{ {
get => _listSelectedApplication; get;
set set
{ {
_listSelectedApplication = value; field = value;
if (_listSelectedApplication != null && ListAppContextMenu == null) if (field != null && ListAppContextMenu == null)
ListAppContextMenu = new ApplicationContextMenu(); ListAppContextMenu = new ApplicationContextMenu();
else if (_listSelectedApplication == null && ListAppContextMenu != null) else if (field == null && ListAppContextMenu != null)
ListAppContextMenu = null!; ListAppContextMenu = null!;
OnPropertyChanged(); OnPropertyChanged();
@ -385,14 +422,14 @@ namespace Ryujinx.Ava.UI.ViewModels
public ApplicationData GridSelectedApplication public ApplicationData GridSelectedApplication
{ {
get => _gridSelectedApplication; get;
set set
{ {
_gridSelectedApplication = value; field = value;
if (_gridSelectedApplication != null && GridAppContextMenu == null) if (field != null && GridAppContextMenu == null)
GridAppContextMenu = new ApplicationContextMenu(); GridAppContextMenu = new ApplicationContextMenu();
else if (_gridSelectedApplication == null && GridAppContextMenu != null) else if (field == null && GridAppContextMenu != null)
GridAppContextMenu = null!; GridAppContextMenu = null!;
OnPropertyChanged(); OnPropertyChanged();
@ -421,13 +458,18 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool HasDlc => ApplicationLibrary.HasDlcs(SelectedApplication.Id); public bool HasDlc => ApplicationLibrary.HasDlcs(SelectedApplication.Id);
public bool OpenUserSaveDirectoryEnabled => SelectedApplication.HasControlHolder && SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0; public bool OpenUserSaveDirectoryEnabled => SelectedApplication.HasControlHolder &&
SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0;
public bool OpenDeviceSaveDirectoryEnabled => SelectedApplication.HasControlHolder && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0; public bool OpenDeviceSaveDirectoryEnabled => SelectedApplication.HasControlHolder &&
SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0;
public bool TrimXCIEnabled => XCIFileTrimmer.CanTrim(SelectedApplication.Path, new XCITrimmerLog.MainWindow(this)); public bool TrimXCIEnabled =>
XCIFileTrimmer.CanTrim(SelectedApplication.Path, new XCITrimmerLog.MainWindow(this));
public bool OpenBcatSaveDirectoryEnabled => SelectedApplication.HasControlHolder && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0; public bool OpenBcatSaveDirectoryEnabled => SelectedApplication.HasControlHolder &&
SelectedApplication.ControlHolder.Value
.BcatDeliveryCacheStorageSize > 0;
public bool ShowCustomVSyncIntervalPicker public bool ShowCustomVSyncIntervalPicker
=> _isGameRunning && AppHost.Device.VSyncMode == VSyncMode.Custom; => _isGameRunning && AppHost.Device.VSyncMode == VSyncMode.Custom;
@ -465,7 +507,6 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
set set
{ {
} }
} }
@ -491,10 +532,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public string VSyncModeText public string VSyncModeText
{ {
get => _vSyncModeText; get;
set set
{ {
_vSyncModeText = value; field = value;
OnPropertyChanged(); OnPropertyChanged();
OnPropertyChanged(nameof(ShowCustomVSyncIntervalPicker)); OnPropertyChanged(nameof(ShowCustomVSyncIntervalPicker));
@ -523,10 +564,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsAppletMenuActive public bool IsAppletMenuActive
{ {
get => _isAppletMenuActive && EnableNonGameRunningControls; get => field && EnableNonGameRunningControls;
set set
{ {
_isAppletMenuActive = value; field = value;
OnPropertyChanged(); OnPropertyChanged();
} }
@ -798,7 +839,8 @@ namespace Ryujinx.Ava.UI.ViewModels
#region PrivateMethods #region PrivateMethods
private static SortExpressionComparer<ApplicationData> CreateComparer(bool ascending, Func<ApplicationData, IComparable> selector) => private static SortExpressionComparer<ApplicationData> CreateComparer(bool ascending,
Func<ApplicationData, IComparable> selector) =>
ascending ascending
? SortExpressionComparer<ApplicationData>.Ascending(selector) ? SortExpressionComparer<ApplicationData>.Ascending(selector)
: SortExpressionComparer<ApplicationData>.Descending(selector); : SortExpressionComparer<ApplicationData>.Descending(selector);
@ -807,15 +849,15 @@ namespace Ryujinx.Ava.UI.ViewModels
=> SortMode switch => SortMode switch
{ {
#pragma warning disable IDE0055 // Disable formatting #pragma warning disable IDE0055 // Disable formatting
ApplicationSort.Title => CreateComparer(IsAscending, app => app.Name), ApplicationSort.Title => CreateComparer(IsAscending, app => app.Name),
ApplicationSort.Developer => CreateComparer(IsAscending, app => app.Developer), ApplicationSort.Developer => CreateComparer(IsAscending, app => app.Developer),
ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending), ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending),
ApplicationSort.TotalTimePlayed => new TimePlayedSortComparer(IsAscending), ApplicationSort.TotalTimePlayed => new TimePlayedSortComparer(IsAscending),
ApplicationSort.FileType => CreateComparer(IsAscending, app => app.FileExtension), ApplicationSort.FileType => CreateComparer(IsAscending, app => app.FileExtension),
ApplicationSort.FileSize => CreateComparer(IsAscending, app => app.FileSize), ApplicationSort.FileSize => CreateComparer(IsAscending, app => app.FileSize),
ApplicationSort.Path => CreateComparer(IsAscending, app => app.Path), ApplicationSort.Path => CreateComparer(IsAscending, app => app.Path),
ApplicationSort.Favorite => CreateComparer(IsAscending, app => new AppListFavoriteComparable(app)), ApplicationSort.Favorite => CreateComparer(IsAscending, app => new AppListFavoriteComparable(app)),
ApplicationSort.TitleId => CreateComparer(IsAscending, app => app.Id), ApplicationSort.TitleId => CreateComparer(IsAscending, app => app.Id),
_ => null, _ => null,
#pragma warning restore IDE0055 #pragma warning restore IDE0055
}; };
@ -847,7 +889,8 @@ namespace Ryujinx.Ava.UI.ViewModels
CompareInfo compareInfo = CultureInfo.CurrentCulture.CompareInfo; CompareInfo compareInfo = CultureInfo.CurrentCulture.CompareInfo;
return compareInfo.IndexOf(app.Name, _searchText, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) >= 0; return compareInfo.IndexOf(app.Name, _searchText,
CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) >= 0;
} }
return false; return false;
@ -861,21 +904,27 @@ namespace Ryujinx.Ava.UI.ViewModels
if (firmwareVersion == null) if (firmwareVersion == null)
{ {
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareNotFoundErrorMessage, filename)); await ContentDialogHelper.CreateErrorDialog(
LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.DialogFirmwareInstallerFirmwareNotFoundErrorMessage, filename));
return; return;
} }
string dialogTitle = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallTitle, firmwareVersion.VersionString); string dialogTitle = LocaleManager.Instance.UpdateAndGetDynamicValue(
string dialogMessage = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallMessage, firmwareVersion.VersionString); LocaleKeys.DialogFirmwareInstallerFirmwareInstallTitle, firmwareVersion.VersionString);
string dialogMessage = LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.DialogFirmwareInstallerFirmwareInstallMessage, firmwareVersion.VersionString);
SystemVersion currentVersion = ContentManager.GetCurrentFirmwareVersion(); SystemVersion currentVersion = ContentManager.GetCurrentFirmwareVersion();
if (currentVersion != null) if (currentVersion != null)
{ {
dialogMessage += LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSubMessage, currentVersion.VersionString); dialogMessage += LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.DialogFirmwareInstallerFirmwareInstallSubMessage, currentVersion.VersionString);
} }
dialogMessage += LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallConfirmMessage]; dialogMessage +=
LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallConfirmMessage];
UserResult result = await ContentDialogHelper.CreateConfirmationDialog( UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
dialogTitle, dialogTitle,
@ -884,7 +933,8 @@ namespace Ryujinx.Ava.UI.ViewModels
LocaleManager.Instance[LocaleKeys.InputDialogNo], LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
UpdateWaitWindow waitingDialog = new(dialogTitle, LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallWaitMessage]); UpdateWaitWindow waitingDialog = new(dialogTitle,
LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallWaitMessage]);
if (result == UserResult.Yes) if (result == UserResult.Yes)
{ {
@ -905,7 +955,9 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
waitingDialog.Close(); waitingDialog.Close();
string message = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSuccessMessage, firmwareVersion.VersionString); string message = LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.DialogFirmwareInstallerFirmwareInstallSuccessMessage,
firmwareVersion.VersionString);
await ContentDialogHelper.CreateInfoDialog( await ContentDialogHelper.CreateInfoDialog(
dialogTitle, dialogTitle,
@ -918,7 +970,8 @@ namespace Ryujinx.Ava.UI.ViewModels
// Purge Applet Cache. // Purge Applet Cache.
DirectoryInfo miiEditorCacheFolder = new(Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache")); DirectoryInfo miiEditorCacheFolder = new(Path.Combine(AppDataManager.GamesDirPath,
"0100000000001009", "cache"));
if (miiEditorCacheFolder.Exists) if (miiEditorCacheFolder.Exists)
{ {
@ -939,10 +992,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
RefreshFirmwareStatus(); RefreshFirmwareStatus();
} }
}) }) { Name = "GUI.FirmwareInstallerThread", };
{
Name = "GUI.FirmwareInstallerThread",
};
thread.Start(); thread.Start();
} }
@ -967,17 +1017,22 @@ namespace Ryujinx.Ava.UI.ViewModels
try try
{ {
string systemDirectory = AppDataManager.KeysDirPath; string systemDirectory = AppDataManager.KeysDirPath;
if (AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && Directory.Exists(AppDataManager.KeysDirPathUser)) if (AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile &&
Directory.Exists(AppDataManager.KeysDirPathUser))
{ {
systemDirectory = AppDataManager.KeysDirPathUser; systemDirectory = AppDataManager.KeysDirPathUser;
} }
string dialogTitle = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallTitle); string dialogTitle =
string dialogMessage = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallMessage); LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallTitle);
string dialogMessage =
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallMessage);
if (ContentManager.AreKeysAlredyPresent(systemDirectory)) if (ContentManager.AreKeysAlredyPresent(systemDirectory))
{ {
dialogMessage += LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallSubMessage); dialogMessage +=
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys
.DialogKeysInstallerKeysInstallSubMessage);
} }
dialogMessage += LocaleManager.Instance[LocaleKeys.DialogKeysInstallerKeysInstallConfirmMessage]; dialogMessage += LocaleManager.Instance[LocaleKeys.DialogKeysInstallerKeysInstallConfirmMessage];
@ -989,7 +1044,8 @@ namespace Ryujinx.Ava.UI.ViewModels
LocaleManager.Instance[LocaleKeys.InputDialogNo], LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
UpdateWaitWindow waitingDialog = new(dialogTitle, LocaleManager.Instance[LocaleKeys.DialogKeysInstallerKeysInstallWaitMessage]); UpdateWaitWindow waitingDialog = new(dialogTitle,
LocaleManager.Instance[LocaleKeys.DialogKeysInstallerKeysInstallWaitMessage]);
if (result == UserResult.Yes) if (result == UserResult.Yes)
{ {
@ -1010,7 +1066,9 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
waitingDialog.Close(); waitingDialog.Close();
string message = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallSuccessMessage); string message =
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys
.DialogKeysInstallerKeysInstallSuccessMessage);
await ContentDialogHelper.CreateInfoDialog( await ContentDialogHelper.CreateInfoDialog(
dialogTitle, dialogTitle,
@ -1031,7 +1089,8 @@ namespace Ryujinx.Ava.UI.ViewModels
string message = ex.Message; string message = ex.Message;
if (ex is FormatException) if (ex is FormatException)
{ {
message = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysNotFoundErrorMessage, filename); message = LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.DialogKeysInstallerKeysNotFoundErrorMessage, filename);
} }
await ContentDialogHelper.CreateErrorDialog(message); await ContentDialogHelper.CreateErrorDialog(message);
@ -1041,10 +1100,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
VirtualFileSystem.ReloadKeySet(); VirtualFileSystem.ReloadKeySet();
} }
}) }) { Name = "GUI.KeysInstallerThread", };
{
Name = "GUI.KeysInstallerThread",
};
thread.Start(); thread.Start();
} }
@ -1063,6 +1119,7 @@ namespace Ryujinx.Ava.UI.ViewModels
await ContentDialogHelper.CreateErrorDialog(ex.Message); await ContentDialogHelper.CreateErrorDialog(ex.Message);
} }
} }
private void ProgressHandler<T>(T state, int current, int total) where T : Enum private void ProgressHandler<T>(T state, int current, int total) where T : Enum
{ {
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
@ -1082,7 +1139,8 @@ namespace Ryujinx.Ava.UI.ViewModels
IsLoadingIndeterminate = false; IsLoadingIndeterminate = false;
break; break;
case LoadState.Loaded: case LoadState.Loaded:
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name); LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading,
_currentApplicationData.Name);
IsLoadingIndeterminate = true; IsLoadingIndeterminate = true;
CacheLoadStatus = string.Empty; CacheLoadStatus = string.Empty;
break; break;
@ -1103,7 +1161,8 @@ namespace Ryujinx.Ava.UI.ViewModels
IsLoadingIndeterminate = false; IsLoadingIndeterminate = false;
break; break;
case ShaderCacheLoadingState.Loaded: case ShaderCacheLoadingState.Loaded:
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name); LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading,
_currentApplicationData.Name);
IsLoadingIndeterminate = true; IsLoadingIndeterminate = true;
CacheLoadStatus = string.Empty; CacheLoadStatus = string.Empty;
break; break;
@ -1203,12 +1262,12 @@ namespace Ryujinx.Ava.UI.ViewModels
_rendererWaitEvent.Set(); _rendererWaitEvent.Set();
} }
private async Task LoadContentFromFolder(LocaleKeys localeMessageAddedKey, LocaleKeys localeMessageRemovedKey, LoadContentFromFolderDelegate onDirsSelected, LocaleKeys dirSelectDialogTitle) private async Task LoadContentFromFolder(LocaleKeys localeMessageAddedKey, LocaleKeys localeMessageRemovedKey,
LoadContentFromFolderDelegate onDirsSelected, LocaleKeys dirSelectDialogTitle)
{ {
Optional<IReadOnlyList<IStorageFolder>> result = await StorageProvider.OpenMultiFolderPickerAsync(new FolderPickerOpenOptions Optional<IReadOnlyList<IStorageFolder>> result =
{ await StorageProvider.OpenMultiFolderPickerAsync(
Title = LocaleManager.Instance[dirSelectDialogTitle] new FolderPickerOpenOptions { Title = LocaleManager.Instance[dirSelectDialogTitle] });
});
if (result.TryGet(out IReadOnlyList<IStorageFolder> foldersToLoad)) if (result.TryGet(out IReadOnlyList<IStorageFolder> foldersToLoad))
{ {
@ -1223,7 +1282,8 @@ namespace Ryujinx.Ava.UI.ViewModels
await Dispatcher.UIThread.InvokeAsync(async () => await Dispatcher.UIThread.InvokeAsync(async () =>
{ {
await ContentDialogHelper.ShowTextDialog( await ContentDialogHelper.ShowTextDialog(
LocaleManager.Instance[numAdded > 0 || numRemoved > 0 ? LocaleKeys.RyujinxConfirm : LocaleKeys.RyujinxInfo], LocaleManager.Instance[
numAdded > 0 || numRemoved > 0 ? LocaleKeys.RyujinxConfirm : LocaleKeys.RyujinxInfo],
msg, msg,
string.Empty, string.Empty,
string.Empty, string.Empty,
@ -1252,17 +1312,21 @@ namespace Ryujinx.Ava.UI.ViewModels
public void LoadConfigurableHotKeys() public void LoadConfigurableHotKeys()
{ {
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI, out Avalonia.Input.Key showUiKey)) if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI,
out Avalonia.Input.Key showUiKey))
{ {
ShowUiKey = new KeyGesture(showUiKey); ShowUiKey = new KeyGesture(showUiKey);
} }
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out Avalonia.Input.Key screenshotKey)) if (AvaloniaKeyboardMappingHelper.TryGetAvaKey(
(Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot,
out Avalonia.Input.Key screenshotKey))
{ {
ScreenshotKey = new KeyGesture(screenshotKey); ScreenshotKey = new KeyGesture(screenshotKey);
} }
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out Avalonia.Input.Key pauseKey)) if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause,
out Avalonia.Input.Key pauseKey))
{ {
PauseKey = new KeyGesture(pauseKey); PauseKey = new KeyGesture(pauseKey);
} }
@ -1282,7 +1346,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public void SetGridMode() => Glyph = Glyph.Grid; public void SetGridMode() => Glyph = Glyph.Grid;
public void SetAspectRatio(AspectRatio aspectRatio) => ConfigurationState.Instance.Graphics.AspectRatio.Value = aspectRatio; public void SetAspectRatio(AspectRatio aspectRatio) =>
ConfigurationState.Instance.Graphics.AspectRatio.Value = aspectRatio;
public async Task InstallFirmwareFromFile() public async Task InstallFirmwareFromFile()
{ {
@ -1374,7 +1439,8 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Error?.Print(LogClass.Application, $"Failed to create directory at path {screenshotsDir}. Error : {ex.GetType().Name}", "Screenshot"); Logger.Error?.Print(LogClass.Application,
$"Failed to create directory at path {screenshotsDir}. Error : {ex.GetType().Name}", "Screenshot");
return; return;
} }
@ -1447,7 +1513,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public async Task ManageProfiles() public async Task ManageProfiles()
{ {
await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient); await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem,
LibHacHorizonManager.RyujinxClient);
} }
public void SimulateWakeUpMessage() public void SimulateWakeUpMessage()
@ -1524,7 +1591,8 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
else else
{ {
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.MenuBarFileOpenFromFileError]); await ContentDialogHelper.CreateErrorDialog(
LocaleManager.Instance[LocaleKeys.MenuBarFileOpenFromFileError]);
} }
} }
} }
@ -1549,17 +1617,17 @@ namespace Ryujinx.Ava.UI.ViewModels
public async Task OpenFolder() public async Task OpenFolder()
{ {
Optional<IStorageFolder> result = await StorageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions Optional<IStorageFolder> result = await StorageProvider.OpenSingleFolderPickerAsync(
{ new FolderPickerOpenOptions
Title = LocaleManager.Instance[LocaleKeys.LoadUnpackedGameFromFolderDialogTitle] {
}); Title = LocaleManager.Instance[LocaleKeys.LoadUnpackedGameFromFolderDialogTitle]
});
if (result.TryGet(out IStorageFolder value)) if (result.TryGet(out IStorageFolder value))
{ {
ApplicationData applicationData = new() ApplicationData applicationData = new()
{ {
Name = Path.GetFileNameWithoutExtension(value.Path.LocalPath), Name = Path.GetFileNameWithoutExtension(value.Path.LocalPath), Path = value.Path.LocalPath,
Path = value.Path.LocalPath,
}; };
await LoadApplication(applicationData); await LoadApplication(applicationData);
@ -1569,29 +1637,34 @@ namespace Ryujinx.Ava.UI.ViewModels
public static bool InitializeUserConfig(ApplicationData application) public static bool InitializeUserConfig(ApplicationData application)
{ {
// Code where conditions will be met before loading the user configuration (Global Config) // Code where conditions will be met before loading the user configuration (Global Config)
string backendThreadingInit = Program.BackendThreadingArg ?? ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString(); string backendThreadingInit = Program.BackendThreadingArg ??
ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString();
// If a configuration is found in the "/games/xxxxxxxxxxxxxx" folder, the program will load the user setting. // If a configuration is found in the "/games/xxxxxxxxxxxxxx" folder, the program will load the user setting.
string idGame = application.IdBaseString; string idGame = application.IdBaseString;
if (ConfigurationFileFormat.TryLoad(Program.GetDirGameUserConfig(idGame), out ConfigurationFileFormat configurationFileFormat)) if (ConfigurationFileFormat.TryLoad(Program.GetDirGameUserConfig(idGame),
out ConfigurationFileFormat configurationFileFormat))
{ {
// Loads the user configuration, having previously changed the global configuration to the user configuration // Loads the user configuration, having previously changed the global configuration to the user configuration
ConfigurationState.Instance.Load(configurationFileFormat, Program.GetDirGameUserConfig(idGame, true), idGame); ConfigurationState.Instance.Load(configurationFileFormat, Program.GetDirGameUserConfig(idGame, true),
idGame);
if (ConfigurationFileFormat.TryLoad(Program.GlobalConfigurationPath, out ConfigurationFileFormat configurationFileFormatExtra)) if (ConfigurationFileFormat.TryLoad(Program.GlobalConfigurationPath,
out ConfigurationFileFormat configurationFileFormatExtra))
{ {
//This is where the global configuration will be stored. //This is where the global configuration will be stored.
//This allows you to change the global configuration settings during the game (for example, the global input setting) //This allows you to change the global configuration settings during the game (for example, the global input setting)
ConfigurationState.InstanceExtra.Load(configurationFileFormatExtra, Program.GlobalConfigurationPath); ConfigurationState.InstanceExtra.Load(configurationFileFormatExtra,
Program.GlobalConfigurationPath);
} }
} }
// Code where conditions will be executed after loading user configuration // Code where conditions will be executed after loading user configuration
if (ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() != backendThreadingInit) if (ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() != backendThreadingInit)
{ {
Rebooter.RebootAppWithGame(application.Path, Rebooter.RebootAppWithGame(application.Path,
[ [
"--bt", "--bt",
ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString()
]); ]);
@ -1601,7 +1674,8 @@ namespace Ryujinx.Ava.UI.ViewModels
return false; return false;
} }
public async Task LoadApplication(ApplicationData application, bool startFullscreen = false, BlitStruct<ApplicationControlProperty>? customNacpData = null) public async Task LoadApplication(ApplicationData application, bool startFullscreen = false,
BlitStruct<ApplicationControlProperty>? customNacpData = null)
{ {
if (InitializeUserConfig(application)) if (InitializeUserConfig(application))
return; return;
@ -1624,7 +1698,8 @@ namespace Ryujinx.Ava.UI.ViewModels
Logger.RestartTime(); Logger.RestartTime();
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path, ConfigurationState.Instance.System.Language, application.Id); SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path,
ConfigurationState.Instance.System.Language, application.Id);
PrepareLoadScreen(); PrepareLoadScreen();
@ -1662,7 +1737,6 @@ namespace Ryujinx.Ava.UI.ViewModels
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" }; Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
gameThread.Start(); gameThread.Start();
} }
public void SwitchToRenderer(bool startFullscreen) => public void SwitchToRenderer(bool startFullscreen) =>
@ -1694,7 +1768,8 @@ namespace Ryujinx.Ava.UI.ViewModels
if (version != null) if (version != null)
{ {
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, version.VersionString); LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion,
version.VersionString);
hasApplet = version.Major > 3; hasApplet = version.Major > 3;
} }
@ -1747,7 +1822,7 @@ namespace Ryujinx.Ava.UI.ViewModels
if (AppHost.Device.System.SearchingForAmiibo(out int deviceId) && IsGameRunning) if (AppHost.Device.System.SearchingForAmiibo(out int deviceId) && IsGameRunning)
{ {
string titleId = AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper(); string titleId = AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper();
AmiiboWindow window = new(ShowAll, LastScannedAmiiboId, titleId); AmiiboWindow window = new(ShowAll, LastScannedAmiiboId ?? string.Empty, titleId);
await StyleableAppWindow.ShowAsync(window); await StyleableAppWindow.ShowAsync(window);
@ -1760,22 +1835,24 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
} }
public async Task OpenBinFile() public async Task OpenBinFile()
{ {
if (AppHost.Device.System.SearchingForAmiibo(out _) && IsGameRunning) if (AppHost.Device.System.SearchingForAmiibo(out _) && IsGameRunning)
{ {
Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(new FilePickerOpenOptions Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(
{ new FilePickerOpenOptions
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
FileTypeFilter = new List<FilePickerFileType>
{ {
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats]) Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
FileTypeFilter = new List<FilePickerFileType>
{ {
Patterns = ["*.bin"], new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
{
Patterns = ["*.bin"],
}
} }
} });
});
if (result.HasValue) if (result.HasValue)
{ {
AppHost.Device.System.ScanAmiiboFromBin(result.Value.Path.LocalPath); AppHost.Device.System.ScanAmiiboFromBin(result.Value.Path.LocalPath);
@ -1826,9 +1903,11 @@ namespace Ryujinx.Ava.UI.ViewModels
if (ConfigurationState.Instance.Logger.EnableTrace.Value) if (ConfigurationState.Instance.Logger.EnableTrace.Value)
{ {
string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledMessage]; string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledMessage];
string secondaryMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledConfirmMessage]; string secondaryMessage =
LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledConfirmMessage];
UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(mainMessage, secondaryMessage); UserResult result =
await ContentDialogHelper.CreateLocalizedConfirmationDialog(mainMessage, secondaryMessage);
if (result == UserResult.Yes) if (result == UserResult.Yes)
{ {
@ -1841,9 +1920,11 @@ namespace Ryujinx.Ava.UI.ViewModels
if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value)) if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value))
{ {
string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledMessage]; string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledMessage];
string secondaryMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledConfirmMessage]; string secondaryMessage =
LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledConfirmMessage];
UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(mainMessage, secondaryMessage); UserResult result =
await ContentDialogHelper.CreateLocalizedConfirmationDialog(mainMessage, secondaryMessage);
if (result == UserResult.Yes) if (result == UserResult.Yes)
{ {
@ -1890,7 +1971,8 @@ namespace Ryujinx.Ava.UI.ViewModels
double savings = (double)trimmer.DiskSpaceSavingsB / 1024.0 / 1024.0; double savings = (double)trimmer.DiskSpaceSavingsB / 1024.0 / 1024.0;
double currentFileSize = (double)trimmer.FileSizeB / 1024.0 / 1024.0; double currentFileSize = (double)trimmer.FileSizeB / 1024.0 / 1024.0;
double cartDataSize = (double)trimmer.DataSizeB / 1024.0 / 1024.0; double cartDataSize = (double)trimmer.DataSizeB / 1024.0 / 1024.0;
string secondaryText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TrimXCIFileDialogSecondaryText, currentFileSize, cartDataSize, savings); string secondaryText = LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.TrimXCIFileDialogSecondaryText, currentFileSize, cartDataSize, savings);
UserResult result = await ContentDialogHelper.CreateConfirmationDialog( UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.TrimXCIFileDialogPrimaryText], LocaleManager.Instance[LocaleKeys.TrimXCIFileDialogPrimaryText],
@ -1906,7 +1988,9 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
StatusBarProgressStatusText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarXCIFileTrimming, Path.GetFileName(filename)); StatusBarProgressStatusText =
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarXCIFileTrimming,
Path.GetFileName(filename));
StatusBarProgressStatusVisible = true; StatusBarProgressStatusVisible = true;
StatusBarProgressMaximum = 1; StatusBarProgressMaximum = 1;
StatusBarProgressValue = 0; StatusBarProgressValue = 0;
@ -1931,11 +2015,7 @@ namespace Ryujinx.Ava.UI.ViewModels
StatusBarVisible = false; StatusBarVisible = false;
}); });
} }
}) }) { Name = "GUI.XCIFileTrimmerThread", IsBackground = true, };
{
Name = "GUI.XCIFileTrimmerThread",
IsBackground = true,
};
XCIFileTrimThread.Start(); XCIFileTrimThread.Start();
} }
} }
@ -1997,7 +2077,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public static RelayCommand<MainWindowViewModel> OpenUserSaveDirectory { get; } = public static RelayCommand<MainWindowViewModel> OpenUserSaveDirectory { get; } =
Commands.CreateConditional<MainWindowViewModel>(vm => vm?.SelectedApplication != null, Commands.CreateConditional<MainWindowViewModel>(vm => vm?.SelectedApplication != null,
viewModel => viewModel =>
OpenSaveDirectory(viewModel, SaveDataType.Account, viewModel.AccountManager.LastOpenedUser.UserId.ToLibHac()) OpenSaveDirectory(viewModel, SaveDataType.Account,
viewModel.AccountManager.LastOpenedUser.UserId.ToLibHac())
); );
public static RelayCommand<MainWindowViewModel> OpenDeviceSaveDirectory { get; } = public static RelayCommand<MainWindowViewModel> OpenDeviceSaveDirectory { get; } =
@ -2053,7 +2134,8 @@ namespace Ryujinx.Ava.UI.ViewModels
viewModel => viewModel =>
{ {
string modsBasePath = ModLoader.GetModsBasePath(); string modsBasePath = ModLoader.GetModsBasePath();
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.IdString); string titleModsPath =
ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.IdString);
OpenHelper.OpenFolder(titleModsPath); OpenHelper.OpenFolder(titleModsPath);
}); });
@ -2063,7 +2145,8 @@ namespace Ryujinx.Ava.UI.ViewModels
viewModel => viewModel =>
{ {
string sdModsBasePath = ModLoader.GetSdModsBasePath(); string sdModsBasePath = ModLoader.GetSdModsBasePath();
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.IdString); string titleModsPath =
ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.IdString);
OpenHelper.OpenFolder(titleModsPath); OpenHelper.OpenFolder(titleModsPath);
}); });

View File

@ -46,47 +46,49 @@ namespace Ryujinx.Ava.UI.ViewModels
private readonly Dictionary<string, string> _networkInterfaces; private readonly Dictionary<string, string> _networkInterfaces;
private float _customResolutionScale;
private int _resolutionScale; private int _resolutionScale;
private int _graphicsBackendMultithreadingIndex; [ObservableProperty]
private float _volume; public partial bool IsVulkanAvailable { get; set; } = true;
[ObservableProperty] private bool _isVulkanAvailable = true;
[ObservableProperty] private bool _gameListNeedsRefresh; [ObservableProperty]
public partial bool GameListNeedsRefresh { get; set; }
private readonly List<string> _gpuIds = []; private readonly List<string> _gpuIds = [];
private bool _useInputGlobalConfig;
private int _graphicsBackendIndex;
private int _scalingFilter; private int _scalingFilter;
private int _scalingFilterLevel;
private int _customVSyncInterval; private int _customVSyncInterval;
private bool _enableCustomVSyncInterval;
private int _customVSyncIntervalPercentageProxy; private int _customVSyncIntervalPercentageProxy;
private VSyncMode _vSyncMode; private VSyncMode _vSyncMode;
private long _turboModeMultiplier;
public event Action CloseWindow; public event Action CloseWindow;
public event Action SaveSettingsEvent; public event Action SaveSettingsEvent;
public event Action<bool> LocalGlobalInputSwitchEvent; public event Action<bool> LocalGlobalInputSwitchEvent;
private int _networkInterfaceIndex;
private int _multiplayerModeIndex;
private string _ldnPassphrase;
[ObservableProperty] private string _ldnServer;
private bool _enableGDBStub;
private ushort _gdbStubPort;
private bool _debuggerSuspendOnStart;
public SettingsHacksViewModel DirtyHacks { get; } public SettingsHacksViewModel DirtyHacks { get; }
private readonly bool _isGameRunning; public bool IsGameRunning
private readonly Bitmap _gameIcon; {
private readonly string _gameTitle; get;
private readonly string _gamePath; }
private readonly string _gameId;
public bool IsGameRunning => _isGameRunning; public Bitmap GameIcon
public Bitmap GameIcon => _gameIcon; {
public string GamePath => _gamePath; get;
public string GameTitle => _gameTitle; }
public string GameId => _gameId;
public string GamePath
{
get;
}
public string GameTitle
{
get;
}
public string GameId
{
get;
}
public bool IsGameTitleNotNull => !string.IsNullOrEmpty(GameTitle); public bool IsGameTitleNotNull => !string.IsNullOrEmpty(GameTitle);
public double PanelOpacity => IsGameTitleNotNull ? 0.5 : 1; public double PanelOpacity => IsGameTitleNotNull ? 0.5 : 1;
@ -104,17 +106,18 @@ namespace Ryujinx.Ava.UI.ViewModels
public int GraphicsBackendMultithreadingIndex public int GraphicsBackendMultithreadingIndex
{ {
get => _graphicsBackendMultithreadingIndex; get;
set set
{ {
_graphicsBackendMultithreadingIndex = value; field = value;
if (_graphicsBackendMultithreadingIndex != (int)ConfigurationState.Instance.Graphics.BackendThreading.Value) if (field != (int)ConfigurationState.Instance.Graphics.BackendThreading.Value)
{ {
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage], ContentDialogHelper.CreateInfoDialog(
string.Empty, LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage],
string.Empty, string.Empty,
string.Empty,
LocaleManager.Instance[LocaleKeys.InputDialogOk], LocaleManager.Instance[LocaleKeys.InputDialogOk],
LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle]) LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle])
); );
@ -126,10 +129,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public float CustomResolutionScale public float CustomResolutionScale
{ {
get => _customResolutionScale; get;
set set
{ {
_customResolutionScale = MathF.Round(value, 1); field = MathF.Round(value, 1);
OnPropertyChanged(); OnPropertyChanged();
} }
@ -152,12 +155,12 @@ namespace Ryujinx.Ava.UI.ViewModels
public int FocusLostActionType { get; set; } public int FocusLostActionType { get; set; }
public bool UseGlobalInputConfig public bool UseGlobalInputConfig
{ {
get => _useInputGlobalConfig; get;
set set
{ {
_useInputGlobalConfig = value; field = value;
LocalGlobalInputSwitchEvent?.Invoke(_useInputGlobalConfig); LocalGlobalInputSwitchEvent?.Invoke(field);
OnPropertyChanged(nameof(InputPanelOpacity)); OnPropertyChanged(nameof(InputPanelOpacity));
OnPropertyChanged(); OnPropertyChanged();
} }
@ -196,10 +199,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool EnableCustomVSyncInterval public bool EnableCustomVSyncInterval
{ {
get => _enableCustomVSyncInterval; get;
set set
{ {
_enableCustomVSyncInterval = value; field = value;
if (_vSyncMode == VSyncMode.Custom && !value) if (_vSyncMode == VSyncMode.Custom && !value)
{ {
VSyncMode = VSyncMode.Switch; VSyncMode = VSyncMode.Switch;
@ -233,12 +236,12 @@ namespace Ryujinx.Ava.UI.ViewModels
public long TurboMultiplier public long TurboMultiplier
{ {
get => _turboModeMultiplier; get;
set set
{ {
if (_turboModeMultiplier != value) if (field != value)
{ {
_turboModeMultiplier = value; field = value;
OnPropertyChanged(); OnPropertyChanged();
OnPropertyChanged((nameof(TurboMultiplierPercentageText))); OnPropertyChanged((nameof(TurboMultiplierPercentageText)));
@ -285,10 +288,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public string LdnPassphrase public string LdnPassphrase
{ {
get => _ldnPassphrase; get;
set set
{ {
_ldnPassphrase = value; field = value;
IsInvalidLdnPassphraseVisible = !ValidateLdnPassphrase(value); IsInvalidLdnPassphraseVisible = !ValidateLdnPassphrase(value);
OnPropertyChanged(); OnPropertyChanged();
@ -304,29 +307,33 @@ namespace Ryujinx.Ava.UI.ViewModels
public int AspectRatio { get; set; } public int AspectRatio { get; set; }
public int AntiAliasingEffect { get; set; } public int AntiAliasingEffect { get; set; }
public string ScalingFilterLevelText => ScalingFilterLevel.ToString("0"); public string ScalingFilterLevelText => ScalingFilterLevel.ToString("0");
public int ScalingFilterLevel public int ScalingFilterLevel
{ {
get => _scalingFilterLevel; get;
set set
{ {
_scalingFilterLevel = value; field = value;
OnPropertyChanged(); OnPropertyChanged();
OnPropertyChanged(nameof(ScalingFilterLevelText)); OnPropertyChanged(nameof(ScalingFilterLevelText));
} }
} }
public int OpenglDebugLevel { get; set; } public int OpenglDebugLevel { get; set; }
public int MemoryMode { get; set; } public int MemoryMode { get; set; }
public int BaseStyleIndex { get; set; } public int BaseStyleIndex { get; set; }
public int GraphicsBackendIndex public int GraphicsBackendIndex
{ {
get => _graphicsBackendIndex; get;
set set
{ {
_graphicsBackendIndex = value; field = value;
OnPropertyChanged(); OnPropertyChanged();
OnPropertyChanged(nameof(IsVulkanSelected)); OnPropertyChanged(nameof(IsVulkanSelected));
} }
} }
public int ScalingFilter public int ScalingFilter
{ {
get => _scalingFilter; get => _scalingFilter;
@ -342,12 +349,12 @@ namespace Ryujinx.Ava.UI.ViewModels
public float Volume public float Volume
{ {
get => _volume; get;
set set
{ {
_volume = value; field = value;
ConfigurationState.Instance.System.AudioVolume.Value = _volume / 100; ConfigurationState.Instance.System.AudioVolume.Value = field / 100;
OnPropertyChanged(); OnPropertyChanged();
} }
@ -373,19 +380,19 @@ namespace Ryujinx.Ava.UI.ViewModels
public int NetworkInterfaceIndex public int NetworkInterfaceIndex
{ {
get => _networkInterfaceIndex; get;
set set
{ {
_networkInterfaceIndex = value != -1 ? value : 0; field = value != -1 ? value : 0;
} }
} }
public int MultiplayerModeIndex public int MultiplayerModeIndex
{ {
get => _multiplayerModeIndex; get;
set set
{ {
_multiplayerModeIndex = value; field = value;
} }
} }
@ -393,31 +400,31 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool EnableGdbStub public bool EnableGdbStub
{ {
get => _enableGDBStub; get;
set set
{ {
_enableGDBStub = value; field = value;
ConfigurationState.Instance.Debug.EnableGdbStub.Value = _enableGDBStub; ConfigurationState.Instance.Debug.EnableGdbStub.Value = field;
} }
} }
public ushort GDBStubPort public ushort GDBStubPort
{ {
get => _gdbStubPort; get;
set set
{ {
_gdbStubPort = value; field = value;
ConfigurationState.Instance.Debug.GdbStubPort.Value = _gdbStubPort; ConfigurationState.Instance.Debug.GdbStubPort.Value = field;
} }
} }
public bool DebuggerSuspendOnStart public bool DebuggerSuspendOnStart
{ {
get => _debuggerSuspendOnStart; get;
set set
{ {
_debuggerSuspendOnStart = value; field = value;
ConfigurationState.Instance.Debug.DebuggerSuspendOnStart.Value = _debuggerSuspendOnStart; ConfigurationState.Instance.Debug.DebuggerSuspendOnStart.Value = field;
} }
} }
@ -450,13 +457,13 @@ namespace Ryujinx.Ava.UI.ViewModels
if (gameIconData != null && gameIconData.Length > 0) if (gameIconData != null && gameIconData.Length > 0)
{ {
using var ms = new MemoryStream(gameIconData); using var ms = new MemoryStream(gameIconData);
_gameIcon = new Bitmap(ms); GameIcon = new Bitmap(ms);
} }
_isGameRunning = gameRunning; IsGameRunning = gameRunning;
_gamePath = gamePath; GamePath = gamePath;
_gameTitle = gameName; GameTitle = gameName;
_gameId = gameId; GameId = gameId;
if (customConfig) // During the game. If there is no user config, then load the global config window if (customConfig) // During the game. If there is no user config, then load the global config window
{ {
@ -718,7 +725,6 @@ namespace Ryujinx.Ava.UI.ViewModels
MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value; MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value;
DisableP2P = config.Multiplayer.DisableP2p; DisableP2P = config.Multiplayer.DisableP2p;
LdnPassphrase = config.Multiplayer.LdnPassphrase; LdnPassphrase = config.Multiplayer.LdnPassphrase;
LdnServer = config.Multiplayer.LdnServer;
// Debug // Debug
EnableGdbStub = config.Debug.EnableGdbStub.Value; EnableGdbStub = config.Debug.EnableGdbStub.Value;
@ -845,7 +851,6 @@ namespace Ryujinx.Ava.UI.ViewModels
config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex; config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex;
config.Multiplayer.DisableP2p.Value = DisableP2P; config.Multiplayer.DisableP2p.Value = DisableP2P;
config.Multiplayer.LdnPassphrase.Value = LdnPassphrase; config.Multiplayer.LdnPassphrase.Value = LdnPassphrase;
config.Multiplayer.LdnServer.Value = LdnServer;
// Debug // Debug
config.Debug.EnableGdbStub.Value = EnableGdbStub; config.Debug.EnableGdbStub.Value = EnableGdbStub;