diff options
| author | Emmanuel Hansen <emmausssss@gmail.com> | 2022-07-24 17:38:38 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-07-24 14:38:38 -0300 |
| commit | 6e02cac952f1a9a34d2777199dde657eba0784e6 (patch) | |
| tree | bfdf6ebc5c5fb062910ffb2feaec56e1240c0596 /Ryujinx.Ava/Ui/Controls | |
| parent | 3a3380fa2578bf1731c6cd7cebdca7b7cc5681b0 (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.cs | 127 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/InputDialog.axaml.cs | 37 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml | 10 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml.cs | 85 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml | 10 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml.cs | 70 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml.cs | 12 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/UserEditor.axaml | 55 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/UserEditor.axaml.cs | 123 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/UserErrorDialog.cs | 2 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/UserSelector.axaml | 90 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/UserSelector.axaml.cs | 79 |
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 |
