From 9dcb71e120a29b4920591fbc53f61132ace6b918 Mon Sep 17 00:00:00 2001 From: GreemDev Date: Sat, 29 Nov 2025 02:17:23 -0600 Subject: [PATCH] add a setup finished screen added the ability to hide the help button (basically just for the finish screen, because it has a bigger discord button in the same place) holding shift while opening the setup wizard now opens it in passive mode, aka it will install only what you need. this is mostly for testing and likely will be nuked before this code as a whole is made part of the official emulator, but it might not --- assets/locales.json | 75 +++++++++++++++++++ .../Pages/Final/SetupFinishedPage.axaml | 40 ++++++++++ .../Pages/Final/SetupFinishedPage.axaml.cs | 22 ++++++ .../Pages/Final/SetupFinishedPageContext.cs | 13 ++++ .../SetupWizard/RyujinxSetupWizard.Steps.cs | 10 ++- .../UI/SetupWizard/RyujinxSetupWizard.cs | 6 +- .../UI/SetupWizard/SetupWizardPage.Builder.cs | 8 +- src/Ryujinx/UI/SetupWizard/SetupWizardPage.cs | 2 + .../UI/SetupWizard/SetupWizardPageContext.cs | 24 ++++-- .../UI/SetupWizard/SetupWizardPageView.axaml | 3 +- .../UI/Views/Main/MainMenuBarView.axaml.cs | 67 ++++++++++++++--- 11 files changed, 247 insertions(+), 23 deletions(-) create mode 100644 src/Ryujinx/UI/SetupWizard/Pages/Final/SetupFinishedPage.axaml create mode 100644 src/Ryujinx/UI/SetupWizard/Pages/Final/SetupFinishedPage.axaml.cs create mode 100644 src/Ryujinx/UI/SetupWizard/Pages/Final/SetupFinishedPageContext.cs diff --git a/assets/locales.json b/assets/locales.json index e12b75be3..351b36e6e 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -25216,6 +25216,81 @@ "zh_CN": "", "zh_TW": "" } + }, + { + "ID": "SetupWizardFinalPageTitle", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Setup Complete", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, + { + "ID": "SetupWizardFinalPageDescription", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Your installation of Ryubing (aka Ryujinx) has been completed.\n\nIf you require assistance, feel free to join our Discord server and ask for help,\nafter verifying your possession of a modded Nintendo Switch.", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, + { + "ID": "SetupWizardFinalPageAction", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Finish", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } } ] } diff --git a/src/Ryujinx/UI/SetupWizard/Pages/Final/SetupFinishedPage.axaml b/src/Ryujinx/UI/SetupWizard/Pages/Final/SetupFinishedPage.axaml new file mode 100644 index 000000000..79f861b2a --- /dev/null +++ b/src/Ryujinx/UI/SetupWizard/Pages/Final/SetupFinishedPage.axaml @@ -0,0 +1,40 @@ + + + + + + + + diff --git a/src/Ryujinx/UI/SetupWizard/Pages/Final/SetupFinishedPage.axaml.cs b/src/Ryujinx/UI/SetupWizard/Pages/Final/SetupFinishedPage.axaml.cs new file mode 100644 index 000000000..4f68d22ca --- /dev/null +++ b/src/Ryujinx/UI/SetupWizard/Pages/Final/SetupFinishedPage.axaml.cs @@ -0,0 +1,22 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Common.Helper; + +namespace Ryujinx.Ava.UI.SetupWizard.Pages +{ + public partial class SetupFinishedPage : RyujinxControl + { + public SetupFinishedPage() + { + InitializeComponent(); + } + + private void Button_OnClick(object sender, RoutedEventArgs e) + { + if (sender is Button { Tag: string url }) + OpenHelper.OpenUrl(url); + } + } +} + diff --git a/src/Ryujinx/UI/SetupWizard/Pages/Final/SetupFinishedPageContext.cs b/src/Ryujinx/UI/SetupWizard/Pages/Final/SetupFinishedPageContext.cs new file mode 100644 index 000000000..5506eb772 --- /dev/null +++ b/src/Ryujinx/UI/SetupWizard/Pages/Final/SetupFinishedPageContext.cs @@ -0,0 +1,13 @@ +using Gommon; +using Ryujinx.Ava.Common.Locale; + +namespace Ryujinx.Ava.UI.SetupWizard.Pages +{ + public class SetupFinishedPageContext() : SetupWizardPageContext(LocaleKeys.SetupWizardFinalPageTitle) + { + public override LocaleKeys ActionContent => LocaleKeys.SetupWizardFinalPageAction; + + // informative step; this implementation is not called. + public override Result CompleteStep() => Result.Success; + } +} diff --git a/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.Steps.cs b/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.Steps.cs index 4d156c6ec..c40e8c02c 100644 --- a/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.Steps.cs +++ b/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.Steps.cs @@ -35,8 +35,9 @@ namespace Ryujinx.Ava.UI.SetupWizard } Retry: - bool result = await NextPage(out SetupFirmwarePageContext fwContext) - .Show(); + bool result = + await NextPage(out SetupFirmwarePageContext fwContext) + .Show(); if (!result) return false; @@ -49,5 +50,10 @@ namespace Ryujinx.Ava.UI.SetupWizard return true; } + + private ValueTask Finish() + => NextPage(out _) + .WithHelpButtonVisible(false) + .Show(); } } diff --git a/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.cs b/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.cs index 6a00910b3..ccf906bfa 100644 --- a/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.cs +++ b/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.cs @@ -43,7 +43,8 @@ namespace Ryujinx.Ava.UI.SetupWizard where TContext : SetupWizardPageContext, new() => NextPage() .WithContent(out boundContext) - .WithTitle(boundContext.Title); + .WithTitle(boundContext.Title) + .WithActionContent(boundContext.ActionContent); public void SignalConfigModified() { @@ -82,6 +83,9 @@ namespace Ryujinx.Ava.UI.SetupWizard if (!await SetupFirmware()) goto Keys; + if (!await Finish()) + goto Firmware; + Return: if (_configWasModified) ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.GlobalConfigurationPath); diff --git a/src/Ryujinx/UI/SetupWizard/SetupWizardPage.Builder.cs b/src/Ryujinx/UI/SetupWizard/SetupWizardPage.Builder.cs index d99c6fff2..e51eb2a17 100644 --- a/src/Ryujinx/UI/SetupWizard/SetupWizardPage.Builder.cs +++ b/src/Ryujinx/UI/SetupWizard/SetupWizardPage.Builder.cs @@ -68,7 +68,7 @@ namespace Ryujinx.Ava.UI.SetupWizard where TControl : RyujinxControl, new() where TContext : SetupWizardPageContext, new() { - boundContext = new() { NotificationManager = ownerWizard.NotificationManager }; + boundContext = new() { OwningWizard = ownerWizard }; if (boundContext.CreateHelpContent() is { } content) WithHelpContent(content); @@ -84,5 +84,11 @@ namespace Ryujinx.Ava.UI.SetupWizard ActionContent = content; return this; } + + public SetupWizardPage WithHelpButtonVisible(bool visible) + { + ShowHelpButton = visible; + return this; + } } } diff --git a/src/Ryujinx/UI/SetupWizard/SetupWizardPage.cs b/src/Ryujinx/UI/SetupWizard/SetupWizardPage.cs index a404612a6..bdea37a01 100644 --- a/src/Ryujinx/UI/SetupWizard/SetupWizardPage.cs +++ b/src/Ryujinx/UI/SetupWizard/SetupWizardPage.cs @@ -28,6 +28,8 @@ namespace Ryujinx.Ava.UI.SetupWizard [ObservableProperty] public partial bool HasHelpContent { get; set; } + [ObservableProperty] public partial bool ShowHelpButton { get; set; } = true; + [ObservableProperty] public partial object? ActionContent { get; set; } = LocaleManager.Instance[LocaleKeys.SetupWizardActionNext]; diff --git a/src/Ryujinx/UI/SetupWizard/SetupWizardPageContext.cs b/src/Ryujinx/UI/SetupWizard/SetupWizardPageContext.cs index d65099bd4..7c7f58122 100644 --- a/src/Ryujinx/UI/SetupWizard/SetupWizardPageContext.cs +++ b/src/Ryujinx/UI/SetupWizard/SetupWizardPageContext.cs @@ -3,23 +3,33 @@ using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.ViewModels; + namespace Ryujinx.Ava.UI.SetupWizard { public abstract class SetupWizardPageContext(LocaleKeys title) : BaseModel { + public RyujinxSetupWizard OwningWizard + { + get; + init + { + field = value; + NotificationManager = field.NotificationManager; + } + } + + public RyujinxNotificationManager NotificationManager { get; private init; } + public LocaleKeys Title => title; - - public RyujinxNotificationManager NotificationManager { get; init; } + + public virtual LocaleKeys ActionContent => LocaleKeys.SetupWizardActionNext; // ReSharper disable once UnusedMemberInSuper.Global // it's used implicitly as we use this type as a where guard for generics for WithContent, // it also ensures all context types implement completion public abstract Result CompleteStep(); + #nullable enable - public virtual object? CreateHelpContent() -#nullable disable - { - return null; - } + public virtual object? CreateHelpContent() => null; } } diff --git a/src/Ryujinx/UI/SetupWizard/SetupWizardPageView.axaml b/src/Ryujinx/UI/SetupWizard/SetupWizardPageView.axaml index bff0aa5d6..224af0676 100644 --- a/src/Ryujinx/UI/SetupWizard/SetupWizardPageView.axaml +++ b/src/Ryujinx/UI/SetupWizard/SetupWizardPageView.axaml @@ -70,8 +70,7 @@ - + diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs index 863d3e4eb..9729c76da 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs @@ -1,5 +1,6 @@ using Avalonia; using Avalonia.Controls; +using Avalonia.Input; using Avalonia.Layout; using Avalonia.Threading; using Gommon; @@ -31,6 +32,7 @@ namespace Ryujinx.Ava.UI.Views.Main { public MainWindow Window { get; private set; } + public MainMenuBarView() { InitializeComponent(); @@ -51,7 +53,9 @@ namespace Ryujinx.Ava.UI.Views.Main AboutWindowMenuItem.Command = Commands.Create(AboutView.Show); CompatibilityListMenuItem.Command = Commands.Create(() => CompatibilityListWindow.Show()); LdnGameListMenuItem.Command = Commands.Create(() => LdnGamesListWindow.Show()); - SetupWizardMenuItem.Command = Commands.Create(() => RyujinxSetupWizardWindow.ShowAsync(overwriteMode: true)); + SetupWizardMenuItem.Command = Commands.Create(() => + RyujinxSetupWizardWindow.ShowAsync(overwriteMode: !PollShiftPressed()) + ); UpdateMenuItem.Command = MainWindowViewModel.UpdateCommand; @@ -64,9 +68,42 @@ namespace Ryujinx.Ava.UI.Views.Main WindowSize1440PMenuItem.Command = WindowSize2160PMenuItem.Command = Commands.Create(ChangeWindowSize); + KeyDown += OnKeyDown; + KeyUp += OnKeyUp; + LocaleManager.Instance.LocaleChanged += OnLocaleChanged; } + /// + /// KeyUp is not reliably invoked (or invoked at all, seemingly) when a window showing up causes the main menu bar to view, + /// as shift is technically raised when that control is no longer the foreground control. + /// + /// This stores to a temp variable, sets to false (if it is true), then returns the temp variable. + /// + private bool PollShiftPressed() + { + bool temp = IsShiftPressed; + if (temp) + IsShiftPressed = false; + return temp; + } + + private bool IsShiftPressed { get; set; } + + private void OnKeyDown(object sender, KeyEventArgs e) + { + if (e.Key is Key.LeftShift or Key.RightShift && !IsShiftPressed) + //down is called even for keys that have been held for a while, aka key repeats. + //the check for shift being pressed prevents setting the variable every time the down event is received, if shift is already known to be pressed. + IsShiftPressed = true; + } + + private void OnKeyUp(object sender, KeyEventArgs e) + { + if (e.Key is Key.LeftShift or Key.RightShift) + IsShiftPressed = false; + } + private void OnLocaleChanged() { ChangeLanguageMenuItem.ItemsSource = GenerateLanguageMenuItems(); @@ -147,11 +184,13 @@ namespace Ryujinx.Ava.UI.Views.Main } else { - bool customConfigExists = File.Exists(Program.GetDirGameUserConfig(ViewModel.SelectedApplication.IdString)); + bool customConfigExists = + File.Exists(Program.GetDirGameUserConfig(ViewModel.SelectedApplication.IdString)); if (!ViewModel.IsGameRunning || !customConfigExists) { - await Window.SettingsWindow.ShowDialog(Window); // The game is not running, or if the user configuration does not exist + await Window.SettingsWindow + .ShowDialog(Window); // The game is not running, or if the user configuration does not exist } else { @@ -175,7 +214,8 @@ namespace Ryujinx.Ava.UI.Views.Main if (!MiiApplet.CanStart(out ApplicationData appData, out BlitStruct nacpData)) return; - await ViewModel.LoadApplication(appData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData); + await ViewModel.LoadApplication(appData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, + nacpData); } public async Task OpenCheatManagerForCurrentApp() @@ -183,7 +223,8 @@ namespace Ryujinx.Ava.UI.Views.Main if (!ViewModel.IsGameRunning) return; - string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString(); + string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties + .Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString(); await StyleableAppWindow.ShowAsync( new CheatWindow( @@ -212,18 +253,24 @@ namespace Ryujinx.Ava.UI.Views.Main { ViewModel.AreMimeTypesRegistered = FileAssociationHelper.Install(); if (ViewModel.AreMimeTypesRegistered) - await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); + await ContentDialogHelper.CreateInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage], string.Empty, + LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); else - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesErrorMessage]); + await ContentDialogHelper.CreateErrorDialog( + LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesErrorMessage]); } private async Task UninstallFileTypes() { ViewModel.AreMimeTypesRegistered = !FileAssociationHelper.Uninstall(); if (!ViewModel.AreMimeTypesRegistered) - await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); + await ContentDialogHelper.CreateInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage], string.Empty, + LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); else - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesErrorMessage]); + await ContentDialogHelper.CreateErrorDialog( + LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesErrorMessage]); } private void ChangeWindowSize(string resolution) @@ -235,7 +282,7 @@ namespace Ryujinx.Ava.UI.Views.Main // Correctly size window when 'TitleBar' is enabled (Nov. 14, 2024) double barsHeight = ((Window.StatusBarHeight + Window.MenuBarHeight) + - (ConfigurationState.Instance.ShowOldUI ? (int)Window.TitleBar.Height : 0)); + (ConfigurationState.Instance.ShowOldUI ? (int)Window.TitleBar.Height : 0)); double windowWidthScaled = (resolutionWidth * Program.WindowScaleFactor); double windowHeightScaled = ((resolutionHeight + barsHeight) * Program.WindowScaleFactor);