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
This commit is contained in:
GreemDev 2025-11-29 02:17:23 -06:00
parent 3bec37e756
commit 9dcb71e120
11 changed files with 247 additions and 23 deletions

View file

@ -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": ""
}
}
]
}

View file

@ -0,0 +1,40 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:markup="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:pages="clr-namespace:Ryujinx.Ava.UI.SetupWizard.Pages"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="pages:SetupFinishedPageContext"
x:Class="Ryujinx.Ava.UI.SetupWizard.Pages.SetupFinishedPage">
<Grid
ColumnDefinitions="*"
RowDefinitions="*,Auto"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch">
<Border
Margin="15"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CornerRadius="5"
Background="{DynamicResource AppListBackgroundColor}">
<TextBlock Margin="15" Text="{markup:Locale SetupWizardFinalPageDescription}" TextAlignment="Center" TextWrapping="Wrap" />
</Border>
<Button Grid.Row="1"
VerticalAlignment="Bottom"
HorizontalAlignment="Center"
MinWidth="45"
MinHeight="32"
Padding="8"
Background="Transparent"
Click="Button_OnClick"
CornerRadius="5"
Tag="https://discord.gg/PEuzjrFXUA"
ToolTip.Tip="{markup:Locale AboutDiscordUrlTooltipMessage}">
<StackPanel Orientation="Horizontal" Spacing="5">
<Image Source="{Binding OwningWizard.DiscordLogo}" />
<TextBlock Text="Discord"/>
</StackPanel>
</Button>
</Grid>
</UserControl>

View file

@ -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<SetupFinishedPageContext>
{
public SetupFinishedPage()
{
InitializeComponent();
}
private void Button_OnClick(object sender, RoutedEventArgs e)
{
if (sender is Button { Tag: string url })
OpenHelper.OpenUrl(url);
}
}
}

View file

@ -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;
}
}

View file

@ -35,7 +35,8 @@ namespace Ryujinx.Ava.UI.SetupWizard
}
Retry:
bool result = await NextPage<SetupFirmwarePage, SetupFirmwarePageContext>(out SetupFirmwarePageContext fwContext)
bool result =
await NextPage<SetupFirmwarePage, SetupFirmwarePageContext>(out SetupFirmwarePageContext fwContext)
.Show();
if (!result)
@ -49,5 +50,10 @@ namespace Ryujinx.Ava.UI.SetupWizard
return true;
}
private ValueTask<bool> Finish()
=> NextPage<SetupFinishedPage, SetupFinishedPageContext>(out _)
.WithHelpButtonVisible(false)
.Show();
}
}

View file

@ -43,7 +43,8 @@ namespace Ryujinx.Ava.UI.SetupWizard
where TContext : SetupWizardPageContext, new()
=> NextPage()
.WithContent<TControl, TContext>(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);

View file

@ -68,7 +68,7 @@ namespace Ryujinx.Ava.UI.SetupWizard
where TControl : RyujinxControl<TContext>, 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;
}
}
}

View file

@ -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];

View file

@ -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<TControl, TContext>,
// 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;
}
}

View file

@ -70,8 +70,7 @@
</ScrollViewer>
<Grid ColumnDefinitions="Auto,Auto,*" Grid.Row="1">
<ToggleButton Name="InfoToggle"
Padding="6">
<ToggleButton Name="InfoToggle" Padding="6" IsVisible="{Binding ShowHelpButton}">
<fa:Icon Value="fa-solid fa-circle-info" />
</ToggleButton>

View file

@ -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<string>(ChangeWindowSize);
KeyDown += OnKeyDown;
KeyUp += OnKeyUp;
LocaleManager.Instance.LocaleChanged += OnLocaleChanged;
}
/// <summary>
/// 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 <see cref="IsShiftPressed"/> to a temp variable, sets <see cref="IsShiftPressed"/> to false (if it is true), then returns the temp variable.
/// </summary>
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<ApplicationControlProperty> 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)