aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Ava/Ui/Controls
diff options
context:
space:
mode:
authorEmmanuel Hansen <emmausssss@gmail.com>2022-07-24 17:38:38 +0000
committerGitHub <noreply@github.com>2022-07-24 14:38:38 -0300
commit6e02cac952f1a9a34d2777199dde657eba0784e6 (patch)
treebfdf6ebc5c5fb062910ffb2feaec56e1240c0596 /Ryujinx.Ava/Ui/Controls
parent3a3380fa2578bf1731c6cd7cebdca7b7cc5681b0 (diff)
Avalonia - Use content dialog for user profile manager (#3455)
* remove content dialog placeholder from all windows * remove redundant window argument * redesign user profile window * wip * use avalonia auto name generator * add edit and new user options * move profile image selection to content dialog * remove usings * fix updater * address review * adjust avatar dialog size * add validation for user editor * fix typo * Shorten some labels
Diffstat (limited to 'Ryujinx.Ava/Ui/Controls')
-rw-r--r--Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs127
-rw-r--r--Ryujinx.Ava/Ui/Controls/InputDialog.axaml.cs37
-rw-r--r--Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml10
-rw-r--r--Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml.cs85
-rw-r--r--Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml10
-rw-r--r--Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml.cs70
-rw-r--r--Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml.cs12
-rw-r--r--Ryujinx.Ava/Ui/Controls/UserEditor.axaml55
-rw-r--r--Ryujinx.Ava/Ui/Controls/UserEditor.axaml.cs123
-rw-r--r--Ryujinx.Ava/Ui/Controls/UserErrorDialog.cs2
-rw-r--r--Ryujinx.Ava/Ui/Controls/UserSelector.axaml90
-rw-r--r--Ryujinx.Ava/Ui/Controls/UserSelector.axaml.cs79
12 files changed, 542 insertions, 158 deletions
diff --git a/Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs b/Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs
index cdc5de93..15ecaa77 100644
--- a/Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs
+++ b/Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs
@@ -16,7 +16,6 @@ namespace Ryujinx.Ava.Ui.Controls
private static bool _isChoiceDialogOpen;
private async static Task<UserResult> ShowContentDialog(
- StyleableWindow window,
string title,
string primaryText,
string secondaryText,
@@ -28,35 +27,32 @@ namespace Ryujinx.Ava.Ui.Controls
{
UserResult result = UserResult.None;
- ContentDialog contentDialog = window.ContentDialog;
+ ContentDialog contentDialog = new ContentDialog();
await ShowDialog();
async Task ShowDialog()
{
- if (contentDialog != null)
- {
- contentDialog.Title = title;
- contentDialog.PrimaryButtonText = primaryButton;
- contentDialog.SecondaryButtonText = secondaryButton;
- contentDialog.CloseButtonText = closeButton;
- contentDialog.Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol);
+ contentDialog.Title = title;
+ contentDialog.PrimaryButtonText = primaryButton;
+ contentDialog.SecondaryButtonText = secondaryButton;
+ contentDialog.CloseButtonText = closeButton;
+ contentDialog.Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol);
- contentDialog.PrimaryButtonCommand = MiniCommand.Create(() =>
- {
- result = primaryButtonResult;
- });
- contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
- {
- result = UserResult.No;
- });
- contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
- {
- result = UserResult.Cancel;
- });
+ contentDialog.PrimaryButtonCommand = MiniCommand.Create(() =>
+ {
+ result = primaryButtonResult;
+ });
+ contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
+ {
+ result = UserResult.No;
+ });
+ contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
+ {
+ result = UserResult.Cancel;
+ });
- await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
- };
+ await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
}
return result;
@@ -78,35 +74,30 @@ namespace Ryujinx.Ava.Ui.Controls
UserResult result = UserResult.None;
- ContentDialog contentDialog = window.ContentDialog;
-
- Window overlay = window;
-
- if (contentDialog != null)
+ ContentDialog contentDialog = new ContentDialog
{
- contentDialog.PrimaryButtonClick += DeferClose;
- contentDialog.Title = title;
- contentDialog.PrimaryButtonText = primaryButton;
- contentDialog.SecondaryButtonText = secondaryButton;
- contentDialog.CloseButtonText = closeButton;
- contentDialog.Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol);
-
- contentDialog.PrimaryButtonCommand = MiniCommand.Create(() =>
+ Title = title,
+ PrimaryButtonText = primaryButton,
+ SecondaryButtonText = secondaryButton,
+ CloseButtonText = closeButton,
+ Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol),
+ PrimaryButtonCommand = MiniCommand.Create(() =>
{
result = primaryButton == LocaleManager.Instance["InputDialogYes"] ? UserResult.Yes : UserResult.Ok;
- });
- contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
- {
- contentDialog.PrimaryButtonClick -= DeferClose;
- result = UserResult.No;
- });
- contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
- {
- contentDialog.PrimaryButtonClick -= DeferClose;
- result = UserResult.Cancel;
- });
- await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
+ }),
};
+ contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
+ {
+ contentDialog.PrimaryButtonClick -= DeferClose;
+ result = UserResult.No;
+ });
+ contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
+ {
+ contentDialog.PrimaryButtonClick -= DeferClose;
+ result = UserResult.Cancel;
+ });
+ contentDialog.PrimaryButtonClick += DeferClose;
+ await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
return result;
@@ -141,7 +132,7 @@ namespace Ryujinx.Ava.Ui.Controls
if (doWhileDeferred != null)
{
- await doWhileDeferred(overlay);
+ await doWhileDeferred(window);
deferResetEvent.Set();
}
@@ -191,7 +182,6 @@ namespace Ryujinx.Ava.Ui.Controls
}
public static async Task<UserResult> CreateInfoDialog(
- StyleableWindow window,
string primary,
string secondaryText,
string acceptButton,
@@ -199,7 +189,6 @@ namespace Ryujinx.Ava.Ui.Controls
string title)
{
return await ShowContentDialog(
- window,
title,
primary,
secondaryText,
@@ -210,7 +199,6 @@ namespace Ryujinx.Ava.Ui.Controls
}
internal static async Task<UserResult> CreateConfirmationDialog(
- StyleableWindow window,
string primaryText,
string secondaryText,
string acceptButtonText,
@@ -219,7 +207,6 @@ namespace Ryujinx.Ava.Ui.Controls
UserResult primaryButtonResult = UserResult.Yes)
{
return await ShowContentDialog(
- window,
string.IsNullOrWhiteSpace(title) ? LocaleManager.Instance["DialogConfirmationTitle"] : title,
primaryText,
secondaryText,
@@ -235,10 +222,9 @@ namespace Ryujinx.Ava.Ui.Controls
return new(mainText, secondaryText);
}
- internal static async Task CreateUpdaterInfoDialog(StyleableWindow window, string primary, string secondaryText)
+ internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText)
{
await ShowContentDialog(
- window,
LocaleManager.Instance["DialogUpdaterTitle"],
primary,
secondaryText,
@@ -248,24 +234,9 @@ namespace Ryujinx.Ava.Ui.Controls
(int)Symbol.Important);
}
- internal static async Task ShowNotAvailableMessage(StyleableWindow window)
- {
- // Temporary placeholder for features to be added
- await ShowContentDialog(
- window,
- "Feature Not Available",
- "The selected feature is not available in this version.",
- "",
- "",
- "",
- LocaleManager.Instance["InputDialogOk"],
- (int)Symbol.Important);
- }
-
- internal static async Task CreateWarningDialog(StyleableWindow window, string primary, string secondaryText)
+ internal static async Task CreateWarningDialog(string primary, string secondaryText)
{
await ShowContentDialog(
- window,
LocaleManager.Instance["DialogWarningTitle"],
primary,
secondaryText,
@@ -275,12 +246,11 @@ namespace Ryujinx.Ava.Ui.Controls
(int)Symbol.Important);
}
- internal static async Task CreateErrorDialog(StyleableWindow owner, string errorMessage, string secondaryErrorMessage = "")
+ internal static async Task CreateErrorDialog(string errorMessage, string secondaryErrorMessage = "")
{
Logger.Error?.Print(LogClass.Application, errorMessage);
await ShowContentDialog(
- owner,
LocaleManager.Instance["DialogErrorTitle"],
LocaleManager.Instance["DialogErrorMessage"],
errorMessage,
@@ -290,7 +260,7 @@ namespace Ryujinx.Ava.Ui.Controls
(int)Symbol.Dismiss);
}
- internal static async Task<bool> CreateChoiceDialog(StyleableWindow window, string title, string primary, string secondaryText)
+ internal static async Task<bool> CreateChoiceDialog(string title, string primary, string secondaryText)
{
if (_isChoiceDialogOpen)
{
@@ -301,7 +271,6 @@ namespace Ryujinx.Ava.Ui.Controls
UserResult response =
await ShowContentDialog(
- window,
title,
primary,
secondaryText,
@@ -316,19 +285,17 @@ namespace Ryujinx.Ava.Ui.Controls
return response == UserResult.Yes;
}
- internal static async Task<bool> CreateExitDialog(StyleableWindow owner)
+ internal static async Task<bool> CreateExitDialog()
{
return await CreateChoiceDialog(
- owner,
LocaleManager.Instance["DialogExitTitle"],
LocaleManager.Instance["DialogExitMessage"],
LocaleManager.Instance["DialogExitSubMessage"]);
}
- internal static async Task<bool> CreateStopEmulationDialog(StyleableWindow owner)
+ internal static async Task<bool> CreateStopEmulationDialog()
{
return await CreateChoiceDialog(
- owner,
LocaleManager.Instance["DialogStopEmulationTitle"],
LocaleManager.Instance["DialogStopEmulationMessage"],
LocaleManager.Instance["DialogExitSubMessage"]);
@@ -338,12 +305,10 @@ namespace Ryujinx.Ava.Ui.Controls
string title,
string mainText,
string subText,
- StyleableWindow owner,
uint maxLength = int.MaxValue,
string input = "")
{
var result = await InputDialog.ShowInputDialog(
- owner,
title,
mainText,
input,
diff --git a/Ryujinx.Ava/Ui/Controls/InputDialog.axaml.cs b/Ryujinx.Ava/Ui/Controls/InputDialog.axaml.cs
index b9bbb66d..e4b37dec 100644
--- a/Ryujinx.Ava/Ui/Controls/InputDialog.axaml.cs
+++ b/Ryujinx.Ava/Ui/Controls/InputDialog.axaml.cs
@@ -8,7 +8,7 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Controls
{
- public class InputDialog : UserControl
+ public partial class InputDialog : UserControl
{
public string Message { get; set; }
public string Input { get; set; }
@@ -24,8 +24,6 @@ namespace Ryujinx.Ava.Ui.Controls
MaxLength = maxLength;
DataContext = this;
-
- InitializeComponent();
}
public InputDialog()
@@ -33,33 +31,26 @@ namespace Ryujinx.Ava.Ui.Controls
InitializeComponent();
}
- private void InitializeComponent()
+ public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, string message,
+ string input = "", string subMessage = "", uint maxLength = int.MaxValue)
{
- AvaloniaXamlLoader.Load(this);
- }
-
- public static async Task<(UserResult Result, string Input)> ShowInputDialog(StyleableWindow window, string title, string message, string input = "", string subMessage = "", uint maxLength = int.MaxValue)
- {
- ContentDialog contentDialog = window.ContentDialog;
-
UserResult result = UserResult.Cancel;
- InputDialog content = new InputDialog(message, input = "", subMessage = "", maxLength);
-
- if (contentDialog != null)
+ InputDialog content = new InputDialog(message, input, subMessage, maxLength);
+ ContentDialog contentDialog = new ContentDialog
{
- contentDialog.Title = title;
- contentDialog.PrimaryButtonText = LocaleManager.Instance["InputDialogOk"];
- contentDialog.SecondaryButtonText = "";
- contentDialog.CloseButtonText = LocaleManager.Instance["InputDialogCancel"];
- contentDialog.Content = content;
- contentDialog.PrimaryButtonCommand = MiniCommand.Create(() =>
+ Title = title,
+ PrimaryButtonText = LocaleManager.Instance["InputDialogOk"],
+ SecondaryButtonText = "",
+ CloseButtonText = LocaleManager.Instance["InputDialogCancel"],
+ Content = content,
+ PrimaryButtonCommand = MiniCommand.Create(() =>
{
result = UserResult.Ok;
input = content.Input;
- });
- await contentDialog.ShowAsync();
- }
+ })
+ };
+ await contentDialog.ShowAsync();
return (result, input);
}
diff --git a/Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml b/Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml
new file mode 100644
index 00000000..b0ab5d30
--- /dev/null
+++ b/Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml
@@ -0,0 +1,10 @@
+<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:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ x:Class="Ryujinx.Ava.Ui.Controls.NavigationDialogHost">
+ <ui:Frame HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
+ x:Name="ContentFrame" />
+</UserControl> \ No newline at end of file
diff --git a/Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml.cs b/Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml.cs
new file mode 100644
index 00000000..9ba631ad
--- /dev/null
+++ b/Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml.cs
@@ -0,0 +1,85 @@
+using Avalonia;
+using Avalonia.Controls;
+using FluentAvalonia.UI.Controls;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.Ui.ViewModels;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.HOS.Services.Account.Acc;
+using System;
+using System.Threading.Tasks;
+
+namespace Ryujinx.Ava.Ui.Controls
+{
+ public partial class NavigationDialogHost : UserControl
+ {
+ public AccountManager AccountManager { get; }
+ public ContentManager ContentManager { get; }
+ public UserProfileViewModel ViewModel { get; set; }
+
+ public NavigationDialogHost()
+ {
+ InitializeComponent();
+ }
+
+ public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager,
+ VirtualFileSystem virtualFileSystem)
+ {
+ AccountManager = accountManager;
+ ContentManager = contentManager;
+ ViewModel = new UserProfileViewModel(this);
+
+
+ if (contentManager.GetCurrentFirmwareVersion() != null)
+ {
+ Task.Run(() =>
+ {
+ AvatarProfileViewModel.PreloadAvatars(contentManager, virtualFileSystem);
+ });
+ }
+ InitializeComponent();
+ }
+
+ public void GoBack(object parameter = null)
+ {
+ if (ContentFrame.BackStack.Count > 0)
+ {
+ ContentFrame.GoBack();
+ }
+
+ ViewModel.LoadProfiles();
+ }
+
+ public void Navigate(Type sourcePageType, object parameter)
+ {
+ ContentFrame.Navigate(sourcePageType, parameter);
+ }
+
+ public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager, VirtualFileSystem ownerVirtualFileSystem)
+ {
+ var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem);
+ ContentDialog contentDialog = new ContentDialog
+ {
+ Title = LocaleManager.Instance["UserProfileWindowTitle"],
+ PrimaryButtonText = "",
+ SecondaryButtonText = "",
+ CloseButtonText = LocaleManager.Instance["UserProfilesClose"],
+ Content = content,
+ Padding = new Thickness(0)
+ };
+
+ contentDialog.Closed += (sender, args) =>
+ {
+ content.ViewModel.Dispose();
+ };
+
+ await contentDialog.ShowAsync();
+ }
+
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+
+ Navigate(typeof(UserSelector), this);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml b/Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml
index c6f43f43..750edf8b 100644
--- a/Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml
+++ b/Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml
@@ -1,14 +1,10 @@
-<Window xmlns="https://github.com/avaloniaui"
+<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"
mc:Ignorable="d"
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
- x:Class="Ryujinx.Ava.Ui.Controls.ProfileImageSelectionDialog"
- SizeToContent="WidthAndHeight"
- WindowStartupLocation="CenterOwner"
- Title="{Locale:Locale ProfileImageSelectionTitle}"
- CanResize="false">
+ x:Class="Ryujinx.Ava.Ui.Controls.ProfileImageSelectionDialog">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5,10,5, 5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@@ -32,4 +28,4 @@
</Button>
</StackPanel>
</Grid>
-</Window> \ No newline at end of file
+</UserControl> \ No newline at end of file
diff --git a/Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml.cs b/Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml.cs
index 728b8906..5d361af9 100644
--- a/Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml.cs
+++ b/Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml.cs
@@ -1,8 +1,10 @@
-using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
-using Avalonia.Markup.Xaml;
+using Avalonia.VisualTree;
+using FluentAvalonia.UI.Controls;
+using FluentAvalonia.UI.Navigation;
using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.HLE.FileSystem;
using SixLabors.ImageSharp;
@@ -12,36 +14,40 @@ using Image = SixLabors.ImageSharp.Image;
namespace Ryujinx.Ava.Ui.Controls
{
- public class ProfileImageSelectionDialog : StyleableWindow
+ public partial class ProfileImageSelectionDialog : UserControl
{
- private readonly ContentManager _contentManager;
+ private ContentManager _contentManager;
+ private NavigationDialogHost _parent;
+ private TempProfile _profile;
public bool FirmwareFound => _contentManager.GetCurrentFirmwareVersion() != null;
- public byte[] BufferImageProfile { get; set; }
-
- public ProfileImageSelectionDialog(ContentManager contentManager)
- {
- _contentManager = contentManager;
- DataContext = this;
- InitializeComponent();
-#if DEBUG
- this.AttachDevTools();
-#endif
- }
-
public ProfileImageSelectionDialog()
{
- DataContext = this;
InitializeComponent();
-#if DEBUG
- this.AttachDevTools();
-#endif
+ AddHandler(Frame.NavigatedToEvent, (s, e) =>
+ {
+ NavigatedTo(e);
+ }, RoutingStrategies.Direct);
}
- private void InitializeComponent()
+ private void NavigatedTo(NavigationEventArgs arg)
{
- AvaloniaXamlLoader.Load(this);
+ if (Program.PreviewerDetached)
+ {
+ switch (arg.NavigationMode)
+ {
+ case NavigationMode.New:
+ (_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter;
+ _contentManager = _parent.ContentManager;
+ break;
+ case NavigationMode.Back:
+ _parent.GoBack();
+ break;
+ }
+
+ DataContext = this;
+ }
}
private async void Import_OnClick(object sender, RoutedEventArgs e)
@@ -58,7 +64,7 @@ namespace Ryujinx.Ava.Ui.Controls
dialog.AllowMultiple = false;
- string[] image = await dialog.ShowAsync(this);
+ string[] image = await dialog.ShowAsync(((TopLevel)_parent.GetVisualRoot()) as Window);
if (image != null)
{
@@ -66,28 +72,22 @@ namespace Ryujinx.Ava.Ui.Controls
{
string imageFile = image[0];
- ProcessProfileImage(File.ReadAllBytes(imageFile));
+ _profile.Image = ProcessProfileImage(File.ReadAllBytes(imageFile));
}
- Close();
+ _parent.GoBack();
}
}
- private async void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e)
+ private void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e)
{
if (FirmwareFound)
{
- AvatarWindow window = new(_contentManager);
-
- await window.ShowDialog(this);
-
- BufferImageProfile = window.SelectedImage;
-
- Close();
+ _parent.Navigate(typeof(AvatarWindow), (_parent, _profile));
}
}
- private void ProcessProfileImage(byte[] buffer)
+ private static byte[] ProcessProfileImage(byte[] buffer)
{
using (Image image = Image.Load(buffer))
{
@@ -97,7 +97,7 @@ namespace Ryujinx.Ava.Ui.Controls
{
image.SaveAsJpeg(streamJpg);
- BufferImageProfile = streamJpg.ToArray();
+ return streamJpg.ToArray();
}
}
}
diff --git a/Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml.cs b/Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml.cs
index e4108ba4..eff15c7b 100644
--- a/Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml.cs
+++ b/Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml.cs
@@ -5,7 +5,7 @@ using Ryujinx.Ava.Ui.Windows;
namespace Ryujinx.Ava.Ui.Controls
{
- public class UpdateWaitWindow : StyleableWindow
+ public partial class UpdateWaitWindow : StyleableWindow
{
public UpdateWaitWindow(string primaryText, string secondaryText) : this()
{
@@ -21,15 +21,5 @@ namespace Ryujinx.Ava.Ui.Controls
this.AttachDevTools();
#endif
}
-
- public TextBlock PrimaryText { get; private set; }
- public TextBlock SecondaryText { get; private set; }
-
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- PrimaryText = this.FindControl<TextBlock>("PrimaryText");
- SecondaryText = this.FindControl<TextBlock>("SecondaryText");
- }
}
} \ No newline at end of file
diff --git a/Ryujinx.Ava/Ui/Controls/UserEditor.axaml b/Ryujinx.Ava/Ui/Controls/UserEditor.axaml
new file mode 100644
index 00000000..fed5f8cb
--- /dev/null
+++ b/Ryujinx.Ava/Ui/Controls/UserEditor.axaml
@@ -0,0 +1,55 @@
+<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"
+ mc:Ignorable="d"
+ Padding="0"
+ Margin="0"
+ xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
+ xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
+ xmlns:models="clr-namespace:Ryujinx.Ava.Ui.Models"
+ xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
+ xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+ x:Class="Ryujinx.Ava.Ui.Controls.UserEditor">
+ <UserControl.Resources>
+ <controls:BitmapArrayValueConverter x:Key="ByteImage" />
+ </UserControl.Resources>
+ <Grid Margin="0">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="Auto"/>
+ <ColumnDefinition/>
+ </Grid.ColumnDefinitions>
+ <Grid.RowDefinitions>
+ <RowDefinition Height="*"/>
+ <RowDefinition Height="Auto"/>
+ </Grid.RowDefinitions>
+ <StackPanel Orientation="Vertical" VerticalAlignment="Stretch" HorizontalAlignment="Left">
+ <Image
+ Margin="0"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Top"
+ Height="96" Width="96"
+ Name="ProfileImage"
+ Source="{Binding Image, Converter={StaticResource ByteImage}}" />
+ <Button Margin="5" Content="{Locale:Locale UserProfilesChangeProfileImage}"
+ Name="ChangePictureButton"
+ Click="ChangePictureButton_Click"
+ HorizontalAlignment="Stretch"/>
+ <Button Margin="5" Content="{Locale:Locale UserProfilesSetProfileImage}"
+ Name="AddPictureButton"
+ Click="ChangePictureButton_Click"
+ HorizontalAlignment="Stretch"/>
+ </StackPanel>
+ <StackPanel Grid.Row="0" Orientation="Vertical" HorizontalAlignment="Stretch" Grid.Column="1" Spacing="10"
+ Margin="5, 10">
+ <TextBox Name="NameBox" Width="300" Text="{Binding Name}" MaxLength="{Binding MaxProfileNameLength}"
+ HorizontalAlignment="Stretch" />
+ <TextBlock Text="{Binding UserId}" Name="IdLabel" />
+ </StackPanel>
+ <StackPanel Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right">
+ <Button Content="{Locale:Locale Save}" Name="SaveButton" Click="SaveButton_Click"/>
+ <Button HorizontalAlignment="Right" Content="{Locale:Locale Discard}"
+ Name="CloseButton" Click="CloseButton_Click"/>
+ </StackPanel>
+ </Grid>
+</UserControl>
diff --git a/Ryujinx.Ava/Ui/Controls/UserEditor.axaml.cs b/Ryujinx.Ava/Ui/Controls/UserEditor.axaml.cs
new file mode 100644
index 00000000..1bc1b325
--- /dev/null
+++ b/Ryujinx.Ava/Ui/Controls/UserEditor.axaml.cs
@@ -0,0 +1,123 @@
+using Avalonia.Controls;
+using Avalonia.Data;
+using Avalonia.Interactivity;
+using FluentAvalonia.UI.Controls;
+using FluentAvalonia.UI.Navigation;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.Ui.Models;
+using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
+
+namespace Ryujinx.Ava.Ui.Controls
+{
+ public partial class UserEditor : UserControl
+ {
+ private NavigationDialogHost _parent;
+ private UserProfile _profile;
+ private bool _isNewUser;
+
+ public TempProfile TempProfile { get; set; }
+ public uint MaxProfileNameLength => 0x20;
+
+ public UserEditor()
+ {
+ InitializeComponent();
+ AddHandler(Frame.NavigatedToEvent, (s, e) =>
+ {
+ NavigatedTo(e);
+ }, RoutingStrategies.Direct);
+ }
+
+ private void NavigatedTo(NavigationEventArgs arg)
+ {
+ if (Program.PreviewerDetached)
+ {
+ switch (arg.NavigationMode)
+ {
+ case NavigationMode.New:
+ var args = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
+ _isNewUser = args.isNewUser;
+ if (!_isNewUser)
+ {
+ _profile = args.profile;
+ TempProfile = new TempProfile(_profile);
+ }
+ else
+ {
+ TempProfile = new TempProfile();
+ }
+
+ _parent = args.parent;
+ break;
+ }
+
+ DataContext = TempProfile;
+
+ AddPictureButton.IsVisible = _isNewUser;
+ IdLabel.IsVisible = !_isNewUser;
+ ChangePictureButton.IsVisible = !_isNewUser;
+ }
+ }
+
+ private void CloseButton_Click(object sender, RoutedEventArgs e)
+ {
+ _parent?.GoBack();
+ }
+
+ private void SaveButton_Click(object sender, RoutedEventArgs e)
+ {
+ DataValidationErrors.ClearErrors(NameBox);
+ bool isInvalid = false;
+
+ if (string.IsNullOrWhiteSpace(TempProfile.Name))
+ {
+ DataValidationErrors.SetError(NameBox, new DataValidationException(LocaleManager.Instance["UserProfileEmptyNameError"]));
+
+ isInvalid = true;
+ }
+
+ if (TempProfile.Image == null)
+ {
+ ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["UserProfileNoImageError"], "");
+
+ isInvalid = true;
+ }
+
+ if(isInvalid)
+ {
+ return;
+ }
+
+ if (_profile != null)
+ {
+ _profile.Name = TempProfile.Name;
+ _profile.Image = TempProfile.Image;
+ _profile.UpdateState();
+ _parent.AccountManager.SetUserName(_profile.UserId, _profile.Name);
+ _parent.AccountManager.SetUserImage(_profile.UserId, _profile.Image);
+ }
+ else if (_isNewUser)
+ {
+ _parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image);
+ }
+ else
+ {
+ return;
+ }
+
+ _parent?.GoBack();
+ }
+
+ public void SelectProfileImage()
+ {
+ _parent.Navigate(typeof(ProfileImageSelectionDialog), (_parent, TempProfile));
+ }
+
+ private void ChangePictureButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (_profile != null || _isNewUser)
+ {
+ SelectProfileImage();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Ava/Ui/Controls/UserErrorDialog.cs b/Ryujinx.Ava/Ui/Controls/UserErrorDialog.cs
index 1f8f68e3..0b2d2c11 100644
--- a/Ryujinx.Ava/Ui/Controls/UserErrorDialog.cs
+++ b/Ryujinx.Ava/Ui/Controls/UserErrorDialog.cs
@@ -75,7 +75,7 @@ namespace Ryujinx.Ava.Ui.Controls
string setupButtonLabel = isInSetupGuide ? LocaleManager.Instance["OpenSetupGuideMessage"] : "";
- var result = await ContentDialogHelper.CreateInfoDialog(owner,
+ var result = await ContentDialogHelper.CreateInfoDialog(
string.Format(LocaleManager.Instance["DialogUserErrorDialogMessage"], errorCode, GetErrorTitle(error)),
GetErrorDescription(error) + (isInSetupGuide
? LocaleManager.Instance["DialogUserErrorDialogInfoMessage"]
diff --git a/Ryujinx.Ava/Ui/Controls/UserSelector.axaml b/Ryujinx.Ava/Ui/Controls/UserSelector.axaml
new file mode 100644
index 00000000..3bca7d7d
--- /dev/null
+++ b/Ryujinx.Ava/Ui/Controls/UserSelector.axaml
@@ -0,0 +1,90 @@
+<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:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
+ xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
+ xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
+ xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
+ xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ x:Class="Ryujinx.Ava.Ui.Controls.UserSelector">
+ <UserControl.Resources>
+ <controls:BitmapArrayValueConverter x:Key="ByteImage" />
+ </UserControl.Resources>
+ <Design.DataContext>
+ <viewModels:UserProfileViewModel />
+ </Design.DataContext>
+ <Grid HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch">
+ <Grid.RowDefinitions>
+ <RowDefinition />
+ <RowDefinition Height="Auto" />
+ </Grid.RowDefinitions>
+ <ListBox HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5" Items="{Binding Profiles}"
+ DoubleTapped="ProfilesList_DoubleTapped"
+ SelectionChanged="SelectingItemsControl_SelectionChanged">
+ <ListBox.ItemsPanel>
+ <ItemsPanelTemplate>
+ <flex:FlexPanel
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ AlignContent="FlexStart"
+ JustifyContent="Center" />
+ </ItemsPanelTemplate>
+ </ListBox.ItemsPanel>
+ <ListBox.ItemTemplate>
+ <DataTemplate>
+ <Grid>
+ <Border
+ Margin="2"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ ClipToBounds="True"
+ CornerRadius="5">
+ <Grid Margin="0">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ </Grid.RowDefinitions>
+ <Image
+ Grid.Row="0"
+ Margin="0"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Top"
+ Height="96" Width="96"
+ Source="{Binding Image, Converter={StaticResource ByteImage}}" />
+ <StackPanel
+ Grid.Row="1"
+ Height="30"
+ Margin="5"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch">
+ <TextBlock
+ HorizontalAlignment="Stretch"
+ Text="{Binding Name}"
+ TextAlignment="Center"
+ TextWrapping="Wrap" />
+ </StackPanel>
+ </Grid>
+ </Border>
+ <Border HorizontalAlignment="Left" VerticalAlignment="Top"
+ IsVisible="{Binding IsOpened}"
+ Background="LimeGreen"
+ Width="10"
+ Height="10"
+ Margin="5"
+ CornerRadius="5" />
+ </Grid>
+ </DataTemplate>
+ </ListBox.ItemTemplate>
+ </ListBox>
+ <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="10,0" Spacing="10" HorizontalAlignment="Center">
+ <Button Content="{Locale:Locale UserProfilesAddNewProfile}" Command="{Binding AddUser}" />
+ <Button IsEnabled="{Binding IsSelectedProfiledEditable}"
+ Content="{Locale:Locale UserProfilesEditProfile}" Command="{Binding EditUser}" />
+ <Button IsEnabled="{Binding IsSelectedProfileDeletable}"
+ Content="{Locale:Locale UserProfilesDeleteSelectedProfile}" Command="{Binding DeleteUser}" />
+ </StackPanel>
+ </Grid>
+</UserControl> \ No newline at end of file
diff --git a/Ryujinx.Ava/Ui/Controls/UserSelector.axaml.cs b/Ryujinx.Ava/Ui/Controls/UserSelector.axaml.cs
new file mode 100644
index 00000000..027351ee
--- /dev/null
+++ b/Ryujinx.Ava/Ui/Controls/UserSelector.axaml.cs
@@ -0,0 +1,79 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using FluentAvalonia.UI.Controls;
+using FluentAvalonia.UI.Navigation;
+using Ryujinx.Ava.Ui.ViewModels;
+using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
+
+namespace Ryujinx.Ava.Ui.Controls
+{
+ public partial class UserSelector : UserControl
+ {
+ private NavigationDialogHost _parent;
+ public UserProfileViewModel ViewModel { get; set; }
+
+ public UserSelector()
+ {
+ InitializeComponent();
+
+ if (Program.PreviewerDetached)
+ {
+ AddHandler(Frame.NavigatedToEvent, (s, e) =>
+ {
+ NavigatedTo(e);
+ }, Avalonia.Interactivity.RoutingStrategies.Direct);
+ }
+ }
+
+ private void NavigatedTo(NavigationEventArgs arg)
+ {
+ if (Program.PreviewerDetached)
+ {
+ switch (arg.NavigationMode)
+ {
+ case NavigationMode.New:
+ _parent = (NavigationDialogHost)arg.Parameter;
+ ViewModel = _parent.ViewModel;
+ break;
+ }
+
+ DataContext = ViewModel;
+ }
+ }
+
+ private void ProfilesList_DoubleTapped(object sender, RoutedEventArgs e)
+ {
+ if (sender is ListBox listBox)
+ {
+ int selectedIndex = listBox.SelectedIndex;
+
+ if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
+ {
+ ViewModel.SelectedProfile = ViewModel.Profiles[selectedIndex];
+
+ _parent?.AccountManager?.OpenUser(ViewModel.SelectedProfile.UserId);
+
+ ViewModel.LoadProfiles();
+
+ foreach (UserProfile profile in ViewModel.Profiles)
+ {
+ profile.UpdateState();
+ }
+ }
+ }
+ }
+
+ private void SelectingItemsControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (sender is ListBox listBox)
+ {
+ int selectedIndex = listBox.SelectedIndex;
+
+ if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
+ {
+ ViewModel.HighlightedProfile = ViewModel.Profiles[selectedIndex];
+ }
+ }
+ }
+ }
+} \ No newline at end of file