From f037fcba9a5713e68cadbbf8a823913943ac2626 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Sat, 13 Jan 2024 00:41:57 +0000 Subject: [PATCH] Ava UI: Better Controller Applet (#5756) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Start work on better Controller Applet * Don’t require title * UI improvements * Border around TBD area * Formatting * Better SVGs * Add missing margin * Use Locale * Rename function * Make buttons ourselves * Make the buttons do shit * Formatting * Adjust SVGs * Fix Open Settings Window * Make field readonly * Final tweaks * Update src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs Co-authored-by: Ac_K * Update src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs Co-authored-by: Ac_K * Apply suggestions from code review Co-authored-by: Ac_K * Update src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Move icons to Ava project * Reorder arguments * Try to focus Settings Window * Fix icons Project shenangians * Add ContentDialogHelper.ShowWindowAsync method * Fix closed SettingsWindow reference in MainWindow * Fix SettingsWindow dialog * Suggestion --------- Co-authored-by: Ac_K Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> --- .../Assets/Icons/Controller_JoyConLeft.svg | 155 ++++++++ .../Assets/Icons/Controller_JoyConPair.svg | 341 ++++++++++++++++++ .../Assets/Icons/Controller_JoyConRight.svg | 185 ++++++++++ .../Assets/Icons/Controller_ProCon.svg | 84 +++++ src/Ryujinx.Ava/Assets/Locales/en_US.json | 7 +- src/Ryujinx.Ava/Ryujinx.Ava.csproj | 8 + src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs | 26 +- .../UI/Applet/ControllerAppletDialog.axaml | 145 ++++++++ .../UI/Applet/ControllerAppletDialog.axaml.cs | 139 +++++++ .../UI/Helpers/ContentDialogHelper.cs | 40 +- .../UI/Views/Main/MainMenuBarView.axaml.cs | 2 + 11 files changed, 1108 insertions(+), 24 deletions(-) create mode 100644 src/Ryujinx.Ava/Assets/Icons/Controller_JoyConLeft.svg create mode 100644 src/Ryujinx.Ava/Assets/Icons/Controller_JoyConPair.svg create mode 100644 src/Ryujinx.Ava/Assets/Icons/Controller_JoyConRight.svg create mode 100644 src/Ryujinx.Ava/Assets/Icons/Controller_ProCon.svg create mode 100644 src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml create mode 100644 src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs diff --git a/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConLeft.svg b/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConLeft.svg new file mode 100644 index 000000000..cf78cf120 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConLeft.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConPair.svg b/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConPair.svg new file mode 100644 index 000000000..8097762df --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConPair.svg @@ -0,0 +1,341 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConRight.svg b/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConRight.svg new file mode 100644 index 000000000..adb6e1e18 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Icons/Controller_JoyConRight.svg @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx.Ava/Assets/Icons/Controller_ProCon.svg b/src/Ryujinx.Ava/Assets/Icons/Controller_ProCon.svg new file mode 100644 index 000000000..53eef82c2 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Icons/Controller_ProCon.svg @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/en_US.json b/src/Ryujinx.Ava/Assets/Locales/en_US.json index 493aaa81f..72b5e8e3c 100644 --- a/src/Ryujinx.Ava/Assets/Locales/en_US.json +++ b/src/Ryujinx.Ava/Assets/Locales/en_US.json @@ -549,9 +549,10 @@ "SoftwareKeyboardModeNumeric": "Must be 0-9 or '.' only", "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", "SoftwareKeyboardModeASCII": "Must be ASCII text only", - "DialogControllerAppletMessagePlayerRange": "Application requests {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.", - "DialogControllerAppletMessage": "Application requests exactly {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.", - "DialogControllerAppletDockModeSet": "Docked mode set. Handheld is also invalid.\n\n", + "ControllerAppletControllers": "Supported Controllers:", + "ControllerAppletPlayers": "Players:", + "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", + "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", "UpdaterRenaming": "Renaming Old Files...", "UpdaterRenameFailed": "Updater was unable to rename file: {0}", "UpdaterAddingFiles": "Adding New Files...", diff --git a/src/Ryujinx.Ava/Ryujinx.Ava.csproj b/src/Ryujinx.Ava/Ryujinx.Ava.csproj index 6812e57c4..054a5c7f7 100644 --- a/src/Ryujinx.Ava/Ryujinx.Ava.csproj +++ b/src/Ryujinx.Ava/Ryujinx.Ava.csproj @@ -132,6 +132,10 @@ + + + + @@ -151,6 +155,10 @@ + + + + diff --git a/src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs b/src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs index 9fc7c6b6d..e11939104 100644 --- a/src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs +++ b/src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs @@ -29,14 +29,24 @@ namespace Ryujinx.Ava.UI.Applet public bool DisplayMessageDialog(ControllerAppletUiArgs args) { - string message = LocaleManager.Instance.UpdateAndGetDynamicValue( - args.PlayerCountMin == args.PlayerCountMax ? LocaleKeys.DialogControllerAppletMessage : LocaleKeys.DialogControllerAppletMessagePlayerRange, - args.PlayerCountMin == args.PlayerCountMax ? args.PlayerCountMin.ToString() : $"{args.PlayerCountMin}-{args.PlayerCountMax}", - args.SupportedStyles, - string.Join(", ", args.SupportedPlayers), - args.IsDocked ? LocaleManager.Instance[LocaleKeys.DialogControllerAppletDockModeSet] : ""); + ManualResetEvent dialogCloseEvent = new(false); - return DisplayMessageDialog(LocaleManager.Instance[LocaleKeys.DialogControllerAppletTitle], message); + bool okPressed = false; + + Dispatcher.UIThread.InvokeAsync(async () => + { + var response = await ControllerAppletDialog.ShowControllerAppletDialog(_parent, args); + if (response == UserResult.Ok) + { + okPressed = true; + } + + dialogCloseEvent.Set(); + }); + + dialogCloseEvent.WaitOne(); + + return okPressed; } public bool DisplayMessageDialog(string title, string message) @@ -75,6 +85,8 @@ namespace Ryujinx.Ava.UI.Applet await _parent.SettingsWindow.ShowDialog(window); + _parent.SettingsWindow = null; + opened = false; }); diff --git a/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml b/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml new file mode 100644 index 000000000..b2c22f6bb --- /dev/null +++ b/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs b/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs new file mode 100644 index 000000000..34de5223b --- /dev/null +++ b/src/Ryujinx.Ava/UI/Applet/ControllerAppletDialog.axaml.cs @@ -0,0 +1,139 @@ +using Avalonia.Controls; +using Avalonia.Styling; +using Avalonia.Svg.Skia; +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Services.Hid; +using System.Linq; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Applet +{ + internal partial class ControllerAppletDialog : UserControl + { + private const string ProControllerResource = "Ryujinx.Ava/Assets/Icons/Controller_ProCon.svg"; + private const string JoyConPairResource = "Ryujinx.Ava/Assets/Icons/Controller_JoyConPair.svg"; + private const string JoyConLeftResource = "Ryujinx.Ava/Assets/Icons/Controller_JoyConLeft.svg"; + private const string JoyConRightResource = "Ryujinx.Ava/Assets/Icons/Controller_JoyConRight.svg"; + + public static SvgImage ProControllerImage => GetResource(ProControllerResource); + public static SvgImage JoyconPairImage => GetResource(JoyConPairResource); + public static SvgImage JoyconLeftImage => GetResource(JoyConLeftResource); + public static SvgImage JoyconRightImage => GetResource(JoyConRightResource); + + public string PlayerCount { get; set; } = ""; + public bool SupportsProController { get; set; } + public bool SupportsLeftJoycon { get; set; } + public bool SupportsRightJoycon { get; set; } + public bool SupportsJoyconPair { get; set; } + public bool IsDocked { get; set; } + + private readonly MainWindow _mainWindow; + + public ControllerAppletDialog(MainWindow mainWindow, ControllerAppletUiArgs args) + { + if (args.PlayerCountMin == args.PlayerCountMax) + { + PlayerCount = args.PlayerCountMin.ToString(); + } + else + { + PlayerCount = $"{args.PlayerCountMin} - {args.PlayerCountMax}"; + } + + SupportsProController = (args.SupportedStyles & ControllerType.ProController) != 0; + SupportsLeftJoycon = (args.SupportedStyles & ControllerType.JoyconLeft) != 0; + SupportsRightJoycon = (args.SupportedStyles & ControllerType.JoyconRight) != 0; + SupportsJoyconPair = (args.SupportedStyles & ControllerType.JoyconPair) != 0; + + IsDocked = args.IsDocked; + + _mainWindow = mainWindow; + + DataContext = this; + + InitializeComponent(); + } + + public ControllerAppletDialog(MainWindow mainWindow) + { + _mainWindow = mainWindow; + DataContext = this; + + InitializeComponent(); + } + + public static async Task ShowControllerAppletDialog(MainWindow window, ControllerAppletUiArgs args) + { + ContentDialog contentDialog = new(); + UserResult result = UserResult.Cancel; + ControllerAppletDialog content = new(window, args); + + contentDialog.Title = LocaleManager.Instance[LocaleKeys.DialogControllerAppletTitle]; + contentDialog.Content = content; + + void Handler(ContentDialog sender, ContentDialogClosedEventArgs eventArgs) + { + if (eventArgs.Result == ContentDialogResult.Primary) + { + result = UserResult.Ok; + } + } + + contentDialog.Closed += Handler; + + Style bottomBorder = new(x => x.OfType().Name("DialogSpace").Child().OfType()); + bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false)); + + contentDialog.Styles.Add(bottomBorder); + + await ContentDialogHelper.ShowAsync(contentDialog); + + return result; + } + + private static SvgImage GetResource(string path) + { + SvgImage image = new(); + + if (!string.IsNullOrWhiteSpace(path)) + { + SvgSource source = new(); + + source.Load(EmbeddedResources.GetStream(path)); + + image.Source = source; + } + + return image; + } + + public void OpenSettingsWindow() + { + if (_mainWindow.SettingsWindow == null) + { + Dispatcher.UIThread.InvokeAsync(async () => + { + _mainWindow.SettingsWindow = new SettingsWindow(_mainWindow.VirtualFileSystem, _mainWindow.ContentManager); + _mainWindow.SettingsWindow.NavPanel.Content = _mainWindow.SettingsWindow.InputPage; + _mainWindow.SettingsWindow.NavPanel.SelectedItem = _mainWindow.SettingsWindow.NavPanel.MenuItems.ElementAt(1); + + await ContentDialogHelper.ShowWindowAsync(_mainWindow.SettingsWindow, _mainWindow); + _mainWindow.SettingsWindow = null; + this.Close(); + }); + } + } + + public void Close() + { + ((ContentDialog)Parent)?.Hide(); + } + } +} + diff --git a/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs b/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs index 086de953d..a57deb1a0 100644 --- a/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs +++ b/src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs @@ -18,6 +18,7 @@ namespace Ryujinx.Ava.UI.Helpers public static class ContentDialogHelper { private static bool _isChoiceDialogOpen; + private static ContentDialogOverlayWindow _contentDialogOverlayWindow; private async static Task ShowContentDialog( string title, @@ -310,16 +311,20 @@ namespace Ryujinx.Ava.UI.Helpers public static async Task ShowAsync(ContentDialog contentDialog) { ContentDialogResult result; - - ContentDialogOverlayWindow contentDialogOverlayWindow = null; + bool isTopDialog = true; Window parent = GetMainWindow(); + if (_contentDialogOverlayWindow != null) + { + isTopDialog = false; + } + if (parent is MainWindow window) { parent.Activate(); - contentDialogOverlayWindow = new() + _contentDialogOverlayWindow = new ContentDialogOverlayWindow { Height = parent.Bounds.Height, Width = parent.Bounds.Width, @@ -331,14 +336,14 @@ namespace Ryujinx.Ava.UI.Helpers void OverlayOnPositionChanged(object sender, PixelPointEventArgs e) { - contentDialogOverlayWindow.Position = parent.PointToScreen(new Point()); + _contentDialogOverlayWindow.Position = parent.PointToScreen(new Point()); } - contentDialogOverlayWindow.ContentDialog = contentDialog; + _contentDialogOverlayWindow.ContentDialog = contentDialog; bool opened = false; - contentDialogOverlayWindow.Opened += OverlayOnActivated; + _contentDialogOverlayWindow.Opened += OverlayOnActivated; async void OverlayOnActivated(object sender, EventArgs e) { @@ -349,12 +354,12 @@ namespace Ryujinx.Ava.UI.Helpers opened = true; - contentDialogOverlayWindow.Position = parent.PointToScreen(new Point()); + _contentDialogOverlayWindow.Position = parent.PointToScreen(new Point()); result = await ShowDialog(); } - result = await contentDialogOverlayWindow.ShowDialog(parent); + result = await _contentDialogOverlayWindow.ShowDialog(parent); } else { @@ -363,11 +368,11 @@ namespace Ryujinx.Ava.UI.Helpers async Task ShowDialog() { - if (contentDialogOverlayWindow is not null) + if (_contentDialogOverlayWindow is not null) { - result = await contentDialog.ShowAsync(contentDialogOverlayWindow); + result = await contentDialog.ShowAsync(_contentDialogOverlayWindow); - contentDialogOverlayWindow!.Close(); + _contentDialogOverlayWindow!.Close(); } else { @@ -379,15 +384,22 @@ namespace Ryujinx.Ava.UI.Helpers return result; } - if (contentDialogOverlayWindow is not null) + if (isTopDialog && _contentDialogOverlayWindow is not null) { - contentDialogOverlayWindow.Content = null; - contentDialogOverlayWindow.Close(); + _contentDialogOverlayWindow.Content = null; + _contentDialogOverlayWindow.Close(); } return result; } + public static Task ShowWindowAsync(Window dialogWindow, Window mainWindow = null) + { + mainWindow ??= GetMainWindow(); + + return dialogWindow.ShowDialog(_contentDialogOverlayWindow ?? mainWindow); + } + private static Window GetMainWindow() { if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime al) diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs index b71fa82b9..9c0e683a0 100644 --- a/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs +++ b/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs @@ -122,6 +122,8 @@ namespace Ryujinx.Ava.UI.Views.Main await Window.SettingsWindow.ShowDialog(Window); + Window.SettingsWindow = null; + ViewModel.LoadConfigurableHotKeys(); }