Ver Fonte

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
Emmanuel Hansen há 3 anos atrás
pai
commit
6e02cac952
46 ficheiros alterados com 967 adições e 952 exclusões
  1. 0 1
      Ryujinx.Ava/App.axaml.cs
  2. 13 11
      Ryujinx.Ava/AppHost.cs
  3. 9 2
      Ryujinx.Ava/Assets/Locales/en_US.json
  4. 4 8
      Ryujinx.Ava/Common/ApplicationHelper.cs
  5. 11 13
      Ryujinx.Ava/Modules/Updater/Updater.cs
  6. 1 0
      Ryujinx.Ava/Ryujinx.Ava.csproj
  7. 3 3
      Ryujinx.Ava/Ui/Applet/AvaHostUiHandler.cs
  8. 1 9
      Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml.cs
  9. 6 16
      Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml.cs
  10. 46 81
      Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs
  11. 14 23
      Ryujinx.Ava/Ui/Controls/InputDialog.axaml.cs
  12. 10 0
      Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml
  13. 85 0
      Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml.cs
  14. 3 7
      Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml
  15. 35 35
      Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml.cs
  16. 1 11
      Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml.cs
  17. 55 0
      Ryujinx.Ava/Ui/Controls/UserEditor.axaml
  18. 123 0
      Ryujinx.Ava/Ui/Controls/UserEditor.axaml.cs
  19. 1 1
      Ryujinx.Ava/Ui/Controls/UserErrorDialog.cs
  20. 90 0
      Ryujinx.Ava/Ui/Controls/UserSelector.axaml
  21. 79 0
      Ryujinx.Ava/Ui/Controls/UserSelector.axaml.cs
  22. 55 0
      Ryujinx.Ava/Ui/Models/TempProfile.cs
  23. 2 2
      Ryujinx.Ava/Ui/ViewModels/AmiiboWindowViewModel.cs
  24. 6 7
      Ryujinx.Ava/Ui/ViewModels/ControllerSettingsViewModel.cs
  25. 16 22
      Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs
  26. 1 2
      Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs
  27. 33 62
      Ryujinx.Ava/Ui/ViewModels/UserProfileViewModel.cs
  28. 115 115
      Ryujinx.Ava/Ui/Windows/AboutWindow.axaml
  29. 1 10
      Ryujinx.Ava/Ui/Windows/AboutWindow.axaml.cs
  30. 1 6
      Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml.cs
  31. 12 13
      Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml
  32. 36 30
      Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml.cs
  33. 1 6
      Ryujinx.Ava/Ui/Windows/CheatWindow.axaml.cs
  34. 1 10
      Ryujinx.Ava/Ui/Windows/ControllerSettingsWindow.axaml.cs
  35. 6 18
      Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml.cs
  36. 0 12
      Ryujinx.Ava/Ui/Windows/MainWindow.axaml
  37. 17 46
      Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs
  38. 24 33
      Ryujinx.Ava/Ui/Windows/MotionSettingsWindow.axaml.cs
  39. 20 29
      Ryujinx.Ava/Ui/Windows/RumbleSettingsWindow.axaml.cs
  40. 3 8
      Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml
  41. 22 55
      Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml.cs
  42. 0 7
      Ryujinx.Ava/Ui/Windows/StyleableWindow.cs
  43. 4 13
      Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs
  44. 1 16
      Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml.cs
  45. 0 107
      Ryujinx.Ava/Ui/Windows/UserProfileWindow.axaml
  46. 0 102
      Ryujinx.Ava/Ui/Windows/UserProfileWindow.axaml.cs

+ 0 - 1
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"],

+ 13 - 11
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

+ 9 - 2
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}]"
 }

+ 4 - 8
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);
                             });
                         }
                     }

+ 11 - 13
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"]);
                 }
             }
 

+ 1 - 0
Ryujinx.Ava/Ryujinx.Ava.csproj

@@ -26,6 +26,7 @@
     <PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
     <PackageReference Include="DynamicData" Version="7.9.4" />
     <PackageReference Include="FluentAvaloniaUI" Version="1.4.1" />
+    <PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" />
 
     <PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
     <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />

+ 3 - 3
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));
                 }
             });
 

+ 1 - 9
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<StackPanel>("ButtonStack");
-        }
     }
 }

+ 6 - 16
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<int> _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<TextBlock>("Error");
-            Input = this.FindControl<TextBox>("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;
 

+ 46 - 81
Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs

@@ -16,7 +16,6 @@ namespace Ryujinx.Ava.Ui.Controls
         private static bool _isChoiceDialogOpen;
 
         private async static Task<UserResult> 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.Title = title;
+                contentDialog.PrimaryButtonText = primaryButton;
+                contentDialog.SecondaryButtonText = secondaryButton;
+                contentDialog.CloseButtonText = closeButton;
+                contentDialog.Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol);
 
-                    contentDialog.PrimaryButtonCommand = MiniCommand.Create(() =>
-                    {
-                        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<UserResult> 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<UserResult> 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<bool> CreateChoiceDialog(StyleableWindow window, string title, string primary, string secondaryText)
+        internal static async Task<bool> 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<bool> CreateExitDialog(StyleableWindow owner)
+        internal static async Task<bool> CreateExitDialog()
         {
             return await CreateChoiceDialog(
-                owner,
                 LocaleManager.Instance["DialogExitTitle"],
                 LocaleManager.Instance["DialogExitMessage"],
                 LocaleManager.Instance["DialogExitSubMessage"]);
         }
 
-        internal static async Task<bool> CreateStopEmulationDialog(StyleableWindow owner)
+        internal static async Task<bool> 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,

+ 14 - 23
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);
         }

+ 10 - 0
Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml

@@ -0,0 +1,10 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="Ryujinx.Ava.Ui.Controls.NavigationDialogHost">
+    <ui:Frame HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
+              x:Name="ContentFrame" />
+</UserControl>

+ 85 - 0
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);
+        }
+    }
+}

+ 3 - 7
Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml

@@ -1,14 +1,10 @@
-<Window xmlns="https://github.com/avaloniaui"
+<UserControl xmlns="https://github.com/avaloniaui"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         mc:Ignorable="d"
         xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
-        x:Class="Ryujinx.Ava.Ui.Controls.ProfileImageSelectionDialog"
-        SizeToContent="WidthAndHeight"
-        WindowStartupLocation="CenterOwner"
-        Title="{Locale:Locale ProfileImageSelectionTitle}"
-        CanResize="false">
+        x:Class="Ryujinx.Ava.Ui.Controls.ProfileImageSelectionDialog">
     <Grid HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5,10,5, 5">
         <Grid.RowDefinitions>
             <RowDefinition Height="Auto" />
@@ -32,4 +28,4 @@
             </Button>
         </StackPanel>
     </Grid>
-</Window>
+</UserControl>

+ 35 - 35
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();
                 }
             }
         }

+ 1 - 11
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<TextBlock>("PrimaryText");
-            SecondaryText = this.FindControl<TextBlock>("SecondaryText");
-        }
     }
 }

+ 55 - 0
Ryujinx.Ava/Ui/Controls/UserEditor.axaml

@@ -0,0 +1,55 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" 
+             Padding="0"
+             Margin="0"
+             xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
+             xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
+             xmlns:models="clr-namespace:Ryujinx.Ava.Ui.Models"
+             xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
+             xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+             x:Class="Ryujinx.Ava.Ui.Controls.UserEditor">
+    <UserControl.Resources>
+        <controls:BitmapArrayValueConverter x:Key="ByteImage" />
+    </UserControl.Resources>
+    <Grid Margin="0">
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="Auto"/>
+            <ColumnDefinition/>
+        </Grid.ColumnDefinitions>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+        <StackPanel Orientation="Vertical" VerticalAlignment="Stretch" HorizontalAlignment="Left">
+            <Image
+                Margin="0"
+                HorizontalAlignment="Stretch"
+                VerticalAlignment="Top"
+                Height="96" Width="96"
+                Name="ProfileImage"
+                Source="{Binding Image, Converter={StaticResource ByteImage}}" />
+            <Button Margin="5" Content="{Locale:Locale UserProfilesChangeProfileImage}"
+                    Name="ChangePictureButton"
+                    Click="ChangePictureButton_Click"
+                    HorizontalAlignment="Stretch"/>
+			<Button Margin="5" Content="{Locale:Locale UserProfilesSetProfileImage}"
+                    Name="AddPictureButton"
+                    Click="ChangePictureButton_Click"
+                    HorizontalAlignment="Stretch"/>
+        </StackPanel>
+        <StackPanel Grid.Row="0" Orientation="Vertical" HorizontalAlignment="Stretch" Grid.Column="1" Spacing="10"
+                    Margin="5, 10">
+            <TextBox Name="NameBox" Width="300" Text="{Binding Name}" MaxLength="{Binding MaxProfileNameLength}"
+                     HorizontalAlignment="Stretch" />
+            <TextBlock Text="{Binding UserId}" Name="IdLabel" />
+        </StackPanel>
+        <StackPanel Grid.Column="0" Grid.ColumnSpan="2"  Grid.Row="1" Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right">
+            <Button Content="{Locale:Locale Save}" Name="SaveButton" Click="SaveButton_Click"/>
+            <Button HorizontalAlignment="Right" Content="{Locale:Locale Discard}"
+                    Name="CloseButton" Click="CloseButton_Click"/>
+        </StackPanel>
+    </Grid>
+</UserControl>

+ 123 - 0
Ryujinx.Ava/Ui/Controls/UserEditor.axaml.cs

@@ -0,0 +1,123 @@
+using Avalonia.Controls;
+using Avalonia.Data;
+using Avalonia.Interactivity;
+using FluentAvalonia.UI.Controls;
+using FluentAvalonia.UI.Navigation;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.Ui.Models;
+using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
+
+namespace Ryujinx.Ava.Ui.Controls
+{
+    public partial class UserEditor : UserControl
+    {
+        private NavigationDialogHost _parent;
+        private UserProfile _profile;
+        private bool _isNewUser;
+
+        public TempProfile TempProfile { get; set; }
+        public uint MaxProfileNameLength => 0x20;
+
+        public UserEditor()
+        {
+            InitializeComponent();
+            AddHandler(Frame.NavigatedToEvent, (s, e) =>
+            {
+                NavigatedTo(e);
+            }, RoutingStrategies.Direct);
+        }
+
+        private void NavigatedTo(NavigationEventArgs arg)
+        {
+            if (Program.PreviewerDetached)
+            {
+                switch (arg.NavigationMode)
+                {
+                    case NavigationMode.New:
+                        var args = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
+                        _isNewUser = args.isNewUser;
+                        if (!_isNewUser)
+                        {
+                            _profile = args.profile;
+                            TempProfile = new TempProfile(_profile);
+                        }
+                        else
+                        {
+                            TempProfile = new TempProfile();
+                        }
+
+                        _parent = args.parent;
+                        break;
+                }
+
+                DataContext = TempProfile;
+
+                AddPictureButton.IsVisible = _isNewUser;
+                IdLabel.IsVisible = !_isNewUser;
+                ChangePictureButton.IsVisible = !_isNewUser;
+            }
+        }
+
+        private void CloseButton_Click(object sender, RoutedEventArgs e)
+        {
+            _parent?.GoBack();
+        }
+
+        private void SaveButton_Click(object sender, RoutedEventArgs e)
+        {
+            DataValidationErrors.ClearErrors(NameBox);
+            bool isInvalid = false;
+
+            if (string.IsNullOrWhiteSpace(TempProfile.Name))
+            {
+                DataValidationErrors.SetError(NameBox, new DataValidationException(LocaleManager.Instance["UserProfileEmptyNameError"]));
+
+                isInvalid = true;
+            }
+
+            if (TempProfile.Image == null)
+            {
+                ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["UserProfileNoImageError"], "");
+
+                isInvalid = true;
+            }
+
+            if(isInvalid)
+            {
+                return;
+            }
+
+            if (_profile != null)
+            {
+                _profile.Name = TempProfile.Name;
+                _profile.Image = TempProfile.Image;
+                _profile.UpdateState();
+                _parent.AccountManager.SetUserName(_profile.UserId, _profile.Name);
+                _parent.AccountManager.SetUserImage(_profile.UserId, _profile.Image);
+            }
+            else if (_isNewUser)
+            {
+                _parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image);
+            }
+            else
+            {
+                return;
+            }
+
+            _parent?.GoBack();
+        }
+
+        public void SelectProfileImage()
+        {
+            _parent.Navigate(typeof(ProfileImageSelectionDialog), (_parent, TempProfile));
+        }
+
+        private void ChangePictureButton_Click(object sender, RoutedEventArgs e)
+        {
+            if (_profile != null || _isNewUser)
+            {
+                SelectProfileImage();
+            }
+        }
+    }
+}

+ 1 - 1
Ryujinx.Ava/Ui/Controls/UserErrorDialog.cs

@@ -75,7 +75,7 @@ namespace Ryujinx.Ava.Ui.Controls
 
             string setupButtonLabel = isInSetupGuide ? LocaleManager.Instance["OpenSetupGuideMessage"] : "";
 
-            var result = await ContentDialogHelper.CreateInfoDialog(owner,
+            var result = await ContentDialogHelper.CreateInfoDialog(
                 string.Format(LocaleManager.Instance["DialogUserErrorDialogMessage"], errorCode, GetErrorTitle(error)),
                 GetErrorDescription(error) + (isInSetupGuide
                     ? LocaleManager.Instance["DialogUserErrorDialogInfoMessage"]

+ 90 - 0
Ryujinx.Ava/Ui/Controls/UserSelector.axaml

@@ -0,0 +1,90 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
+             xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
+             xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
+             xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
+             xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="Ryujinx.Ava.Ui.Controls.UserSelector">
+    <UserControl.Resources>
+        <controls:BitmapArrayValueConverter x:Key="ByteImage" />
+    </UserControl.Resources>
+    <Design.DataContext>
+        <viewModels:UserProfileViewModel />
+    </Design.DataContext>
+    <Grid HorizontalAlignment="Stretch"
+          VerticalAlignment="Stretch">
+        <Grid.RowDefinitions>
+            <RowDefinition />
+            <RowDefinition Height="Auto" />
+        </Grid.RowDefinitions>
+        <ListBox HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5" Items="{Binding Profiles}"
+                 DoubleTapped="ProfilesList_DoubleTapped"
+                 SelectionChanged="SelectingItemsControl_SelectionChanged">
+            <ListBox.ItemsPanel>
+                <ItemsPanelTemplate>
+                    <flex:FlexPanel
+                        HorizontalAlignment="Stretch"
+                        VerticalAlignment="Stretch"
+                        AlignContent="FlexStart"
+                        JustifyContent="Center" />
+                </ItemsPanelTemplate>
+            </ListBox.ItemsPanel>
+            <ListBox.ItemTemplate>
+                <DataTemplate>
+                    <Grid>
+                        <Border
+                            Margin="2"
+                            HorizontalAlignment="Stretch"
+                            VerticalAlignment="Stretch"
+                            ClipToBounds="True"
+                            CornerRadius="5">
+                            <Grid Margin="0">
+                                <Grid.RowDefinitions>
+                                    <RowDefinition Height="Auto" />
+                                    <RowDefinition Height="Auto" />
+                                </Grid.RowDefinitions>
+                                <Image
+                                    Grid.Row="0"
+                                    Margin="0"
+                                    HorizontalAlignment="Stretch"
+                                    VerticalAlignment="Top"
+                                    Height="96" Width="96"
+                                    Source="{Binding Image, Converter={StaticResource ByteImage}}" />
+                                <StackPanel
+                                    Grid.Row="1"
+                                    Height="30"
+                                    Margin="5"
+                                    HorizontalAlignment="Stretch"
+                                    VerticalAlignment="Stretch">
+                                    <TextBlock
+                                        HorizontalAlignment="Stretch"
+                                        Text="{Binding Name}"
+                                        TextAlignment="Center"
+                                        TextWrapping="Wrap" />
+                                </StackPanel>
+                            </Grid>
+                        </Border>
+                        <Border HorizontalAlignment="Left" VerticalAlignment="Top"
+                                IsVisible="{Binding IsOpened}"
+                                Background="LimeGreen"
+                                Width="10"
+                                Height="10"
+                                Margin="5"
+                                CornerRadius="5" />
+                    </Grid>
+                </DataTemplate>
+            </ListBox.ItemTemplate>
+        </ListBox>
+        <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="10,0" Spacing="10" HorizontalAlignment="Center">
+            <Button Content="{Locale:Locale UserProfilesAddNewProfile}" Command="{Binding AddUser}" />
+            <Button IsEnabled="{Binding  IsSelectedProfiledEditable}"
+                    Content="{Locale:Locale UserProfilesEditProfile}" Command="{Binding EditUser}" />
+            <Button IsEnabled="{Binding IsSelectedProfileDeletable}"
+                    Content="{Locale:Locale UserProfilesDeleteSelectedProfile}" Command="{Binding DeleteUser}" />
+        </StackPanel>
+    </Grid>
+</UserControl>

+ 79 - 0
Ryujinx.Ava/Ui/Controls/UserSelector.axaml.cs

@@ -0,0 +1,79 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using FluentAvalonia.UI.Controls;
+using FluentAvalonia.UI.Navigation;
+using Ryujinx.Ava.Ui.ViewModels;
+using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
+
+namespace Ryujinx.Ava.Ui.Controls
+{
+    public partial class UserSelector : UserControl
+    {
+        private NavigationDialogHost _parent;
+        public UserProfileViewModel ViewModel { get; set; }
+
+        public UserSelector()
+        {
+            InitializeComponent();
+
+            if (Program.PreviewerDetached)
+            {
+                AddHandler(Frame.NavigatedToEvent, (s, e) =>
+                {
+                    NavigatedTo(e);
+                }, Avalonia.Interactivity.RoutingStrategies.Direct);
+            }
+        }
+
+        private void NavigatedTo(NavigationEventArgs arg)
+        {
+            if (Program.PreviewerDetached)
+            {
+                switch (arg.NavigationMode)
+                {
+                    case NavigationMode.New:
+                        _parent = (NavigationDialogHost)arg.Parameter;
+                        ViewModel = _parent.ViewModel;
+                        break;
+                }
+
+                DataContext = ViewModel;
+            }
+        }
+
+        private void ProfilesList_DoubleTapped(object sender, RoutedEventArgs e)
+        {
+            if (sender is ListBox listBox)
+            {
+                int selectedIndex = listBox.SelectedIndex;
+
+                if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
+                {
+                    ViewModel.SelectedProfile = ViewModel.Profiles[selectedIndex];
+
+                    _parent?.AccountManager?.OpenUser(ViewModel.SelectedProfile.UserId);
+
+                    ViewModel.LoadProfiles();
+
+                    foreach (UserProfile profile in ViewModel.Profiles)
+                    {
+                        profile.UpdateState();
+                    }
+                }
+            }
+        }
+
+        private void SelectingItemsControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            if (sender is ListBox listBox)
+            {
+                int selectedIndex = listBox.SelectedIndex;
+
+                if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
+                {
+                    ViewModel.HighlightedProfile = ViewModel.Profiles[selectedIndex];
+                }
+            }
+        }
+    }
+}

+ 55 - 0
Ryujinx.Ava/Ui/Models/TempProfile.cs

@@ -0,0 +1,55 @@
+using Ryujinx.Ava.Ui.ViewModels;
+using Ryujinx.HLE.HOS.Services.Account.Acc;
+using System;
+
+namespace Ryujinx.Ava.Ui.Models
+{
+    public class TempProfile : BaseModel
+    {
+        private readonly UserProfile _profile;
+        private byte[] _image = null;
+        private string _name = String.Empty;
+        private UserId _userId;
+
+        public byte[] Image
+        {
+            get => _image;
+            set
+            {
+                _image = value;
+                OnPropertyChanged();
+            }
+        }
+
+        public UserId UserId
+        {
+            get => _userId;
+            set
+            {
+                _userId = value;
+                OnPropertyChanged();
+            }
+        }
+
+        public string Name
+        {
+            get => _name;
+            set
+            {
+                _name = value;
+                OnPropertyChanged();
+            }
+        }
+
+        public TempProfile(UserProfile profile)
+        {
+            _profile = profile;
+
+            Image = profile.Image;
+            Name = profile.Name;
+            UserId = profile.UserId;
+        }
+
+        public TempProfile(){}
+    }
+}

+ 2 - 2
Ryujinx.Ava/Ui/ViewModels/AmiiboWindowViewModel.cs

@@ -390,7 +390,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
                 return amiiboJsonString;
             }
 
-            await ContentDialogHelper.CreateInfoDialog(_owner, LocaleManager.Instance["DialogAmiiboApiTitle"],
+            await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance["DialogAmiiboApiTitle"],
                 LocaleManager.Instance["DialogAmiiboApiFailFetchMessage"],
                 LocaleManager.Instance["InputDialogOk"],
                 "",
@@ -440,7 +440,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
 
         private async void ShowInfoDialog()
         {
-            await ContentDialogHelper.CreateInfoDialog(_owner, LocaleManager.Instance["DialogAmiiboApiTitle"],
+            await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance["DialogAmiiboApiTitle"],
                 LocaleManager.Instance["DialogAmiiboApiConnectErrorMessage"],
                 LocaleManager.Instance["InputDialogOk"],
                 "",

+ 6 - 7
Ryujinx.Ava/Ui/ViewModels/ControllerSettingsViewModel.cs

@@ -327,12 +327,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
 
         public async void ShowMotionConfig()
         {
-            await MotionSettingsWindow.Show(this, _owner.GetVisualRoot() as StyleableWindow);
+            await MotionSettingsWindow.Show(this);
         }
 
         public async void ShowRumbleConfig()
         {
-            await RumbleSettingsWindow.Show(this, _owner.GetVisualRoot() as StyleableWindow);
+            await RumbleSettingsWindow.Show(this);
         }
 
         private void LoadInputDriver()
@@ -701,8 +701,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
                 catch (InvalidOperationException)
                 {
                     Logger.Error?.Print(LogClass.Configuration, $"Profile {ProfileName} is incompatible with the current input configuration system.");
-                    await ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow,
-                        String.Format(LocaleManager.Instance["DialogProfileInvalidProfileErrorMessage"], ProfileName));
+
+                    await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogProfileInvalidProfileErrorMessage"], ProfileName));
 
                     return;
                 }
@@ -736,7 +736,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
 
             if (ProfileName == LocaleManager.Instance["ControllerSettingsProfileDefault"])
             {
-                await ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow, LocaleManager.Instance["DialogProfileDefaultProfileOverwriteErrorMessage"]);
+                await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogProfileDefaultProfileOverwriteErrorMessage"]);
 
                 return;
             }
@@ -769,7 +769,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
                 }
                 else
                 {
-                    await ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow, LocaleManager.Instance["DialogProfileInvalidProfileNameErrorMessage"]);
+                    await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogProfileInvalidProfileNameErrorMessage"]);
                 }
             }
         }
@@ -782,7 +782,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
             }
 
             UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
-                _owner.GetVisualRoot() as StyleableWindow,
                 LocaleManager.Instance["DialogProfileDeleteProfileTitle"],
                 LocaleManager.Instance["DialogProfileDeleteProfileMessage"],
                 LocaleManager.Instance["InputDialogYes"],

+ 16 - 22
Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs

@@ -708,7 +708,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
             {
                 return;
             }
-            
+
             if (_owner.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
             {
                 string titleId = _owner.AppHost.Device.Application.TitleIdText.ToUpper();
@@ -975,9 +975,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
 
         public async void ManageProfiles()
         {
-            UserProfileWindow window = new(_owner.AccountManager, _owner.ContentManager, _owner.VirtualFileSystem);
-
-            await window.ShowDialog(_owner);
+            await NavigationDialogHost.Show(_owner.AccountManager, _owner.ContentManager, _owner.VirtualFileSystem);
         }
 
         public async void OpenAboutWindow()
@@ -1054,8 +1052,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
                     {
                         Dispatcher.UIThread.Post(async () =>
                         {
-                            await ContentDialogHelper.CreateErrorDialog(_owner,
-                                LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
+                            await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
                         });
 
                         return;
@@ -1138,7 +1135,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
                 DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "1"));
 
                 // FIXME: Found a way to reproduce the bold effect on the title name (fork?).
-                UserResult result = await ContentDialogHelper.CreateConfirmationDialog(_owner, LocaleManager.Instance["DialogWarning"],
+                UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
                     string.Format(LocaleManager.Instance["DialogPPTCDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
 
                 List<FileInfo> cacheFiles = new();
@@ -1163,7 +1160,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
                         }
                         catch (Exception e)
                         {
-                            await ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], file.Name, e));
+                            await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], file.Name, e));
                         }
                     }
                 }
@@ -1201,7 +1198,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
                 DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader"));
 
                 // FIXME: Found a way to reproduce the bold effect on the title name (fork?).
-                UserResult result = await ContentDialogHelper.CreateConfirmationDialog(_owner, LocaleManager.Instance["DialogWarning"],
+                UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
                     string.Format(LocaleManager.Instance["DialogShaderDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
 
                 List<DirectoryInfo> oldCacheDirectories = new List<DirectoryInfo>();
@@ -1224,7 +1221,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
                         }
                         catch (Exception e)
                         {
-                            await ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], directory.Name, e));
+                            await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], directory.Name, e));
                         }
                     }
                 }
@@ -1237,7 +1234,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
                     }
                     catch (Exception e)
                     {
-                        await ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["ShaderCachePurgeError"], file.Name, e));
+                        await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["ShaderCachePurgeError"], file.Name, e));
                     }
                 }
             }
@@ -1316,12 +1313,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
                 Task.Run(() =>
                 {
                     if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
-                       out ulong titleIdNumber))
+                        out ulong titleIdNumber))
                     {
                         Dispatcher.UIThread.Post(async () =>
                         {
-                            await ContentDialogHelper.CreateErrorDialog(_owner,
-                                LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
+                            await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
                         });
 
                         return;
@@ -1342,12 +1338,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
                 Task.Run(() =>
                 {
                     if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
-                       out ulong titleIdNumber))
+                        out ulong titleIdNumber))
                     {
                         Dispatcher.UIThread.Post(async () =>
                         {
-                            await ContentDialogHelper.CreateErrorDialog(_owner,
-                                LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
+                            await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
                         });
 
                         return;
@@ -1406,7 +1401,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
 
                 if (firmwareVersion == null)
                 {
-                    await ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareNotFoundErrorMessage"], filename));
+                    await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareNotFoundErrorMessage"], filename));
 
                     return;
                 }
@@ -1426,7 +1421,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
                 dialogMessage += LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallConfirmMessage"];
 
                 UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
-                    _owner,
                     dialogTitle,
                     dialogMessage,
                     LocaleManager.Instance["InputDialogYes"],
@@ -1456,7 +1450,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
 
                                 string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallSuccessMessage"], firmwareVersion.VersionString);
 
-                                await ContentDialogHelper.CreateInfoDialog(_owner, dialogTitle, message, LocaleManager.Instance["InputDialogOk"], "", LocaleManager.Instance["RyujinxInfo"]);
+                                await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance["InputDialogOk"], "", LocaleManager.Instance["RyujinxInfo"]);
                                 Logger.Info?.Print(LogClass.Application, message);
 
                                 // Purge Applet Cache.
@@ -1475,7 +1469,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
                             {
                                 waitingDialog.Close();
 
-                                await ContentDialogHelper.CreateErrorDialog(_owner, ex.Message);
+                                await ContentDialogHelper.CreateErrorDialog(ex.Message);
                             });
                         }
                         finally
@@ -1496,7 +1490,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
             }
             catch (Exception ex)
             {
-                await ContentDialogHelper.CreateErrorDialog(_owner, ex.Message);
+                await ContentDialogHelper.CreateErrorDialog(ex.Message);
             }
         }
 

+ 1 - 2
Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs

@@ -63,8 +63,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
                     {
                         Dispatcher.UIThread.Post(async () =>
                         {
-                            await ContentDialogHelper.CreateInfoDialog(_owner,
-                                                                       LocaleManager.Instance["DialogSettingsBackendThreadingWarningMessage"],
+                            await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance["DialogSettingsBackendThreadingWarningMessage"],
                                                                        "",
                                                                        "",
                                                                        LocaleManager.Instance["InputDialogOk"],

+ 33 - 62
Ryujinx.Ava/Ui/ViewModels/UserProfileViewModel.cs

@@ -1,31 +1,27 @@
 using Avalonia.Threading;
 using Ryujinx.Ava.Common.Locale;
 using Ryujinx.Ava.Ui.Controls;
-using Ryujinx.Ava.Ui.Windows;
 using Ryujinx.HLE.HOS.Services.Account.Acc;
 using System;
 using System.Collections.ObjectModel;
 using System.Linq;
-using System.Threading.Tasks;
 using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
 
 namespace Ryujinx.Ava.Ui.ViewModels
 {
     public class UserProfileViewModel : BaseModel, IDisposable
     {
-        private const uint MaxProfileNameLength = 0x20;
-
-        private readonly UserProfileWindow _owner;
+        private readonly NavigationDialogHost _owner;
 
         private UserProfile _selectedProfile;
-        private string _tempUserName;
+        private UserProfile _highlightedProfile;
 
         public UserProfileViewModel()
         {
             Profiles = new ObservableCollection<UserProfile>();
         }
 
-        public UserProfileViewModel(UserProfileWindow owner) : this()
+        public UserProfileViewModel(NavigationDialogHost owner) : this()
         {
             _owner = owner;
 
@@ -42,12 +38,29 @@ namespace Ryujinx.Ava.Ui.ViewModels
                 _selectedProfile = value;
 
                 OnPropertyChanged(nameof(SelectedProfile));
-                OnPropertyChanged(nameof(IsSelectedProfileDeletable));
+                OnPropertyChanged(nameof(IsHighlightedProfileDeletable));
+                OnPropertyChanged(nameof(IsHighlightedProfileEditable));
             }
         }
 
-        public bool IsSelectedProfileDeletable =>
-            _selectedProfile != null && _selectedProfile.UserId != AccountManager.DefaultUserId;
+        public bool IsHighlightedProfileEditable =>
+            _highlightedProfile != null;
+
+        public bool IsHighlightedProfileDeletable =>
+            _highlightedProfile != null && _highlightedProfile.UserId != AccountManager.DefaultUserId;
+
+        public UserProfile HighlightedProfile
+        {
+            get => _highlightedProfile;
+            set
+            {
+                _highlightedProfile = value;
+
+                OnPropertyChanged(nameof(HighlightedProfile));
+                OnPropertyChanged(nameof(IsHighlightedProfileDeletable));
+                OnPropertyChanged(nameof(IsHighlightedProfileEditable));
+            }
+        }
 
         public void Dispose()
         {
@@ -78,64 +91,24 @@ namespace Ryujinx.Ava.Ui.ViewModels
             }
         }
 
-        public async void ChooseProfileImage()
+        public void AddUser()
         {
-            await SelectProfileImage();
+            UserProfile userProfile = null;
+            _owner.Navigate(typeof(UserEditor), (this._owner, userProfile, true));
         }
 
-        public async Task SelectProfileImage(bool isNewUser = false)
+        public void EditUser()
         {
-            ProfileImageSelectionDialog selectionDialog = new(_owner.ContentManager);
-
-            await selectionDialog.ShowDialog(_owner);
-
-            if (selectionDialog.BufferImageProfile != null)
-            {
-                if (isNewUser)
-                {
-                    if (!string.IsNullOrWhiteSpace(_tempUserName))
-                    {
-                        _owner.AccountManager.AddUser(_tempUserName, selectionDialog.BufferImageProfile);
-                    }
-                }
-                else if (SelectedProfile != null)
-                {
-                    _owner.AccountManager.SetUserImage(SelectedProfile.UserId, selectionDialog.BufferImageProfile);
-                    SelectedProfile.Image = selectionDialog.BufferImageProfile;
-
-                    SelectedProfile = null;
-                }
-
-                LoadProfiles();
-            }
-        }
-
-        public async void AddUser()
-        {
-            var dlgTitle = LocaleManager.Instance["InputDialogAddNewProfileTitle"];
-            var dlgMainText = LocaleManager.Instance["InputDialogAddNewProfileHeader"];
-            var dlgSubText = string.Format(LocaleManager.Instance["InputDialogAddNewProfileSubtext"],
-                MaxProfileNameLength);
-
-            _tempUserName =
-                await ContentDialogHelper.CreateInputDialog(dlgTitle, dlgMainText, dlgSubText, _owner,
-                    MaxProfileNameLength);
-
-            if (!string.IsNullOrWhiteSpace(_tempUserName))
-            {
-                await SelectProfileImage(true);
-            }
-
-            _tempUserName = String.Empty;
+            _owner.Navigate(typeof(UserEditor), (this._owner, _highlightedProfile ?? SelectedProfile, false));
         }
 
         public async void DeleteUser()
         {
-            if (_selectedProfile != null)
+            if (_highlightedProfile != null)
             {
                 var lastUserId = _owner.AccountManager.LastOpenedUser.UserId;
 
-                if (_selectedProfile.UserId == lastUserId)
+                if (_highlightedProfile.UserId == lastUserId)
                 {
                     // If we are deleting the currently open profile, then we must open something else before deleting.
                     var profile = Profiles.FirstOrDefault(x => x.UserId != lastUserId);
@@ -144,8 +117,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
                     {
                         Dispatcher.UIThread.Post(async () =>
                         {
-                            await ContentDialogHelper.CreateErrorDialog(_owner,
-                                LocaleManager.Instance["DialogUserProfileDeletionWarningMessage"]);
+                            await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUserProfileDeletionWarningMessage"]);
                         });
 
                         return;
@@ -155,13 +127,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
                 }
 
                 var result =
-                    await ContentDialogHelper.CreateConfirmationDialog(_owner,
-                        LocaleManager.Instance["DialogUserProfileDeletionConfirmMessage"], "",
+                    await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogUserProfileDeletionConfirmMessage"], "",
                         LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], "");
 
                 if (result == UserResult.Yes)
                 {
-                    _owner.AccountManager.DeleteUser(_selectedProfile.UserId);
+                    _owner.AccountManager.DeleteUser(_highlightedProfile.UserId);
                 }
             }
 

+ 115 - 115
Ryujinx.Ava/Ui/Windows/AboutWindow.axaml

@@ -17,43 +17,43 @@
     SizeToContent="Width"
     WindowStartupLocation="CenterOwner"
     mc:Ignorable="d">
-    <Grid
+	<Grid
         Margin="15"
         HorizontalAlignment="Stretch"
         VerticalAlignment="Stretch">
-        <Grid.RowDefinitions>
-            <RowDefinition Height="Auto" />
-            <RowDefinition Height="*" />
-        </Grid.RowDefinitions>
-        <Grid.ColumnDefinitions>
-            <ColumnDefinition Width="Auto" />
-            <ColumnDefinition Width="Auto" />
-            <ColumnDefinition Width="*" />
-        </Grid.ColumnDefinitions>
-        <Grid
+		<Grid.RowDefinitions>
+			<RowDefinition Height="Auto" />
+			<RowDefinition Height="*" />
+		</Grid.RowDefinitions>
+		<Grid.ColumnDefinitions>
+			<ColumnDefinition Width="Auto" />
+			<ColumnDefinition Width="Auto" />
+			<ColumnDefinition Width="*" />
+		</Grid.ColumnDefinitions>
+		<Grid
             Grid.Row="1"
             Grid.Column="0"
             Margin="20"
             HorizontalAlignment="Stretch"
             VerticalAlignment="Stretch">
-            <Grid.RowDefinitions>
-                <RowDefinition Height="*" />
-                <RowDefinition Height="*" />
-                <RowDefinition />
-                <RowDefinition />
-                <RowDefinition Height="Auto" />
-            </Grid.RowDefinitions>
-            <Grid Grid.Row="0">
-                <Grid.ColumnDefinitions>
-                    <ColumnDefinition />
-                    <ColumnDefinition />
-                </Grid.ColumnDefinitions>
-                <Grid.RowDefinitions>
-                    <RowDefinition Height="Auto" />
-                    <RowDefinition Height="Auto" />
-                    <RowDefinition Height="Auto" />
-                </Grid.RowDefinitions>
-                <Image
+			<Grid.RowDefinitions>
+				<RowDefinition Height="*" />
+				<RowDefinition Height="*" />
+				<RowDefinition />
+				<RowDefinition />
+				<RowDefinition Height="Auto" />
+			</Grid.RowDefinitions>
+			<Grid Grid.Row="0">
+				<Grid.ColumnDefinitions>
+					<ColumnDefinition />
+					<ColumnDefinition />
+				</Grid.ColumnDefinitions>
+				<Grid.RowDefinitions>
+					<RowDefinition Height="Auto" />
+					<RowDefinition Height="Auto" />
+					<RowDefinition Height="Auto" />
+				</Grid.RowDefinitions>
+				<Image
                     Grid.Row="0"
                     Grid.RowSpan="3"
                     Grid.Column="0"
@@ -61,21 +61,21 @@
                     MinWidth="50"
                     Margin="5,10,20,10"
                     Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
-                <TextBlock
+				<TextBlock
                     Grid.Row="0"
                     Grid.Column="1"
                     Margin="0,20,0,0"
                     FontSize="35"
                     Text="Ryujinx"
                     TextAlignment="Center" />
-                <TextBlock
+				<TextBlock
                     Grid.Row="1"
                     Grid.Column="1"
                     Margin="0,0,0,0"
                     FontSize="16"
                     Text="(REE-YOU-JINX)"
                     TextAlignment="Center" />
-                <Button
+				<Button
                     Grid.Row="2"
                     Grid.Column="1"
                     Margin="0"
@@ -83,27 +83,27 @@
                     Background="Transparent"
                     Click="Button_OnClick"
                     Tag="https://www.ryujinx.org/">
-                    <TextBlock
+					<TextBlock
                         Text="www.ryujinx.org"
                         TextAlignment="Center"
                         TextDecorations="Underline"
                         ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}" />
-                </Button>
-            </Grid>
-            <TextBlock
+				</Button>
+			</Grid>
+			<TextBlock
                 Grid.Row="1"
                 HorizontalAlignment="Center"
                 VerticalAlignment="Center"
                 Text="{Binding Version}"
                 TextAlignment="Center" />
-            <TextBlock
+			<TextBlock
                 Grid.Row="2"
                 Margin="20"
                 HorizontalAlignment="Center"
                 MaxLines="2"
                 Text="{locale:Locale AboutDisclaimerMessage}"
                 TextAlignment="Center" />
-            <TextBlock
+			<TextBlock
                 Name="AmiiboLabel"
                 Grid.Row="3"
                 Margin="20"
@@ -112,94 +112,94 @@
                 PointerPressed="AmiiboLabel_OnPointerPressed"
                 Text="{locale:Locale AboutAmiiboDisclaimerMessage}"
                 TextAlignment="Center" />
-            <StackPanel
+			<StackPanel
                 Grid.Row="4"
                 HorizontalAlignment="Center"
                 Orientation="Horizontal"
                 Spacing="10">
-                <StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}">
-                    <Button
+				<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}">
+					<Button
                         Height="65"
                         Background="Transparent"
                         Click="Button_OnClick"
                         Tag="https://www.patreon.com/ryujinx">
-                        <Grid>
-                            <Grid.RowDefinitions>
-                                <RowDefinition />
-                                <RowDefinition Height="Auto" />
-                            </Grid.RowDefinitions>
-                            <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Patreon.png?assembly=Ryujinx.Ui.Common" />
-                            <TextBlock
+						<Grid>
+							<Grid.RowDefinitions>
+								<RowDefinition />
+								<RowDefinition Height="Auto" />
+							</Grid.RowDefinitions>
+							<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Patreon.png?assembly=Ryujinx.Ui.Common" />
+							<TextBlock
                                 Grid.Row="1"
                                 Margin="0,5,0,0"
                                 HorizontalAlignment="Center"
                                 Text="Patreon" />
-                        </Grid>
-                    </Button>
-                </StackPanel>
-                <StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
-                    <Button
+						</Grid>
+					</Button>
+				</StackPanel>
+				<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
+					<Button
                         Height="65"
                         Background="Transparent"
                         Click="Button_OnClick"
                         Tag="https://github.com/Ryujinx/Ryujinx">
-                        <Grid>
-                            <Grid.RowDefinitions>
-                                <RowDefinition />
-                                <RowDefinition Height="Auto" />
-                            </Grid.RowDefinitions>
-                            <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_GitHub.png?assembly=Ryujinx.Ui.Common" />
-                            <TextBlock
+						<Grid>
+							<Grid.RowDefinitions>
+								<RowDefinition />
+								<RowDefinition Height="Auto" />
+							</Grid.RowDefinitions>
+							<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_GitHub.png?assembly=Ryujinx.Ui.Common" />
+							<TextBlock
                                 Grid.Row="1"
                                 Margin="0,5,0,0"
                                 HorizontalAlignment="Center"
                                 Text="GitHub" />
-                        </Grid>
-                    </Button>
-                </StackPanel>
-                <StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
-                    <Button
+						</Grid>
+					</Button>
+				</StackPanel>
+				<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
+					<Button
                         Height="65"
                         Background="Transparent"
                         Click="Button_OnClick"
                         Tag="https://discordapp.com/invite/N2FmfVc">
-                        <Grid>
-                            <Grid.RowDefinitions>
-                                <RowDefinition />
-                                <RowDefinition Height="Auto" />
-                            </Grid.RowDefinitions>
-                            <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Discord.png?assembly=Ryujinx.Ui.Common" />
-                            <TextBlock
+						<Grid>
+							<Grid.RowDefinitions>
+								<RowDefinition />
+								<RowDefinition Height="Auto" />
+							</Grid.RowDefinitions>
+							<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Discord.png?assembly=Ryujinx.Ui.Common" />
+							<TextBlock
                                 Grid.Row="1"
                                 Margin="0,5,0,0"
                                 HorizontalAlignment="Center"
                                 Text="Discord" />
-                        </Grid>
-                    </Button>
-                </StackPanel>
-                <StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}">
-                    <Button
+						</Grid>
+					</Button>
+				</StackPanel>
+				<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}">
+					<Button
                         Height="65"
                         Background="Transparent"
                         Click="Button_OnClick"
                         Tag="https://twitter.com/RyujinxEmu">
-                        <Grid>
-                            <Grid.RowDefinitions>
-                                <RowDefinition />
-                                <RowDefinition Height="Auto" />
-                            </Grid.RowDefinitions>
-                            <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Twitter.png?assembly=Ryujinx.Ui.Common" />
-                            <TextBlock
+						<Grid>
+							<Grid.RowDefinitions>
+								<RowDefinition />
+								<RowDefinition Height="Auto" />
+							</Grid.RowDefinitions>
+							<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Twitter.png?assembly=Ryujinx.Ui.Common" />
+							<TextBlock
                                 Grid.Row="1"
                                 Margin="0,5,0,0"
                                 HorizontalAlignment="Center"
                                 Text="Twitter" />
-                        </Grid>
-                    </Button>
-                </StackPanel>
-            </StackPanel>
-        </Grid>
-        <Border
+						</Grid>
+					</Button>
+				</StackPanel>
+			</StackPanel>
+		</Grid>
+		<Border
             Grid.Row="1"
             Grid.Column="1"
             Width="2"
@@ -207,62 +207,62 @@
             VerticalAlignment="Stretch"
             BorderBrush="White"
             BorderThickness="1,0,0,0">
-            <Separator Width="0" />
-        </Border>
-        <Grid
+			<Separator Width="0" />
+		</Border>
+		<Grid
             Grid.Row="1"
             Grid.Column="2"
             Margin="20"
             HorizontalAlignment="Stretch"
             VerticalAlignment="Stretch">
-            <Grid.RowDefinitions>
-                <RowDefinition Height="Auto" />
-                <RowDefinition Height="Auto" />
-                <RowDefinition Height="Auto" />
-                <RowDefinition Height="Auto" />
-                <RowDefinition Height="Auto" />
-                <RowDefinition Height="Auto" />
-                <RowDefinition Height="Auto" />
-            </Grid.RowDefinitions>
-            <TextBlock
+			<Grid.RowDefinitions>
+				<RowDefinition Height="Auto" />
+				<RowDefinition Height="Auto" />
+				<RowDefinition Height="Auto" />
+				<RowDefinition Height="Auto" />
+				<RowDefinition Height="Auto" />
+				<RowDefinition Height="Auto" />
+				<RowDefinition Height="Auto" />
+			</Grid.RowDefinitions>
+			<TextBlock
                 FontWeight="Bold"
                 Text="{locale:Locale AboutRyujinxAboutTitle}"
                 TextDecorations="Underline" />
-            <TextBlock
+			<TextBlock
                 Grid.Row="1"
                 Margin="20,5,5,5"
                 LineHeight="20"
                 Text="{locale:Locale AboutRyujinxAboutContent}" />
-            <TextBlock
+			<TextBlock
                 Grid.Row="2"
                 Margin="0,10,0,0"
                 FontWeight="Bold"
                 Text="{locale:Locale AboutRyujinxMaintainersTitle}"
                 TextDecorations="Underline" />
-            <TextBlock
+			<TextBlock
                 Grid.Row="3"
                 Margin="20,5,5,5"
                 LineHeight="20"
                 Text="{Binding Developers}" />
-            <Button
+			<Button
                 Grid.Row="4"
                 HorizontalAlignment="Right"
                 Background="Transparent"
                 Click="Button_OnClick"
                 Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a">
-                <TextBlock
+				<TextBlock
                     Text="{locale:Locale AboutRyujinxContributorsButtonHeader}"
                     TextAlignment="Right"
                     TextDecorations="Underline"
                     ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" />
-            </Button>
-            <TextBlock
+			</Button>
+			<TextBlock
                 Grid.Row="5"
                 Margin="0,0,0,0"
                 FontWeight="Bold"
                 Text="{locale:Locale AboutRyujinxSupprtersTitle}"
                 TextDecorations="Underline" />
-            <Border
+			<Border
                 Grid.Row="6"
                 Width="460"
                 Height="200"
@@ -271,12 +271,12 @@
                 VerticalAlignment="Stretch"
                 BorderBrush="White"
                 BorderThickness="1">
-                <TextBlock
+				<TextBlock
                     Name="SupportersTextBlock"
                     VerticalAlignment="Top"
                     Text="{Binding Supporters}"
                     TextWrapping="Wrap" />
-            </Border>
-        </Grid>
-    </Grid>
+			</Border>
+		</Grid>
+	</Grid>
 </window:StyleableWindow>

+ 1 - 10
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<TextBlock>("SupportersTextBlock");
-        }
-
         private void Button_OnClick(object sender, RoutedEventArgs e)
         {
             if (sender is Button button)

+ 1 - 6
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)

+ 12 - 13
Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml

@@ -1,36 +1,35 @@
-<Window xmlns="https://github.com/avaloniaui"
+<UserControl xmlns="https://github.com/avaloniaui"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350"
         x:Class="Ryujinx.Ava.Ui.Windows.AvatarWindow"
-        CanResize="False"
+		Margin="0"
+		Padding="0"
         xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
         xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
         xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
-        WindowStartupLocation="CenterOwner"
         x:CompileBindings="True"
-        x:DataType="viewModels:AvatarProfileViewModel"
-        SizeToContent="WidthAndHeight">
+        x:DataType="viewModels:AvatarProfileViewModel">
     <Design.DataContext>
         <viewModels:AvatarProfileViewModel />
     </Design.DataContext>
-    <Window.Resources>
-        <controls:BitmapArrayValueConverter x:Key="ByteImage" />
-    </Window.Resources>
-    <Grid Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
+	<UserControl.Resources>
+		<controls:BitmapArrayValueConverter x:Key="ByteImage" />
+	</UserControl.Resources>
+    <Grid Margin="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
         <Grid.RowDefinitions>
             <RowDefinition Height="Auto" />
             <RowDefinition Height="*" />
             <RowDefinition Height="Auto" />
             <RowDefinition Height="Auto" />
         </Grid.RowDefinitions>
-        <ListBox Grid.Row="1" BorderThickness="0" SelectedIndex="{Binding SelectedIndex}" Width="600" Height="500"
+        <ListBox Grid.Row="1" BorderThickness="0" SelectedIndex="{Binding SelectedIndex}" Height="400"
                  Items="{Binding Images}" HorizontalAlignment="Stretch" VerticalAlignment="Center">
             <ListBox.ItemsPanel>
                 <ItemsPanelTemplate>
-                    <WrapPanel Orientation="Horizontal" MaxWidth="600" Margin="0" HorizontalAlignment="Center" />
+                    <WrapPanel Orientation="Horizontal" MaxWidth="700" Margin="0" HorizontalAlignment="Center" />
                 </ItemsPanelTemplate>
             </ListBox.ItemsPanel>
             <ListBox.ItemTemplate>
@@ -45,9 +44,9 @@
         <StackPanel Grid.Row="3" Orientation="Horizontal" Spacing="10" Margin="10" HorizontalAlignment="Center">
             <Button Content="{Locale:Locale AvatarChoose}" Width="200" Name="ChooseButton" Click="ChooseButton_OnClick" />
             <ui:ColorPickerButton Color="{Binding BackgroundColor, Mode=TwoWay}" Name="ColorButton" />
-            <Button HorizontalAlignment="Right" Content="{Locale:Locale AvatarClose}" Click="CloseButton_OnClick"
+            <Button HorizontalAlignment="Right" Content="{Locale:Locale Discard}" Click="CloseButton_OnClick"
                     Name="CloseButton"
                     Width="200" />
         </StackPanel>
     </Grid>
-</Window>
+</UserControl>

+ 36 - 30
Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml.cs

@@ -1,70 +1,76 @@
-using Avalonia;
+using Avalonia.Controls;
 using Avalonia.Interactivity;
-using Avalonia.Markup.Xaml;
-using Ryujinx.Ava.Common.Locale;
+using FluentAvalonia.UI.Controls;
+using FluentAvalonia.UI.Navigation;
+using Ryujinx.Ava.Ui.Controls;
+using Ryujinx.Ava.Ui.Models;
 using Ryujinx.Ava.Ui.ViewModels;
 using Ryujinx.HLE.FileSystem;
-using System;
 
 namespace Ryujinx.Ava.Ui.Windows
 {
-    public class AvatarWindow : StyleableWindow
+    public partial class AvatarWindow : UserControl
     {
+        private NavigationDialogHost _parent;
+        private TempProfile _profile;
+
         public AvatarWindow(ContentManager contentManager)
         {
             ContentManager = contentManager;
-            ViewModel = new AvatarProfileViewModel(() => ViewModel.ReloadImages());
 
             DataContext = ViewModel;
 
             InitializeComponent();
-#if DEBUG
-            this.AttachDevTools();
-#endif
-            Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["AvatarWindowTitle"];
         }
 
         public AvatarWindow()
         {
             InitializeComponent();
-#if DEBUG
-            this.AttachDevTools();
-#endif
+
+            AddHandler(Frame.NavigatedToEvent, (s, e) =>
+            {
+                NavigatedTo(e);
+            }, RoutingStrategies.Direct);
+        }
+
+        private void NavigatedTo(NavigationEventArgs arg)
+        {
             if (Program.PreviewerDetached)
             {
-                Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["AvatarWindowTitle"];
+                if (arg.NavigationMode == NavigationMode.New)
+                {
+                    (_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter;
+                    ContentManager = _parent.ContentManager;
+                    if (Program.PreviewerDetached)
+                    {
+                        ViewModel = new AvatarProfileViewModel(() => ViewModel.ReloadImages());
+                    }
+
+                    DataContext = ViewModel;
+                }
             }
         }
 
-        public ContentManager ContentManager { get; }
-
-        public byte[] SelectedImage { get; set; }
+        public ContentManager ContentManager { get; private set; }
 
         internal AvatarProfileViewModel ViewModel { get; set; }
 
-        private void InitializeComponent()
-        {
-            AvaloniaXamlLoader.Load(this);
-        }
-
-        protected override void OnClosed(EventArgs e)
+        private void CloseButton_OnClick(object sender, RoutedEventArgs e)
         {
             ViewModel.Dispose();
-            base.OnClosed(e);
-        }
 
-        private void CloseButton_OnClick(object sender, RoutedEventArgs e)
-        {
-            Close();
+            _parent.GoBack();
         }
 
         private void ChooseButton_OnClick(object sender, RoutedEventArgs e)
         {
             if (ViewModel.SelectedIndex > -1)
             {
-                SelectedImage = ViewModel.SelectedImage;
+                _profile.Image = ViewModel.SelectedImage;
+
+                ViewModel.Dispose();
 
-                Close();
+                _parent.GoBack();
             }
         }
     }

+ 1 - 6
Ryujinx.Ava/Ui/Windows/CheatWindow.axaml.cs

@@ -12,7 +12,7 @@ using System.Linq;
 
 namespace Ryujinx.Ava.Ui.Windows
 {
-    public class CheatWindow : StyleableWindow
+    public partial class CheatWindow : StyleableWindow
     {
         private readonly string _enabledCheatsPath;
         public bool NoCheatsFound { get; }
@@ -102,11 +102,6 @@ namespace Ryujinx.Ava.Ui.Windows
             this.AttachDevTools();
         }
 
-        private void InitializeComponent()
-        {
-            AvaloniaXamlLoader.Load(this);
-        }
-
         public void Save()
         {
             if (NoCheatsFound)

+ 1 - 10
Ryujinx.Ava/Ui/Windows/ControllerSettingsWindow.axaml.cs

@@ -24,11 +24,10 @@ using Key = Ryujinx.Input.Key;
 
 namespace Ryujinx.Ava.Ui.Windows
 {
-    public class ControllerSettingsWindow : UserControl
+    public partial class ControllerSettingsWindow : UserControl
     {
         private bool _dialogOpen;
 
-        public Grid SettingButtons { get; set; }
         private ButtonKeyAssigner _currentAssigner;
         internal ControllerSettingsViewModel ViewModel { get; set; }
 
@@ -48,13 +47,6 @@ namespace Ryujinx.Ava.Ui.Windows
             }
         }
 
-        private void InitializeComponent()
-        {
-            AvaloniaXamlLoader.Load(this);
-
-            SettingButtons = this.FindControl<Grid>("SettingButtons");
-        }
-
         protected override void OnPointerReleased(PointerReleasedEventArgs e)
         {
             base.OnPointerReleased(e);
@@ -165,7 +157,6 @@ namespace Ryujinx.Ava.Ui.Windows
                 _dialogOpen = true;
 
                 var result = await ContentDialogHelper.CreateConfirmationDialog(
-                    this.GetVisualRoot() as StyleableWindow,
                     LocaleManager.Instance["DialogControllerSettingsModifiedConfirmMessage"],
                     LocaleManager.Instance["DialogControllerSettingsModifiedConfirmSubMessage"],
                     LocaleManager.Instance["InputDialogYes"],

+ 6 - 18
Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml.cs

@@ -1,7 +1,6 @@
 using Avalonia;
 using Avalonia.Collections;
 using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
 using Avalonia.Threading;
 using LibHac.Common;
 using LibHac.Fs;
@@ -27,7 +26,7 @@ using Path = System.IO.Path;
 
 namespace Ryujinx.Ava.Ui.Windows
 {
-    public class DlcManagerWindow : StyleableWindow
+    public partial class DlcManagerWindow : StyleableWindow
     {
         private readonly List<DlcContainer> _dlcContainerList;
         private readonly string _dlcJsonPath;
@@ -35,7 +34,6 @@ namespace Ryujinx.Ava.Ui.Windows
         public VirtualFileSystem VirtualFileSystem { get; }
 
         public AvaloniaList<DlcModel> Dlcs { get; set; }
-        public Grid DlcGrid { get; private set; }
         public ulong TitleId { get; }
         public string TitleName { get; }
 
@@ -47,7 +45,7 @@ namespace Ryujinx.Ava.Ui.Windows
 
             InitializeComponent();
             AttachDebugDevTools();
-            
+
             Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"];
         }
 
@@ -72,7 +70,7 @@ namespace Ryujinx.Ava.Ui.Windows
 
             InitializeComponent();
             AttachDebugDevTools();
-            
+
             Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"];
 
             LoadDlcs();
@@ -84,15 +82,6 @@ namespace Ryujinx.Ava.Ui.Windows
             this.AttachDevTools();
         }
 
-        private void InitializeComponent()
-        {
-            Dlcs = new AvaloniaList<DlcModel>();
-
-            AvaloniaXamlLoader.Load(this);
-
-            DlcGrid = this.FindControl<Grid>("DlcGrid");
-        }
-
         private void LoadDlcs()
         {
             foreach (DlcContainer dlcContainer in _dlcContainerList)
@@ -129,8 +118,7 @@ namespace Ryujinx.Ava.Ui.Windows
             {
                 Dispatcher.UIThread.InvokeAsync(async () =>
                 {
-                    await ContentDialogHelper.CreateErrorDialog(this,
-                    string.Format(LocaleManager.Instance[
+                    await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[
                         "DialogDlcLoadNcaErrorMessage"], ex.Message, containerPath));
                 });
             }
@@ -180,7 +168,7 @@ namespace Ryujinx.Ava.Ui.Windows
 
                 if (!containsDlc)
                 {
-                    await ContentDialogHelper.CreateErrorDialog(this, LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]);
+                    await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]);
                 }
             }
         }
@@ -189,7 +177,7 @@ namespace Ryujinx.Ava.Ui.Windows
         {
             if (removeSelectedOnly)
             {
-               Dlcs.RemoveAll(Dlcs.Where(x => x.IsEnabled).ToList());
+                Dlcs.RemoveAll(Dlcs.Where(x => x.IsEnabled).ToList());
             }
             else
             {

+ 0 - 12
Ryujinx.Ava/Ui/Windows/MainWindow.axaml

@@ -38,18 +38,6 @@
             <RowDefinition Height="*" />
         </Grid.RowDefinitions>
         <controls:OffscreenTextBox Name="HiddenTextBox" Grid.Row="0" />
-        <ContentControl
-            Grid.Row="1"
-            Focusable="False"
-            IsVisible="False"
-            KeyboardNavigation.IsTabStop="False">
-            <ui:ContentDialog
-                Name="ContentDialog"
-                IsPrimaryButtonEnabled="True"
-                IsSecondaryButtonEnabled="True"
-                IsVisible="True"
-                KeyboardNavigation.IsTabStop="False" />
-        </ContentControl>
         <StackPanel Grid.Row="0" IsVisible="False">
             <controls:HotKeyControl Name="FullscreenHotKey" Command="{ReflectionBinding ToggleFullscreen}" />
             <controls:HotKeyControl Name="FullscreenHotKey2" Command="{ReflectionBinding ToggleFullscreen}" />

+ 17 - 46
Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs

@@ -36,7 +36,7 @@ using InputManager = Ryujinx.Input.HLE.InputManager;
 using ProgressBar = Avalonia.Controls.ProgressBar;
 namespace Ryujinx.Ava.Ui.Windows
 {
-    public class MainWindow : StyleableWindow
+    public partial class MainWindow : StyleableWindow
     {
         private bool _canUpdate;
         private bool _isClosing;
@@ -62,22 +62,6 @@ namespace Ryujinx.Ava.Ui.Windows
         public InputManager InputManager { get; private set; }
 
         internal RendererControl GlRenderer { get; private set; }
-        public ContentControl ContentFrame { get; private set; }
-        public TextBlock LoadStatus { get; private set; }
-        public TextBlock FirmwareStatus { get; private set; }
-        public TextBox SearchBox { get; private set; }
-        public ProgressBar LoadProgressBar { get; private set; }
-        public Menu Menu { get; private set; }
-        public MenuItem UpdateMenuItem { get; private set; }
-        public MenuItem ActionsMenuItem { get; private set; }
-        public GameGridView GameGrid { get; private set; }
-        public GameListView GameList { get; private set; }
-        public OffscreenTextBox HiddenTextBox { get; private set; }
-        public HotKeyControl FullscreenHotKey { get; private set; }
-        public HotKeyControl FullscreenHotKey2 { get; private set; }
-        public HotKeyControl DockToggleHotKey { get; private set; }
-        public HotKeyControl ExitHotKey { get; private set; }
-        public ToggleSplitButton VolumeStatus { get; set; }
         internal MainWindowViewModel ViewModel { get; private set; }
         public SettingsWindow SettingsWindow { get; set; }
 
@@ -102,6 +86,7 @@ namespace Ryujinx.Ava.Ui.Windows
             DataContext = ViewModel;
 
             InitializeComponent();
+            Load();
             AttachDebugDevTools();
 
             UiHandler = new AvaHostUiHandler(this);
@@ -192,7 +177,9 @@ namespace Ryujinx.Ava.Ui.Windows
                 string mainMessage = LocaleManager.Instance["DialogPerformanceCheckLoggingEnabledMessage"];
                 string secondaryMessage = LocaleManager.Instance["DialogPerformanceCheckLoggingEnabledConfirmMessage"];
 
-                UserResult result = await ContentDialogHelper.CreateConfirmationDialog(this, mainMessage, secondaryMessage, LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
+                UserResult result = await ContentDialogHelper.CreateConfirmationDialog(mainMessage, secondaryMessage,
+                    LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"],
+                    LocaleManager.Instance["RyujinxConfirm"]);
 
                 if (result != UserResult.Yes)
                 {
@@ -205,9 +192,12 @@ namespace Ryujinx.Ava.Ui.Windows
             if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value))
             {
                 string mainMessage = LocaleManager.Instance["DialogPerformanceCheckShaderDumpEnabledMessage"];
-                string secondaryMessage = LocaleManager.Instance["DialogPerformanceCheckShaderDumpEnabledConfirmMessage"];
+                string secondaryMessage =
+                    LocaleManager.Instance["DialogPerformanceCheckShaderDumpEnabledConfirmMessage"];
 
-                UserResult result = await ContentDialogHelper.CreateConfirmationDialog(this, mainMessage, secondaryMessage, LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
+                UserResult result = await ContentDialogHelper.CreateConfirmationDialog(mainMessage, secondaryMessage,
+                    LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"],
+                    LocaleManager.Instance["RyujinxConfirm"]);
 
                 if (result != UserResult.Yes)
                 {
@@ -231,7 +221,7 @@ namespace Ryujinx.Ava.Ui.Windows
         {
             if (AppHost != null)
             {
-                await ContentDialogHelper.CreateInfoDialog(this,
+                await ContentDialogHelper.CreateInfoDialog(
                     LocaleManager.Instance["DialogLoadAppGameAlreadyLoadedMessage"],
                     LocaleManager.Instance["DialogLoadAppGameAlreadyLoadedSubMessage"],
                     LocaleManager.Instance["InputDialogOk"],
@@ -254,7 +244,7 @@ namespace Ryujinx.Ava.Ui.Windows
 
             PrepareLoadScreen();
 
-            _mainViewContent = ContentFrame.Content as Control;
+            _mainViewContent = Content.Content as Control;
 
             GlRenderer = new RendererControl(3, 3, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
             AppHost = new AppHost(GlRenderer, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this);
@@ -321,7 +311,7 @@ namespace Ryujinx.Ava.Ui.Windows
 
             Dispatcher.UIThread.InvokeAsync(() =>
             {
-                ContentFrame.Content = GlRenderer;
+                Content.Content = GlRenderer;
 
                 if (startFullscreen && WindowState != WindowState.FullScreen)
                 {
@@ -365,9 +355,9 @@ namespace Ryujinx.Ava.Ui.Windows
 
             Dispatcher.UIThread.InvokeAsync(() =>
             {
-                if (ContentFrame.Content != _mainViewContent)
+                if (Content.Content != _mainViewContent)
                 {
-                    ContentFrame.Content = _mainViewContent;
+                    Content.Content = _mainViewContent;
                 }
 
                 ViewModel.ShowMenuAndStatusBar = true;
@@ -501,27 +491,8 @@ namespace Ryujinx.Ava.Ui.Windows
             ViewModel.IsAppletMenuActive = hasApplet;
         }
 
-        private void InitializeComponent()
+        private void Load()
         {
-            AvaloniaXamlLoader.Load(this);
-
-            ContentFrame = this.FindControl<ContentControl>("Content");
-            GameList = this.FindControl<GameListView>("GameList");
-            LoadStatus = this.FindControl<TextBlock>("LoadStatus");
-            FirmwareStatus = this.FindControl<TextBlock>("FirmwareStatus");
-            LoadProgressBar = this.FindControl<ProgressBar>("LoadProgressBar");
-            SearchBox = this.FindControl<TextBox>("SearchBox");
-            Menu = this.FindControl<Menu>("Menu");
-            UpdateMenuItem = this.FindControl<MenuItem>("UpdateMenuItem");
-            GameGrid = this.FindControl<GameGridView>("GameGrid");
-            HiddenTextBox = this.FindControl<OffscreenTextBox>("HiddenTextBox");
-            FullscreenHotKey = this.FindControl<HotKeyControl>("FullscreenHotKey");
-            FullscreenHotKey2 = this.FindControl<HotKeyControl>("FullscreenHotKey2");
-            DockToggleHotKey = this.FindControl<HotKeyControl>("DockToggleHotKey");
-            ExitHotKey = this.FindControl<HotKeyControl>("ExitHotKey");
-            VolumeStatus = this.FindControl<ToggleSplitButton>("VolumeStatus");
-            ActionsMenuItem = this.FindControl<MenuItem>("ActionsMenuItem");
-
             VolumeStatus.Click += VolumeStatus_CheckedChanged;
 
             GameGrid.ApplicationOpened += Application_Opened;
@@ -710,7 +681,7 @@ namespace Ryujinx.Ava.Ui.Windows
         {
             Dispatcher.UIThread.InvokeAsync(async () =>
            {
-               _isClosing = await ContentDialogHelper.CreateExitDialog(this);
+               _isClosing = await ContentDialogHelper.CreateExitDialog();
 
                if (_isClosing)
                {

+ 24 - 33
Ryujinx.Ava/Ui/Windows/MotionSettingsWindow.axaml.cs

@@ -9,13 +9,14 @@ using System.Threading.Tasks;
 
 namespace Ryujinx.Ava.Ui.Windows
 {
-    public class MotionSettingsWindow : UserControl
+    public partial class MotionSettingsWindow : UserControl
     {
         private readonly InputConfiguration<GamepadInputId, StickInputId> _viewmodel;
 
         public MotionSettingsWindow()
         {
             InitializeComponent();
+            DataContext = _viewmodel;
         }
 
         public MotionSettingsWindow(ControllerSettingsViewModel viewmodel)
@@ -36,46 +37,36 @@ namespace Ryujinx.Ava.Ui.Windows
             };
 
             InitializeComponent();
-        }
-
-        private void InitializeComponent()
-        {
             DataContext = _viewmodel;
-
-            AvaloniaXamlLoader.Load(this);
         }
 
-        public static async Task Show(ControllerSettingsViewModel viewmodel, StyleableWindow window)
+        public static async Task Show(ControllerSettingsViewModel viewmodel)
         {
-            ContentDialog contentDialog = window.ContentDialog;
-
-            string name = string.Empty;
-
             MotionSettingsWindow content = new MotionSettingsWindow(viewmodel);
 
-            if (contentDialog != null)
+            ContentDialog contentDialog = new ContentDialog
             {
-                contentDialog.Title = LocaleManager.Instance["ControllerMotionTitle"];
-                contentDialog.PrimaryButtonText = LocaleManager.Instance["ControllerSettingsSave"];
-                contentDialog.SecondaryButtonText = "";
-                contentDialog.CloseButtonText = LocaleManager.Instance["ControllerSettingsClose"];
-                contentDialog.Content = content;
-                contentDialog.PrimaryButtonClick += (sender, args) =>
-                {
-                    var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
-                    config.Slot = content._viewmodel.Slot;
-                    config.EnableMotion = content._viewmodel.EnableMotion;
-                    config.Sensitivity = content._viewmodel.Sensitivity;
-                    config.GyroDeadzone = content._viewmodel.GyroDeadzone;
-                    config.AltSlot = content._viewmodel.AltSlot;
-                    config.DsuServerHost = content._viewmodel.DsuServerHost;
-                    config.DsuServerPort = content._viewmodel.DsuServerPort;
-                    config.EnableCemuHookMotion = content._viewmodel.EnableCemuHookMotion;
-                    config.MirrorInput = content._viewmodel.MirrorInput;
-                };
+                Title = LocaleManager.Instance["ControllerMotionTitle"],
+                PrimaryButtonText = LocaleManager.Instance["ControllerSettingsSave"],
+                SecondaryButtonText = "",
+                CloseButtonText = LocaleManager.Instance["ControllerSettingsClose"],
+                Content = content
+            };
+            contentDialog.PrimaryButtonClick += (sender, args) =>
+            {
+                var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
+                config.Slot = content._viewmodel.Slot;
+                config.EnableMotion = content._viewmodel.EnableMotion;
+                config.Sensitivity = content._viewmodel.Sensitivity;
+                config.GyroDeadzone = content._viewmodel.GyroDeadzone;
+                config.AltSlot = content._viewmodel.AltSlot;
+                config.DsuServerHost = content._viewmodel.DsuServerHost;
+                config.DsuServerPort = content._viewmodel.DsuServerPort;
+                config.EnableCemuHookMotion = content._viewmodel.EnableCemuHookMotion;
+                config.MirrorInput = content._viewmodel.MirrorInput;
+            };
 
-                await contentDialog.ShowAsync();
-            }
+            await contentDialog.ShowAsync();
         }
     }
 }

+ 20 - 29
Ryujinx.Ava/Ui/Windows/RumbleSettingsWindow.axaml.cs

@@ -9,13 +9,14 @@ using System.Threading.Tasks;
 
 namespace Ryujinx.Ava.Ui.Windows
 {
-    public class RumbleSettingsWindow : UserControl
+    public partial class RumbleSettingsWindow : UserControl
     {
         private readonly InputConfiguration<GamepadInputId, StickInputId> _viewmodel;
 
         public RumbleSettingsWindow()
         {
             InitializeComponent();
+            DataContext = _viewmodel;
         }
 
         public RumbleSettingsWindow(ControllerSettingsViewModel viewmodel)
@@ -24,44 +25,34 @@ namespace Ryujinx.Ava.Ui.Windows
 
             _viewmodel = new InputConfiguration<GamepadInputId, StickInputId>()
             {
-                StrongRumble = config.StrongRumble,
-                WeakRumble = config.WeakRumble
+                StrongRumble = config.StrongRumble, WeakRumble = config.WeakRumble
             };
 
             InitializeComponent();
-        }
-
-        private void InitializeComponent()
-        {
             DataContext = _viewmodel;
-
-            AvaloniaXamlLoader.Load(this);
         }
 
-        public static async Task Show(ControllerSettingsViewModel viewmodel, StyleableWindow window)
+        public static async Task Show(ControllerSettingsViewModel viewmodel)
         {
-            ContentDialog contentDialog = window.ContentDialog;
-
-            string name = string.Empty;
-
             RumbleSettingsWindow content = new RumbleSettingsWindow(viewmodel);
 
-            if (contentDialog != null)
+            ContentDialog contentDialog = new ContentDialog
             {
-                contentDialog.Title = LocaleManager.Instance["ControllerRumbleTitle"];
-                contentDialog.PrimaryButtonText = LocaleManager.Instance["ControllerSettingsSave"];
-                contentDialog.SecondaryButtonText = "";
-                contentDialog.CloseButtonText = LocaleManager.Instance["ControllerSettingsClose"];
-                contentDialog.Content = content;
-                contentDialog.PrimaryButtonClick += (sender, args) =>
-                {
-                    var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
-                    config.StrongRumble = content._viewmodel.StrongRumble;
-                    config.WeakRumble = content._viewmodel.WeakRumble;
-                };
-
-                await contentDialog.ShowAsync();
-            }
+                Title = LocaleManager.Instance["ControllerRumbleTitle"],
+                PrimaryButtonText = LocaleManager.Instance["ControllerSettingsSave"],
+                SecondaryButtonText = "",
+                CloseButtonText = LocaleManager.Instance["ControllerSettingsClose"],
+                Content = content,
+            };
+            
+            contentDialog.PrimaryButtonClick += (sender, args) =>
+            {
+                var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
+                config.StrongRumble = content._viewmodel.StrongRumble;
+                config.WeakRumble = content._viewmodel.WeakRumble;
+            };
+            
+            await contentDialog.ShowAsync();
         }
     }
 }

+ 3 - 8
Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml

@@ -31,16 +31,11 @@
             <RowDefinition />
             <RowDefinition Height="Auto" />
         </Grid.RowDefinitions>
-        <ContentControl
+        <ContentPresenter
+            x:Name="ContentPresenter"
             Grid.Row="1"
-            Focusable="False"
             IsVisible="False"
-            KeyboardNavigation.IsTabStop="False">
-            <ui:ContentDialog Name="ContentDialog"
-                              IsPrimaryButtonEnabled="True"
-                              IsSecondaryButtonEnabled="True"
-                              IsVisible="False" />
-        </ContentControl>
+            KeyboardNavigation.IsTabStop="False"/>
         <Grid Name="Pages" IsVisible="False" Grid.Row="2">
             <ScrollViewer Name="UiPage"
                           Margin="0,0,10,0"

+ 22 - 55
Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml.cs

@@ -1,5 +1,6 @@
 using Avalonia;
 using Avalonia.Controls;
+using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 using Avalonia.Data;
 using Avalonia.Data.Converters;
@@ -28,24 +29,8 @@ using TimeZone = Ryujinx.Ava.Ui.Models.TimeZone;
 
 namespace Ryujinx.Ava.Ui.Windows
 {
-    public class SettingsWindow : StyleableWindow
+    public partial class SettingsWindow : StyleableWindow
     {
-        private ListBox _gameList;
-        private TextBox _pathBox;
-        private AutoCompleteBox _timeZoneBox;
-        private ControllerSettingsWindow _controllerSettings;
-
-        // Pages
-        private Control _uiPage;
-        private Control _inputPage;
-        private Control _hotkeysPage;
-        private Control _systemPage;
-        private Control _cpuPage;
-        private Control _graphicsPage;
-        private Control _audioPage;
-        private Control _networkPage;
-        private Control _loggingPage;
-        private NavigationView _navPanel;
         private ButtonKeyAssigner _currentAssigner;
 
         internal SettingsViewModel ViewModel { get; set; }
@@ -58,6 +43,7 @@ namespace Ryujinx.Ava.Ui.Windows
             DataContext = ViewModel;
 
             InitializeComponent();
+            Load();
             AttachDebugDevTools();
 
             FuncMultiValueConverter<string, string> converter = new(parts => string.Format("{0}  {1}   {2}", parts.ToArray()));
@@ -66,7 +52,7 @@ namespace Ryujinx.Ava.Ui.Windows
             tzMultiBinding.Bindings.Add(new Binding("Location"));
             tzMultiBinding.Bindings.Add(new Binding("Abbreviation"));
 
-            _timeZoneBox.ValueMemberBinding = tzMultiBinding;
+            TimeZoneBox.ValueMemberBinding = tzMultiBinding;
         }
 
         public SettingsWindow()
@@ -75,6 +61,7 @@ namespace Ryujinx.Ava.Ui.Windows
             DataContext = ViewModel;
 
             InitializeComponent();
+            Load();
             AttachDebugDevTools();
         }
 
@@ -84,31 +71,11 @@ namespace Ryujinx.Ava.Ui.Windows
             this.AttachDevTools();
         }
 
-        private void InitializeComponent()
+        private void Load()
         {
-            AvaloniaXamlLoader.Load(this);
-
-            _pathBox = this.FindControl<TextBox>("PathBox");
-            _gameList = this.FindControl<ListBox>("GameList");
-            _timeZoneBox = this.FindControl<AutoCompleteBox>("TimeZoneBox");
-            _controllerSettings = this.FindControl<ControllerSettingsWindow>("ControllerSettings");
-
-            _uiPage = this.FindControl<Control>("UiPage");
-            _inputPage = this.FindControl<Control>("InputPage");
-            _hotkeysPage = this.FindControl<Control>("HotkeysPage");
-            _systemPage = this.FindControl<Control>("SystemPage");
-            _cpuPage = this.FindControl<Control>("CpuPage");
-            _graphicsPage = this.FindControl<Control>("GraphicsPage");
-            _audioPage = this.FindControl<Control>("AudioPage");
-            _networkPage = this.FindControl<Control>("NetworkPage");
-            _loggingPage = this.FindControl<Control>("LoggingPage");
-
-            var pageGrid = this.FindControl<Grid>("Pages");
-            pageGrid.Children.Clear();
-
-            _navPanel = this.FindControl<NavigationView>("NavPanel");
-            _navPanel.SelectionChanged += NavPanelOnSelectionChanged;
-            _navPanel.SelectedItem = _navPanel.MenuItems.ElementAt(0);
+            Pages.Children.Clear();
+            NavPanel.SelectionChanged += NavPanelOnSelectionChanged;
+            NavPanel.SelectedItem = NavPanel.MenuItems.ElementAt(0);
         }
 
         private void Button_Checked(object sender, RoutedEventArgs e)
@@ -174,31 +141,31 @@ namespace Ryujinx.Ava.Ui.Windows
                 switch (navitem.Tag.ToString())
                 {
                     case "UiPage":
-                        _navPanel.Content = _uiPage;
+                        NavPanel.Content = UiPage;
                         break;
                     case "InputPage":
-                        _navPanel.Content = _inputPage;
+                        NavPanel.Content = InputPage;
                         break;
                     case "HotkeysPage":
-                        _navPanel.Content = _hotkeysPage;
+                        NavPanel.Content = HotkeysPage;
                         break;
                     case "SystemPage":
-                        _navPanel.Content = _systemPage;
+                        NavPanel.Content = SystemPage;
                         break;
                     case "CpuPage":
-                        _navPanel.Content = _cpuPage;
+                        NavPanel.Content = CpuPage;
                         break;
                     case "GraphicsPage":
-                        _navPanel.Content = _graphicsPage;
+                        NavPanel.Content = GraphicsPage;
                         break;
                     case "AudioPage":
-                        _navPanel.Content = _audioPage;
+                        NavPanel.Content = AudioPage;
                         break;
                     case "NetworkPage":
-                        _navPanel.Content = _networkPage;
+                        NavPanel.Content = NetworkPage;
                         break;
                     case "LoggingPage":
-                        _navPanel.Content = _loggingPage;
+                        NavPanel.Content = LoggingPage;
                         break;
                 }
             }
@@ -206,7 +173,7 @@ namespace Ryujinx.Ava.Ui.Windows
 
         private async void AddButton_OnClick(object sender, RoutedEventArgs e)
         {
-            string path = _pathBox.Text;
+            string path = PathBox.Text;
 
             if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !ViewModel.GameDirectories.Contains(path))
             {
@@ -225,7 +192,7 @@ namespace Ryujinx.Ava.Ui.Windows
 
         private void RemoveButton_OnClick(object sender, RoutedEventArgs e)
         {
-            List<string> selected = new(_gameList.SelectedItems.Cast<string>());
+            List<string> selected = new(GameList.SelectedItems.Cast<string>());
 
             foreach (string path in selected)
             {
@@ -279,7 +246,7 @@ namespace Ryujinx.Ava.Ui.Windows
         {
             ViewModel.SaveSettings();
 
-            _controllerSettings?.SaveCurrentProfile();
+            ControllerSettings?.SaveCurrentProfile();
 
             if (Owner is MainWindow window)
             {
@@ -289,7 +256,7 @@ namespace Ryujinx.Ava.Ui.Windows
 
         protected override void OnClosed(EventArgs e)
         {
-            _controllerSettings.Dispose();
+            ControllerSettings.Dispose();
             _currentAssigner?.Cancel();
             _currentAssigner = null;
             base.OnClosed(e);

+ 0 - 7
Ryujinx.Ava/Ui/Windows/StyleableWindow.cs

@@ -11,7 +11,6 @@ namespace Ryujinx.Ava.Ui.Windows
 {
     public class StyleableWindow : Window
     {
-        public ContentDialog ContentDialog { get; private set; }
         public IBitmap IconImage { get; set; }
 
         public StyleableWindow()
@@ -26,15 +25,9 @@ namespace Ryujinx.Ava.Ui.Windows
             IconImage = new Bitmap(stream);
         }
 
-        public void LoadDialog()
-        {
-            ContentDialog = this.FindControl<ContentDialog>("ContentDialog");
-        }
-
         protected override void OnOpened(EventArgs e)
         {
             base.OnOpened(e);
-            ContentDialog = this.FindControl<ContentDialog>("ContentDialog");
         }
 
         protected override void OnApplyTemplate(TemplateAppliedEventArgs e)

+ 4 - 13
Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs

@@ -28,14 +28,14 @@ using Avalonia.Threading;
 
 namespace Ryujinx.Ava.Ui.Windows
 {
-    public class TitleUpdateWindow : StyleableWindow
+    public partial class TitleUpdateWindow : StyleableWindow
     {
         private readonly string _updateJsonPath;
         private TitleUpdateMetadata _titleUpdateWindowData;
 
         public VirtualFileSystem VirtualFileSystem { get; }
 
-        internal AvaloniaList<TitleUpdateModel> TitleUpdates { get; set; }
+        internal AvaloniaList<TitleUpdateModel> TitleUpdates { get; set; } = new AvaloniaList<TitleUpdateModel>();
         public string TitleId { get; }
         public string TitleName { get; }
 
@@ -84,13 +84,6 @@ namespace Ryujinx.Ava.Ui.Windows
             this.AttachDevTools();
         }
 
-        private void InitializeComponent()
-        {
-            TitleUpdates = new AvaloniaList<TitleUpdateModel>();
-
-            AvaloniaXamlLoader.Load(this);
-        }
-
         private void LoadUpdates()
         {
             TitleUpdates.Add(new TitleUpdateModel(default, string.Empty, true));
@@ -154,8 +147,7 @@ namespace Ryujinx.Ava.Ui.Windows
                         {
                             Dispatcher.UIThread.Post(async () =>
                             {
-                                await ContentDialogHelper.CreateErrorDialog(this,
-                                 LocaleManager.Instance["DialogUpdateAddUpdateErrorMessage"]);
+                                await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUpdateAddUpdateErrorMessage"]);
                             });
                         }
                     }
@@ -163,8 +155,7 @@ namespace Ryujinx.Ava.Ui.Windows
                     {
                         Dispatcher.UIThread.Post(async () =>
                         {
-                            await ContentDialogHelper.CreateErrorDialog(this,
-                                string.Format(LocaleManager.Instance["DialogDlcLoadNcaErrorMessage"], ex.Message, path));
+                            await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogDlcLoadNcaErrorMessage"], ex.Message, path));
                         });
                     }
                 }

+ 1 - 16
Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml.cs

@@ -11,7 +11,7 @@ using System.Runtime.InteropServices;
 
 namespace Ryujinx.Ava.Ui.Windows
 {
-    public class UpdaterWindow : StyleableWindow
+    public partial class UpdaterWindow : StyleableWindow
     {
         private readonly string _buildUrl;
         private readonly MainWindow _mainWindow;
@@ -36,21 +36,6 @@ namespace Ryujinx.Ava.Ui.Windows
             _buildUrl = buildUrl;
         }
 
-        public TextBlock MainText { get; set; }
-        public TextBlock SecondaryText { get; set; }
-        public ProgressBar ProgressBar { get; set; }
-        public StackPanel ButtonBox { get; set; }
-
-        private void InitializeComponent()
-        {
-            AvaloniaXamlLoader.Load(this);
-
-            MainText = this.FindControl<TextBlock>("MainText");
-            SecondaryText = this.FindControl<TextBlock>("SecondaryText");
-            ProgressBar = this.FindControl<ProgressBar>("ProgressBar");
-            ButtonBox = this.FindControl<StackPanel>("ButtonBox");
-        }
-
         [DllImport("libc", SetLastError = true)]
         private static extern int chmod(string path, uint mode);
 

+ 0 - 107
Ryujinx.Ava/Ui/Windows/UserProfileWindow.axaml

@@ -1,107 +0,0 @@
-<window:StyleableWindow xmlns="https://github.com/avaloniaui"
-                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-                        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-                        xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
-                        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-                        mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350"
-                        x:Class="Ryujinx.Ava.Ui.Windows.UserProfileWindow"
-                        xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
-                        xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
-                        xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
-                        xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
-                        CanResize="False"
-                        Width="850" MinHeight="550" Height="550"
-                        WindowStartupLocation="CenterOwner"
-                        SizeToContent="Manual"
-                        MinWidth="600">
-    <Design.DataContext>
-        <viewModels:UserProfileViewModel />
-    </Design.DataContext>
-    <Window.Resources>
-        <controls:BitmapArrayValueConverter x:Key="ByteImage" />
-    </Window.Resources>
-    <Grid Margin="15" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
-        <Grid.RowDefinitions>
-            <RowDefinition Height="Auto" />
-            <RowDefinition Height="Auto" />
-            <RowDefinition />
-            <RowDefinition Height="Auto" />
-        </Grid.RowDefinitions>
-        <Grid Grid.Row="1">
-            <Grid.RowDefinitions>
-                <RowDefinition Height="Auto" />
-                <RowDefinition />
-            </Grid.RowDefinitions>
-            <ContentControl
-                Focusable="False"
-                IsVisible="False"
-                KeyboardNavigation.IsTabStop="False">
-                <ui:ContentDialog Name="ContentDialog"
-                                  IsPrimaryButtonEnabled="True"
-                                  IsSecondaryButtonEnabled="True"
-                                  IsVisible="False" />
-            </ContentControl>
-            <TextBlock Text="{Locale:Locale UserProfilesSelectedUserProfile}" />
-            <Grid Grid.Row="1" Margin="10">
-                <Grid.ColumnDefinitions>
-                    <ColumnDefinition Width="Auto" />
-                    <ColumnDefinition />
-                    <ColumnDefinition Width="Auto" />
-                </Grid.ColumnDefinitions>
-                <Image Height="96" Width="96"
-                       Source="{Binding SelectedProfile.Image, Converter={StaticResource ByteImage}}" />
-                <StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" Grid.Column="1" Spacing="10"
-                            Margin="5, 10">
-                    <TextBox Name="NameBox" Text="{Binding SelectedProfile.Name, Mode=OneWay}"
-                             HorizontalAlignment="Stretch" />
-                    <TextBlock Text="{Binding SelectedProfile.UserId}" />
-                </StackPanel>
-                <StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" Grid.Column="2" Spacing="10"
-                            Margin="5">
-                    <Button Content="{Locale:Locale UserProfilesSaveProfileName}" Name="SetNameButton"
-                            Click="SetNameButton_OnClick" />
-                    <Button Name="SelectProfileImage" Command="{Binding ChooseProfileImage}"
-                            Content="{Locale:Locale UserProfilesChangeProfileImage}" />
-                </StackPanel>
-            </Grid>
-        </Grid>
-        <Grid Grid.Row="2">
-            <Grid.RowDefinitions>
-                <RowDefinition Height="Auto" />
-                <RowDefinition />
-            </Grid.RowDefinitions>
-            <TextBlock Text="{Locale:Locale UserProfilesAvailableUserProfiles}" />
-            <ListBox Grid.Row="1" Margin="10" Name="ProfilesList" DoubleTapped="ProfilesList_DoubleTapped"
-                     Items="{Binding Profiles}">
-                <ListBox.ItemTemplate>
-                    <DataTemplate>
-                        <Grid HorizontalAlignment="Stretch">
-                            <Grid.ColumnDefinitions>
-                                <ColumnDefinition Width="Auto" />
-                                <ColumnDefinition />
-                            </Grid.ColumnDefinitions>
-                            <Grid Grid.Column="0" Background="{DynamicResource ThemeAccentColorBrush}"
-                                  Grid.ColumnSpan="2"
-                                  HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinHeight="5" MinWidth="5"
-                                  IsVisible="{Binding IsOpened}" />
-                            <Image Grid.Column="0" Height="96" Width="96"
-                                   Source="{Binding Image, Converter={StaticResource ByteImage}}" />
-                            <StackPanel Margin="10" Orientation="Vertical" HorizontalAlignment="Stretch"
-                                        VerticalAlignment="Center" Grid.Column="1">
-                                <TextBlock Text="{Binding Name}" />
-                                <TextBlock Text="{Binding  UserId}" />
-                            </StackPanel>
-                        </Grid>
-                    </DataTemplate>
-                </ListBox.ItemTemplate>
-            </ListBox>
-        </Grid>
-        <StackPanel Grid.Row="3" Orientation="Horizontal" Margin="10,0" Spacing="10" HorizontalAlignment="Stretch">
-            <Button Content="{Locale:Locale UserProfilesAddNewProfile}" Command="{Binding AddUser}" />
-            <Button IsEnabled="{Binding IsSelectedProfileDeletable}"
-                    Content="{Locale:Locale UserProfilesDeleteSelectedProfile}" Command="{Binding DeleteUser}" />
-            <Button HorizontalAlignment="Right" Content="{Locale:Locale UserProfilesClose}" Click="CloseButton_OnClick"
-                    Name="CloseButton" />
-        </StackPanel>
-    </Grid>
-</window:StyleableWindow>

+ 0 - 102
Ryujinx.Ava/Ui/Windows/UserProfileWindow.axaml.cs

@@ -1,102 +0,0 @@
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Interactivity;
-using Avalonia.Markup.Xaml;
-using Ryujinx.Ava.Common.Locale;
-using Ryujinx.Ava.Ui.ViewModels;
-using Ryujinx.HLE.FileSystem;
-using Ryujinx.HLE.HOS.Services.Account.Acc;
-using System.Threading.Tasks;
-using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
-
-namespace Ryujinx.Ava.Ui.Windows
-{
-    public class UserProfileWindow : StyleableWindow
-    {
-        private TextBox _nameBox;
-
-        public UserProfileWindow(AccountManager accountManager, ContentManager contentManager,
-            VirtualFileSystem virtualFileSystem)
-        {
-            AccountManager = accountManager;
-            ContentManager = contentManager;
-            ViewModel = new UserProfileViewModel(this);
-
-            DataContext = ViewModel;
-
-            InitializeComponent();
-#if DEBUG
-            this.AttachDevTools();
-#endif
-            if (contentManager.GetCurrentFirmwareVersion() != null)
-            {
-                Task.Run(() =>
-                {
-                    AvatarProfileViewModel.PreloadAvatars(contentManager, virtualFileSystem);
-                });
-            }
-
-            Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UserProfileWindowTitle"];
-        }
-
-        public UserProfileWindow()
-        {
-            ViewModel = new UserProfileViewModel();
-
-            DataContext = ViewModel;
-
-            InitializeComponent();
-#if DEBUG
-            this.AttachDevTools();
-#endif
-            Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UserProfileWindowTitle"];
-        }
-
-        public AccountManager AccountManager { get; }
-        public ContentManager ContentManager { get; }
-
-        public UserProfileViewModel ViewModel { get; set; }
-
-        private void InitializeComponent()
-        {
-            AvaloniaXamlLoader.Load(this);
-            _nameBox = this.FindControl<TextBox>("NameBox");
-        }
-
-        private void ProfilesList_DoubleTapped(object sender, RoutedEventArgs e)
-        {
-            if (sender is ListBox listBox)
-            {
-                int selectedIndex = listBox.SelectedIndex;
-
-                if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
-                {
-                    ViewModel.SelectedProfile = ViewModel.Profiles[selectedIndex];
-
-                    AccountManager.OpenUser(ViewModel.SelectedProfile.UserId);
-
-                    ViewModel.LoadProfiles();
-
-                    foreach (UserProfile profile in ViewModel.Profiles)
-                    {
-                        profile.UpdateState();
-                    }
-                }
-            }
-        }
-
-        private void CloseButton_OnClick(object sender, RoutedEventArgs e)
-        {
-            Close();
-        }
-
-        private void SetNameButton_OnClick(object sender, RoutedEventArgs e)
-        {
-            if (!string.IsNullOrWhiteSpace(_nameBox.Text))
-            {
-                ViewModel.SelectedProfile.Name = _nameBox.Text;
-                AccountManager.SetUserName(ViewModel.SelectedProfile.UserId, _nameBox.Text);
-            }
-        }
-    }
-}