From 76671d63d4f3ea18f8ad99e9ce9f0b2ec9a2599d Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Thu, 29 Dec 2022 14:24:05 +0000 Subject: Ava GUI: Restructure `Ryujinx.Ava` (#4165) * Restructure `Ryujinx.Ava` * Stylistic consistency * Update Ryujinx.Ava/UI/Controls/UserEditor.axaml.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Controls/UserEditor.axaml.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Controls/UserSelector.axaml.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Controls/SaveManager.axaml.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Controls/SaveManager.axaml.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/ViewModels/UserProfileViewModel.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/ViewModels/UserProfileViewModel.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Fix redundancies * Remove redunancies * Add back elses Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> --- Ryujinx.Ava/App.axaml | 2 +- Ryujinx.Ava/App.axaml.cs | 5 +- Ryujinx.Ava/AppHost.cs | 7 +- Ryujinx.Ava/Common/ApplicationHelper.cs | 5 +- Ryujinx.Ava/Common/Locale/LocaleManager.cs | 2 +- Ryujinx.Ava/Helper/MetalHelper.cs | 2 +- Ryujinx.Ava/Modules/Updater/Updater.cs | 5 +- Ryujinx.Ava/Program.cs | 2 +- Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs | 198 +++ .../UI/Applet/AvaloniaDynamicTextInputHandler.cs | 164 +++ Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs | 43 + Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml | 52 + Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml.cs | 80 + Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml | 64 + Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs | 177 +++ Ryujinx.Ava/UI/Controls/GameGridView.axaml | 195 +++ Ryujinx.Ava/UI/Controls/GameGridView.axaml.cs | 83 ++ Ryujinx.Ava/UI/Controls/GameListView.axaml | 234 +++ Ryujinx.Ava/UI/Controls/GameListView.axaml.cs | 83 ++ Ryujinx.Ava/UI/Controls/InputDialog.axaml | 32 + Ryujinx.Ava/UI/Controls/InputDialog.axaml.cs | 57 + Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml | 16 + .../UI/Controls/NavigationDialogHost.axaml.cs | 91 ++ .../UI/Controls/ProfileImageSelectionDialog.axaml | 57 + .../Controls/ProfileImageSelectionDialog.axaml.cs | 105 ++ Ryujinx.Ava/UI/Controls/RendererHost.axaml | 11 + Ryujinx.Ava/UI/Controls/RendererHost.axaml.cs | 127 ++ Ryujinx.Ava/UI/Controls/SaveManager.axaml | 175 +++ Ryujinx.Ava/UI/Controls/SaveManager.axaml.cs | 160 ++ Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml | 42 + Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml.cs | 20 + Ryujinx.Ava/UI/Controls/UserEditor.axaml | 86 ++ Ryujinx.Ava/UI/Controls/UserEditor.axaml.cs | 118 ++ Ryujinx.Ava/UI/Controls/UserRecoverer.axaml | 72 + Ryujinx.Ava/UI/Controls/UserRecoverer.axaml.cs | 44 + Ryujinx.Ava/UI/Controls/UserSelector.axaml | 145 ++ Ryujinx.Ava/UI/Controls/UserSelector.axaml.cs | 77 + .../UI/Helpers/ApplicationOpenedEventArgs.cs | 16 + Ryujinx.Ava/UI/Helpers/AvaloniaGlxContext.cs | 16 + Ryujinx.Ava/UI/Helpers/AvaloniaWglContext.cs | 16 + .../UI/Helpers/BitmapArrayValueConverter.cs | 35 + Ryujinx.Ava/UI/Helpers/ButtonKeyAssigner.cs | 118 ++ Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs | 400 +++++ Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs | 233 +++ Ryujinx.Ava/UI/Helpers/Glyph.cs | 9 + Ryujinx.Ava/UI/Helpers/GlyphValueConverter.cs | 49 + Ryujinx.Ava/UI/Helpers/HotKeyControl.cs | 52 + Ryujinx.Ava/UI/Helpers/IGlContextExtension.cs | 25 + Ryujinx.Ava/UI/Helpers/KeyValueConverter.cs | 46 + Ryujinx.Ava/UI/Helpers/MiniCommand.cs | 71 + Ryujinx.Ava/UI/Helpers/OffscreenTextBox.cs | 40 + Ryujinx.Ava/UI/Helpers/OpenGLEmbeddedWindow.cs | 82 ++ .../UI/Helpers/OpenToolkitBindingsContext.cs | 20 + Ryujinx.Ava/UI/Helpers/SPBOpenGLContext.cs | 47 + Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs | 91 ++ Ryujinx.Ava/UI/Helpers/UserResult.cs | 12 + Ryujinx.Ava/UI/Helpers/VulkanEmbeddedWindow.cs | 52 + Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs | 107 ++ Ryujinx.Ava/UI/Models/Amiibo.cs | 72 + Ryujinx.Ava/UI/Models/CheatModel.cs | 40 + Ryujinx.Ava/UI/Models/CheatsList.cs | 51 + Ryujinx.Ava/UI/Models/ControllerModel.cs | 6 + Ryujinx.Ava/UI/Models/DeviceType.cs | 9 + Ryujinx.Ava/UI/Models/DownloadableContentModel.cs | 32 + .../UI/Models/Generic/LastPlayedSortComparer.cs | 33 + Ryujinx.Ava/UI/Models/InputConfiguration.cs | 456 ++++++ Ryujinx.Ava/UI/Models/PlayerModel.cs | 6 + Ryujinx.Ava/UI/Models/ProfileImageModel.cs | 14 + Ryujinx.Ava/UI/Models/SaveModel.cs | 123 ++ Ryujinx.Ava/UI/Models/StatusUpdatedEventArgs.cs | 28 + Ryujinx.Ava/UI/Models/TempProfile.cs | 58 + Ryujinx.Ava/UI/Models/TimeZone.cs | 16 + Ryujinx.Ava/UI/Models/TitleUpdateModel.cs | 25 + Ryujinx.Ava/UI/Models/UserProfile.cs | 69 + Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs | 452 ++++++ .../UI/ViewModels/AvatarProfileViewModel.cs | 363 +++++ Ryujinx.Ava/UI/ViewModels/BaseModel.cs | 15 + .../UI/ViewModels/ControllerSettingsViewModel.cs | 901 ++++++++++++ Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs | 1540 ++++++++++++++++++++ Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs | 516 +++++++ Ryujinx.Ava/UI/ViewModels/UserProfileViewModel.cs | 215 +++ Ryujinx.Ava/UI/Windows/AboutWindow.axaml | 282 ++++ Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs | 78 + Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml | 74 + Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml.cs | 59 + Ryujinx.Ava/UI/Windows/AvatarWindow.axaml | 54 + Ryujinx.Ava/UI/Windows/AvatarWindow.axaml.cs | 77 + Ryujinx.Ava/UI/Windows/CheatWindow.axaml | 106 ++ Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs | 119 ++ .../UI/Windows/ContentDialogOverlayWindow.axaml | 29 + .../UI/Windows/ContentDialogOverlayWindow.axaml.cs | 25 + .../UI/Windows/ControllerSettingsWindow.axaml | 1169 +++++++++++++++ .../UI/Windows/ControllerSettingsWindow.axaml.cs | 181 +++ .../Windows/DownloadableContentManagerWindow.axaml | 172 +++ .../DownloadableContentManagerWindow.axaml.cs | 314 ++++ Ryujinx.Ava/UI/Windows/IconColorPicker.cs | 192 +++ Ryujinx.Ava/UI/Windows/MainWindow.axaml | 770 ++++++++++ Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs | 721 +++++++++ Ryujinx.Ava/UI/Windows/MotionSettingsWindow.axaml | 141 ++ .../UI/Windows/MotionSettingsWindow.axaml.cs | 71 + Ryujinx.Ava/UI/Windows/RumbleSettingsWindow.axaml | 57 + .../UI/Windows/RumbleSettingsWindow.axaml.cs | 57 + Ryujinx.Ava/UI/Windows/SettingsWindow.axaml | 980 +++++++++++++ Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs | 213 +++ Ryujinx.Ava/UI/Windows/StyleableWindow.cs | 39 + Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml | 115 ++ Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs | 271 ++++ Ryujinx.Ava/Ui/Applet/AvaHostUiHandler.cs | 197 --- .../Ui/Applet/AvaloniaDynamicTextInputHandler.cs | 163 --- Ryujinx.Ava/Ui/Applet/AvaloniaHostUiTheme.cs | 43 - Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml | 53 - Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml.cs | 80 - Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml | 65 - Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml.cs | 176 --- .../Ui/Controls/ApplicationOpenedEventArgs.cs | 16 - Ryujinx.Ava/Ui/Controls/AvaloniaGlxContext.cs | 16 - Ryujinx.Ava/Ui/Controls/AvaloniaWglContext.cs | 16 - .../Ui/Controls/BitmapArrayValueConverter.cs | 35 - Ryujinx.Ava/Ui/Controls/ButtonKeyAssigner.cs | 118 -- Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs | 401 ----- Ryujinx.Ava/Ui/Controls/EmbeddedWindow.cs | 233 --- Ryujinx.Ava/Ui/Controls/GameGridView.axaml | 195 --- Ryujinx.Ava/Ui/Controls/GameGridView.axaml.cs | 82 -- Ryujinx.Ava/Ui/Controls/GameListView.axaml | 235 --- Ryujinx.Ava/Ui/Controls/GameListView.axaml.cs | 82 -- Ryujinx.Ava/Ui/Controls/Glyph.cs | 9 - Ryujinx.Ava/Ui/Controls/GlyphValueConverter.cs | 49 - Ryujinx.Ava/Ui/Controls/HotKeyControl.cs | 52 - Ryujinx.Ava/Ui/Controls/IGlContextExtension.cs | 25 - Ryujinx.Ava/Ui/Controls/InputDialog.axaml | 32 - Ryujinx.Ava/Ui/Controls/InputDialog.axaml.cs | 56 - Ryujinx.Ava/Ui/Controls/KeyValueConverter.cs | 46 - Ryujinx.Ava/Ui/Controls/MiniCommand.cs | 71 - Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml | 11 - .../Ui/Controls/NavigationDialogHost.axaml.cs | 91 -- Ryujinx.Ava/Ui/Controls/OffscreenTextBox.cs | 40 - Ryujinx.Ava/Ui/Controls/OpenGLEmbeddedWindow.cs | 85 -- .../Ui/Controls/OpenToolkitBindingsContext.cs | 20 - .../Ui/Controls/ProfileImageSelectionDialog.axaml | 32 - .../Controls/ProfileImageSelectionDialog.axaml.cs | 105 -- Ryujinx.Ava/Ui/Controls/RendererHost.axaml | 8 - Ryujinx.Ava/Ui/Controls/RendererHost.axaml.cs | 126 -- Ryujinx.Ava/Ui/Controls/SPBOpenGLContext.cs | 47 - Ryujinx.Ava/Ui/Controls/SaveManager.axaml | 103 -- Ryujinx.Ava/Ui/Controls/SaveManager.axaml.cs | 160 -- Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml | 43 - Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml.cs | 20 - Ryujinx.Ava/Ui/Controls/UserEditor.axaml | 89 -- Ryujinx.Ava/Ui/Controls/UserEditor.axaml.cs | 117 -- Ryujinx.Ava/Ui/Controls/UserErrorDialog.cs | 91 -- Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml | 71 - Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml.cs | 44 - Ryujinx.Ava/Ui/Controls/UserResult.cs | 12 - Ryujinx.Ava/Ui/Controls/UserSelector.axaml | 146 -- Ryujinx.Ava/Ui/Controls/UserSelector.axaml.cs | 77 - Ryujinx.Ava/Ui/Controls/VulkanEmbeddedWindow.cs | 53 - Ryujinx.Ava/Ui/Controls/Win32NativeInterop.cs | 107 -- Ryujinx.Ava/Ui/Models/Amiibo.cs | 72 - Ryujinx.Ava/Ui/Models/CheatModel.cs | 40 - Ryujinx.Ava/Ui/Models/CheatsList.cs | 51 - Ryujinx.Ava/Ui/Models/ControllerModel.cs | 6 - Ryujinx.Ava/Ui/Models/DeviceType.cs | 9 - Ryujinx.Ava/Ui/Models/DownloadableContentModel.cs | 32 - .../Ui/Models/Generic/LastPlayedSortComparer.cs | 33 - Ryujinx.Ava/Ui/Models/InputConfiguration.cs | 456 ------ Ryujinx.Ava/Ui/Models/PlayerModel.cs | 6 - Ryujinx.Ava/Ui/Models/ProfileImageModel.cs | 14 - Ryujinx.Ava/Ui/Models/SaveModel.cs | 122 -- Ryujinx.Ava/Ui/Models/StatusUpdatedEventArgs.cs | 28 - Ryujinx.Ava/Ui/Models/TempProfile.cs | 58 - Ryujinx.Ava/Ui/Models/TimeZone.cs | 16 - Ryujinx.Ava/Ui/Models/TitleUpdateModel.cs | 25 - Ryujinx.Ava/Ui/Models/UserProfile.cs | 69 - Ryujinx.Ava/Ui/ViewModels/AmiiboWindowViewModel.cs | 451 ------ .../Ui/ViewModels/AvatarProfileViewModel.cs | 363 ----- Ryujinx.Ava/Ui/ViewModels/BaseModel.cs | 15 - .../Ui/ViewModels/ControllerSettingsViewModel.cs | 900 ------------ Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs | 1539 ------------------- Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs | 520 ------- Ryujinx.Ava/Ui/ViewModels/UserProfileViewModel.cs | 213 --- Ryujinx.Ava/Ui/Windows/AboutWindow.axaml | 282 ---- Ryujinx.Ava/Ui/Windows/AboutWindow.axaml.cs | 78 - Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml | 69 - Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml.cs | 59 - Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml | 53 - Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml.cs | 77 - Ryujinx.Ava/Ui/Windows/CheatWindow.axaml | 106 -- Ryujinx.Ava/Ui/Windows/CheatWindow.axaml.cs | 119 -- .../Ui/Windows/ContentDialogOverlayWindow.axaml | 28 - .../Ui/Windows/ContentDialogOverlayWindow.axaml.cs | 25 - .../Ui/Windows/ControllerSettingsWindow.axaml | 1169 --------------- .../Ui/Windows/ControllerSettingsWindow.axaml.cs | 180 --- .../Windows/DownloadableContentManagerWindow.axaml | 172 --- .../DownloadableContentManagerWindow.axaml.cs | 313 ---- Ryujinx.Ava/Ui/Windows/IconColorPicker.cs | 192 --- Ryujinx.Ava/Ui/Windows/MainWindow.axaml | 770 ---------- Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs | 720 --------- Ryujinx.Ava/Ui/Windows/MotionSettingsWindow.axaml | 141 -- .../Ui/Windows/MotionSettingsWindow.axaml.cs | 71 - Ryujinx.Ava/Ui/Windows/RumbleSettingsWindow.axaml | 58 - .../Ui/Windows/RumbleSettingsWindow.axaml.cs | 57 - Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml | 980 ------------- Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml.cs | 212 --- Ryujinx.Ava/Ui/Windows/StyleableWindow.cs | 39 - Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml | 115 -- Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs | 270 ---- 206 files changed, 15768 insertions(+), 15650 deletions(-) create mode 100644 Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs create mode 100644 Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs create mode 100644 Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs create mode 100644 Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml create mode 100644 Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml.cs create mode 100644 Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml create mode 100644 Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs create mode 100644 Ryujinx.Ava/UI/Controls/GameGridView.axaml create mode 100644 Ryujinx.Ava/UI/Controls/GameGridView.axaml.cs create mode 100644 Ryujinx.Ava/UI/Controls/GameListView.axaml create mode 100644 Ryujinx.Ava/UI/Controls/GameListView.axaml.cs create mode 100644 Ryujinx.Ava/UI/Controls/InputDialog.axaml create mode 100644 Ryujinx.Ava/UI/Controls/InputDialog.axaml.cs create mode 100644 Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml create mode 100644 Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml.cs create mode 100644 Ryujinx.Ava/UI/Controls/ProfileImageSelectionDialog.axaml create mode 100644 Ryujinx.Ava/UI/Controls/ProfileImageSelectionDialog.axaml.cs create mode 100644 Ryujinx.Ava/UI/Controls/RendererHost.axaml create mode 100644 Ryujinx.Ava/UI/Controls/RendererHost.axaml.cs create mode 100644 Ryujinx.Ava/UI/Controls/SaveManager.axaml create mode 100644 Ryujinx.Ava/UI/Controls/SaveManager.axaml.cs create mode 100644 Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml create mode 100644 Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml.cs create mode 100644 Ryujinx.Ava/UI/Controls/UserEditor.axaml create mode 100644 Ryujinx.Ava/UI/Controls/UserEditor.axaml.cs create mode 100644 Ryujinx.Ava/UI/Controls/UserRecoverer.axaml create mode 100644 Ryujinx.Ava/UI/Controls/UserRecoverer.axaml.cs create mode 100644 Ryujinx.Ava/UI/Controls/UserSelector.axaml create mode 100644 Ryujinx.Ava/UI/Controls/UserSelector.axaml.cs create mode 100644 Ryujinx.Ava/UI/Helpers/ApplicationOpenedEventArgs.cs create mode 100644 Ryujinx.Ava/UI/Helpers/AvaloniaGlxContext.cs create mode 100644 Ryujinx.Ava/UI/Helpers/AvaloniaWglContext.cs create mode 100644 Ryujinx.Ava/UI/Helpers/BitmapArrayValueConverter.cs create mode 100644 Ryujinx.Ava/UI/Helpers/ButtonKeyAssigner.cs create mode 100644 Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs create mode 100644 Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs create mode 100644 Ryujinx.Ava/UI/Helpers/Glyph.cs create mode 100644 Ryujinx.Ava/UI/Helpers/GlyphValueConverter.cs create mode 100644 Ryujinx.Ava/UI/Helpers/HotKeyControl.cs create mode 100644 Ryujinx.Ava/UI/Helpers/IGlContextExtension.cs create mode 100644 Ryujinx.Ava/UI/Helpers/KeyValueConverter.cs create mode 100644 Ryujinx.Ava/UI/Helpers/MiniCommand.cs create mode 100644 Ryujinx.Ava/UI/Helpers/OffscreenTextBox.cs create mode 100644 Ryujinx.Ava/UI/Helpers/OpenGLEmbeddedWindow.cs create mode 100644 Ryujinx.Ava/UI/Helpers/OpenToolkitBindingsContext.cs create mode 100644 Ryujinx.Ava/UI/Helpers/SPBOpenGLContext.cs create mode 100644 Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs create mode 100644 Ryujinx.Ava/UI/Helpers/UserResult.cs create mode 100644 Ryujinx.Ava/UI/Helpers/VulkanEmbeddedWindow.cs create mode 100644 Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs create mode 100644 Ryujinx.Ava/UI/Models/Amiibo.cs create mode 100644 Ryujinx.Ava/UI/Models/CheatModel.cs create mode 100644 Ryujinx.Ava/UI/Models/CheatsList.cs create mode 100644 Ryujinx.Ava/UI/Models/ControllerModel.cs create mode 100644 Ryujinx.Ava/UI/Models/DeviceType.cs create mode 100644 Ryujinx.Ava/UI/Models/DownloadableContentModel.cs create mode 100644 Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs create mode 100644 Ryujinx.Ava/UI/Models/InputConfiguration.cs create mode 100644 Ryujinx.Ava/UI/Models/PlayerModel.cs create mode 100644 Ryujinx.Ava/UI/Models/ProfileImageModel.cs create mode 100644 Ryujinx.Ava/UI/Models/SaveModel.cs create mode 100644 Ryujinx.Ava/UI/Models/StatusUpdatedEventArgs.cs create mode 100644 Ryujinx.Ava/UI/Models/TempProfile.cs create mode 100644 Ryujinx.Ava/UI/Models/TimeZone.cs create mode 100644 Ryujinx.Ava/UI/Models/TitleUpdateModel.cs create mode 100644 Ryujinx.Ava/UI/Models/UserProfile.cs create mode 100644 Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs create mode 100644 Ryujinx.Ava/UI/ViewModels/AvatarProfileViewModel.cs create mode 100644 Ryujinx.Ava/UI/ViewModels/BaseModel.cs create mode 100644 Ryujinx.Ava/UI/ViewModels/ControllerSettingsViewModel.cs create mode 100644 Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs create mode 100644 Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs create mode 100644 Ryujinx.Ava/UI/ViewModels/UserProfileViewModel.cs create mode 100644 Ryujinx.Ava/UI/Windows/AboutWindow.axaml create mode 100644 Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs create mode 100644 Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml create mode 100644 Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml.cs create mode 100644 Ryujinx.Ava/UI/Windows/AvatarWindow.axaml create mode 100644 Ryujinx.Ava/UI/Windows/AvatarWindow.axaml.cs create mode 100644 Ryujinx.Ava/UI/Windows/CheatWindow.axaml create mode 100644 Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs create mode 100644 Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml create mode 100644 Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml.cs create mode 100644 Ryujinx.Ava/UI/Windows/ControllerSettingsWindow.axaml create mode 100644 Ryujinx.Ava/UI/Windows/ControllerSettingsWindow.axaml.cs create mode 100644 Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml create mode 100644 Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml.cs create mode 100644 Ryujinx.Ava/UI/Windows/IconColorPicker.cs create mode 100644 Ryujinx.Ava/UI/Windows/MainWindow.axaml create mode 100644 Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs create mode 100644 Ryujinx.Ava/UI/Windows/MotionSettingsWindow.axaml create mode 100644 Ryujinx.Ava/UI/Windows/MotionSettingsWindow.axaml.cs create mode 100644 Ryujinx.Ava/UI/Windows/RumbleSettingsWindow.axaml create mode 100644 Ryujinx.Ava/UI/Windows/RumbleSettingsWindow.axaml.cs create mode 100644 Ryujinx.Ava/UI/Windows/SettingsWindow.axaml create mode 100644 Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs create mode 100644 Ryujinx.Ava/UI/Windows/StyleableWindow.cs create mode 100644 Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml create mode 100644 Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Applet/AvaHostUiHandler.cs delete mode 100644 Ryujinx.Ava/Ui/Applet/AvaloniaDynamicTextInputHandler.cs delete mode 100644 Ryujinx.Ava/Ui/Applet/AvaloniaHostUiTheme.cs delete mode 100644 Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml delete mode 100644 Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml delete mode 100644 Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/ApplicationOpenedEventArgs.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/AvaloniaGlxContext.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/AvaloniaWglContext.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/BitmapArrayValueConverter.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/ButtonKeyAssigner.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/EmbeddedWindow.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/GameGridView.axaml delete mode 100644 Ryujinx.Ava/Ui/Controls/GameGridView.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/GameListView.axaml delete mode 100644 Ryujinx.Ava/Ui/Controls/GameListView.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/Glyph.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/GlyphValueConverter.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/HotKeyControl.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/IGlContextExtension.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/InputDialog.axaml delete mode 100644 Ryujinx.Ava/Ui/Controls/InputDialog.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/KeyValueConverter.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/MiniCommand.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml delete mode 100644 Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/OffscreenTextBox.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/OpenGLEmbeddedWindow.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/OpenToolkitBindingsContext.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml delete mode 100644 Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/RendererHost.axaml delete mode 100644 Ryujinx.Ava/Ui/Controls/RendererHost.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/SPBOpenGLContext.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/SaveManager.axaml delete mode 100644 Ryujinx.Ava/Ui/Controls/SaveManager.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml delete mode 100644 Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/UserEditor.axaml delete mode 100644 Ryujinx.Ava/Ui/Controls/UserEditor.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/UserErrorDialog.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml delete mode 100644 Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/UserResult.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/UserSelector.axaml delete mode 100644 Ryujinx.Ava/Ui/Controls/UserSelector.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/VulkanEmbeddedWindow.cs delete mode 100644 Ryujinx.Ava/Ui/Controls/Win32NativeInterop.cs delete mode 100644 Ryujinx.Ava/Ui/Models/Amiibo.cs delete mode 100644 Ryujinx.Ava/Ui/Models/CheatModel.cs delete mode 100644 Ryujinx.Ava/Ui/Models/CheatsList.cs delete mode 100644 Ryujinx.Ava/Ui/Models/ControllerModel.cs delete mode 100644 Ryujinx.Ava/Ui/Models/DeviceType.cs delete mode 100644 Ryujinx.Ava/Ui/Models/DownloadableContentModel.cs delete mode 100644 Ryujinx.Ava/Ui/Models/Generic/LastPlayedSortComparer.cs delete mode 100644 Ryujinx.Ava/Ui/Models/InputConfiguration.cs delete mode 100644 Ryujinx.Ava/Ui/Models/PlayerModel.cs delete mode 100644 Ryujinx.Ava/Ui/Models/ProfileImageModel.cs delete mode 100644 Ryujinx.Ava/Ui/Models/SaveModel.cs delete mode 100644 Ryujinx.Ava/Ui/Models/StatusUpdatedEventArgs.cs delete mode 100644 Ryujinx.Ava/Ui/Models/TempProfile.cs delete mode 100644 Ryujinx.Ava/Ui/Models/TimeZone.cs delete mode 100644 Ryujinx.Ava/Ui/Models/TitleUpdateModel.cs delete mode 100644 Ryujinx.Ava/Ui/Models/UserProfile.cs delete mode 100644 Ryujinx.Ava/Ui/ViewModels/AmiiboWindowViewModel.cs delete mode 100644 Ryujinx.Ava/Ui/ViewModels/AvatarProfileViewModel.cs delete mode 100644 Ryujinx.Ava/Ui/ViewModels/BaseModel.cs delete mode 100644 Ryujinx.Ava/Ui/ViewModels/ControllerSettingsViewModel.cs delete mode 100644 Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs delete mode 100644 Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs delete mode 100644 Ryujinx.Ava/Ui/ViewModels/UserProfileViewModel.cs delete mode 100644 Ryujinx.Ava/Ui/Windows/AboutWindow.axaml delete mode 100644 Ryujinx.Ava/Ui/Windows/AboutWindow.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml delete mode 100644 Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml delete mode 100644 Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Windows/CheatWindow.axaml delete mode 100644 Ryujinx.Ava/Ui/Windows/CheatWindow.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Windows/ContentDialogOverlayWindow.axaml delete mode 100644 Ryujinx.Ava/Ui/Windows/ContentDialogOverlayWindow.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Windows/ControllerSettingsWindow.axaml delete mode 100644 Ryujinx.Ava/Ui/Windows/ControllerSettingsWindow.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml delete mode 100644 Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Windows/IconColorPicker.cs delete mode 100644 Ryujinx.Ava/Ui/Windows/MainWindow.axaml delete mode 100644 Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Windows/MotionSettingsWindow.axaml delete mode 100644 Ryujinx.Ava/Ui/Windows/MotionSettingsWindow.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Windows/RumbleSettingsWindow.axaml delete mode 100644 Ryujinx.Ava/Ui/Windows/RumbleSettingsWindow.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml delete mode 100644 Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml.cs delete mode 100644 Ryujinx.Ava/Ui/Windows/StyleableWindow.cs delete mode 100644 Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml delete mode 100644 Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs diff --git a/Ryujinx.Ava/App.axaml b/Ryujinx.Ava/App.axaml index eb9ffd86..72bc0dee 100644 --- a/Ryujinx.Ava/App.axaml +++ b/Ryujinx.Ava/App.axaml @@ -4,6 +4,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sty="using:FluentAvalonia.Styling"> - + \ No newline at end of file diff --git a/Ryujinx.Ava/App.axaml.cs b/Ryujinx.Ava/App.axaml.cs index 3c28c019..e59f9bd3 100644 --- a/Ryujinx.Ava/App.axaml.cs +++ b/Ryujinx.Ava/App.axaml.cs @@ -5,8 +5,9 @@ using Avalonia.Styling; using Avalonia.Threading; using FluentAvalonia.Styling; using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.Ui.Controls; -using Ryujinx.Ava.Ui.Windows; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.Ui.Common.Configuration; diff --git a/Ryujinx.Ava/AppHost.cs b/Ryujinx.Ava/AppHost.cs index c1b3355a..f8bd032c 100644 --- a/Ryujinx.Ava/AppHost.cs +++ b/Ryujinx.Ava/AppHost.cs @@ -11,9 +11,10 @@ using Ryujinx.Audio.Integration; using Ryujinx.Ava.Common; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Input; -using Ryujinx.Ava.Ui.Controls; -using Ryujinx.Ava.Ui.Models; -using Ryujinx.Ava.Ui.Windows; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.Windows; using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; diff --git a/Ryujinx.Ava/Common/ApplicationHelper.cs b/Ryujinx.Ava/Common/ApplicationHelper.cs index 7f766614..0c562dfe 100644 --- a/Ryujinx.Ava/Common/ApplicationHelper.cs +++ b/Ryujinx.Ava/Common/ApplicationHelper.cs @@ -12,8 +12,9 @@ using LibHac.Tools.Fs; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.Ui.Controls; -using Ryujinx.Ava.Ui.Windows; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; using Ryujinx.Common.Logging; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; diff --git a/Ryujinx.Ava/Common/Locale/LocaleManager.cs b/Ryujinx.Ava/Common/Locale/LocaleManager.cs index 41b98136..acbbf2df 100644 --- a/Ryujinx.Ava/Common/Locale/LocaleManager.cs +++ b/Ryujinx.Ava/Common/Locale/LocaleManager.cs @@ -1,4 +1,4 @@ -using Ryujinx.Ava.Ui.ViewModels; +using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Common; using Ryujinx.Common.Utilities; using Ryujinx.Ui.Common.Configuration; diff --git a/Ryujinx.Ava/Helper/MetalHelper.cs b/Ryujinx.Ava/Helper/MetalHelper.cs index 2d4d828b..ea3477eb 100644 --- a/Ryujinx.Ava/Helper/MetalHelper.cs +++ b/Ryujinx.Ava/Helper/MetalHelper.cs @@ -3,7 +3,7 @@ using System.Runtime.Versioning; using System.Runtime.InteropServices; using Avalonia; -namespace Ryujinx.Ava.Ui.Helper +namespace Ryujinx.Ava.UI.Helper { public delegate void UpdateBoundsCallbackDelegate(Rect rect); diff --git a/Ryujinx.Ava/Modules/Updater/Updater.cs b/Ryujinx.Ava/Modules/Updater/Updater.cs index b3a1ef30..d495131f 100644 --- a/Ryujinx.Ava/Modules/Updater/Updater.cs +++ b/Ryujinx.Ava/Modules/Updater/Updater.cs @@ -7,8 +7,9 @@ using ICSharpCode.SharpZipLib.Zip; using Newtonsoft.Json.Linq; using Ryujinx.Ava; using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.Ui.Controls; -using Ryujinx.Ava.Ui.Windows; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.Ui.Common.Helper; diff --git a/Ryujinx.Ava/Program.cs b/Ryujinx.Ava/Program.cs index 023e31df..142d7820 100644 --- a/Ryujinx.Ava/Program.cs +++ b/Ryujinx.Ava/Program.cs @@ -1,7 +1,7 @@ using ARMeilleure.Translation.PTC; using Avalonia; using Avalonia.Threading; -using Ryujinx.Ava.Ui.Windows; +using Ryujinx.Ava.UI.Windows; using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.GraphicsDriver; diff --git a/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs b/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs new file mode 100644 index 00000000..a8e76275 --- /dev/null +++ b/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs @@ -0,0 +1,198 @@ +using Avalonia.Controls; +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.HLE; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; +using Ryujinx.HLE.Ui; +using System; +using System.Threading; + +namespace Ryujinx.Ava.UI.Applet +{ + internal class AvaHostUiHandler : IHostUiHandler + { + private readonly MainWindow _parent; + + public IHostUiTheme HostUiTheme { get; } + + public AvaHostUiHandler(MainWindow parent) + { + _parent = parent; + + HostUiTheme = new AvaloniaHostUiTheme(parent); + } + + public bool DisplayMessageDialog(ControllerAppletUiArgs args) + { + string playerCount = args.PlayerCountMin == args.PlayerCountMax + ? args.PlayerCountMin.ToString() + : $"{args.PlayerCountMin}-{args.PlayerCountMax}"; + + string key = args.PlayerCountMin == args.PlayerCountMax ? "DialogControllerAppletMessage" : "DialogControllerAppletMessagePlayerRange"; + + string message = string.Format(LocaleManager.Instance[key], + playerCount, + args.SupportedStyles, + string.Join(", ", args.SupportedPlayers), + args.IsDocked ? LocaleManager.Instance["DialogControllerAppletDockModeSet"] : ""); + + return DisplayMessageDialog(LocaleManager.Instance["DialogControllerAppletTitle"], message); + } + + public bool DisplayMessageDialog(string title, string message) + { + ManualResetEvent dialogCloseEvent = new(false); + + bool okPressed = false; + + Dispatcher.UIThread.InvokeAsync(async () => + { + try + { + ManualResetEvent deferEvent = new(false); + + bool opened = false; + + UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent, + title, + message, + "", + LocaleManager.Instance["DialogOpenSettingsWindowLabel"], + "", + LocaleManager.Instance["SettingsButtonClose"], + (int)Symbol.Important, + deferEvent, + async (window) => + { + if (opened) + { + return; + } + + opened = true; + + _parent.SettingsWindow = new SettingsWindow(_parent.VirtualFileSystem, _parent.ContentManager); + + await _parent.SettingsWindow.ShowDialog(window); + + opened = false; + }); + + if (response == UserResult.Ok) + { + okPressed = true; + } + + dialogCloseEvent.Set(); + } + catch (Exception ex) + { + await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogMessageDialogErrorExceptionMessage"], ex)); + + dialogCloseEvent.Set(); + } + }); + + dialogCloseEvent.WaitOne(); + + return okPressed; + } + + public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText) + { + ManualResetEvent dialogCloseEvent = new(false); + + bool okPressed = false; + bool error = false; + string inputText = args.InitialText ?? ""; + + Dispatcher.UIThread.Post(async () => + { + try + { + var response = await SwkbdAppletDialog.ShowInputDialog(_parent, LocaleManager.Instance["SoftwareKeyboard"], args); + + if (response.Result == UserResult.Ok) + { + inputText = response.Input; + okPressed = true; + } + } + catch (Exception ex) + { + error = true; + await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogSoftwareKeyboardErrorExceptionMessage"], ex)); + } + finally + { + dialogCloseEvent.Set(); + } + }); + + dialogCloseEvent.WaitOne(); + + userText = error ? null : inputText; + + return error || okPressed; + } + + public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value) + { + device.Configuration.UserChannelPersistence.ExecuteProgram(kind, value); + if (_parent.AppHost != null) + { + _parent.AppHost.Stop(); + } + } + + public bool DisplayErrorAppletDialog(string title, string message, string[] buttons) + { + ManualResetEvent dialogCloseEvent = new(false); + + bool showDetails = false; + + Dispatcher.UIThread.Post(async () => + { + try + { + ErrorAppletWindow msgDialog = new(_parent, buttons, message) + { + Title = title, + WindowStartupLocation = WindowStartupLocation.CenterScreen, + Width = 400 + }; + + object response = await msgDialog.Run(); + + if (response != null && buttons != null && buttons.Length > 1 && (int)response != buttons.Length - 1) + { + showDetails = true; + } + + dialogCloseEvent.Set(); + + msgDialog.Close(); + } + catch (Exception ex) + { + dialogCloseEvent.Set(); + await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogErrorAppletErrorExceptionMessage"], ex)); + } + }); + + dialogCloseEvent.WaitOne(); + + return showDetails; + } + + public IDynamicTextInputHandler CreateDynamicTextInputHandler() + { + return new AvaloniaDynamicTextInputHandler(_parent); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs b/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs new file mode 100644 index 00000000..314746e7 --- /dev/null +++ b/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs @@ -0,0 +1,164 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Threading; +using Ryujinx.Ava.Input; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.HLE.Ui; +using System; +using System.Threading; + +using HidKey = Ryujinx.Common.Configuration.Hid.Key; + +namespace Ryujinx.Ava.UI.Applet +{ + class AvaloniaDynamicTextInputHandler : IDynamicTextInputHandler + { + private MainWindow _parent; + private OffscreenTextBox _hiddenTextBox; + private bool _canProcessInput; + private IDisposable _textChangedSubscription; + private IDisposable _selectionStartChangedSubscription; + private IDisposable _selectionEndtextChangedSubscription; + + public AvaloniaDynamicTextInputHandler(MainWindow parent) + { + _parent = parent; + + (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).KeyPressed += AvaloniaDynamicTextInputHandler_KeyPressed; + (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).KeyRelease += AvaloniaDynamicTextInputHandler_KeyRelease; + (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).TextInput += AvaloniaDynamicTextInputHandler_TextInput; + + _hiddenTextBox = _parent.HiddenTextBox; + + Dispatcher.UIThread.Post(() => + { + _textChangedSubscription = _hiddenTextBox.GetObservable(TextBox.TextProperty).Subscribe(TextChanged); + _selectionStartChangedSubscription = _hiddenTextBox.GetObservable(TextBox.SelectionStartProperty).Subscribe(SelectionChanged); + _selectionEndtextChangedSubscription = _hiddenTextBox.GetObservable(TextBox.SelectionEndProperty).Subscribe(SelectionChanged); + }); + } + + private void TextChanged(string text) + { + TextChangedEvent?.Invoke(text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, true); + } + + private void SelectionChanged(int selection) + { + if (_hiddenTextBox.SelectionEnd < _hiddenTextBox.SelectionStart) + { + _hiddenTextBox.SelectionStart = _hiddenTextBox.SelectionEnd; + } + + TextChangedEvent?.Invoke(_hiddenTextBox.Text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, true); + } + + private void AvaloniaDynamicTextInputHandler_TextInput(object sender, string text) + { + Dispatcher.UIThread.InvokeAsync(() => + { + if (_canProcessInput) + { + _hiddenTextBox.SendText(text); + } + }); + } + + private void AvaloniaDynamicTextInputHandler_KeyRelease(object sender, KeyEventArgs e) + { + var key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key); + + if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true)) + { + return; + } + + e.RoutedEvent = _hiddenTextBox.GetKeyUpRoutedEvent(); + + Dispatcher.UIThread.InvokeAsync(() => + { + if (_canProcessInput) + { + _hiddenTextBox.SendKeyUpEvent(e); + } + }); + } + + private void AvaloniaDynamicTextInputHandler_KeyPressed(object sender, KeyEventArgs e) + { + var key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key); + + if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true)) + { + return; + } + + e.RoutedEvent = _hiddenTextBox.GetKeyUpRoutedEvent(); + + Dispatcher.UIThread.InvokeAsync(() => + { + if (_canProcessInput) + { + _hiddenTextBox.SendKeyDownEvent(e); + } + }); + } + + public bool TextProcessingEnabled + { + get + { + return Volatile.Read(ref _canProcessInput); + } + set + { + Volatile.Write(ref _canProcessInput, value); + } + } + + public event DynamicTextChangedHandler TextChangedEvent; + public event KeyPressedHandler KeyPressedEvent; + public event KeyReleasedHandler KeyReleasedEvent; + + public void Dispose() + { + (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).KeyPressed -= AvaloniaDynamicTextInputHandler_KeyPressed; + (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).KeyRelease -= AvaloniaDynamicTextInputHandler_KeyRelease; + (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).TextInput -= AvaloniaDynamicTextInputHandler_TextInput; + + _textChangedSubscription?.Dispose(); + _selectionStartChangedSubscription?.Dispose(); + _selectionEndtextChangedSubscription?.Dispose(); + + Dispatcher.UIThread.Post(() => + { + _hiddenTextBox.Clear(); + _parent.RendererControl.Focus(); + + _parent = null; + }); + } + + public void SetText(string text, int cursorBegin) + { + Dispatcher.UIThread.Post(() => + { + _hiddenTextBox.Text = text; + _hiddenTextBox.CaretIndex = cursorBegin; + }); + } + + public void SetText(string text, int cursorBegin, int cursorEnd) + { + Dispatcher.UIThread.Post(() => + { + _hiddenTextBox.Text = text; + _hiddenTextBox.SelectionStart = cursorBegin; + _hiddenTextBox.SelectionEnd = cursorEnd; + }); + } + } +} diff --git a/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs b/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs new file mode 100644 index 00000000..fe5e2721 --- /dev/null +++ b/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs @@ -0,0 +1,43 @@ +using Avalonia.Media; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.HLE.Ui; +using System; + +namespace Ryujinx.Ava.UI.Applet +{ + class AvaloniaHostUiTheme : IHostUiTheme + { + public AvaloniaHostUiTheme(MainWindow parent) + { + FontFamily = OperatingSystem.IsWindows() && OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000, 0) ? "Segoe UI Variable" : parent.FontFamily.Name; + DefaultBackgroundColor = BrushToThemeColor(parent.Background); + DefaultForegroundColor = BrushToThemeColor(parent.Foreground); + DefaultBorderColor = BrushToThemeColor(parent.BorderBrush); + SelectionBackgroundColor = BrushToThemeColor(parent.SearchBox.SelectionBrush); + SelectionForegroundColor = BrushToThemeColor(parent.SearchBox.SelectionForegroundBrush); + } + + public string FontFamily { get; } + + public ThemeColor DefaultBackgroundColor { get; } + public ThemeColor DefaultForegroundColor { get; } + public ThemeColor DefaultBorderColor { get; } + public ThemeColor SelectionBackgroundColor { get; } + public ThemeColor SelectionForegroundColor { get; } + + private ThemeColor BrushToThemeColor(IBrush brush) + { + if (brush is SolidColorBrush solidColor) + { + return new ThemeColor((float)solidColor.Color.A / 255, + (float)solidColor.Color.R / 255, + (float)solidColor.Color.G / 255, + (float)solidColor.Color.B / 255); + } + else + { + return new ThemeColor(); + } + } + } +} diff --git a/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml b/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml new file mode 100644 index 00000000..211b4725 --- /dev/null +++ b/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml.cs b/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml.cs new file mode 100644 index 00000000..a17826f8 --- /dev/null +++ b/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml.cs @@ -0,0 +1,80 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Threading; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Windows; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Applet +{ + internal partial class ErrorAppletWindow : StyleableWindow + { + private readonly Window _owner; + private object _buttonResponse; + + public ErrorAppletWindow(Window owner, string[] buttons, string message) + { + _owner = owner; + Message = message; + DataContext = this; + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + int responseId = 0; + + if (buttons != null) + { + foreach (string buttonText in buttons) + { + AddButton(buttonText, responseId); + responseId++; + } + } + else + { + AddButton(LocaleManager.Instance["InputDialogOk"], 0); + } + } + + public ErrorAppletWindow() + { + DataContext = this; + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + } + + public string Message { get; set; } + + private void AddButton(string label, object tag) + { + Dispatcher.UIThread.InvokeAsync(() => + { + Button button = new() { Content = label, Tag = tag }; + + button.Click += Button_Click; + ButtonStack.Children.Add(button); + }); + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + _buttonResponse = button.Tag; + } + + Close(); + } + + public async Task Run() + { + await ShowDialog(_owner); + + return _buttonResponse; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml b/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml new file mode 100644 index 00000000..43ccf9e7 --- /dev/null +++ b/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs b/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs new file mode 100644 index 00000000..80be2979 --- /dev/null +++ b/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs @@ -0,0 +1,177 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Media; +using FluentAvalonia.Core; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.HLE.HOS.Applets; +using System; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Controls +{ + internal partial class SwkbdAppletDialog : UserControl + { + private Predicate _checkLength; + private int _inputMax; + private int _inputMin; + private string _placeholder; + + private ContentDialog _host; + + public SwkbdAppletDialog(string mainText, string secondaryText, string placeholder) + { + MainText = mainText; + SecondaryText = secondaryText; + DataContext = this; + _placeholder = placeholder; + InitializeComponent(); + + Input.Watermark = _placeholder; + + Input.AddHandler(TextInputEvent, Message_TextInput, RoutingStrategies.Tunnel, true); + + SetInputLengthValidation(0, int.MaxValue); // Disable by default. + } + + public SwkbdAppletDialog() + { + DataContext = this; + InitializeComponent(); + } + + public string Message { get; set; } = ""; + public string MainText { get; set; } = ""; + public string SecondaryText { get; set; } = ""; + + public static async Task<(UserResult Result, string Input)> ShowInputDialog(StyleableWindow window, string title, SoftwareKeyboardUiArgs args) + { + ContentDialog contentDialog = new ContentDialog(); + + UserResult result = UserResult.Cancel; + + SwkbdAppletDialog content = new SwkbdAppletDialog(args.HeaderText, args.SubtitleText, args.GuideText) + { + Message = args.InitialText ?? "" + }; + + string input = string.Empty; + + var overlay = new ContentDialogOverlayWindow() + { + Height = window.Bounds.Height, + Width = window.Bounds.Width, + Position = window.PointToScreen(new Point()) + }; + + window.PositionChanged += OverlayOnPositionChanged; + + void OverlayOnPositionChanged(object sender, PixelPointEventArgs e) + { + overlay.Position = window.PointToScreen(new Point()); + } + + contentDialog = overlay.ContentDialog; + + bool opened = false; + + content.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax); + + content._host = contentDialog; + contentDialog.Title = title; + contentDialog.PrimaryButtonText = args.SubmitText; + contentDialog.IsPrimaryButtonEnabled = content._checkLength(content.Message.Length); + contentDialog.SecondaryButtonText = ""; + contentDialog.CloseButtonText = LocaleManager.Instance["InputDialogCancel"]; + contentDialog.Content = content; + + TypedEventHandler handler = (sender, eventArgs) => + { + if (eventArgs.Result == ContentDialogResult.Primary) + { + result = UserResult.Ok; + input = content.Input.Text; + } + }; + contentDialog.Closed += handler; + + overlay.Opened += OverlayOnActivated; + + async void OverlayOnActivated(object sender, EventArgs e) + { + if (opened) + { + return; + } + + opened = true; + + overlay.Position = window.PointToScreen(new Point()); + + await contentDialog.ShowAsync(overlay); + contentDialog.Closed -= handler; + overlay.Close(); + }; + + await overlay.ShowDialog(window); + + return (result, input); + } + + public void SetInputLengthValidation(int min, int max) + { + _inputMin = Math.Min(min, max); + _inputMax = Math.Max(min, max); + + Error.IsVisible = false; + Error.FontStyle = FontStyle.Italic; + + if (_inputMin <= 0 && _inputMax == int.MaxValue) // Disable. + { + Error.IsVisible = false; + + _checkLength = length => true; + } + else if (_inputMin > 0 && _inputMax == int.MaxValue) + { + Error.IsVisible = true; + Error.Text = string.Format(LocaleManager.Instance["SwkbdMinCharacters"], _inputMin); + + _checkLength = length => _inputMin <= length; + } + else + { + Error.IsVisible = true; + Error.Text = string.Format(LocaleManager.Instance["SwkbdMinRangeCharacters"], _inputMin, _inputMax); + + _checkLength = length => _inputMin <= length && length <= _inputMax; + } + + Message_TextInput(this, new TextInputEventArgs()); + } + + private void Message_TextInput(object sender, TextInputEventArgs e) + { + if (_host != null) + { + _host.IsPrimaryButtonEnabled = _checkLength(Message.Length); + } + } + + private void Message_KeyUp(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter && _host.IsPrimaryButtonEnabled) + { + _host.Hide(ContentDialogResult.Primary); + } + else + { + _host.IsPrimaryButtonEnabled = _checkLength(Message.Length); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Controls/GameGridView.axaml b/Ryujinx.Ava/UI/Controls/GameGridView.axaml new file mode 100644 index 00000000..1c4d7638 --- /dev/null +++ b/Ryujinx.Ava/UI/Controls/GameGridView.axaml @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Controls/GameGridView.axaml.cs b/Ryujinx.Ava/UI/Controls/GameGridView.axaml.cs new file mode 100644 index 00000000..9965f750 --- /dev/null +++ b/Ryujinx.Ava/UI/Controls/GameGridView.axaml.cs @@ -0,0 +1,83 @@ +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using LibHac.Common; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ui.App.Common; +using System; + +namespace Ryujinx.Ava.UI.Controls +{ + public partial class GameGridView : UserControl + { + private ApplicationData _selectedApplication; + public static readonly RoutedEvent ApplicationOpenedEvent = + RoutedEvent.Register(nameof(ApplicationOpened), RoutingStrategies.Bubble); + + public event EventHandler ApplicationOpened + { + add { AddHandler(ApplicationOpenedEvent, value); } + remove { RemoveHandler(ApplicationOpenedEvent, value); } + } + + public void GameList_DoubleTapped(object sender, RoutedEventArgs args) + { + if (sender is ListBox listBox) + { + if (listBox.SelectedItem is ApplicationData selected) + { + RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent)); + } + } + } + + public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs args) + { + if (sender is ListBox listBox) + { + var selected = listBox.SelectedItem as ApplicationData; + + _selectedApplication = selected; + } + } + + public ApplicationData SelectedApplication => _selectedApplication; + + public GameGridView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void SearchBox_OnKeyUp(object sender, KeyEventArgs e) + { + (DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text; + } + + private void MenuBase_OnMenuOpened(object sender, EventArgs e) + { + var selection = SelectedApplication; + + if (selection != null) + { + if (sender is ContextMenu menu) + { + bool canHaveUserSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.UserAccountSaveDataSize > 0; + bool canHaveDeviceSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.DeviceSaveDataSize > 0; + bool canHaveBcatSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0; + + ((menu.Items as AvaloniaList)[2] as MenuItem).IsEnabled = canHaveUserSave; + ((menu.Items as AvaloniaList)[3] as MenuItem).IsEnabled = canHaveDeviceSave; + ((menu.Items as AvaloniaList)[4] as MenuItem).IsEnabled = canHaveBcatSave; + } + } + } + } +} diff --git a/Ryujinx.Ava/UI/Controls/GameListView.axaml b/Ryujinx.Ava/UI/Controls/GameListView.axaml new file mode 100644 index 00000000..d886ecbe --- /dev/null +++ b/Ryujinx.Ava/UI/Controls/GameListView.axaml @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Controls/GameListView.axaml.cs b/Ryujinx.Ava/UI/Controls/GameListView.axaml.cs new file mode 100644 index 00000000..01e35990 --- /dev/null +++ b/Ryujinx.Ava/UI/Controls/GameListView.axaml.cs @@ -0,0 +1,83 @@ +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using LibHac.Common; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ui.App.Common; +using System; + +namespace Ryujinx.Ava.UI.Controls +{ + public partial class GameListView : UserControl + { + private ApplicationData _selectedApplication; + public static readonly RoutedEvent ApplicationOpenedEvent = + RoutedEvent.Register(nameof(ApplicationOpened), RoutingStrategies.Bubble); + + public event EventHandler ApplicationOpened + { + add { AddHandler(ApplicationOpenedEvent, value); } + remove { RemoveHandler(ApplicationOpenedEvent, value); } + } + + public void GameList_DoubleTapped(object sender, RoutedEventArgs args) + { + if (sender is ListBox listBox) + { + if (listBox.SelectedItem is ApplicationData selected) + { + RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent)); + } + } + } + + public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs args) + { + if (sender is ListBox listBox) + { + var selected = listBox.SelectedItem as ApplicationData; + + _selectedApplication = selected; + } + } + + public ApplicationData SelectedApplication => _selectedApplication; + + public GameListView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void SearchBox_OnKeyUp(object sender, KeyEventArgs e) + { + (DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text; + } + + private void MenuBase_OnMenuOpened(object sender, EventArgs e) + { + var selection = SelectedApplication; + + if (selection != null) + { + if (sender is ContextMenu menu) + { + bool canHaveUserSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.UserAccountSaveDataSize > 0; + bool canHaveDeviceSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.DeviceSaveDataSize > 0; + bool canHaveBcatSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0; + + ((menu.Items as AvaloniaList)[2] as MenuItem).IsEnabled = canHaveUserSave; + ((menu.Items as AvaloniaList)[3] as MenuItem).IsEnabled = canHaveDeviceSave; + ((menu.Items as AvaloniaList)[4] as MenuItem).IsEnabled = canHaveBcatSave; + } + } + } + } +} diff --git a/Ryujinx.Ava/UI/Controls/InputDialog.axaml b/Ryujinx.Ava/UI/Controls/InputDialog.axaml new file mode 100644 index 00000000..ed1ceda3 --- /dev/null +++ b/Ryujinx.Ava/UI/Controls/InputDialog.axaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Controls/InputDialog.axaml.cs b/Ryujinx.Ava/UI/Controls/InputDialog.axaml.cs new file mode 100644 index 00000000..abaabd3b --- /dev/null +++ b/Ryujinx.Ava/UI/Controls/InputDialog.axaml.cs @@ -0,0 +1,57 @@ +using Avalonia.Controls; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Controls +{ + public partial class InputDialog : UserControl + { + public string Message { get; set; } + public string Input { get; set; } + public string SubMessage { get; set; } + + public uint MaxLength { get; } + + public InputDialog(string message, string input = "", string subMessage = "", uint maxLength = int.MaxValue) + { + Message = message; + Input = input; + SubMessage = subMessage; + MaxLength = maxLength; + + DataContext = this; + } + + public InputDialog() + { + InitializeComponent(); + } + + public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, string message, + string input = "", string subMessage = "", uint maxLength = int.MaxValue) + { + UserResult result = UserResult.Cancel; + + InputDialog content = new InputDialog(message, input, subMessage, maxLength); + ContentDialog contentDialog = new ContentDialog + { + 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(); + + return (result, input); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml b/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml new file mode 100644 index 00000000..90720478 --- /dev/null +++ b/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml @@ -0,0 +1,16 @@ + + + \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml.cs b/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml.cs new file mode 100644 index 00000000..98f9e9e3 --- /dev/null +++ b/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml.cs @@ -0,0 +1,91 @@ +using Avalonia; +using Avalonia.Controls; +using FluentAvalonia.UI.Controls; +using LibHac; +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 VirtualFileSystem VirtualFileSystem { get; } + public HorizonClient HorizonClient { get; } + public UserProfileViewModel ViewModel { get; set; } + + public NavigationDialogHost() + { + InitializeComponent(); + } + + public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager, + VirtualFileSystem virtualFileSystem, HorizonClient horizonClient) + { + AccountManager = accountManager; + ContentManager = contentManager; + VirtualFileSystem = virtualFileSystem; + HorizonClient = horizonClient; + 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, HorizonClient ownerHorizonClient) + { + var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient); + ContentDialog contentDialog = new ContentDialog + { + Title = LocaleManager.Instance["UserProfileWindowTitle"], + PrimaryButtonText = "", + SecondaryButtonText = "", + CloseButtonText = LocaleManager.Instance["UserProfilesClose"], + Content = content, + Padding = new Thickness(0) + }; + + contentDialog.Closed += (sender, args) => + { + content.ViewModel.Dispose(); + }; + + await contentDialog.ShowAsync(); + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + Navigate(typeof(UserSelector), this); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Controls/ProfileImageSelectionDialog.axaml b/Ryujinx.Ava/UI/Controls/ProfileImageSelectionDialog.axaml new file mode 100644 index 00000000..56f8152a --- /dev/null +++ b/Ryujinx.Ava/UI/Controls/ProfileImageSelectionDialog.axaml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Controls/ProfileImageSelectionDialog.axaml.cs b/Ryujinx.Ava/UI/Controls/ProfileImageSelectionDialog.axaml.cs new file mode 100644 index 00000000..00183b69 --- /dev/null +++ b/Ryujinx.Ava/UI/Controls/ProfileImageSelectionDialog.axaml.cs @@ -0,0 +1,105 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +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; +using SixLabors.ImageSharp.Processing; +using System.IO; +using Image = SixLabors.ImageSharp.Image; + +namespace Ryujinx.Ava.UI.Controls +{ + public partial class ProfileImageSelectionDialog : UserControl + { + private ContentManager _contentManager; + private NavigationDialogHost _parent; + private TempProfile _profile; + + public bool FirmwareFound => _contentManager.GetCurrentFirmwareVersion() != null; + + public ProfileImageSelectionDialog() + { + InitializeComponent(); + AddHandler(Frame.NavigatedToEvent, (s, e) => + { + NavigatedTo(e); + }, RoutingStrategies.Direct); + } + + private void NavigatedTo(NavigationEventArgs arg) + { + 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) + { + OpenFileDialog dialog = new(); + dialog.Filters.Add(new FileDialogFilter + { + Name = LocaleManager.Instance["AllSupportedFormats"], + Extensions = { "jpg", "jpeg", "png", "bmp" } + }); + dialog.Filters.Add(new FileDialogFilter { Name = "JPEG", Extensions = { "jpg", "jpeg" } }); + dialog.Filters.Add(new FileDialogFilter { Name = "PNG", Extensions = { "png" } }); + dialog.Filters.Add(new FileDialogFilter { Name = "BMP", Extensions = { "bmp" } }); + + dialog.AllowMultiple = false; + + string[] image = await dialog.ShowAsync(((TopLevel)_parent.GetVisualRoot()) as Window); + + if (image != null) + { + if (image.Length > 0) + { + string imageFile = image[0]; + + _profile.Image = ProcessProfileImage(File.ReadAllBytes(imageFile)); + } + + _parent.GoBack(); + } + } + + private void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e) + { + if (FirmwareFound) + { + _parent.Navigate(typeof(AvatarWindow), (_parent, _profile)); + } + } + + private static byte[] ProcessProfileImage(byte[] buffer) + { + using (Image image = Image.Load(buffer)) + { + image.Mutate(x => x.Resize(256, 256)); + + using (MemoryStream streamJpg = new()) + { + image.SaveAsJpeg(streamJpg); + + return streamJpg.ToArray(); + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Controls/RendererHost.axaml b/Ryujinx.Ava/UI/Controls/RendererHost.axaml new file mode 100644 index 00000000..1cc557f0 --- /dev/null +++ b/Ryujinx.Ava/UI/Controls/RendererHost.axaml @@ -0,0 +1,11 @@ + + diff --git a/Ryujinx.Ava/UI/Controls/RendererHost.axaml.cs b/Ryujinx.Ava/UI/Controls/RendererHost.axaml.cs new file mode 100644 index 00000000..97058fa4 --- /dev/null +++ b/Ryujinx.Ava/UI/Controls/RendererHost.axaml.cs @@ -0,0 +1,127 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Common.Configuration; +using Silk.NET.Vulkan; +using SPB.Graphics.OpenGL; +using SPB.Windowing; +using System; + +namespace Ryujinx.Ava.UI.Controls +{ + public partial class RendererHost : UserControl, IDisposable + { + private readonly GraphicsDebugLevel _graphicsDebugLevel; + private EmbeddedWindow _currentWindow; + + public bool IsVulkan { get; private set; } + + public RendererHost(GraphicsDebugLevel graphicsDebugLevel) + { + _graphicsDebugLevel = graphicsDebugLevel; + InitializeComponent(); + } + + public RendererHost() + { + InitializeComponent(); + } + + public void CreateOpenGL() + { + Dispose(); + + _currentWindow = new OpenGLEmbeddedWindow(3, 3, _graphicsDebugLevel); + Initialize(); + + IsVulkan = false; + } + + private void Initialize() + { + _currentWindow.WindowCreated += CurrentWindow_WindowCreated; + _currentWindow.SizeChanged += CurrentWindow_SizeChanged; + Content = _currentWindow; + } + + public void CreateVulkan() + { + Dispose(); + + _currentWindow = new VulkanEmbeddedWindow(); + Initialize(); + + IsVulkan = true; + } + + public OpenGLContextBase GetContext() + { + if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow) + { + return openGlEmbeddedWindow.Context; + } + + return null; + } + + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); + + Dispose(); + } + + private void CurrentWindow_SizeChanged(object sender, Size e) + { + SizeChanged?.Invoke(sender, e); + } + + private void CurrentWindow_WindowCreated(object sender, IntPtr e) + { + RendererInitialized?.Invoke(this, EventArgs.Empty); + } + + public void MakeCurrent() + { + if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow) + { + openGlEmbeddedWindow.MakeCurrent(); + } + } + + public void MakeCurrent(SwappableNativeWindowBase window) + { + if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow) + { + openGlEmbeddedWindow.MakeCurrent(window); + } + } + + public void SwapBuffers() + { + if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow) + { + openGlEmbeddedWindow.SwapBuffers(); + } + } + + public event EventHandler RendererInitialized; + public event Action SizeChanged; + public void Dispose() + { + if (_currentWindow != null) + { + _currentWindow.WindowCreated -= CurrentWindow_WindowCreated; + _currentWindow.SizeChanged -= CurrentWindow_SizeChanged; + } + } + + public SurfaceKHR CreateVulkanSurface(Instance instance, Vk api) + { + return (_currentWindow is VulkanEmbeddedWindow vulkanEmbeddedWindow) + ? vulkanEmbeddedWindow.CreateSurface(instance) + : default; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Controls/SaveManager.axaml b/Ryujinx.Ava/UI/Controls/SaveManager.axaml new file mode 100644 index 00000000..b0dc4c6f --- /dev/null +++ b/Ryujinx.Ava/UI/Controls/SaveManager.axaml @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Controls/SaveManager.axaml.cs b/Ryujinx.Ava/UI/Controls/SaveManager.axaml.cs new file mode 100644 index 00000000..9910481c --- /dev/null +++ b/Ryujinx.Ava/UI/Controls/SaveManager.axaml.cs @@ -0,0 +1,160 @@ +using Avalonia.Controls; +using DynamicData; +using DynamicData.Binding; +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Shim; +using Ryujinx.Ava.Common; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Models; +using Ryujinx.HLE.FileSystem; +using Ryujinx.Ui.App.Common; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using UserProfile = Ryujinx.Ava.UI.Models.UserProfile; + +namespace Ryujinx.Ava.UI.Controls +{ + public partial class SaveManager : UserControl + { + private readonly UserProfile _userProfile; + private readonly HorizonClient _horizonClient; + private readonly VirtualFileSystem _virtualFileSystem; + private int _sortIndex; + private int _orderIndex; + private ObservableCollection _view = new ObservableCollection(); + private string _search; + + public ObservableCollection Saves { get; set; } = new ObservableCollection(); + + public ObservableCollection View + { + get => _view; + set => _view = value; + } + + public int SortIndex + { + get => _sortIndex; + set + { + _sortIndex = value; + Sort(); + } + } + + public int OrderIndex + { + get => _orderIndex; + set + { + _orderIndex = value; + Sort(); + } + } + + public string Search + { + get => _search; + set + { + _search = value; + Sort(); + } + } + + public SaveManager() + { + InitializeComponent(); + } + + public SaveManager(UserProfile userProfile, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem) + { + _userProfile = userProfile; + _horizonClient = horizonClient; + _virtualFileSystem = virtualFileSystem; + InitializeComponent(); + + DataContext = this; + + Task.Run(LoadSaves); + } + + public void LoadSaves() + { + Saves.Clear(); + var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account, + new UserId((ulong)_userProfile.UserId.High, (ulong)_userProfile.UserId.Low), saveDataId: default, index: default); + + using var saveDataIterator = new UniqueRef(); + + _horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure(); + + Span saveDataInfo = stackalloc SaveDataInfo[10]; + + while (true) + { + saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure(); + + if (readCount == 0) + { + break; + } + + for (int i = 0; i < readCount; i++) + { + var save = saveDataInfo[i]; + if (save.ProgramId.Value != 0) + { + var saveModel = new SaveModel(save, _horizonClient, _virtualFileSystem); + Saves.Add(saveModel); + saveModel.DeleteAction = () => { Saves.Remove(saveModel); }; + } + + Sort(); + } + } + } + + private void Sort() + { + Saves.AsObservableChangeSet() + .Filter(Filter) + .Sort(GetComparer()) + .Bind(out var view).AsObservableList(); + + _view.Clear(); + _view.AddRange(view); + } + + private IComparer GetComparer() + { + switch (SortIndex) + { + case 0: + return OrderIndex == 0 + ? SortExpressionComparer.Ascending(save => save.Title) + : SortExpressionComparer.Descending(save => save.Title); + case 1: + return OrderIndex == 0 + ? SortExpressionComparer.Ascending(save => save.Size) + : SortExpressionComparer.Descending(save => save.Size); + default: + return null; + } + } + + private bool Filter(object arg) + { + if (arg is SaveModel save) + { + return string.IsNullOrWhiteSpace(_search) || save.Title.ToLower().Contains(_search.ToLower()); + } + + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml b/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml new file mode 100644 index 00000000..c5041230 --- /dev/null +++ b/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml.cs b/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml.cs new file mode 100644 index 00000000..9db7b5d4 --- /dev/null +++ b/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml.cs @@ -0,0 +1,20 @@ +using Avalonia.Controls; +using Ryujinx.Ava.UI.Windows; + +namespace Ryujinx.Ava.UI.Controls +{ + public partial class UpdateWaitWindow : StyleableWindow + { + public UpdateWaitWindow(string primaryText, string secondaryText) : this() + { + PrimaryText.Text = primaryText; + SecondaryText.Text = secondaryText; + WindowStartupLocation = WindowStartupLocation.CenterOwner; + } + + public UpdateWaitWindow() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Controls/UserEditor.axaml b/Ryujinx.Ava/UI/Controls/UserEditor.axaml new file mode 100644 index 00000000..155f1cfe --- /dev/null +++ b/Ryujinx.Ava/UI/Controls/UserEditor.axaml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +