diff options
Diffstat (limited to 'Ryujinx.Ava/Ui/Controls')
-rw-r--r-- | Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml.cs | 12 | ||||
-rw-r--r-- | Ryujinx.Ava/Ui/Controls/SaveManager.axaml | 102 | ||||
-rw-r--r-- | Ryujinx.Ava/Ui/Controls/SaveManager.axaml.cs | 160 | ||||
-rw-r--r-- | Ryujinx.Ava/Ui/Controls/UserEditor.axaml | 3 | ||||
-rw-r--r-- | Ryujinx.Ava/Ui/Controls/UserEditor.axaml.cs | 18 | ||||
-rw-r--r-- | Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml | 70 | ||||
-rw-r--r-- | Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml.cs | 44 | ||||
-rw-r--r-- | Ryujinx.Ava/Ui/Controls/UserSelector.axaml | 51 |
8 files changed, 437 insertions, 23 deletions
diff --git a/Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml.cs b/Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml.cs index 9ba631ad..ced88328 100644 --- a/Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml.cs +++ b/Ryujinx.Ava/Ui/Controls/NavigationDialogHost.axaml.cs @@ -1,6 +1,7 @@ using Avalonia; using Avalonia.Controls; using FluentAvalonia.UI.Controls; +using LibHac; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Ui.ViewModels; using Ryujinx.HLE.FileSystem; @@ -14,6 +15,8 @@ namespace Ryujinx.Ava.Ui.Controls { public AccountManager AccountManager { get; } public ContentManager ContentManager { get; } + public VirtualFileSystem VirtualFileSystem { get; } + public HorizonClient HorizonClient { get; } public UserProfileViewModel ViewModel { get; set; } public NavigationDialogHost() @@ -22,10 +25,12 @@ namespace Ryujinx.Ava.Ui.Controls } public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager, - VirtualFileSystem virtualFileSystem) + VirtualFileSystem virtualFileSystem, HorizonClient horizonClient) { AccountManager = accountManager; ContentManager = contentManager; + VirtualFileSystem = virtualFileSystem; + HorizonClient = horizonClient; ViewModel = new UserProfileViewModel(this); @@ -54,9 +59,10 @@ namespace Ryujinx.Ava.Ui.Controls ContentFrame.Navigate(sourcePageType, parameter); } - public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager, VirtualFileSystem ownerVirtualFileSystem) + public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager, + VirtualFileSystem ownerVirtualFileSystem, HorizonClient ownerHorizonClient) { - var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem); + var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient); ContentDialog contentDialog = new ContentDialog { Title = LocaleManager.Instance["UserProfileWindowTitle"], diff --git a/Ryujinx.Ava/Ui/Controls/SaveManager.axaml b/Ryujinx.Ava/Ui/Controls/SaveManager.axaml new file mode 100644 index 00000000..ce337c7b --- /dev/null +++ b/Ryujinx.Ava/Ui/Controls/SaveManager.axaml @@ -0,0 +1,102 @@ +<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:controls="clr-namespace:Ryujinx.Ava.Ui.Controls" + xmlns:models="clr-namespace:Ryujinx.Ava.Ui.Models" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + Height="400" + Width="550" + x:Class="Ryujinx.Ava.Ui.Controls.SaveManager"> + <UserControl.Resources> + <controls:BitmapArrayValueConverter x:Key="ByteImage" /> + </UserControl.Resources> + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition /> + </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 Descending}" /> + </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" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> + <ListBox Name="SaveList" Items="{Binding View}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> + <ListBox.ItemTemplate> + <DataTemplate x:DataType="models:SaveModel"> + <Grid HorizontalAlignment="Stretch" Margin="0,5"> + <Grid.ColumnDefinitions> + <ColumnDefinition /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <StackPanel Grid.Column="0" Orientation="Horizontal"> + <Border Height="42" Margin="2" Width="42" Padding="10" + IsVisible="{Binding !InGameList}"> + <ui:SymbolIcon Symbol="Help" FontSize="30" HorizontalAlignment="Center" + VerticalAlignment="Center" /> + </Border> + <Image IsVisible="{Binding InGameList}" + Margin="2" + 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" Command="{Binding OpenLocation}"> + <ui:SymbolIcon Symbol="OpenFolder" HorizontalAlignment="Center" + VerticalAlignment="Center" /> + </Button> + <Button VerticalAlignment="Center" HorizontalAlignment="Right" Padding="10" + MinWidth="0" MinHeight="0" Name="Delete" Command="{Binding Delete}"> + <ui:SymbolIcon Symbol="Delete" HorizontalAlignment="Center" + VerticalAlignment="Center" /> + </Button> + </StackPanel> + </Grid> + </DataTemplate> + </ListBox.ItemTemplate> + </ListBox> + </Border> + </Grid> +</UserControl>
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Controls/SaveManager.axaml.cs b/Ryujinx.Ava/Ui/Controls/SaveManager.axaml.cs new file mode 100644 index 00000000..499cd918 --- /dev/null +++ b/Ryujinx.Ava/Ui/Controls/SaveManager.axaml.cs @@ -0,0 +1,160 @@ +using Avalonia.Controls; +using DynamicData; +using DynamicData.Binding; +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.Models; +using Ryujinx.HLE.FileSystem; +using Ryujinx.Ui.App.Common; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile; + +namespace Ryujinx.Ava.Ui.Controls +{ + public partial class SaveManager : UserControl + { + private readonly UserProfile _userProfile; + private readonly HorizonClient _horizonClient; + private readonly VirtualFileSystem _virtualFileSystem; + private int _sortIndex; + private int _orderIndex; + private ObservableCollection<SaveModel> _view = new ObservableCollection<SaveModel>(); + private string _search; + + public ObservableCollection<SaveModel> Saves { get; set; } = new ObservableCollection<SaveModel>(); + + public ObservableCollection<SaveModel> View + { + get => _view; + set => _view = value; + } + + public int SortIndex + { + get => _sortIndex; + set + { + _sortIndex = value; + Sort(); + } + } + + public int OrderIndex + { + get => _orderIndex; + set + { + _orderIndex = value; + Sort(); + } + } + + public string Search + { + get => _search; + set + { + _search = value; + Sort(); + } + } + + public SaveManager() + { + InitializeComponent(); + } + + public SaveManager(UserProfile userProfile, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem) + { + _userProfile = userProfile; + _horizonClient = horizonClient; + _virtualFileSystem = virtualFileSystem; + InitializeComponent(); + + DataContext = this; + + Task.Run(LoadSaves); + } + + public void LoadSaves() + { + Saves.Clear(); + var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account, + new UserId((ulong)_userProfile.UserId.High, (ulong)_userProfile.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); + saveModel.DeleteAction = () => { Saves.Remove(saveModel); }; + } + + Sort(); + } + } + } + + private void Sort() + { + Saves.AsObservableChangeSet() + .Filter(Filter) + .Sort(GetComparer()) + .Bind(out var view).AsObservableList(); + + _view.Clear(); + _view.AddRange(view); + } + + private IComparer<SaveModel> GetComparer() + { + switch (SortIndex) + { + case 0: + return OrderIndex == 0 + ? SortExpressionComparer<SaveModel>.Ascending(save => save.Title) + : SortExpressionComparer<SaveModel>.Descending(save => save.Title); + case 1: + return OrderIndex == 0 + ? SortExpressionComparer<SaveModel>.Ascending(save => save.Size) + : SortExpressionComparer<SaveModel>.Descending(save => save.Size); + default: + return null; + } + } + + private bool Filter(object arg) + { + if (arg is SaveModel save) + { + return string.IsNullOrWhiteSpace(_search) || save.Title.ToLower().Contains(_search.ToLower()); + } + + return false; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Controls/UserEditor.axaml b/Ryujinx.Ava/Ui/Controls/UserEditor.axaml index 90c5c1fe..898527e6 100644 --- a/Ryujinx.Ava/Ui/Controls/UserEditor.axaml +++ b/Ryujinx.Ava/Ui/Controls/UserEditor.axaml @@ -10,6 +10,7 @@ xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels" Margin="0" + MinWidth="500" Padding="0" mc:Ignorable="d"> <UserControl.Resources> @@ -63,7 +64,7 @@ HorizontalAlignment="Stretch" MaxLength="{Binding MaxProfileNameLength}" Text="{Binding Name}" /> - <TextBlock Text="{Locale:Locale UserProfilesUserId}" /> + <TextBlock Name="IdText" Text="{Locale:Locale UserProfilesUserId}" /> <TextBlock Name="IdLabel" Text="{Binding UserId}" /> </StackPanel> <StackPanel diff --git a/Ryujinx.Ava/Ui/Controls/UserEditor.axaml.cs b/Ryujinx.Ava/Ui/Controls/UserEditor.axaml.cs index ea996da8..f5b51e4e 100644 --- a/Ryujinx.Ava/Ui/Controls/UserEditor.axaml.cs +++ b/Ryujinx.Ava/Ui/Controls/UserEditor.axaml.cs @@ -36,15 +36,8 @@ namespace Ryujinx.Ava.Ui.Controls 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(); - } + _profile = args.profile; + TempProfile = new TempProfile(_profile); _parent = args.parent; break; @@ -53,7 +46,8 @@ namespace Ryujinx.Ava.Ui.Controls DataContext = TempProfile; AddPictureButton.IsVisible = _isNewUser; - IdLabel.IsVisible = !_isNewUser; + IdLabel.IsVisible = _profile != null; + IdText.IsVisible = _profile != null; ChangePictureButton.IsVisible = !_isNewUser; } } @@ -87,7 +81,7 @@ namespace Ryujinx.Ava.Ui.Controls return; } - if (_profile != null) + if (_profile != null && !_isNewUser) { _profile.Name = TempProfile.Name; _profile.Image = TempProfile.Image; @@ -97,7 +91,7 @@ namespace Ryujinx.Ava.Ui.Controls } else if (_isNewUser) { - _parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image); + _parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image, TempProfile.UserId); } else { diff --git a/Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml b/Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml new file mode 100644 index 00000000..6345ec67 --- /dev/null +++ b/Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml @@ -0,0 +1,70 @@ +<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="800" + d:DesignHeight="450" + MinWidth="500" + MinHeight="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.Controls.UserRecoverer"> + <Design.DataContext> + <viewModels:UserProfileViewModel /> + </Design.DataContext> + <Grid HorizontalAlignment="Stretch" + VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto"/> + <RowDefinition Height="Auto"/> + <RowDefinition/> + </Grid.RowDefinitions> + <Button Grid.Row="0" + Margin="5" + Height="30" + Width="50" + MinWidth="50" + HorizontalAlignment="Left" + Command="{Binding GoBack}"> + <ui:SymbolIcon Symbol="Back"/> + </Button> + <TextBlock Grid.Row="1" + Text="{Locale:Locale UserProfilesRecoverHeading}"/> + <ListBox + Margin="5" + Grid.Row="2" + 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" + Command="{Binding Recover}" + CommandParameter="{Binding}" + Content="{Locale:Locale Recover}"/> + </Grid> + </Border> + </DataTemplate> + </ListBox.ItemTemplate> + </ListBox> + </Grid> +</UserControl> diff --git a/Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml.cs b/Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml.cs new file mode 100644 index 00000000..f093686d --- /dev/null +++ b/Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml.cs @@ -0,0 +1,44 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using FluentAvalonia.UI.Controls; +using FluentAvalonia.UI.Navigation; +using Ryujinx.Ava.Ui.Models; +using Ryujinx.Ava.Ui.ViewModels; + +namespace Ryujinx.Ava.Ui.Controls +{ + public partial class UserRecoverer : UserControl + { + private UserProfileViewModel _viewModel; + private NavigationDialogHost _parent; + + public UserRecoverer() + { + 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, UserProfileViewModel viewModel))arg.Parameter; + + _viewModel = args.viewModel; + _parent = args.parent; + break; + } + + DataContext = _viewModel; + } + } + } +} diff --git a/Ryujinx.Ava/Ui/Controls/UserSelector.axaml b/Ryujinx.Ava/Ui/Controls/UserSelector.axaml index c06bce23..bc6d8c09 100644 --- a/Ryujinx.Ava/Ui/Controls/UserSelector.axaml +++ b/Ryujinx.Ava/Ui/Controls/UserSelector.axaml @@ -10,6 +10,7 @@ xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels" d:DesignHeight="450" + MinWidth="500" d:DesignWidth="800" mc:Ignorable="d"> <UserControl.Resources> @@ -25,6 +26,7 @@ </Grid.RowDefinitions> <ListBox Margin="5" + MaxHeight="300" HorizontalAlignment="Stretch" VerticalAlignment="Center" DoubleTapped="ProfilesList_DoubleTapped" @@ -88,21 +90,56 @@ </DataTemplate> </ListBox.ItemTemplate> </ListBox> - <StackPanel + <Grid Grid.Row="1" - Margin="10,0" - HorizontalAlignment="Center" - Orientation="Horizontal" - Spacing="10"> - <Button Command="{Binding AddUser}" Content="{Locale:Locale UserProfilesAddNewProfile}" /> + HorizontalAlignment="Center"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto"/> + <RowDefinition Height="Auto"/> + <RowDefinition Height="Auto"/> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition Width="Auto"/> + </Grid.ColumnDefinitions> + <Button + HorizontalAlignment="Stretch" + Grid.Row="0" + Grid.Column="0" + Margin="2" + Command="{Binding AddUser}" + Content="{Locale:Locale UserProfilesAddNewProfile}" /> <Button + HorizontalAlignment="Stretch" + Grid.Row="0" + Margin="2" + Grid.Column="1" Command="{Binding EditUser}" Content="{Locale:Locale UserProfilesEditProfile}" IsEnabled="{Binding IsSelectedProfiledEditable}" /> + <Button + HorizontalAlignment="Stretch" + Grid.Row="1" + Grid.Column="0" + Margin="2" + Content="{Locale:Locale UserProfilesManageSaves}" + Command="{Binding ManageSaves}" /> <Button + HorizontalAlignment="Stretch" + Grid.Row="1" + Grid.Column="1" + Margin="2" Command="{Binding DeleteUser}" Content="{Locale:Locale UserProfilesDeleteSelectedProfile}" IsEnabled="{Binding IsSelectedProfileDeletable}" /> - </StackPanel> + <Button + HorizontalAlignment="Stretch" + Grid.Row="2" + Grid.ColumnSpan="2" + Grid.Column="0" + Margin="2" + Command="{Binding RecoverLostAccounts}" + Content="{Locale:Locale UserProfilesRecoverLostAccounts}" /> + </Grid> </Grid> </UserControl>
\ No newline at end of file |