mirror of
https://git.743378673.xyz/MeloNX/MeloNX.git
synced 2025-07-24 15:37:10 +02:00
Ava GUI: User Profile Manager + Other Fixes (#4166)
* Fix redundancies * Add back elses * Loading Screen fixes * Redesign User Profile Manager - Backported long selection bar in Grid/List view not working - Backported UserSelector is jank * Fix SelectionIndicator * Fix DataType * Fix SaveManager bug * Remove debug log * Load saves on UIThread * Reduce UI thread blocking * Fix locale keys * Use block namespaces * Fix close button width * Make UserProfile ordering consistent * Alphabetical order * Adjust layout, remove green circle for blue selector * Fix some inconsistencies * Fix no inital selected profile * Adjust appearance of edit button * Adjust SaveManager * Remove redundant warning dialog * Make firmware avatar selector clearer * View redesign again :hero_depressed: * Consistency adjustments * Adjust margins * Make `UserProfileImageSelector` consistent * Make `UserFirmwareAvatarSelector` consistent * Fix long grid view selector * Switch case * Remove long selection bar Handled in #4178 * Consistency * Started dialog titles * Fixes * Remaining titles * Update Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml Co-authored-by: Mary-nyan <thog@protonmail.com> * Fix build * Hide UserRecoverer if no LostProfiles are found * UserEditor Avatar Placeholder * Watermark + locale adjustment * Border radius * Remove unnecessary styles * Fix firmware avatar image order * Cleanup `ColorPickerButton` * Make `UserId` copy/paste able * Make `FirmwareAvatarSelector` 6 images wide * Make selection bar better * Unsaved changes dialogue * Fix indentation * Remove extra check * Address suggestions * Reorganise - Remove unused views - Rename views to match convention - Fix weird namespacing * Update Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml Co-authored-by: Ac_K <Acoustik666@gmail.com> * UserRecovererView empty placeholder * Update Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/Models/UserProfile.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Remove AddModel * Update Ryujinx.Ava/Assets/Locales/en_US.json Co-authored-by: Ac_K <Acoustik666@gmail.com> * Fix bug Co-authored-by: Mary-nyan <thog@protonmail.com> Co-authored-by: Ac_K <Acoustik666@gmail.com>
This commit is contained in:
parent
cee667b491
commit
934b5a64e5
49 changed files with 1787 additions and 1170 deletions
123
Ryujinx.Ava/UI/Views/User/UserEditorView.axaml
Normal file
123
Ryujinx.Ava/UI/Views/User/UserEditorView.axaml
Normal file
|
@ -0,0 +1,123 @@
|
|||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Views.User.UserEditorView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||
Margin="0"
|
||||
MinWidth="500"
|
||||
Padding="0"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="models:TempProfile">
|
||||
<UserControl.Resources>
|
||||
<helpers: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
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<TextBlock Text="{locale:Locale UserProfilesName}" />
|
||||
<TextBox
|
||||
Name="NameBox"
|
||||
Width="300"
|
||||
HorizontalAlignment="Stretch"
|
||||
MaxLength="{Binding MaxProfileNameLength}"
|
||||
Watermark="{locale:Locale ProfileNameSelectionWatermark}"
|
||||
Text="{Binding Name}" />
|
||||
<TextBlock Name="IdText" Text="{locale:Locale UserProfilesUserId}" />
|
||||
<TextBox
|
||||
Name="IdLabel"
|
||||
Width="300"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding UserIdString}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Stretch"
|
||||
Orientation="Vertical">
|
||||
<Border
|
||||
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||
BorderThickness="1">
|
||||
<Panel>
|
||||
<ui:SymbolIcon
|
||||
FontSize="60"
|
||||
Width="96"
|
||||
Height="96"
|
||||
Margin="0"
|
||||
Foreground="{DynamicResource AppListHoverBackgroundColor}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Symbol="Camera" />
|
||||
<Image
|
||||
Name="ProfileImage"
|
||||
Width="96"
|
||||
Height="96"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Source="{Binding Image, Converter={StaticResource ByteImage}}" />
|
||||
</Panel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
HorizontalAlignment="Left"
|
||||
Orientation="Horizontal"
|
||||
Margin="0 24 0 0"
|
||||
Spacing="10">
|
||||
<Button
|
||||
Width="50"
|
||||
MinWidth="50"
|
||||
Click="BackButton_Click">
|
||||
<ui:SymbolIcon Symbol="Back" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal"
|
||||
Margin="0 24 0 0"
|
||||
Spacing="10">
|
||||
<Button
|
||||
Name="DeleteButton"
|
||||
Click="DeleteButton_Click"
|
||||
Content="{locale:Locale UserProfilesDelete}" />
|
||||
<Button
|
||||
Name="ChangePictureButton"
|
||||
Click="ChangePictureButton_Click"
|
||||
Content="{locale:Locale UserProfilesChangeProfileImage}" />
|
||||
<Button
|
||||
Name="AddPictureButton"
|
||||
Click="ChangePictureButton_Click"
|
||||
Content="{locale:Locale UserProfilesSetProfileImage}" />
|
||||
<Button
|
||||
Name="SaveButton"
|
||||
Click="SaveButton_Click"
|
||||
Content="{locale:Locale Save}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
165
Ryujinx.Ava/UI/Views/User/UserEditorView.axaml.cs
Normal file
165
Ryujinx.Ava/UI/Views/User/UserEditorView.axaml.cs
Normal file
|
@ -0,0 +1,165 @@
|
|||
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.Controls;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System;
|
||||
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.User
|
||||
{
|
||||
public partial class UserEditorView : UserControl
|
||||
{
|
||||
private NavigationDialogHost _parent;
|
||||
private UserProfile _profile;
|
||||
private bool _isNewUser;
|
||||
|
||||
public TempProfile TempProfile { get; set; }
|
||||
public uint MaxProfileNameLength => 0x20;
|
||||
public bool IsDeletable => _profile.UserId != AccountManager.DefaultUserId;
|
||||
|
||||
public UserEditorView()
|
||||
{
|
||||
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;
|
||||
_profile = args.profile;
|
||||
TempProfile = new TempProfile(_profile);
|
||||
|
||||
_parent = args.parent;
|
||||
break;
|
||||
}
|
||||
|
||||
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - " +
|
||||
$"{ (_isNewUser ? LocaleManager.Instance[LocaleKeys.UserEditorTitleCreate] : LocaleManager.Instance[LocaleKeys.UserEditorTitle])}";
|
||||
|
||||
DataContext = TempProfile;
|
||||
|
||||
AddPictureButton.IsVisible = _isNewUser;
|
||||
ChangePictureButton.IsVisible = !_isNewUser;
|
||||
IdLabel.IsVisible = _profile != null;
|
||||
IdText.IsVisible = _profile != null;
|
||||
if (!_isNewUser && IsDeletable)
|
||||
{
|
||||
DeleteButton.IsVisible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
DeleteButton.IsVisible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void BackButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isNewUser)
|
||||
{
|
||||
if (TempProfile.Name != String.Empty || TempProfile.Image != null)
|
||||
{
|
||||
if (await ContentDialogHelper.CreateChoiceDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesSubMessage]))
|
||||
{
|
||||
_parent?.GoBack();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_parent?.GoBack();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_profile.Name != TempProfile.Name || _profile.Image != TempProfile.Image)
|
||||
{
|
||||
if (await ContentDialogHelper.CreateChoiceDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesSubMessage]))
|
||||
{
|
||||
_parent?.GoBack();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_parent?.GoBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_parent.DeleteUser(_profile);
|
||||
}
|
||||
|
||||
private void SaveButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DataValidationErrors.ClearErrors(NameBox);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(TempProfile.Name))
|
||||
{
|
||||
DataValidationErrors.SetError(NameBox, new DataValidationException(LocaleManager.Instance[LocaleKeys.UserProfileEmptyNameError]));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (TempProfile.Image == null)
|
||||
{
|
||||
_parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, TempProfile));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_profile != null && !_isNewUser)
|
||||
{
|
||||
_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, TempProfile.UserId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_parent?.GoBack();
|
||||
}
|
||||
|
||||
public void SelectProfileImage()
|
||||
{
|
||||
_parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, TempProfile));
|
||||
}
|
||||
|
||||
private void ChangePictureButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_profile != null || _isNewUser)
|
||||
{
|
||||
SelectProfileImage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
114
Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml
Normal file
114
Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml
Normal file
|
@ -0,0 +1,114 @@
|
|||
<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:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Width="528"
|
||||
d:DesignWidth="578"
|
||||
d:DesignHeight="350"
|
||||
x:Class="Ryujinx.Ava.UI.Views.User.UserFirmwareAvatarSelectorView"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:UserFirmwareAvatarSelectorViewModel"
|
||||
Focusable="True">
|
||||
<Design.DataContext>
|
||||
<viewModels:UserFirmwareAvatarSelectorViewModel />
|
||||
</Design.DataContext>
|
||||
<UserControl.Resources>
|
||||
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
</UserControl.Resources>
|
||||
<Grid
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox
|
||||
Grid.Row="1"
|
||||
BorderThickness="0"
|
||||
SelectedIndex="{Binding SelectedIndex}"
|
||||
Height="400"
|
||||
Items="{Binding Images}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel
|
||||
Orientation="Horizontal"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="Width" Value="85" />
|
||||
<Setter Property="MaxWidth" Value="85" />
|
||||
<Setter Property="MinWidth" Value="85" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem /template/ Border#SelectionIndicator">
|
||||
<Setter Property="MinHeight" Value="70" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Panel
|
||||
Background="{Binding BackgroundColor}"
|
||||
Margin="5">
|
||||
<Image Source="{Binding Data, Converter={StaticResource ByteImage}}" />
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<StackPanel
|
||||
Grid.Row="3"
|
||||
Orientation="Horizontal"
|
||||
Spacing="10"
|
||||
Margin="0 24 0 0"
|
||||
HorizontalAlignment="Left">
|
||||
<Button
|
||||
Width="50"
|
||||
MinWidth="50"
|
||||
Height="35"
|
||||
Click="GoBack">
|
||||
<ui:SymbolIcon Symbol="Back" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Row="3"
|
||||
Orientation="Horizontal"
|
||||
Spacing="10"
|
||||
Margin="0 24 0 0"
|
||||
HorizontalAlignment="Right">
|
||||
<ui:ColorPickerButton
|
||||
FlyoutPlacement="Top"
|
||||
IsMoreButtonVisible="False"
|
||||
UseColorPalette="False"
|
||||
UseColorTriangle="False"
|
||||
UseColorWheel="False"
|
||||
ShowAcceptDismissButtons="False"
|
||||
IsAlphaEnabled="False"
|
||||
Color="{Binding BackgroundColor, Mode=TwoWay}"
|
||||
Name="ColorButton">
|
||||
<ui:ColorPickerButton.Styles>
|
||||
<Style Selector="Grid#Root > DockPanel > Grid">
|
||||
<Setter Property="IsVisible" Value="False" />
|
||||
</Style>
|
||||
</ui:ColorPickerButton.Styles>
|
||||
</ui:ColorPickerButton>
|
||||
<Button
|
||||
Content="{locale:Locale AvatarChoose}"
|
||||
Height="35"
|
||||
Name="ChooseButton"
|
||||
Click="ChooseButton_OnClick" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -0,0 +1,88 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using FluentAvalonia.UI.Navigation;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats.Png;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.User
|
||||
{
|
||||
public partial class UserFirmwareAvatarSelectorView : UserControl
|
||||
{
|
||||
private NavigationDialogHost _parent;
|
||||
private TempProfile _profile;
|
||||
|
||||
public UserFirmwareAvatarSelectorView(ContentManager contentManager)
|
||||
{
|
||||
ContentManager = contentManager;
|
||||
|
||||
DataContext = ViewModel;
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public UserFirmwareAvatarSelectorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
AddHandler(Frame.NavigatedToEvent, (s, e) =>
|
||||
{
|
||||
NavigatedTo(e);
|
||||
}, RoutingStrategies.Direct);
|
||||
}
|
||||
|
||||
private void NavigatedTo(NavigationEventArgs arg)
|
||||
{
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
if (arg.NavigationMode == NavigationMode.New)
|
||||
{
|
||||
(_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter;
|
||||
ContentManager = _parent.ContentManager;
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
ViewModel = new UserFirmwareAvatarSelectorViewModel();
|
||||
}
|
||||
|
||||
DataContext = ViewModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ContentManager ContentManager { get; private set; }
|
||||
|
||||
internal UserFirmwareAvatarSelectorViewModel ViewModel { get; set; }
|
||||
|
||||
private void GoBack(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_parent.GoBack();
|
||||
}
|
||||
|
||||
private void ChooseButton_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ViewModel.SelectedImage != null)
|
||||
{
|
||||
MemoryStream streamJpg = new();
|
||||
SixLabors.ImageSharp.Image avatarImage = SixLabors.ImageSharp.Image.Load(ViewModel.SelectedImage, new PngDecoder());
|
||||
|
||||
avatarImage.Mutate(x => x.BackgroundColor(new Rgba32(
|
||||
ViewModel.BackgroundColor.R,
|
||||
ViewModel.BackgroundColor.G,
|
||||
ViewModel.BackgroundColor.B,
|
||||
ViewModel.BackgroundColor.A)));
|
||||
avatarImage.SaveAsJpeg(streamJpg);
|
||||
|
||||
_profile.Image = streamJpg.ToArray();
|
||||
|
||||
_parent.GoBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
63
Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml
Normal file
63
Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml
Normal file
|
@ -0,0 +1,63 @@
|
|||
<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"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:viewModles="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
Focusable="True"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Ryujinx.Ava.UI.Views.User.UserProfileImageSelectorView"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModles:UserProfileImageSelectorViewModel"
|
||||
Width="500"
|
||||
d:DesignWidth="500">
|
||||
<Design.DataContext>
|
||||
<viewModles:UserProfileImageSelectorViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="70" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
TextWrapping="Wrap"
|
||||
HorizontalAlignment="Left"
|
||||
TextAlignment="Left"
|
||||
Text="{locale:Locale ProfileImageSelectionNote}" />
|
||||
<StackPanel
|
||||
Grid.Row="2"
|
||||
Spacing="10"
|
||||
HorizontalAlignment="Left"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Width="50"
|
||||
MinWidth="50"
|
||||
Click="GoBack">
|
||||
<ui:SymbolIcon Symbol="Back" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Row="2"
|
||||
Spacing="10"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Name="Import"
|
||||
Click="Import_OnClick">
|
||||
<TextBlock Text="{locale:Locale ProfileImageSelectionImportImage}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="SelectFirmwareImage"
|
||||
IsEnabled="{Binding FirmwareFound}"
|
||||
Click="SelectFirmwareImage_OnClick">
|
||||
<TextBlock Text="{locale:Locale ProfileImageSelectionSelectAvatar}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
124
Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml.cs
Normal file
124
Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml.cs
Normal file
|
@ -0,0 +1,124 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.VisualTree;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using FluentAvalonia.UI.Navigation;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System.IO;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.User
|
||||
{
|
||||
public partial class UserProfileImageSelectorView : UserControl
|
||||
{
|
||||
private ContentManager _contentManager;
|
||||
private NavigationDialogHost _parent;
|
||||
private TempProfile _profile;
|
||||
|
||||
internal UserProfileImageSelectorViewModel ViewModel { get; private set; }
|
||||
|
||||
public UserProfileImageSelectorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
AddHandler(Frame.NavigatedToEvent, (s, e) =>
|
||||
{
|
||||
NavigatedTo(e);
|
||||
}, RoutingStrategies.Direct);
|
||||
}
|
||||
|
||||
private void NavigatedTo(NavigationEventArgs arg)
|
||||
{
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
switch (arg.NavigationMode)
|
||||
{
|
||||
case NavigationMode.New:
|
||||
(_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter;
|
||||
_contentManager = _parent.ContentManager;
|
||||
|
||||
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {LocaleManager.Instance[LocaleKeys.ProfileImageSelectionHeader]}";
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
DataContext = ViewModel = new UserProfileImageSelectorViewModel();
|
||||
ViewModel.FirmwareFound = _contentManager.GetCurrentFirmwareVersion() != null;
|
||||
}
|
||||
|
||||
break;
|
||||
case NavigationMode.Back:
|
||||
if (_profile.Image != null)
|
||||
{
|
||||
_parent.GoBack();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void Import_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OpenFileDialog dialog = new();
|
||||
dialog.Filters.Add(new FileDialogFilter
|
||||
{
|
||||
Name = LocaleManager.Instance[LocaleKeys.AllSupportedFormats],
|
||||
Extensions = { "jpg", "jpeg", "png", "bmp" }
|
||||
});
|
||||
dialog.Filters.Add(new FileDialogFilter { Name = "JPEG", Extensions = { "jpg", "jpeg" } });
|
||||
dialog.Filters.Add(new FileDialogFilter { Name = "PNG", Extensions = { "png" } });
|
||||
dialog.Filters.Add(new FileDialogFilter { Name = "BMP", Extensions = { "bmp" } });
|
||||
|
||||
dialog.AllowMultiple = false;
|
||||
|
||||
string[] image = await dialog.ShowAsync(((TopLevel)_parent.GetVisualRoot()) as Window);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
if (image.Length > 0)
|
||||
{
|
||||
string imageFile = image[0];
|
||||
|
||||
_profile.Image = ProcessProfileImage(File.ReadAllBytes(imageFile));
|
||||
|
||||
if (_profile.Image != null)
|
||||
{
|
||||
_parent.GoBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GoBack(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_parent.GoBack();
|
||||
}
|
||||
|
||||
private void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ViewModel.FirmwareFound)
|
||||
{
|
||||
_parent.Navigate(typeof(UserFirmwareAvatarSelectorView), (_parent, _profile));
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] ProcessProfileImage(byte[] buffer)
|
||||
{
|
||||
using (Image image = Image.Load(buffer))
|
||||
{
|
||||
image.Mutate(x => x.Resize(256, 256));
|
||||
|
||||
using (MemoryStream streamJpg = new())
|
||||
{
|
||||
image.SaveAsJpeg(streamJpg);
|
||||
|
||||
return streamJpg.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
83
Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml
Normal file
83
Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml
Normal file
|
@ -0,0 +1,83 @@
|
|||
<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"
|
||||
d:DesignWidth="550"
|
||||
d:DesignHeight="450"
|
||||
Width="500"
|
||||
Height="400"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
x:Class="Ryujinx.Ava.UI.Views.User.UserRecovererView"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:UserProfileViewModel"
|
||||
Focusable="True">
|
||||
<Design.DataContext>
|
||||
<viewModels:UserProfileViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Border
|
||||
CornerRadius="5"
|
||||
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||
BorderThickness="1"
|
||||
Grid.Row="0">
|
||||
<Panel>
|
||||
<ListBox
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Items="{Binding LostProfiles}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5">
|
||||
<Grid Margin="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding UserId}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
<Button Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
Click="Recover"
|
||||
CommandParameter="{Binding}"
|
||||
Content="{locale:Locale Recover}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<TextBlock
|
||||
IsVisible="{Binding IsEmpty}"
|
||||
TextAlignment="Center"
|
||||
Text="{locale:Locale UserProfilesRecoverEmptyList}"/>
|
||||
</Panel>
|
||||
</Border>
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Margin="0 24 0 0"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Width="50"
|
||||
MinWidth="50"
|
||||
Click="GoBack">
|
||||
<ui:SymbolIcon Symbol="Back"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
51
Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml.cs
Normal file
51
Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml.cs
Normal file
|
@ -0,0 +1,51 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using FluentAvalonia.UI.Navigation;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.User
|
||||
{
|
||||
public partial class UserRecovererView : UserControl
|
||||
{
|
||||
private NavigationDialogHost _parent;
|
||||
|
||||
public UserRecovererView()
|
||||
{
|
||||
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 parent = (NavigationDialogHost)arg.Parameter;
|
||||
|
||||
_parent = parent;
|
||||
|
||||
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {LocaleManager.Instance[LocaleKeys.UserProfilesRecoverHeading]}";
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GoBack(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_parent?.GoBack();
|
||||
}
|
||||
|
||||
private void Recover(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_parent?.RecoverLostAccounts();
|
||||
}
|
||||
}
|
||||
}
|
199
Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml
Normal file
199
Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml
Normal file
|
@ -0,0 +1,199 @@
|
|||
<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"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="600"
|
||||
d:DesignHeight="500"
|
||||
Height="450"
|
||||
Width="550"
|
||||
x:Class="Ryujinx.Ava.UI.Views.User.UserSaveManagerView"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:UserSaveManagerViewModel"
|
||||
Focusable="True">
|
||||
<Design.DataContext>
|
||||
<viewModels:UserSaveManagerViewModel />
|
||||
</Design.DataContext>
|
||||
<UserControl.Resources>
|
||||
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid
|
||||
Grid.Row="0"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel
|
||||
Spacing="10"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center">
|
||||
<Label Content="{locale:Locale CommonSort}" VerticalAlignment="Center" />
|
||||
<ComboBox SelectedIndex="{Binding SortIndex}" Width="100">
|
||||
<ComboBoxItem>
|
||||
<Label
|
||||
VerticalAlignment="Center"
|
||||
HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale Name}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<Label
|
||||
VerticalAlignment="Center"
|
||||
HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale Size}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
<ComboBox SelectedIndex="{Binding OrderIndex}" Width="150">
|
||||
<ComboBoxItem>
|
||||
<Label
|
||||
VerticalAlignment="Center"
|
||||
HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale OrderAscending}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<Label
|
||||
VerticalAlignment="Center"
|
||||
HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale OrderDescending}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<Grid
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="10,0, 0, 0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Content="{locale:Locale Search}" VerticalAlignment="Center" />
|
||||
<TextBox
|
||||
Margin="5,0,0,0"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding Search}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Margin="0,5"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||
CornerRadius="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<ListBox
|
||||
Name="SaveList"
|
||||
Items="{Binding Views}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="10" />
|
||||
<Setter Property="Margin" Value="5" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:SaveModel">
|
||||
<Grid HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel
|
||||
Grid.Column="0"
|
||||
Orientation="Horizontal"
|
||||
Spacing="5">
|
||||
<Border
|
||||
Height="42"
|
||||
Width="42"
|
||||
Padding="10"
|
||||
IsVisible="{Binding !InGameList}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Help"
|
||||
FontSize="30"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<Image
|
||||
IsVisible="{Binding InGameList}"
|
||||
Width="42"
|
||||
Height="42"
|
||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||
<TextBlock
|
||||
MaxLines="3"
|
||||
Width="320"
|
||||
Margin="5"
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding Title}"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
Spacing="10"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Label
|
||||
Content="{Binding SizeString}"
|
||||
IsVisible="{Binding SizeAvailable}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right" />
|
||||
<Button
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Padding="10"
|
||||
MinWidth="0"
|
||||
MinHeight="0"
|
||||
Name="OpenLocation"
|
||||
Click="OpenLocation">
|
||||
<ui:SymbolIcon
|
||||
Symbol="OpenFolder"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Button>
|
||||
<Button
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Padding="10"
|
||||
MinWidth="0"
|
||||
MinHeight="0"
|
||||
Name="Delete"
|
||||
Click="Delete">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Delete"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
<StackPanel
|
||||
Grid.Row="2"
|
||||
Margin="0 24 0 0"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Width="50"
|
||||
MinWidth="50"
|
||||
Click="GoBack">
|
||||
<ui:SymbolIcon Symbol="Back" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
148
Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml.cs
Normal file
148
Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml.cs
Normal file
|
@ -0,0 +1,148 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using FluentAvalonia.UI.Navigation;
|
||||
using LibHac;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Shim;
|
||||
using Ryujinx.Ava.Common;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using UserId = LibHac.Fs.UserId;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.User
|
||||
{
|
||||
public partial class UserSaveManagerView : UserControl
|
||||
{
|
||||
internal UserSaveManagerViewModel ViewModel { get; private set; }
|
||||
|
||||
private AccountManager _accountManager;
|
||||
private HorizonClient _horizonClient;
|
||||
private VirtualFileSystem _virtualFileSystem;
|
||||
private NavigationDialogHost _parent;
|
||||
|
||||
public UserSaveManagerView()
|
||||
{
|
||||
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, AccountManager accountManager, HorizonClient client, VirtualFileSystem virtualFileSystem))arg.Parameter;
|
||||
_accountManager = args.accountManager;
|
||||
_horizonClient = args.client;
|
||||
_virtualFileSystem = args.virtualFileSystem;
|
||||
|
||||
_parent = args.parent;
|
||||
break;
|
||||
}
|
||||
|
||||
DataContext = ViewModel = new UserSaveManagerViewModel(_accountManager);
|
||||
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {ViewModel.SaveManagerHeading}";
|
||||
|
||||
Task.Run(LoadSaves);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadSaves()
|
||||
{
|
||||
ViewModel.Saves.Clear();
|
||||
var saves = new ObservableCollection<SaveModel>();
|
||||
var saveDataFilter = SaveDataFilter.Make(
|
||||
programId: default,
|
||||
saveType: SaveDataType.Account,
|
||||
new UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low),
|
||||
saveDataId: default,
|
||||
index: default);
|
||||
|
||||
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
|
||||
|
||||
_horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
|
||||
|
||||
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
||||
|
||||
while (true)
|
||||
{
|
||||
saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure();
|
||||
|
||||
if (readCount == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < readCount; i++)
|
||||
{
|
||||
var save = saveDataInfo[i];
|
||||
if (save.ProgramId.Value != 0)
|
||||
{
|
||||
var saveModel = new SaveModel(save, _horizonClient, _virtualFileSystem);
|
||||
saves.Add(saveModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
ViewModel.Saves = saves;
|
||||
ViewModel.Sort();
|
||||
});
|
||||
}
|
||||
|
||||
private void GoBack(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_parent?.GoBack();
|
||||
}
|
||||
|
||||
private void OpenLocation(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Avalonia.Controls.Button button)
|
||||
{
|
||||
if (button.DataContext is SaveModel saveModel)
|
||||
{
|
||||
ApplicationHelper.OpenSaveDir(saveModel.SaveId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void Delete(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Avalonia.Controls.Button button)
|
||||
{
|
||||
if (button.DataContext is SaveModel saveModel)
|
||||
{
|
||||
var result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DeleteUserSave],
|
||||
LocaleManager.Instance[LocaleKeys.IrreversibleActionNote],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogNo], "");
|
||||
|
||||
if (result == UserResult.Yes)
|
||||
{
|
||||
_horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, saveModel.SaveId);
|
||||
}
|
||||
|
||||
ViewModel.Saves.Remove(saveModel);
|
||||
ViewModel.Views.Remove(saveModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
165
Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml
Normal file
165
Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml
Normal file
|
@ -0,0 +1,165 @@
|
|||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Views.User.UserSelectorViews"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
d:DesignHeight="450"
|
||||
MinWidth="500"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:UserProfileViewModel">
|
||||
<UserControl.Resources>
|
||||
<helpers: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>
|
||||
<Border
|
||||
CornerRadius="5"
|
||||
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||
BorderThickness="1">
|
||||
<ListBox
|
||||
MaxHeight="300"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
SelectionChanged="ProfilesList_SelectionChanged"
|
||||
Background="Transparent"
|
||||
Items="{Binding Profiles}">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<flex:FlexPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
AlignContent="FlexStart"
|
||||
JustifyContent="FlexStart" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Margin" Value="5 5 0 5" />
|
||||
<Setter Property="CornerRadius" Value="5" />
|
||||
</Style>
|
||||
<Style Selector="Border#SelectionIndicator">
|
||||
<Setter Property="Opacity" Value="0" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.DataTemplates>
|
||||
<DataTemplate
|
||||
DataType="models:UserProfile">
|
||||
<Grid
|
||||
PointerEnter="Grid_PointerEntered"
|
||||
PointerLeave="Grid_OnPointerExited">
|
||||
<Border
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5"
|
||||
Background="{Binding BackgroundColor}">
|
||||
<StackPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Image
|
||||
Width="96"
|
||||
Height="96"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Source="{Binding Image, Converter={StaticResource ByteImage}}" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
MaxWidth="90"
|
||||
Text="{Binding Name}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
MaxLines="2"
|
||||
Margin="5" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border
|
||||
Margin="2"
|
||||
Height="24"
|
||||
Width="24"
|
||||
CornerRadius="12"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Background="{DynamicResource ThemeContentBackgroundColor}"
|
||||
IsVisible="{Binding IsPointerOver}">
|
||||
<Button
|
||||
MaxHeight="24"
|
||||
MaxWidth="24"
|
||||
MinHeight="24"
|
||||
MinWidth="24"
|
||||
CornerRadius="12"
|
||||
Padding="0"
|
||||
Click="EditUser">
|
||||
<ui:SymbolIcon Symbol="Edit" />
|
||||
</Button>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate
|
||||
DataType="viewModels:BaseModel">
|
||||
<Panel
|
||||
Height="118"
|
||||
Width="96">
|
||||
<Button
|
||||
MinWidth="50"
|
||||
MinHeight="50"
|
||||
MaxWidth="50"
|
||||
MaxHeight="50"
|
||||
CornerRadius="25"
|
||||
Margin="10"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Click="AddUser">
|
||||
<ui:SymbolIcon Symbol="Add" />
|
||||
</Button>
|
||||
<Panel.Styles>
|
||||
<Style Selector="Panel">
|
||||
<Setter Property="Background" Value="{DynamicResource ListBoxBackground}"/>
|
||||
</Style>
|
||||
</Panel.Styles>
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</ListBox.DataTemplates>
|
||||
</ListBox>
|
||||
</Border>
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Margin="0 24 0 0"
|
||||
HorizontalAlignment="Left"
|
||||
Orientation="Horizontal"
|
||||
Spacing="10">
|
||||
<Button
|
||||
Click="ManageSaves"
|
||||
Content="{locale:Locale UserProfilesManageSaves}" />
|
||||
<Button
|
||||
Click="RecoverLostAccounts"
|
||||
Content="{locale:Locale UserProfilesRecoverLostAccounts}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Margin="0 24 0 0"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Click="Close"
|
||||
Content="{locale:Locale UserProfilesClose}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
128
Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml.cs
Normal file
128
Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml.cs
Normal file
|
@ -0,0 +1,128 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using FluentAvalonia.UI.Navigation;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.User
|
||||
{
|
||||
public partial class UserSelectorViews : UserControl
|
||||
{
|
||||
private NavigationDialogHost _parent;
|
||||
|
||||
public UserProfileViewModel ViewModel { get; set; }
|
||||
|
||||
public UserSelectorViews()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
AddHandler(Frame.NavigatedToEvent, (s, e) =>
|
||||
{
|
||||
NavigatedTo(e);
|
||||
}, RoutingStrategies.Direct);
|
||||
}
|
||||
}
|
||||
|
||||
private void NavigatedTo(NavigationEventArgs arg)
|
||||
{
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
if (arg.NavigationMode == NavigationMode.New)
|
||||
{
|
||||
_parent = (NavigationDialogHost)arg.Parameter;
|
||||
ViewModel = _parent.ViewModel;
|
||||
}
|
||||
|
||||
if (arg.NavigationMode == NavigationMode.Back)
|
||||
{
|
||||
((ContentDialog)_parent.Parent).Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle];
|
||||
}
|
||||
|
||||
DataContext = ViewModel;
|
||||
}
|
||||
}
|
||||
|
||||
private void Grid_PointerEntered(object sender, PointerEventArgs e)
|
||||
{
|
||||
if (sender is Grid grid)
|
||||
{
|
||||
if (grid.DataContext is UserProfile profile)
|
||||
{
|
||||
profile.IsPointerOver = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Grid_OnPointerExited(object sender, PointerEventArgs e)
|
||||
{
|
||||
if (sender is Grid grid)
|
||||
{
|
||||
if (grid.DataContext is UserProfile profile)
|
||||
{
|
||||
profile.IsPointerOver = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProfilesList_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (sender is ListBox listBox)
|
||||
{
|
||||
int selectedIndex = listBox.SelectedIndex;
|
||||
|
||||
if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
|
||||
{
|
||||
if (ViewModel.Profiles[selectedIndex] is UserProfile userProfile)
|
||||
{
|
||||
_parent?.AccountManager?.OpenUser(userProfile.UserId);
|
||||
|
||||
foreach (BaseModel profile in ViewModel.Profiles)
|
||||
{
|
||||
if (profile is UserProfile uProfile)
|
||||
{
|
||||
uProfile.UpdateState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddUser(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_parent.AddUser();
|
||||
}
|
||||
|
||||
private void EditUser(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Avalonia.Controls.Button button)
|
||||
{
|
||||
if (button.DataContext is UserProfile userProfile)
|
||||
{
|
||||
_parent.EditUser(userProfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ManageSaves(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_parent.ManageSaves();
|
||||
}
|
||||
|
||||
private void RecoverLostAccounts(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_parent.RecoverLostAccounts();
|
||||
}
|
||||
|
||||
private void Close(object sender, RoutedEventArgs e)
|
||||
{
|
||||
((ContentDialog)_parent.Parent).Hide();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue