Initial work on a setup wizard

Setup wizard abstraction & architecture from TKMM
This commit is contained in:
GreemDev
2025-11-21 00:20:15 -06:00
parent 66f339d265
commit b033adbde7
19 changed files with 743 additions and 10 deletions

View File

@@ -0,0 +1,31 @@
using Avalonia.Controls.Presenters;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Systems.SetupWizard;
using System.Threading.Tasks;
namespace Ryujinx.Ava.Systems.SetupWizard
{
public abstract class BaseSetupWizard(ContentPresenter presenter)
{
/// <summary>
/// Define the logic and flow of this <see cref="BaseSetupWizard"/>.
/// </summary>
public abstract ValueTask Start();
protected ValueTask<bool> FirstPage()
{
SetupWizardPageBuilder builder = new(presenter, isFirstPage: true);
return builder
.WithTitle(LocaleKeys.SetupWizardFirstPageTitle)
.WithContent(LocaleKeys.SetupWizardFirstPageContent)
.WithActionContent(LocaleKeys.SetupWizardFirstPageAction)
.Show();
}
protected SetupWizardPageBuilder NextPage()
{
return new SetupWizardPageBuilder(presenter);
}
}
}

View File

@@ -0,0 +1,3 @@
# Ryubing Setup Wizard
Directly modified from the code found [here](https://github.com/TKMM-Team/Tkmm/tree/master/src/Tkmm/Wizard).

View File

@@ -0,0 +1,62 @@
using Avalonia.Controls.Presenters;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Systems.SetupWizard;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.Ava.Systems.SetupWizard
{
public partial class SetupWizardPage(bool isFirstPage = false) : BaseModel
{
protected bool? _result;
protected readonly CancellationTokenSource _cancellationTokenSource = new();
public bool IsFirstPage { get; } = isFirstPage;
[ObservableProperty]
private string? _title;
[ObservableProperty]
private object? _content;
[ObservableProperty]
private object? _helpContent;
[ObservableProperty]
private object? _actionContent = LocaleManager.Instance[LocaleKeys.SetupWizardActionNext];
[RelayCommand]
private void MoveBack()
{
_result = false;
_cancellationTokenSource.Cancel();
}
[RelayCommand]
private void MoveNext()
{
_result = true;
_cancellationTokenSource.Cancel();
}
public async ValueTask<bool> Show(ContentPresenter presenter)
{
presenter.Content = new SetupWizardPageView
{
DataContext = this,
};
try {
await Task.Delay(-1, _cancellationTokenSource.Token);
}
catch (TaskCanceledException) {
return _result ?? false;
}
return false;
}
}
}

View File

@@ -0,0 +1,69 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Systems.SetupWizard;
using System.Threading.Tasks;
namespace Ryujinx.Systems.SetupWizard
{
public class SetupWizardPageBuilder(ContentPresenter presenter, bool isFirstPage = false)
{
private readonly SetupWizardPage _page = new(isFirstPage);
public SetupWizardPage Build()
{
return _page;
}
public SetupWizardPageBuilder WithTitle(LocaleKeys title) => WithTitle(LocaleManager.Instance[title]);
public SetupWizardPageBuilder WithTitle(string title)
{
_page.Title = title;
return this;
}
public SetupWizardPageBuilder WithContent(LocaleKeys content) => WithContent(LocaleManager.Instance[content]);
public SetupWizardPageBuilder WithContent(object? content)
{
if (content is StyledElement { Parent: ContentControl parent }) {
parent.Content = null;
}
_page.Content = content;
return this;
}
public SetupWizardPageBuilder WithHelpContent(LocaleKeys content) => WithHelpContent(LocaleManager.Instance[content]);
public SetupWizardPageBuilder WithHelpContent(object? content)
{
_page.HelpContent = content;
return this;
}
public SetupWizardPageBuilder WithContent<TControl>(object? context = null) where TControl : Control, new()
{
_page.Content = new TControl {
DataContext = context
};
return this;
}
public SetupWizardPageBuilder WithActionContent(LocaleKeys content) => WithActionContent(LocaleManager.Instance[content]);
public SetupWizardPageBuilder WithActionContent(object? content)
{
_page.ActionContent = content;
return this;
}
public ValueTask<bool> Show()
{
return _page.Show(presenter);
}
}
}

View File

@@ -0,0 +1,77 @@
<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:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:fa="using:Projektanker.Icons.Avalonia"
xmlns:wiz="using:Ryujinx.Ava.Systems.SetupWizard"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="wiz:SetupWizardPage"
x:Class="Ryujinx.Systems.SetupWizard.SetupWizardPageView">
<Grid RowDefinitions="*,Auto" Margin="60">
<ScrollViewer>
<Grid RowDefinitions="Auto,*,Auto">
<TextBlock Grid.Row="0"
TextWrapping="WrapWithOverflow"
FontFamily="{StaticResource HyliaGlyph}"
FontSize="46"
Text="{Binding Title}" />
<ContentPresenter Grid.Row="1"
Content="{Binding}"
IsVisible="{Binding !#InfoToggle.IsChecked}"
TextWrapping="WrapWithOverflow"
Margin="0,15,0,0">
<ContentPresenter.DataTemplates>
<DataTemplate DataType="{x:Type wiz:SetupWizardPage}">
<ContentControl Content="{Binding Content}" VerticalAlignment="Stretch"/>
</DataTemplate>
</ContentPresenter.DataTemplates>
</ContentPresenter>
<Grid Grid.Row="2"
ColumnDefinitions="Auto,*"
IsVisible="{Binding #InfoToggle.IsChecked}">
<StackPanel Spacing="5" VerticalAlignment="Top">
<HyperlinkButton NavigateUri="https://discord.gg/PEuzjrFXUA" Content="Join Discord" />
</StackPanel>
<ContentPresenter Content="{Binding}"
Grid.Column="1"
Margin="20,0,0,0"
TextWrapping="WrapWithOverflow">
<ContentPresenter.DataTemplates>
<DataTemplate DataType="{x:Type wiz:SetupWizardPage}">
<ContentControl Content="{Binding HelpContent}" />
</DataTemplate>
</ContentPresenter.DataTemplates>
</ContentPresenter>
</Grid>
</Grid>
</ScrollViewer>
<Grid ColumnDefinitions="Auto,Auto,*" Grid.Row="1">
<ToggleButton Name="InfoToggle"
Padding="6">
<fa:Icon Value="fa-solid fa-circle-info" />
</ToggleButton>
<Button IsVisible="{Binding !IsFirstPage}"
Grid.Column="1"
Content="{ext:Locale SetupWizardActionBack}"
Margin="10,0,0,0"
Command="{Binding MoveBackCommand}" />
<StackPanel Orientation="Horizontal">
</StackPanel>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Right"
Grid.Column="2">
<Button Content="{Binding ActionContent}"
Command="{Binding MoveNextCommand}" />
</StackPanel>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,17 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Ryujinx.Ava.Systems.SetupWizard;
using Ryujinx.Ava.UI.Controls;
namespace Ryujinx.Systems.SetupWizard
{
public partial class SetupWizardPageView : RyujinxControl<SetupWizardPage>
{
public SetupWizardPageView()
{
InitializeComponent();
}
}
}