From 6e02cac952f1a9a34d2777199dde657eba0784e6 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Sun, 24 Jul 2022 17:38:38 +0000 Subject: [PATCH] Avalonia - Use content dialog for user profile manager (#3455) * remove content dialog placeholder from all windows * remove redundant window argument * redesign user profile window * wip * use avalonia auto name generator * add edit and new user options * move profile image selection to content dialog * remove usings * fix updater * address review * adjust avatar dialog size * add validation for user editor * fix typo * Shorten some labels --- Ryujinx.Ava/App.axaml.cs | 1 - Ryujinx.Ava/AppHost.cs | 24 +- Ryujinx.Ava/Assets/Locales/en_US.json | 11 +- Ryujinx.Ava/Common/ApplicationHelper.cs | 12 +- Ryujinx.Ava/Modules/Updater/Updater.cs | 24 +- Ryujinx.Ava/Ryujinx.Ava.csproj | 1 + Ryujinx.Ava/Ui/Applet/AvaHostUiHandler.cs | 6 +- .../Ui/Applet/ErrorAppletWindow.axaml.cs | 10 +- .../Ui/Applet/SwkbdAppletDialog.axaml.cs | 22 +- .../Ui/Controls/ContentDialogHelper.cs | 127 ++++------ Ryujinx.Ava/Ui/Controls/InputDialog.axaml.cs | 37 ++- .../Ui/Controls/NavigationDialogHost.axaml | 10 + .../Ui/Controls/NavigationDialogHost.axaml.cs | 85 +++++++ .../ProfileImageSelectionDialog.axaml | 10 +- .../ProfileImageSelectionDialog.axaml.cs | 70 +++--- .../Ui/Controls/UpdateWaitWindow.axaml.cs | 12 +- Ryujinx.Ava/Ui/Controls/UserEditor.axaml | 55 +++++ Ryujinx.Ava/Ui/Controls/UserEditor.axaml.cs | 123 ++++++++++ Ryujinx.Ava/Ui/Controls/UserErrorDialog.cs | 2 +- Ryujinx.Ava/Ui/Controls/UserSelector.axaml | 90 +++++++ Ryujinx.Ava/Ui/Controls/UserSelector.axaml.cs | 79 ++++++ Ryujinx.Ava/Ui/Models/TempProfile.cs | 55 +++++ .../Ui/ViewModels/AmiiboWindowViewModel.cs | 4 +- .../ViewModels/ControllerSettingsViewModel.cs | 13 +- .../Ui/ViewModels/MainWindowViewModel.cs | 38 ++- .../Ui/ViewModels/SettingsViewModel.cs | 3 +- .../Ui/ViewModels/UserProfileViewModel.cs | 95 +++----- Ryujinx.Ava/Ui/Windows/AboutWindow.axaml | 230 +++++++++--------- Ryujinx.Ava/Ui/Windows/AboutWindow.axaml.cs | 11 +- Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml.cs | 7 +- Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml | 25 +- Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml.cs | 68 +++--- Ryujinx.Ava/Ui/Windows/CheatWindow.axaml.cs | 7 +- .../Windows/ControllerSettingsWindow.axaml.cs | 11 +- .../Ui/Windows/DlcManagerWindow.axaml.cs | 24 +- Ryujinx.Ava/Ui/Windows/MainWindow.axaml | 12 - Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs | 63 ++--- .../Ui/Windows/MotionSettingsWindow.axaml.cs | 57 ++--- .../Ui/Windows/RumbleSettingsWindow.axaml.cs | 49 ++-- Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml | 11 +- .../Ui/Windows/SettingsWindow.axaml.cs | 77 ++---- Ryujinx.Ava/Ui/Windows/StyleableWindow.cs | 7 - .../Ui/Windows/TitleUpdateWindow.axaml.cs | 17 +- Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml.cs | 17 +- .../Ui/Windows/UserProfileWindow.axaml | 107 -------- .../Ui/Windows/UserProfileWindow.axaml.cs | 102 -------- 46 files changed, 968 insertions(+), 953 deletions(-) create mode 100644 Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml create mode 100644 Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml.cs create mode 100644 Ryujinx.Ava/Ui/Controls/UserEditor.axaml create mode 100644 Ryujinx.Ava/Ui/Controls/UserEditor.axaml.cs create mode 100644 Ryujinx.Ava/Ui/Controls/UserSelector.axaml create mode 100644 Ryujinx.Ava/Ui/Controls/UserSelector.axaml.cs create mode 100644 Ryujinx.Ava/Ui/Models/TempProfile.cs delete mode 100644 Ryujinx.Ava/Ui/Windows/UserProfileWindow.axaml delete mode 100644 Ryujinx.Ava/Ui/Windows/UserProfileWindow.axaml.cs diff --git a/Ryujinx.Ava/App.axaml.cs b/Ryujinx.Ava/App.axaml.cs index ef295a611..180c74b43 100644 --- a/Ryujinx.Ava/App.axaml.cs +++ b/Ryujinx.Ava/App.axaml.cs @@ -55,7 +55,6 @@ namespace Ryujinx.Ava if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { var result = await ContentDialogHelper.CreateConfirmationDialog( - (desktop.MainWindow as MainWindow).SettingsWindow, LocaleManager.Instance["DialogThemeRestartMessage"], LocaleManager.Instance["DialogThemeRestartSubMessage"], LocaleManager.Instance["InputDialogYes"], diff --git a/Ryujinx.Ava/AppHost.cs b/Ryujinx.Ava/AppHost.cs index 55693ed38..c6f2265cc 100644 --- a/Ryujinx.Ava/AppHost.cs +++ b/Ryujinx.Ava/AppHost.cs @@ -417,10 +417,12 @@ namespace Ryujinx.Ava { if (userError == UserError.NoFirmware) { - string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedMessage"], firmwareVersion.VersionString); + string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedMessage"], + firmwareVersion.VersionString); - UserResult result = await ContentDialogHelper.CreateConfirmationDialog(_parent, - LocaleManager.Instance["DialogFirmwareNoFirmwareInstalledMessage"], message, LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], ""); + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance["DialogFirmwareNoFirmwareInstalledMessage"], message, + LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], ""); if (result != UserResult.Yes) { @@ -450,12 +452,12 @@ namespace Ryujinx.Ava string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedSuccessMessage"], firmwareVersion.VersionString); - await ContentDialogHelper.CreateInfoDialog(_parent, - string.Format(LocaleManager.Instance["DialogFirmwareInstalledMessage"], firmwareVersion.VersionString), - message, - LocaleManager.Instance["InputDialogOk"], - "", - LocaleManager.Instance["RyujinxInfo"]); + await ContentDialogHelper.CreateInfoDialog( + string.Format(LocaleManager.Instance["DialogFirmwareInstalledMessage"], firmwareVersion.VersionString), + message, + LocaleManager.Instance["InputDialogOk"], + "", + LocaleManager.Instance["RyujinxInfo"]); } } else @@ -879,7 +881,7 @@ namespace Ryujinx.Ava } _dialogShown = true; - shouldExit = await ContentDialogHelper.CreateStopEmulationDialog(_parent); + shouldExit = await ContentDialogHelper.CreateStopEmulationDialog(); _dialogShown = false; } @@ -896,7 +898,7 @@ namespace Ryujinx.Ava { Dispatcher.UIThread.Post(() => { - _parent.Cursor = _isMouseInRenderer ? InvisibleCursor : Cursor.Default; + _parent.Cursor = _isMouseInRenderer ? InvisibleCursor : Cursor.Default; }); } else diff --git a/Ryujinx.Ava/Assets/Locales/en_US.json b/Ryujinx.Ava/Assets/Locales/en_US.json index 2aaf52e09..dd18dd9ee 100644 --- a/Ryujinx.Ava/Assets/Locales/en_US.json +++ b/Ryujinx.Ava/Assets/Locales/en_US.json @@ -256,8 +256,8 @@ "UserProfilesSaveProfileName": "Save Profile Name", "UserProfilesChangeProfileImage": "Change Profile Image", "UserProfilesAvailableUserProfiles": "Available User Profiles:", - "UserProfilesAddNewProfile": "Add New Profile", - "UserProfilesDeleteSelectedProfile": "Delete Selected Profile", + "UserProfilesAddNewProfile": "Create Profile", + "UserProfilesDeleteSelectedProfile": "Delete Selected", "UserProfilesClose": "Close", "ProfileImageSelectionTitle": "Profile Image Selection", "ProfileImageSelectionHeader": "Choose a profile Image", @@ -568,5 +568,12 @@ "UpdateWindowTitle": "Manage Game Updates", "CheatWindowHeading": "Cheats Available for {0} [{1}]", "DlcWindowHeading": "DLC Available for {0} [{1}]", + "UserProfilesEditProfile": "Edit Selected", + "Cancel": "Cancel", + "Save": "Save", + "Discard": "Discard", + "UserProfilesSetProfileImage": "Set Profile Image", + "UserProfileEmptyNameError": "Name is required", + "UserProfileNoImageError": "Profile image must be set", "GameUpdateWindowHeading": "Updates Available for {0} [{1}]" } diff --git a/Ryujinx.Ava/Common/ApplicationHelper.cs b/Ryujinx.Ava/Common/ApplicationHelper.cs index d83ca2c11..6cbe9d160 100644 --- a/Ryujinx.Ava/Common/ApplicationHelper.cs +++ b/Ryujinx.Ava/Common/ApplicationHelper.cs @@ -81,7 +81,6 @@ namespace Ryujinx.Ava.Common Dispatcher.UIThread.Post(async () => { await ContentDialogHelper.CreateErrorDialog( - _owner, string.Format(LocaleManager.Instance["DialogMessageCreateSaveErrorMessage"], result.ToStringWithName())); }); @@ -101,8 +100,7 @@ namespace Ryujinx.Ava.Common Dispatcher.UIThread.Post(async () => { - await ContentDialogHelper.CreateErrorDialog(_owner, - string.Format(LocaleManager.Instance["DialogMessageFindSaveErrorMessage"], result.ToStringWithName())); + await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogMessageFindSaveErrorMessage"], result.ToStringWithName())); }); return false; @@ -161,7 +159,6 @@ namespace Ryujinx.Ava.Common Dispatcher.UIThread.Post(async () => { UserResult result = await ContentDialogHelper.CreateConfirmationDialog( - _owner, string.Format(LocaleManager.Instance["DialogNcaExtractionMessage"], ncaSectionType, Path.GetFileName(titleFilePath)), "", "", @@ -232,7 +229,7 @@ namespace Ryujinx.Ava.Common "Extraction failure. The main NCA was not present in the selected file"); Dispatcher.UIThread.InvokeAsync(async () => { - await ContentDialogHelper.CreateErrorDialog(_owner, LocaleManager.Instance["DialogNcaExtractionMainNcaNotFoundErrorMessage"]); + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogNcaExtractionMainNcaNotFoundErrorMessage"]); }); return; } @@ -273,7 +270,7 @@ namespace Ryujinx.Ava.Common $"LibHac returned error code: {resultCode.Value.ErrorCode}"); Dispatcher.UIThread.InvokeAsync(async () => { - await ContentDialogHelper.CreateErrorDialog(_owner, LocaleManager.Instance["DialogNcaExtractionCheckLogErrorMessage"]); + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogNcaExtractionCheckLogErrorMessage"]); }); } else if (resultCode.Value.IsSuccess()) @@ -281,7 +278,6 @@ namespace Ryujinx.Ava.Common Dispatcher.UIThread.InvokeAsync(async () => { await ContentDialogHelper.CreateInfoDialog( - _owner, LocaleManager.Instance["DialogNcaExtractionSuccessMessage"], "", LocaleManager.Instance["InputDialogOk"], @@ -298,7 +294,7 @@ namespace Ryujinx.Ava.Common { Dispatcher.UIThread.InvokeAsync(async () => { - await ContentDialogHelper.CreateErrorDialog(_owner, ex.Message); + await ContentDialogHelper.CreateErrorDialog(ex.Message); }); } } diff --git a/Ryujinx.Ava/Modules/Updater/Updater.cs b/Ryujinx.Ava/Modules/Updater/Updater.cs index 362435c87..bbcf66790 100644 --- a/Ryujinx.Ava/Modules/Updater/Updater.cs +++ b/Ryujinx.Ava/Modules/Updater/Updater.cs @@ -76,7 +76,7 @@ namespace Ryujinx.Modules Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!"); Dispatcher.UIThread.Post(async () => { - await ContentDialogHelper.CreateWarningDialog(mainWindow, LocaleManager.Instance["DialogUpdaterConvertFailedMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]); + await ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterConvertFailedMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]); }); return; @@ -111,7 +111,7 @@ namespace Ryujinx.Modules { Dispatcher.UIThread.Post(async () => { - await ContentDialogHelper.CreateUpdaterInfoDialog(mainWindow, LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], ""); + await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], ""); }); } @@ -129,7 +129,7 @@ namespace Ryujinx.Modules { Dispatcher.UIThread.Post(async () => { - await ContentDialogHelper.CreateUpdaterInfoDialog(mainWindow, LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], ""); + await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], ""); }); } @@ -142,7 +142,7 @@ namespace Ryujinx.Modules Logger.Error?.Print(LogClass.Application, exception.Message); Dispatcher.UIThread.Post(async () => { - await ContentDialogHelper.CreateErrorDialog(mainWindow, LocaleManager.Instance["DialogUpdaterFailedToGetVersionMessage"]); + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUpdaterFailedToGetVersionMessage"]); }); return; @@ -157,7 +157,7 @@ namespace Ryujinx.Modules Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!"); Dispatcher.UIThread.Post(async () => { - await ContentDialogHelper.CreateWarningDialog(mainWindow, LocaleManager.Instance["DialogUpdaterConvertFailedGithubMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]); + await ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterConvertFailedGithubMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]); }); return; @@ -169,7 +169,7 @@ namespace Ryujinx.Modules { Dispatcher.UIThread.Post(async () => { - await ContentDialogHelper.CreateUpdaterInfoDialog(mainWindow, LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], ""); + await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], ""); }); } @@ -550,7 +550,7 @@ namespace Ryujinx.Modules { if (showWarnings) { - ContentDialogHelper.CreateWarningDialog(parent, LocaleManager.Instance["DialogUpdaterArchNotSupportedMessage"], + ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterArchNotSupportedMessage"], LocaleManager.Instance["DialogUpdaterArchNotSupportedSubMessage"]); } @@ -561,7 +561,7 @@ namespace Ryujinx.Modules { if (showWarnings) { - ContentDialogHelper.CreateWarningDialog(parent, LocaleManager.Instance["DialogUpdaterNoInternetMessage"], + ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterNoInternetMessage"], LocaleManager.Instance["DialogUpdaterNoInternetSubMessage"]); } @@ -572,7 +572,7 @@ namespace Ryujinx.Modules { if (showWarnings) { - ContentDialogHelper.CreateWarningDialog(parent, LocaleManager.Instance["DialogUpdaterDirtyBuildMessage"], + ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterDirtyBuildMessage"], LocaleManager.Instance["DialogUpdaterDirtyBuildSubMessage"]); } @@ -585,13 +585,11 @@ namespace Ryujinx.Modules { if (ReleaseInformations.IsFlatHubBuild()) { - ContentDialogHelper.CreateWarningDialog(parent, - LocaleManager.Instance["UpdaterDisabledWarningTitle"], LocaleManager.Instance["DialogUpdaterFlatpakNotSupportedMessage"]); + ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["UpdaterDisabledWarningTitle"], LocaleManager.Instance["DialogUpdaterFlatpakNotSupportedMessage"]); } else { - ContentDialogHelper.CreateWarningDialog(parent, - LocaleManager.Instance["UpdaterDisabledWarningTitle"], LocaleManager.Instance["DialogUpdaterDirtyBuildSubMessage"]); + ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["UpdaterDisabledWarningTitle"], LocaleManager.Instance["DialogUpdaterDirtyBuildSubMessage"]); } } diff --git a/Ryujinx.Ava/Ryujinx.Ava.csproj b/Ryujinx.Ava/Ryujinx.Ava.csproj index 2f0f342d4..67e159f62 100644 --- a/Ryujinx.Ava/Ryujinx.Ava.csproj +++ b/Ryujinx.Ava/Ryujinx.Ava.csproj @@ -26,6 +26,7 @@ + diff --git a/Ryujinx.Ava/Ui/Applet/AvaHostUiHandler.cs b/Ryujinx.Ava/Ui/Applet/AvaHostUiHandler.cs index f129cd710..133bcc038 100644 --- a/Ryujinx.Ava/Ui/Applet/AvaHostUiHandler.cs +++ b/Ryujinx.Ava/Ui/Applet/AvaHostUiHandler.cs @@ -92,7 +92,7 @@ namespace Ryujinx.Ava.Ui.Applet } catch (Exception ex) { - await ContentDialogHelper.CreateErrorDialog(_parent, string.Format(LocaleManager.Instance["DialogMessageDialogErrorExceptionMessage"], ex)); + await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogMessageDialogErrorExceptionMessage"], ex)); dialogCloseEvent.Set(); } @@ -126,7 +126,7 @@ namespace Ryujinx.Ava.Ui.Applet catch (Exception ex) { error = true; - await ContentDialogHelper.CreateErrorDialog(_parent, string.Format(LocaleManager.Instance["DialogSoftwareKeyboardErrorExceptionMessage"], ex)); + await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogSoftwareKeyboardErrorExceptionMessage"], ex)); } finally { @@ -181,7 +181,7 @@ namespace Ryujinx.Ava.Ui.Applet catch (Exception ex) { dialogCloseEvent.Set(); - await ContentDialogHelper.CreateErrorDialog(_parent, string.Format(LocaleManager.Instance["DialogErrorAppletErrorExceptionMessage"], ex)); + await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogErrorAppletErrorExceptionMessage"], ex)); } }); diff --git a/Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml.cs b/Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml.cs index 12fb8b0fa..b4acd155c 100644 --- a/Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; namespace Ryujinx.Ava.Ui.Applet { - internal class ErrorAppletWindow : StyleableWindow + internal partial class ErrorAppletWindow : StyleableWindow { private readonly Window _owner; private object _buttonResponse; @@ -50,8 +50,6 @@ namespace Ryujinx.Ava.Ui.Applet public string Message { get; set; } - public StackPanel ButtonStack { get; set; } - private void AddButton(string label, object tag) { Dispatcher.UIThread.InvokeAsync(() => @@ -79,11 +77,5 @@ namespace Ryujinx.Ava.Ui.Applet return _buttonResponse; } - - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - ButtonStack = this.FindControl("ButtonStack"); - } } } \ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml.cs b/Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml.cs index 2d56c0b5a..d7bb4b74e 100644 --- a/Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml.cs +++ b/Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml.cs @@ -13,7 +13,7 @@ using System.Threading.Tasks; namespace Ryujinx.Ava.Ui.Controls { - internal class SwkbdAppletDialog : UserControl + internal partial class SwkbdAppletDialog : UserControl { private Predicate _checkLength; private int _inputMax; @@ -30,6 +30,10 @@ namespace Ryujinx.Ava.Ui.Controls _placeholder = placeholder; InitializeComponent(); + Input.Watermark = _placeholder; + + Input.AddHandler(TextInputEvent, Message_TextInput, RoutingStrategies.Tunnel, true); + SetInputLengthValidation(0, int.MaxValue); // Disable by default. } @@ -43,23 +47,9 @@ namespace Ryujinx.Ava.Ui.Controls public string MainText { get; set; } = ""; public string SecondaryText { get; set; } = ""; - public TextBlock Error { get; private set; } - public TextBox Input { get; set; } - - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - Error = this.FindControl("Error"); - Input = this.FindControl("Input"); - - Input.Watermark = _placeholder; - - Input.AddHandler(TextInputEvent, Message_TextInput, RoutingStrategies.Tunnel, true); - } - public static async Task<(UserResult Result, string Input)> ShowInputDialog(StyleableWindow window, string title, SoftwareKeyboardUiArgs args) { - ContentDialog contentDialog = window.ContentDialog; + ContentDialog contentDialog = new ContentDialog(); UserResult result = UserResult.Cancel; diff --git a/Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs b/Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs index cdc5de93e..15ecaa77b 100644 --- a/Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs +++ b/Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs @@ -16,7 +16,6 @@ namespace Ryujinx.Ava.Ui.Controls private static bool _isChoiceDialogOpen; private async static Task ShowContentDialog( - StyleableWindow window, string title, string primaryText, string secondaryText, @@ -28,35 +27,32 @@ namespace Ryujinx.Ava.Ui.Controls { UserResult result = UserResult.None; - ContentDialog contentDialog = window.ContentDialog; + ContentDialog contentDialog = new ContentDialog(); await ShowDialog(); async Task ShowDialog() { - if (contentDialog != null) + contentDialog.Title = title; + contentDialog.PrimaryButtonText = primaryButton; + contentDialog.SecondaryButtonText = secondaryButton; + contentDialog.CloseButtonText = closeButton; + contentDialog.Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol); + + contentDialog.PrimaryButtonCommand = MiniCommand.Create(() => { - contentDialog.Title = title; - contentDialog.PrimaryButtonText = primaryButton; - contentDialog.SecondaryButtonText = secondaryButton; - contentDialog.CloseButtonText = closeButton; - contentDialog.Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol); + result = primaryButtonResult; + }); + contentDialog.SecondaryButtonCommand = MiniCommand.Create(() => + { + result = UserResult.No; + }); + contentDialog.CloseButtonCommand = MiniCommand.Create(() => + { + result = UserResult.Cancel; + }); - contentDialog.PrimaryButtonCommand = MiniCommand.Create(() => - { - result = primaryButtonResult; - }); - contentDialog.SecondaryButtonCommand = MiniCommand.Create(() => - { - result = UserResult.No; - }); - contentDialog.CloseButtonCommand = MiniCommand.Create(() => - { - result = UserResult.Cancel; - }); - - await contentDialog.ShowAsync(ContentDialogPlacement.Popup); - }; + await contentDialog.ShowAsync(ContentDialogPlacement.Popup); } return result; @@ -78,35 +74,30 @@ namespace Ryujinx.Ava.Ui.Controls UserResult result = UserResult.None; - ContentDialog contentDialog = window.ContentDialog; - - Window overlay = window; - - if (contentDialog != null) + ContentDialog contentDialog = new ContentDialog { - contentDialog.PrimaryButtonClick += DeferClose; - contentDialog.Title = title; - contentDialog.PrimaryButtonText = primaryButton; - contentDialog.SecondaryButtonText = secondaryButton; - contentDialog.CloseButtonText = closeButton; - contentDialog.Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol); - - contentDialog.PrimaryButtonCommand = MiniCommand.Create(() => + Title = title, + PrimaryButtonText = primaryButton, + SecondaryButtonText = secondaryButton, + CloseButtonText = closeButton, + Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol), + PrimaryButtonCommand = MiniCommand.Create(() => { result = primaryButton == LocaleManager.Instance["InputDialogYes"] ? UserResult.Yes : UserResult.Ok; - }); - contentDialog.SecondaryButtonCommand = MiniCommand.Create(() => - { - contentDialog.PrimaryButtonClick -= DeferClose; - result = UserResult.No; - }); - contentDialog.CloseButtonCommand = MiniCommand.Create(() => - { - contentDialog.PrimaryButtonClick -= DeferClose; - result = UserResult.Cancel; - }); - await contentDialog.ShowAsync(ContentDialogPlacement.Popup); + }), }; + contentDialog.SecondaryButtonCommand = MiniCommand.Create(() => + { + contentDialog.PrimaryButtonClick -= DeferClose; + result = UserResult.No; + }); + contentDialog.CloseButtonCommand = MiniCommand.Create(() => + { + contentDialog.PrimaryButtonClick -= DeferClose; + result = UserResult.Cancel; + }); + contentDialog.PrimaryButtonClick += DeferClose; + await contentDialog.ShowAsync(ContentDialogPlacement.Popup); return result; @@ -141,7 +132,7 @@ namespace Ryujinx.Ava.Ui.Controls if (doWhileDeferred != null) { - await doWhileDeferred(overlay); + await doWhileDeferred(window); deferResetEvent.Set(); } @@ -191,7 +182,6 @@ namespace Ryujinx.Ava.Ui.Controls } public static async Task CreateInfoDialog( - StyleableWindow window, string primary, string secondaryText, string acceptButton, @@ -199,7 +189,6 @@ namespace Ryujinx.Ava.Ui.Controls string title) { return await ShowContentDialog( - window, title, primary, secondaryText, @@ -210,7 +199,6 @@ namespace Ryujinx.Ava.Ui.Controls } internal static async Task CreateConfirmationDialog( - StyleableWindow window, string primaryText, string secondaryText, string acceptButtonText, @@ -219,7 +207,6 @@ namespace Ryujinx.Ava.Ui.Controls UserResult primaryButtonResult = UserResult.Yes) { return await ShowContentDialog( - window, string.IsNullOrWhiteSpace(title) ? LocaleManager.Instance["DialogConfirmationTitle"] : title, primaryText, secondaryText, @@ -235,10 +222,9 @@ namespace Ryujinx.Ava.Ui.Controls return new(mainText, secondaryText); } - internal static async Task CreateUpdaterInfoDialog(StyleableWindow window, string primary, string secondaryText) + internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText) { await ShowContentDialog( - window, LocaleManager.Instance["DialogUpdaterTitle"], primary, secondaryText, @@ -248,24 +234,9 @@ namespace Ryujinx.Ava.Ui.Controls (int)Symbol.Important); } - internal static async Task ShowNotAvailableMessage(StyleableWindow window) - { - // Temporary placeholder for features to be added - await ShowContentDialog( - window, - "Feature Not Available", - "The selected feature is not available in this version.", - "", - "", - "", - LocaleManager.Instance["InputDialogOk"], - (int)Symbol.Important); - } - - internal static async Task CreateWarningDialog(StyleableWindow window, string primary, string secondaryText) + internal static async Task CreateWarningDialog(string primary, string secondaryText) { await ShowContentDialog( - window, LocaleManager.Instance["DialogWarningTitle"], primary, secondaryText, @@ -275,12 +246,11 @@ namespace Ryujinx.Ava.Ui.Controls (int)Symbol.Important); } - internal static async Task CreateErrorDialog(StyleableWindow owner, string errorMessage, string secondaryErrorMessage = "") + internal static async Task CreateErrorDialog(string errorMessage, string secondaryErrorMessage = "") { Logger.Error?.Print(LogClass.Application, errorMessage); await ShowContentDialog( - owner, LocaleManager.Instance["DialogErrorTitle"], LocaleManager.Instance["DialogErrorMessage"], errorMessage, @@ -290,7 +260,7 @@ namespace Ryujinx.Ava.Ui.Controls (int)Symbol.Dismiss); } - internal static async Task CreateChoiceDialog(StyleableWindow window, string title, string primary, string secondaryText) + internal static async Task CreateChoiceDialog(string title, string primary, string secondaryText) { if (_isChoiceDialogOpen) { @@ -301,7 +271,6 @@ namespace Ryujinx.Ava.Ui.Controls UserResult response = await ShowContentDialog( - window, title, primary, secondaryText, @@ -316,19 +285,17 @@ namespace Ryujinx.Ava.Ui.Controls return response == UserResult.Yes; } - internal static async Task CreateExitDialog(StyleableWindow owner) + internal static async Task CreateExitDialog() { return await CreateChoiceDialog( - owner, LocaleManager.Instance["DialogExitTitle"], LocaleManager.Instance["DialogExitMessage"], LocaleManager.Instance["DialogExitSubMessage"]); } - internal static async Task CreateStopEmulationDialog(StyleableWindow owner) + internal static async Task CreateStopEmulationDialog() { return await CreateChoiceDialog( - owner, LocaleManager.Instance["DialogStopEmulationTitle"], LocaleManager.Instance["DialogStopEmulationMessage"], LocaleManager.Instance["DialogExitSubMessage"]); @@ -338,12 +305,10 @@ namespace Ryujinx.Ava.Ui.Controls string title, string mainText, string subText, - StyleableWindow owner, uint maxLength = int.MaxValue, string input = "") { var result = await InputDialog.ShowInputDialog( - owner, title, mainText, input, diff --git a/Ryujinx.Ava/Ui/Controls/InputDialog.axaml.cs b/Ryujinx.Ava/Ui/Controls/InputDialog.axaml.cs index b9bbb66da..e4b37decf 100644 --- a/Ryujinx.Ava/Ui/Controls/InputDialog.axaml.cs +++ b/Ryujinx.Ava/Ui/Controls/InputDialog.axaml.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; namespace Ryujinx.Ava.Ui.Controls { - public class InputDialog : UserControl + public partial class InputDialog : UserControl { public string Message { get; set; } public string Input { get; set; } @@ -24,8 +24,6 @@ namespace Ryujinx.Ava.Ui.Controls MaxLength = maxLength; DataContext = this; - - InitializeComponent(); } public InputDialog() @@ -33,33 +31,26 @@ namespace Ryujinx.Ava.Ui.Controls InitializeComponent(); } - private void InitializeComponent() + public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, string message, + string input = "", string subMessage = "", uint maxLength = int.MaxValue) { - AvaloniaXamlLoader.Load(this); - } - - public static async Task<(UserResult Result, string Input)> ShowInputDialog(StyleableWindow window, string title, string message, string input = "", string subMessage = "", uint maxLength = int.MaxValue) - { - ContentDialog contentDialog = window.ContentDialog; - UserResult result = UserResult.Cancel; - InputDialog content = new InputDialog(message, input = "", subMessage = "", maxLength); - - if (contentDialog != null) + InputDialog content = new InputDialog(message, input, subMessage, maxLength); + ContentDialog contentDialog = new ContentDialog { - contentDialog.Title = title; - contentDialog.PrimaryButtonText = LocaleManager.Instance["InputDialogOk"]; - contentDialog.SecondaryButtonText = ""; - contentDialog.CloseButtonText = LocaleManager.Instance["InputDialogCancel"]; - contentDialog.Content = content; - contentDialog.PrimaryButtonCommand = MiniCommand.Create(() => + Title = title, + PrimaryButtonText = LocaleManager.Instance["InputDialogOk"], + SecondaryButtonText = "", + CloseButtonText = LocaleManager.Instance["InputDialogCancel"], + Content = content, + PrimaryButtonCommand = MiniCommand.Create(() => { result = UserResult.Ok; input = content.Input; - }); - await contentDialog.ShowAsync(); - } + }) + }; + await contentDialog.ShowAsync(); return (result, input); } diff --git a/Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml b/Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml new file mode 100644 index 000000000..b0ab5d301 --- /dev/null +++ b/Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml.cs b/Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml.cs new file mode 100644 index 000000000..9ba631ad7 --- /dev/null +++ b/Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml.cs @@ -0,0 +1,85 @@ +using Avalonia; +using Avalonia.Controls; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Ui.ViewModels; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.Ui.Controls +{ + public partial class NavigationDialogHost : UserControl + { + public AccountManager AccountManager { get; } + public ContentManager ContentManager { get; } + public UserProfileViewModel ViewModel { get; set; } + + public NavigationDialogHost() + { + InitializeComponent(); + } + + public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager, + VirtualFileSystem virtualFileSystem) + { + AccountManager = accountManager; + ContentManager = contentManager; + ViewModel = new UserProfileViewModel(this); + + + if (contentManager.GetCurrentFirmwareVersion() != null) + { + Task.Run(() => + { + AvatarProfileViewModel.PreloadAvatars(contentManager, virtualFileSystem); + }); + } + InitializeComponent(); + } + + public void GoBack(object parameter = null) + { + if (ContentFrame.BackStack.Count > 0) + { + ContentFrame.GoBack(); + } + + ViewModel.LoadProfiles(); + } + + public void Navigate(Type sourcePageType, object parameter) + { + ContentFrame.Navigate(sourcePageType, parameter); + } + + public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager, VirtualFileSystem ownerVirtualFileSystem) + { + var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem); + ContentDialog contentDialog = new ContentDialog + { + Title = LocaleManager.Instance["UserProfileWindowTitle"], + PrimaryButtonText = "", + SecondaryButtonText = "", + CloseButtonText = LocaleManager.Instance["UserProfilesClose"], + Content = content, + Padding = new Thickness(0) + }; + + contentDialog.Closed += (sender, args) => + { + content.ViewModel.Dispose(); + }; + + await contentDialog.ShowAsync(); + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + Navigate(typeof(UserSelector), this); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml b/Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml index c6f43f43f..750edf8b0 100644 --- a/Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml +++ b/Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml @@ -1,14 +1,10 @@ - + x:Class="Ryujinx.Ava.Ui.Controls.ProfileImageSelectionDialog"> @@ -32,4 +28,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml.cs b/Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml.cs index 728b89069..5d361af92 100644 --- a/Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml.cs +++ b/Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml.cs @@ -1,8 +1,10 @@ -using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; -using Avalonia.Markup.Xaml; +using Avalonia.VisualTree; +using FluentAvalonia.UI.Controls; +using FluentAvalonia.UI.Navigation; using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Ui.Models; using Ryujinx.Ava.Ui.Windows; using Ryujinx.HLE.FileSystem; using SixLabors.ImageSharp; @@ -12,36 +14,40 @@ using Image = SixLabors.ImageSharp.Image; namespace Ryujinx.Ava.Ui.Controls { - public class ProfileImageSelectionDialog : StyleableWindow + public partial class ProfileImageSelectionDialog : UserControl { - private readonly ContentManager _contentManager; + private ContentManager _contentManager; + private NavigationDialogHost _parent; + private TempProfile _profile; public bool FirmwareFound => _contentManager.GetCurrentFirmwareVersion() != null; - public byte[] BufferImageProfile { get; set; } - - public ProfileImageSelectionDialog(ContentManager contentManager) - { - _contentManager = contentManager; - DataContext = this; - InitializeComponent(); -#if DEBUG - this.AttachDevTools(); -#endif - } - public ProfileImageSelectionDialog() { - DataContext = this; InitializeComponent(); -#if DEBUG - this.AttachDevTools(); -#endif + AddHandler(Frame.NavigatedToEvent, (s, e) => + { + NavigatedTo(e); + }, RoutingStrategies.Direct); } - private void InitializeComponent() + private void NavigatedTo(NavigationEventArgs arg) { - AvaloniaXamlLoader.Load(this); + if (Program.PreviewerDetached) + { + switch (arg.NavigationMode) + { + case NavigationMode.New: + (_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter; + _contentManager = _parent.ContentManager; + break; + case NavigationMode.Back: + _parent.GoBack(); + break; + } + + DataContext = this; + } } private async void Import_OnClick(object sender, RoutedEventArgs e) @@ -58,7 +64,7 @@ namespace Ryujinx.Ava.Ui.Controls dialog.AllowMultiple = false; - string[] image = await dialog.ShowAsync(this); + string[] image = await dialog.ShowAsync(((TopLevel)_parent.GetVisualRoot()) as Window); if (image != null) { @@ -66,28 +72,22 @@ namespace Ryujinx.Ava.Ui.Controls { string imageFile = image[0]; - ProcessProfileImage(File.ReadAllBytes(imageFile)); + _profile.Image = ProcessProfileImage(File.ReadAllBytes(imageFile)); } - Close(); + _parent.GoBack(); } } - private async void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e) + private void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e) { if (FirmwareFound) { - AvatarWindow window = new(_contentManager); - - await window.ShowDialog(this); - - BufferImageProfile = window.SelectedImage; - - Close(); + _parent.Navigate(typeof(AvatarWindow), (_parent, _profile)); } } - private void ProcessProfileImage(byte[] buffer) + private static byte[] ProcessProfileImage(byte[] buffer) { using (Image image = Image.Load(buffer)) { @@ -97,7 +97,7 @@ namespace Ryujinx.Ava.Ui.Controls { image.SaveAsJpeg(streamJpg); - BufferImageProfile = streamJpg.ToArray(); + return streamJpg.ToArray(); } } } diff --git a/Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml.cs b/Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml.cs index e4108ba40..eff15c7ba 100644 --- a/Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml.cs @@ -5,7 +5,7 @@ using Ryujinx.Ava.Ui.Windows; namespace Ryujinx.Ava.Ui.Controls { - public class UpdateWaitWindow : StyleableWindow + public partial class UpdateWaitWindow : StyleableWindow { public UpdateWaitWindow(string primaryText, string secondaryText) : this() { @@ -21,15 +21,5 @@ namespace Ryujinx.Ava.Ui.Controls this.AttachDevTools(); #endif } - - public TextBlock PrimaryText { get; private set; } - public TextBlock SecondaryText { get; private set; } - - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - PrimaryText = this.FindControl("PrimaryText"); - SecondaryText = this.FindControl("SecondaryText"); - } } } \ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Controls/UserEditor.axaml b/Ryujinx.Ava/Ui/Controls/UserEditor.axaml new file mode 100644 index 000000000..fed5f8cb2 --- /dev/null +++ b/Ryujinx.Ava/Ui/Controls/UserEditor.axaml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + - - + + - - - - - - - - + + + - - - + + + - - - + + + - - - - + + + + + - - - + + - - - - - - - - - - + + + + + + + + + - - - - - + - - - - - + + + diff --git a/Ryujinx.Ava/Ui/Windows/AboutWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/AboutWindow.axaml.cs index 3f93ccb75..75e9901b0 100644 --- a/Ryujinx.Ava/Ui/Windows/AboutWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Windows/AboutWindow.axaml.cs @@ -13,7 +13,7 @@ using System.Threading.Tasks; namespace Ryujinx.Ava.Ui.Windows { - public class AboutWindow : StyleableWindow + public partial class AboutWindow : StyleableWindow { public AboutWindow() { @@ -39,15 +39,6 @@ namespace Ryujinx.Ava.Ui.Windows public string Developers => string.Format(LocaleManager.Instance["AboutPageDeveloperListMore"], "gdkchan, Ac_K, Thog, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, Xpl0itR, GoffyDude, »jD«"); - public TextBlock SupportersTextBlock { get; set; } - - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - - SupportersTextBlock = this.FindControl("SupportersTextBlock"); - } - private void Button_OnClick(object sender, RoutedEventArgs e) { if (sender is Button button) diff --git a/Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml.cs index bd0935a9c..5ed845710 100644 --- a/Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml.cs @@ -7,7 +7,7 @@ using Ryujinx.Ava.Ui.ViewModels; namespace Ryujinx.Ava.Ui.Windows { - public class AmiiboWindow : StyleableWindow + public partial class AmiiboWindow : StyleableWindow { public AmiiboWindow(bool showAll, string lastScannedAmiiboId, string titleId) { @@ -44,11 +44,6 @@ namespace Ryujinx.Ava.Ui.Windows public Amiibo.AmiiboApi ScannedAmiibo { get; set; } public AmiiboWindowViewModel ViewModel { get; set; } - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } - private void ScanButton_Click(object sender, RoutedEventArgs e) { if (ViewModel.AmiiboSelectedIndex > -1) diff --git a/Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml b/Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml index 6c7576bc0..0be651300 100644 --- a/Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml +++ b/Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml @@ -1,36 +1,35 @@ - + x:DataType="viewModels:AvatarProfileViewModel"> - - - - + + + + - - + @@ -45,9 +44,9 @@