From 51692a94d92d209d5f84c557f79e5506a6e9e7dd Mon Sep 17 00:00:00 2001 From: GreemDev Date: Thu, 27 Nov 2025 21:00:44 -0600 Subject: [PATCH] reorganize RyujinxSetupWizard additionally, the CreateHelpContent method is now no longer locked to returning an Avalonia `Control`. the WithHelpContent method also has logic to handle it returning a string directly, and will wrap it in a textblock with h1 style and size 20 font. otherwise it's up to the ContentPresenter for the help content to choose how to display it if it's none of the above mentioned types. --- .../Common/EmbeddedAvaloniaResources.cs | 31 ++++ .../Pages/Fw/SetupFirmwarePageContext.cs | 2 +- .../Pages/Keys/SetupKeysPageContext.cs | 2 +- .../SetupWizard/RyujinxSetupWizard.Steps.cs | 57 +++++++ .../UI/SetupWizard/RyujinxSetupWizard.cs | 142 +++++------------- .../UI/SetupWizard/SetupWizardPage.Builder.cs | 19 +++ .../UI/SetupWizard/SetupWizardPageContext.cs | 6 +- .../UI/ViewModels/AboutWindowViewModel.cs | 11 +- 8 files changed, 152 insertions(+), 118 deletions(-) create mode 100644 src/Ryujinx/Common/EmbeddedAvaloniaResources.cs create mode 100644 src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.Steps.cs diff --git a/src/Ryujinx/Common/EmbeddedAvaloniaResources.cs b/src/Ryujinx/Common/EmbeddedAvaloniaResources.cs new file mode 100644 index 000000000..57be9da4a --- /dev/null +++ b/src/Ryujinx/Common/EmbeddedAvaloniaResources.cs @@ -0,0 +1,31 @@ +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using Avalonia.Styling; +using Gommon; +using System; + +namespace Ryujinx.Ava.Common +{ + public static class EmbeddedAvaloniaResources + { + public const string LogoPathFormat = "resm:Ryujinx.Assets.UIImages.Logo_{0}_{1}.png?assembly=Ryujinx"; + + public static Bitmap LoadBitmap(string uri) + => new(AssetLoader.Open(new Uri(uri))); + + public static Bitmap GetIconByNameAndTheme(string iconName, bool isDarkTheme) + { + string themeName = isDarkTheme ? "Dark" : "Light"; + + return LoadBitmap(LogoPathFormat.Format(iconName, themeName)); + } + + public static Bitmap GetIconByNameAndTheme(string iconName, string theme) + { + bool isDarkTheme = theme == "Dark" || + (theme == "Auto" && RyujinxApp.DetectSystemTheme() == ThemeVariant.Dark); + + return GetIconByNameAndTheme(iconName, isDarkTheme); + } + } +} diff --git a/src/Ryujinx/UI/SetupWizard/Pages/Fw/SetupFirmwarePageContext.cs b/src/Ryujinx/UI/SetupWizard/Pages/Fw/SetupFirmwarePageContext.cs index 34c4c492e..7c7d93340 100644 --- a/src/Ryujinx/UI/SetupWizard/Pages/Fw/SetupFirmwarePageContext.cs +++ b/src/Ryujinx/UI/SetupWizard/Pages/Fw/SetupFirmwarePageContext.cs @@ -71,7 +71,7 @@ namespace Ryujinx.Ava.UI.SetupWizard.Pages } } - public override Control CreateHelpContent() + public override object CreateHelpContent() { Grid grid = new() { diff --git a/src/Ryujinx/UI/SetupWizard/Pages/Keys/SetupKeysPageContext.cs b/src/Ryujinx/UI/SetupWizard/Pages/Keys/SetupKeysPageContext.cs index 60f2957e4..35af2efa7 100644 --- a/src/Ryujinx/UI/SetupWizard/Pages/Keys/SetupKeysPageContext.cs +++ b/src/Ryujinx/UI/SetupWizard/Pages/Keys/SetupKeysPageContext.cs @@ -26,7 +26,7 @@ namespace Ryujinx.Ava.UI.SetupWizard.Pages ? Result.Fail : InstallKeys(KeysFolderPath); - public override Control CreateHelpContent() + public override object CreateHelpContent() { Grid grid = new() { diff --git a/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.Steps.cs b/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.Steps.cs new file mode 100644 index 000000000..9f5637baa --- /dev/null +++ b/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.Steps.cs @@ -0,0 +1,57 @@ +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.SetupWizard.Pages; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.SetupWizard +{ + public partial class RyujinxSetupWizard + { + private async ValueTask SetupKeys() + { + if (_overwrite || !RyujinxApp.MainWindow.VirtualFileSystem.HasKeySet) + { + Retry: + bool result = await NextPage() + .WithTitle(LocaleKeys.SetupWizardKeysPageTitle) + .WithContent(out SetupKeysPageContext keyContext) + .Show(); + + if (!result) + return false; + + if (!keyContext.CompleteStep()) + goto Retry; + } + + return true; + } + + private async ValueTask SetupFirmware() + { + if (_overwrite || !HasFirmware) + { + if (!RyujinxApp.MainWindow.VirtualFileSystem.HasKeySet) + { + NotificationManager.Error("Keys still seem to not be installed. Please try again."); + return false; + } + + Retry: + bool result = await NextPage() + .WithTitle(LocaleKeys.SetupWizardFirmwarePageTitle) + .WithContent(out SetupFirmwarePageContext fwContext) + .Show(); + + if (!result) + return false; + + if (!fwContext.CompleteStep()) + goto Retry; + + OnPropertyChanged(nameof(HasFirmware)); + } + + return true; + } + } +} diff --git a/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.cs b/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.cs index 79433295e..8843b0f18 100644 --- a/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.cs +++ b/src/Ryujinx/UI/SetupWizard/RyujinxSetupWizard.cs @@ -3,24 +3,46 @@ using Avalonia.Controls.Notifications; using Avalonia.Media.Imaging; using Avalonia.Styling; using Avalonia.Threading; -using Gommon; +using CommunityToolkit.Mvvm.ComponentModel; +using Ryujinx.Ava.Common; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Systems.Configuration; using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.SetupWizard.Pages; +using Ryujinx.Ava.UI.ViewModels; using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace Ryujinx.Ava.UI.SetupWizard { - public class RyujinxSetupWizard : IDisposable, INotifyPropertyChanged + public partial class RyujinxSetupWizard : BaseModel, IDisposable { private bool _configWasModified; - public bool HasFirmware => RyujinxApp.MainWindow.ContentManager.GetCurrentFirmwareVersion() != null; + private readonly RyujinxSetupWizardWindow _window; + private readonly bool _overwrite; + + public RyujinxSetupWizard(RyujinxSetupWizardWindow wizardWindow, bool overwriteMode) + { + _window = wizardWindow; + _overwrite = overwriteMode; + + if (Program.PreviewerDetached) + { + UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle); + RyujinxApp.ThemeChanged += Ryujinx_ThemeChanged; + } + } + + private SetupWizardPage FirstPage() => new(_window.WizardPresenter, this, isFirstPage: true); + + private SetupWizardPage NextPage() => new(_window.WizardPresenter, this); + + public void SignalConfigModified() + { + _configWasModified = true; + } + + public static bool HasFirmware => RyujinxApp.MainWindow.ContentManager.GetCurrentFirmwareVersion() != null; public RyujinxNotificationManager NotificationManager { get; private set; } @@ -40,7 +62,7 @@ namespace Ryujinx.Ava.UI.SetupWizard .WithTitle(LocaleKeys.SetupWizardFirstPageTitle) .WithContent(LocaleKeys.SetupWizardFirstPageContent) .WithActionContent(LocaleKeys.SetupWizardFirstPageAction) - .Show(); + .Show(); // result is unhandled as the first page cannot display anything other than the next button. // back does not need to be handled @@ -61,99 +83,22 @@ namespace Ryujinx.Ava.UI.SetupWizard RyujinxSetupWizardWindow.IsOpen = false; } - public Bitmap DiscordLogo - { - get; - set => SetField(ref field, value); - } + #region Discord logo stuff + + [ObservableProperty] public partial Bitmap DiscordLogo { get; set; } private void Ryujinx_ThemeChanged() { - Dispatcher.UIThread.Post(() => UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value)); + Dispatcher.UIThread.Post(() => UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle)); } - private const string LogoPathFormat = "resm:Ryujinx.Assets.UIImages.Logo_{0}_{1}.png?assembly=Ryujinx"; - private void UpdateLogoTheme(string theme) { bool isDarkTheme = theme == "Dark" || (theme == "Auto" && RyujinxApp.DetectSystemTheme() == ThemeVariant.Dark); - string themeName = isDarkTheme ? "Dark" : "Light"; - - DiscordLogo = LoadBitmap(LogoPathFormat.Format("Discord", themeName)); - } - - private static Bitmap LoadBitmap(string uri) => new(Avalonia.Platform.AssetLoader.Open(new Uri(uri))); - - private async ValueTask SetupKeys() - { - if (_overwrite || !RyujinxApp.MainWindow.VirtualFileSystem.HasKeySet) - { - Retry: - bool result = await NextPage() - .WithTitle(LocaleKeys.SetupWizardKeysPageTitle) - .WithContent(out SetupKeysPageContext keyContext) - .Show(); - - if (!result) - return false; - - if (!keyContext.CompleteStep()) - goto Retry; - } - - return true; - } - - private async ValueTask SetupFirmware() - { - if (_overwrite || !HasFirmware) - { - if (!RyujinxApp.MainWindow.VirtualFileSystem.HasKeySet) - { - NotificationManager.Error("Keys still seem to not be installed. Please try again."); - return false; - } - - Retry: - bool result = await NextPage() - .WithTitle(LocaleKeys.SetupWizardFirmwarePageTitle) - .WithContent(out SetupFirmwarePageContext fwContext) - .Show(); - - if (!result) - return false; - - if (!fwContext.CompleteStep()) - goto Retry; - } - - return true; - } - - private SetupWizardPage FirstPage() => new(_window.WizardPresenter, this, isFirstPage: true); - - private SetupWizardPage NextPage() => new(_window.WizardPresenter, this); - - public void SignalConfigModified() - { - _configWasModified = true; - } - - private readonly RyujinxSetupWizardWindow _window; - private readonly bool _overwrite; - - public RyujinxSetupWizard(RyujinxSetupWizardWindow wizardWindow, bool overwriteMode) - { - _window = wizardWindow; - _overwrite = overwriteMode; - - if (Program.PreviewerDetached) - { - UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle); - RyujinxApp.ThemeChanged += Ryujinx_ThemeChanged; - } + DiscordLogo = EmbeddedAvaloniaResources + .GetIconByNameAndTheme("Discord", isDarkTheme); } public void Dispose() @@ -165,19 +110,6 @@ namespace Ryujinx.Ava.UI.SetupWizard GC.SuppressFinalize(this); } - public event PropertyChangedEventHandler PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null) - { - if (EqualityComparer.Default.Equals(field, value)) return false; - field = value; - OnPropertyChanged(propertyName); - return true; - } + #endregion } } diff --git a/src/Ryujinx/UI/SetupWizard/SetupWizardPage.Builder.cs b/src/Ryujinx/UI/SetupWizard/SetupWizardPage.Builder.cs index 6b3a09fa1..30d84e554 100644 --- a/src/Ryujinx/UI/SetupWizard/SetupWizardPage.Builder.cs +++ b/src/Ryujinx/UI/SetupWizard/SetupWizardPage.Builder.cs @@ -1,5 +1,7 @@ using Avalonia; using Avalonia.Controls; +using Avalonia.Layout; +using Avalonia.Media; using Gommon; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.UI.Controls; @@ -35,6 +37,23 @@ namespace Ryujinx.Ava.UI.SetupWizard public SetupWizardPage WithHelpContent(object? content) { + if (content is string str) + { + TextBlock tb = new() + { + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + TextAlignment = TextAlignment.Center, + TextWrapping = TextWrapping.Wrap, + FontSize = 20.0, + Text = str + }; + + tb.Classes.Add("h1"); + + content = tb; + } + HelpContent = content; HasHelpContent = content != null; return this; diff --git a/src/Ryujinx/UI/SetupWizard/SetupWizardPageContext.cs b/src/Ryujinx/UI/SetupWizard/SetupWizardPageContext.cs index 147f995bf..605270761 100644 --- a/src/Ryujinx/UI/SetupWizard/SetupWizardPageContext.cs +++ b/src/Ryujinx/UI/SetupWizard/SetupWizardPageContext.cs @@ -1,4 +1,3 @@ -using Avalonia.Controls; using Gommon; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.ViewModels; @@ -10,8 +9,9 @@ namespace Ryujinx.Ava.UI.SetupWizard public RyujinxNotificationManager NotificationManager { get; init; } public abstract Result CompleteStep(); - - public virtual Control CreateHelpContent() +#nullable enable + public virtual object? CreateHelpContent() +#nullable disable { return null; } diff --git a/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs index 47a99d886..d30c619aa 100644 --- a/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs @@ -3,6 +3,7 @@ using Avalonia.Styling; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using Gommon; +using Ryujinx.Ava.Common; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Systems.Configuration; using System; @@ -36,21 +37,15 @@ namespace Ryujinx.Ava.UI.ViewModels Dispatcher.UIThread.Post(() => UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value)); } - private const string LogoPathFormat = "resm:Ryujinx.Assets.UIImages.Logo_{0}_{1}.png?assembly=Ryujinx"; - private void UpdateLogoTheme(string theme) { bool isDarkTheme = theme == "Dark" || (theme == "Auto" && RyujinxApp.DetectSystemTheme() == ThemeVariant.Dark); - string themeName = isDarkTheme ? "Dark" : "Light"; - - DiscordLogo = LoadBitmap(LogoPathFormat.Format("Discord", themeName)); - GitLabLogo = LoadBitmap(LogoPathFormat.Format("GitLab", themeName)); + DiscordLogo = EmbeddedAvaloniaResources.GetIconByNameAndTheme("Discord", isDarkTheme); + GitLabLogo = EmbeddedAvaloniaResources.GetIconByNameAndTheme("GitLab", isDarkTheme); } - private static Bitmap LoadBitmap(string uri) => new(Avalonia.Platform.AssetLoader.Open(new Uri(uri))); - public void Dispose() { RyujinxApp.ThemeChanged -= Ryujinx_ThemeChanged;