diff options
| author | Emmanuel Hansen <emmausssss@gmail.com> | 2022-07-08 18:47:11 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-07-08 15:47:11 -0300 |
| commit | 3af42d6c7e9e71c504b87a7b0f7f960fe83418fb (patch) | |
| tree | 2b27f12ee0273d5316229d31383619d915c3210d /Ryujinx.Ava/Ui/Windows | |
| parent | bccf5e8b5a8f3870dbf03bedb0eb46b85b78d5f4 (diff) | |
UI - Avalonia Part 3 (#3441)
* Add all other windows
* addreesed review
* Prevent "No Update" option from being deleted
* Select no update is the current update is removed from the title update window
* fix amiibo crash
Diffstat (limited to 'Ryujinx.Ava/Ui/Windows')
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml | 68 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml.cs | 70 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml | 53 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml.cs | 71 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/CheatWindow.axaml | 90 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/CheatWindow.axaml.cs | 137 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml | 132 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml.cs | 261 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml | 104 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs | 265 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/UserProfileWindow.axaml | 107 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/UserProfileWindow.axaml.cs | 102 |
12 files changed, 1460 insertions, 0 deletions
diff --git a/Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml b/Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml new file mode 100644 index 00000000..f91bb313 --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml @@ -0,0 +1,68 @@ +<window:StyleableWindow 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="400" d:DesignHeight="350" + x:Class="Ryujinx.Ava.Ui.Windows.AmiiboWindow" + xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + CanResize="False" + WindowStartupLocation="CenterOwner" + Width="800" MinHeight="650" Height="650" + SizeToContent="Manual" + MinWidth="600"> + <Design.DataContext> + <viewModels:AmiiboWindowViewModel /> + </Design.DataContext> + <Grid Margin="15" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Grid Grid.Row="1" HorizontalAlignment="Stretch"> + <Grid.ColumnDefinitions> + <ColumnDefinition /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <StackPanel Spacing="10" Orientation="Horizontal" HorizontalAlignment="Left"> + <TextBlock VerticalAlignment="Center" Text="{locale:Locale AmiiboSeriesLabel}" /> + <ComboBox SelectedIndex="{Binding SeriesSelectedIndex}" Items="{Binding AmiiboSeries}" MinWidth="100" /> + </StackPanel> + <StackPanel Spacing="10" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right"> + <TextBlock VerticalAlignment="Center" Text="{locale:Locale AmiiboCharacterLabel}" /> + <ComboBox SelectedIndex="{Binding AmiiboSelectedIndex}" MinWidth="100" Items="{Binding AmiiboList}" /> + </StackPanel> + </Grid> + <StackPanel Margin="20" Grid.Row="2"> + <Image Source="{Binding AmiiboImage}" Height="350" Width="350" HorizontalAlignment="Center" /> + <ScrollViewer MaxHeight="120" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" + Margin="20" VerticalAlignment="Top" HorizontalAlignment="Stretch"> + <TextBlock TextWrapping="Wrap" Text="{Binding Usage}" HorizontalAlignment="Center" + TextAlignment="Center" /> + </ScrollViewer> + </StackPanel> + <Grid Grid.Row="3"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <CheckBox Margin="10" Grid.Column="0" VerticalContentAlignment="Center" IsChecked="{Binding ShowAllAmiibo}" + Content="{locale:Locale AmiiboOptionsShowAllLabel}" /> + <CheckBox Margin="10" VerticalContentAlignment="Center" Grid.Column="1" IsChecked="{Binding UseRandomUuid}" + Content="{locale:Locale AmiiboOptionsUsRandomTagLabel}" /> + + <Button Grid.Column="3" IsEnabled="{Binding EnableScanning}" Width="80" + Content="{locale:Locale AmiiboScanButtonLabel}" Name="ScanButton" + Click="ScanButton_Click" /> + <Button Grid.Column="4" Margin="10,0" Width="80" Content="{locale:Locale InputDialogCancel}" + Name="CancelButton" + Click="CancelButton_Click" /> + </Grid> + </Grid> +</window:StyleableWindow>
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml.cs new file mode 100644 index 00000000..bd0935a9 --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml.cs @@ -0,0 +1,70 @@ +using Avalonia; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Ui.Models; +using Ryujinx.Ava.Ui.ViewModels; + +namespace Ryujinx.Ava.Ui.Windows +{ + public class AmiiboWindow : StyleableWindow + { + public AmiiboWindow(bool showAll, string lastScannedAmiiboId, string titleId) + { + ViewModel = new AmiiboWindowViewModel(this, lastScannedAmiiboId, titleId); + + ViewModel.ShowAllAmiibo = showAll; + + DataContext = ViewModel; + + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["Amiibo"]; + } + + public AmiiboWindow() + { + ViewModel = new AmiiboWindowViewModel(this, string.Empty, string.Empty); + + DataContext = ViewModel; + + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + if (Program.PreviewerDetached) + { + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["Amiibo"]; + } + } + + public bool IsScanned { get; set; } + public Amiibo.AmiiboApi ScannedAmiibo { get; set; } + public AmiiboWindowViewModel ViewModel { get; set; } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void ScanButton_Click(object sender, RoutedEventArgs e) + { + if (ViewModel.AmiiboSelectedIndex > -1) + { + Amiibo.AmiiboApi amiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex]; + ScannedAmiibo = amiibo; + IsScanned = true; + Close(); + } + } + + private void CancelButton_Click(object sender, RoutedEventArgs e) + { + IsScanned = false; + + Close(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml b/Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml new file mode 100644 index 00000000..6c7576bc --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml @@ -0,0 +1,53 @@ +<Window 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" d:DesignWidth="400" d:DesignHeight="350" + x:Class="Ryujinx.Ava.Ui.Windows.AvatarWindow" + CanResize="False" + xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels" + xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls" + WindowStartupLocation="CenterOwner" + x:CompileBindings="True" + x:DataType="viewModels:AvatarProfileViewModel" + SizeToContent="WidthAndHeight"> + <Design.DataContext> + <viewModels:AvatarProfileViewModel /> + </Design.DataContext> + <Window.Resources> + <controls:BitmapArrayValueConverter x:Key="ByteImage" /> + </Window.Resources> + <Grid Margin="5" 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}" Width="600" Height="500" + Items="{Binding Images}" HorizontalAlignment="Stretch" VerticalAlignment="Center"> + <ListBox.ItemsPanel> + <ItemsPanelTemplate> + <WrapPanel Orientation="Horizontal" MaxWidth="600" Margin="0" HorizontalAlignment="Center" /> + </ItemsPanelTemplate> + </ListBox.ItemsPanel> + <ListBox.ItemTemplate> + <DataTemplate> + <Image Margin="5" Height="96" Width="96" + Source="{Binding Data, Converter={StaticResource ByteImage}}" /> + </DataTemplate> + </ListBox.ItemTemplate> + </ListBox> + <ProgressBar Grid.Row="2" IsIndeterminate="{Binding IsIndeterminate}" Value="{Binding ImagesLoaded}" HorizontalAlignment="Stretch" Margin="5" + Maximum="{Binding ImageCount}" Minimum="0" /> + <StackPanel Grid.Row="3" Orientation="Horizontal" Spacing="10" Margin="10" HorizontalAlignment="Center"> + <Button Content="{Locale:Locale AvatarChoose}" Width="200" Name="ChooseButton" Click="ChooseButton_OnClick" /> + <ui:ColorPickerButton Color="{Binding BackgroundColor, Mode=TwoWay}" Name="ColorButton" /> + <Button HorizontalAlignment="Right" Content="{Locale:Locale AvatarClose}" Click="CloseButton_OnClick" + Name="CloseButton" + Width="200" /> + </StackPanel> + </Grid> +</Window>
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml.cs new file mode 100644 index 00000000..25e89846 --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml.cs @@ -0,0 +1,71 @@ +using Avalonia; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Ui.ViewModels; +using Ryujinx.HLE.FileSystem; +using System; + +namespace Ryujinx.Ava.Ui.Windows +{ + public class AvatarWindow : StyleableWindow + { + public AvatarWindow(ContentManager contentManager) + { + ContentManager = contentManager; + ViewModel = new AvatarProfileViewModel(() => ViewModel.ReloadImages()); + + DataContext = ViewModel; + + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["AvatarWindowTitle"]; + } + + public AvatarWindow() + { + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + if (Program.PreviewerDetached) + { + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["AvatarWindowTitle"]; + } + } + + public ContentManager ContentManager { get; } + + public byte[] SelectedImage { get; set; } + + internal AvatarProfileViewModel ViewModel { get; set; } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + protected override void OnClosed(EventArgs e) + { + ViewModel.Dispose(); + base.OnClosed(e); + } + + private void CloseButton_OnClick(object sender, RoutedEventArgs e) + { + Close(); + } + + private void ChooseButton_OnClick(object sender, RoutedEventArgs e) + { + if (ViewModel.SelectedIndex > -1) + { + SelectedImage = ViewModel.SelectedImage; + + Close(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/CheatWindow.axaml b/Ryujinx.Ava/Ui/Windows/CheatWindow.axaml new file mode 100644 index 00000000..1685ee80 --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/CheatWindow.axaml @@ -0,0 +1,90 @@ +<window:StyleableWindow x:Class="Ryujinx.Ava.Ui.Windows.CheatWindow" + 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:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:model="clr-namespace:Ryujinx.Ava.Ui.Models" + xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" + mc:Ignorable="d" + SizeToContent="Height" + Width="500" MinHeight="500" Height="500" + WindowStartupLocation="CenterOwner" + MinWidth="500"> + <Window.Styles> + <Style Selector="TreeViewItem"> + <Setter Property="IsExpanded" Value="True" /> + </Style> + </Window.Styles> + <Grid Name="DlcGrid" Margin="15"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <TextBlock + Grid.Row="1" + Margin="20,15,20,20" + HorizontalAlignment="Center" + VerticalAlignment="Center" + MaxWidth="500" + LineHeight="18" + TextWrapping="Wrap" + Text="{Binding Heading}" + TextAlignment="Center" /> + <Border + Grid.Row="2" + Margin="5" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + BorderBrush="Gray" + BorderThickness="1"> + <TreeView Items="{Binding LoadedCheats}" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + Name="CheatsView" + MinHeight="300"> + <TreeView.DataTemplates> + <TreeDataTemplate DataType="model:CheatsList" ItemsSource="{Binding}"> + <StackPanel HorizontalAlignment="Left" Orientation="Horizontal"> + <CheckBox IsChecked="{Binding IsEnabled}" MinWidth="20" /> + <TextBlock Width="150" + Text="{Binding BuildId}" /> + <TextBlock + Text="{Binding Path}" /> + </StackPanel> + </TreeDataTemplate> + <DataTemplate x:DataType="model:CheatModel"> + <StackPanel Orientation="Horizontal" HorizontalAlignment="Left"> + <CheckBox IsChecked="{Binding IsEnabled}" MinWidth="20" /> + <TextBlock Text="{Binding CleanName}" /> + </StackPanel> + </DataTemplate> + </TreeView.DataTemplates> + </TreeView> + </Border> + <DockPanel + Grid.Row="3" + Margin="0" + HorizontalAlignment="Stretch"> + <DockPanel Margin="0" HorizontalAlignment="Right"> + <Button + Name="SaveButton" + MinWidth="90" + Margin="5" + IsVisible="{Binding !NoCheatsFound}" + Command="{Binding Save}"> + <TextBlock Text="{locale:Locale SettingsButtonSave}" /> + </Button> + <Button + Name="CancelButton" + MinWidth="90" + Margin="5" + Command="{Binding Close}"> + <TextBlock Text="{locale:Locale InputDialogCancel}" /> + </Button> + </DockPanel> + </DockPanel> + </Grid> +</window:StyleableWindow>
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/CheatWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/CheatWindow.axaml.cs new file mode 100644 index 00000000..33abeb8d --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/CheatWindow.axaml.cs @@ -0,0 +1,137 @@ +using Avalonia; +using Avalonia.Collections; +using Avalonia.Markup.Xaml; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Ui.Models; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; + +namespace Ryujinx.Ava.Ui.Windows +{ + public class CheatWindow : StyleableWindow + { + private readonly string _enabledCheatsPath; + public bool NoCheatsFound { get; } + + private AvaloniaList<CheatsList> LoadedCheats { get; } + + public string Heading { get; } + + public CheatWindow() + { + DataContext = this; + + InitializeComponent(); + AttachDebugDevTools(); + + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["CheatWindowTitle"]; + } + + public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) + { + LoadedCheats = new AvaloniaList<CheatsList>(); + + Heading = string.Format(LocaleManager.Instance["CheatWindowHeading"], titleName, titleId.ToUpper()); + + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + + string modsBasePath = virtualFileSystem.ModLoader.GetModsBasePath(); + string titleModsPath = virtualFileSystem.ModLoader.GetTitleDir(modsBasePath, titleId); + ulong titleIdValue = ulong.Parse(titleId, System.Globalization.NumberStyles.HexNumber); + + _enabledCheatsPath = Path.Combine(titleModsPath, "cheats", "enabled.txt"); + + string[] enabled = { }; + + if (File.Exists(_enabledCheatsPath)) + { + enabled = File.ReadAllLines(_enabledCheatsPath); + } + + int cheatAdded = 0; + + var mods = new ModLoader.ModCache(); + + ModLoader.QueryContentsDir(mods, new DirectoryInfo(Path.Combine(modsBasePath, "contents")), titleIdValue); + + string currentCheatFile = string.Empty; + string buildId = string.Empty; + string parentPath = string.Empty; + + CheatsList currentGroup = null; + + foreach (var cheat in mods.Cheats) + { + if (cheat.Path.FullName != currentCheatFile) + { + currentCheatFile = cheat.Path.FullName; + parentPath = currentCheatFile.Replace(titleModsPath, ""); + + buildId = Path.GetFileNameWithoutExtension(currentCheatFile).ToUpper(); + currentGroup = new CheatsList(buildId, parentPath); + + LoadedCheats.Add(currentGroup); + } + + var model = new CheatModel(cheat.Name, buildId, enabled.Contains($"{buildId}-{cheat.Name}")); + currentGroup?.Add(model); + + cheatAdded++; + } + + if (cheatAdded == 0) + { + NoCheatsFound = true; + } + + DataContext = this; + + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["CheatWindowTitle"]; + } + + [Conditional("DEBUG")] + private void AttachDebugDevTools() + { + this.AttachDevTools(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + public void Save() + { + if (NoCheatsFound) + { + return; + } + + List<string> enabledCheats = new List<string>(); + + foreach (var cheats in LoadedCheats) + { + foreach (var cheat in cheats) + { + if (cheat.IsEnabled) + { + enabledCheats.Add(cheat.BuildIdKey); + } + } + } + + Directory.CreateDirectory(Path.GetDirectoryName(_enabledCheatsPath)); + + File.WriteAllLines(_enabledCheatsPath, enabledCheats); + + Close(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml b/Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml new file mode 100644 index 00000000..94b3895e --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml @@ -0,0 +1,132 @@ +<window:StyleableWindow + x:Class="Ryujinx.Ava.Ui.Windows.DlcManagerWindow" + 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:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" + SizeToContent="Height" + Width="600" MinHeight="500" Height="500" + WindowStartupLocation="CenterOwner" + MinWidth="600" + mc:Ignorable="d"> + <Grid Name="DlcGrid" Margin="15"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <TextBlock + Grid.Row="1" + Margin="20,15,20,20" + HorizontalAlignment="Center" + VerticalAlignment="Center" + MaxWidth="500" + LineHeight="18" + TextWrapping="Wrap" + Text="{Binding Heading}" + TextAlignment="Center" /> + <Border + Grid.Row="2" + Margin="5" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + BorderBrush="Gray" + BorderThickness="1"> + <DataGrid + MinHeight="200" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + HorizontalScrollBarVisibility="Auto" + Items="{Binding Dlcs}" + VerticalScrollBarVisibility="Auto"> + <DataGrid.Columns> + <DataGridTemplateColumn Width="90"> + <DataGridTemplateColumn.CellTemplate> + <DataTemplate> + <CheckBox + Width="50" + MinWidth="40" + HorizontalAlignment="Right" + IsChecked="{Binding IsEnabled}" /> + </DataTemplate> + </DataGridTemplateColumn.CellTemplate> + <DataGridTemplateColumn.Header> + <TextBlock Text="{locale:Locale DlcManagerTableHeadingEnabledLabel}" /> + </DataGridTemplateColumn.Header> + </DataGridTemplateColumn> + <DataGridTextColumn + Width="190" + Binding="{Binding TitleId}" + CanUserResize="True"> + <DataGridTextColumn.Header> + <TextBlock Text="{locale:Locale DlcManagerTableHeadingTitleIdLabel}" /> + </DataGridTextColumn.Header> + </DataGridTextColumn> + <DataGridTextColumn + Width="*" + Binding="{Binding ContainerPath}" + CanUserResize="True"> + <DataGridTextColumn.Header> + <TextBlock Text="{locale:Locale DlcManagerTableHeadingContainerPathLabel}" /> + </DataGridTextColumn.Header> + </DataGridTextColumn> + <DataGridTextColumn + Width="*" + Binding="{Binding FullPath}" + CanUserResize="True"> + <DataGridTextColumn.Header> + <TextBlock Text="{locale:Locale DlcManagerTableHeadingFullPathLabel}" /> + </DataGridTextColumn.Header> + </DataGridTextColumn> + </DataGrid.Columns> + </DataGrid> + </Border> + <DockPanel + Grid.Row="3" + Margin="0" + HorizontalAlignment="Stretch"> + <DockPanel Margin="0" HorizontalAlignment="Left"> + <Button + Name="AddButton" + MinWidth="90" + Margin="5" + Command="{Binding Add}"> + <TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" /> + </Button> + <Button + Name="RemoveButton" + MinWidth="90" + Margin="5" + Command="{Binding RemoveSelected}"> + <TextBlock Text="{locale:Locale SettingsTabGeneralRemove}" /> + </Button> + <Button + Name="RemoveAllButton" + MinWidth="90" + Margin="5" + Command="{Binding RemoveAll}"> + <TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" /> + </Button> + </DockPanel> + <DockPanel Margin="0" HorizontalAlignment="Right"> + <Button + Name="SaveButton" + MinWidth="90" + Margin="5" + Command="{Binding Save}"> + <TextBlock Text="{locale:Locale SettingsButtonSave}" /> + </Button> + <Button + Name="CancelButton" + MinWidth="90" + Margin="5" + Command="{Binding Close}"> + <TextBlock Text="{locale:Locale InputDialogCancel}" /> + </Button> + </DockPanel> + </DockPanel> + </Grid> +</window:StyleableWindow>
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml.cs new file mode 100644 index 00000000..cb2ed324 --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml.cs @@ -0,0 +1,261 @@ +using Avalonia; +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Ui.Controls; +using Ryujinx.Ava.Ui.Models; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.FileSystem; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using Path = System.IO.Path; + +namespace Ryujinx.Ava.Ui.Windows +{ + public class DlcManagerWindow : StyleableWindow + { + private readonly List<DlcContainer> _dlcContainerList; + private readonly string _dlcJsonPath; + + public VirtualFileSystem VirtualFileSystem { get; } + + public AvaloniaList<DlcModel> Dlcs { get; set; } + public Grid DlcGrid { get; private set; } + public ulong TitleId { get; } + public string TitleName { get; } + + public string Heading => string.Format(LocaleManager.Instance["DlcWindowHeading"], TitleName, TitleId.ToString("X16")); + + public DlcManagerWindow() + { + DataContext = this; + + InitializeComponent(); + AttachDebugDevTools(); + + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"]; + } + + public DlcManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) + { + VirtualFileSystem = virtualFileSystem; + TitleId = titleId; + TitleName = titleName; + + _dlcJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json"); + + try + { + _dlcContainerList = JsonHelper.DeserializeFromFile<List<DlcContainer>>(_dlcJsonPath); + } + catch + { + _dlcContainerList = new List<DlcContainer>(); + } + + DataContext = this; + + InitializeComponent(); + AttachDebugDevTools(); + + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"]; + + LoadDlcs(); + } + + [Conditional("DEBUG")] + private void AttachDebugDevTools() + { + this.AttachDevTools(); + } + + private void InitializeComponent() + { + Dlcs = new AvaloniaList<DlcModel>(); + + AvaloniaXamlLoader.Load(this); + + DlcGrid = this.FindControl<Grid>("DlcGrid"); + } + + private void LoadDlcs() + { + foreach (DlcContainer dlcContainer in _dlcContainerList) + { + using FileStream containerFile = File.OpenRead(dlcContainer.Path); + + PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage()); + + VirtualFileSystem.ImportTickets(pfs); + + foreach (DlcNca dlcNca in dlcContainer.DlcNcaList) + { + using var ncaFile = new UniqueRef<IFile>(); + pfs.OpenFile(ref ncaFile.Ref(), dlcNca.Path.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), dlcContainer.Path); + + if (nca != null) + { + Dlcs.Add(new DlcModel(nca.Header.TitleId.ToString("X16"), dlcContainer.Path, dlcNca.Path, + dlcNca.Enabled)); + } + } + } + } + + private Nca TryCreateNca(IStorage ncaStorage, string containerPath) + { + try + { + return new Nca(VirtualFileSystem.KeySet, ncaStorage); + } + catch (Exception ex) + { + ContentDialogHelper.CreateErrorDialog(this, + string.Format(LocaleManager.Instance[ + "DialogDlcLoadNcaErrorMessage"], ex.Message, containerPath)); + } + + return null; + } + + private void AddDlc(string path) + { + if (!File.Exists(path) || Dlcs.FirstOrDefault(x => x.ContainerPath == path) != null) + { + return; + } + + using (FileStream containerFile = File.OpenRead(path)) + { + PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage()); + bool containsDlc = false; + + VirtualFileSystem.ImportTickets(pfs); + + foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) + { + using var ncaFile = new UniqueRef<IFile>(); + + pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), path); + + if (nca == null) + { + continue; + } + + if (nca.Header.ContentType == NcaContentType.PublicData) + { + if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != TitleId) + { + break; + } + + Dlcs.Add(new DlcModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true)); + + containsDlc = true; + } + } + + if (!containsDlc) + { + ContentDialogHelper.CreateErrorDialog(this, LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]); + } + } + } + + private void RemoveDlcs(bool removeSelectedOnly = false) + { + if (removeSelectedOnly) + { + Dlcs.RemoveAll(Dlcs.Where(x => x.IsEnabled).ToList()); + } + else + { + Dlcs.Clear(); + } + } + + public void RemoveSelected() + { + RemoveDlcs(true); + } + + public void RemoveAll() + { + RemoveDlcs(); + } + + public async void Add() + { + OpenFileDialog dialog = new OpenFileDialog() { Title = LocaleManager.Instance["SelectDlcDialogTitle"], AllowMultiple = true }; + + dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } }); + + string[] files = await dialog.ShowAsync(this); + + if (files != null) + { + foreach (string file in files) + { + AddDlc(file); + } + } + } + + public void Save() + { + _dlcContainerList.Clear(); + + DlcContainer container = default; + + foreach (DlcModel dlc in Dlcs) + { + if (container.Path != dlc.ContainerPath) + { + if (!string.IsNullOrWhiteSpace(container.Path)) + { + _dlcContainerList.Add(container); + } + + container = new DlcContainer { Path = dlc.ContainerPath, DlcNcaList = new List<DlcNca>() }; + } + + container.DlcNcaList.Add(new DlcNca + { + Enabled = dlc.IsEnabled, + TitleId = Convert.ToUInt64(dlc.TitleId, 16), + Path = dlc.FullPath + }); + } + + if (!string.IsNullOrWhiteSpace(container.Path)) + { + _dlcContainerList.Add(container); + } + + using (FileStream dlcJsonStream = File.Create(_dlcJsonPath, 4096, FileOptions.WriteThrough)) + { + dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_dlcContainerList, true))); + } + + Close(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml b/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml new file mode 100644 index 00000000..347c2cf5 --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml @@ -0,0 +1,104 @@ +<window:StyleableWindow + x:Class="Ryujinx.Ava.Ui.Windows.TitleUpdateWindow" + 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:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" + SizeToContent="Height" + Width="600" MinHeight="500" Height="500" + WindowStartupLocation="CenterOwner" + MinWidth="600" + mc:Ignorable="d"> + <Grid Margin="15"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <TextBlock + Grid.Row="1" + Margin="20,15,20,20" + HorizontalAlignment="Center" + VerticalAlignment="Center" + MaxWidth="500" + LineHeight="18" + TextWrapping="Wrap" + Text="{Binding Heading}" + TextAlignment="Center" /> + <Border + Grid.Row="2" + Margin="5" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + BorderBrush="Gray" + BorderThickness="1"> + <ScrollViewer + Width="550" + MinHeight="200" + VerticalAlignment="Stretch" + HorizontalScrollBarVisibility="Auto" + VerticalScrollBarVisibility="Auto"> + <ItemsControl + Margin="10" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + Items="{Binding TitleUpdates}"> + <ItemsControl.ItemTemplate> + <DataTemplate> + <RadioButton Padding="8, 0" VerticalContentAlignment="Center" GroupName="Update" IsChecked="{Binding IsEnabled, Mode=TwoWay}"> + <Label Margin="0" VerticalAlignment="Center" Content="{Binding Label}" /> + </RadioButton> + </DataTemplate> + </ItemsControl.ItemTemplate> + </ItemsControl> + </ScrollViewer> + </Border> + <DockPanel + Grid.Row="3" + Margin="0" + HorizontalAlignment="Stretch"> + <DockPanel Margin="0" HorizontalAlignment="Left"> + <Button + Name="AddButton" + MinWidth="90" + Margin="5" + Command="{Binding Add}"> + <TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" /> + </Button> + <Button + Name="RemoveButton" + MinWidth="90" + Margin="5" + Command="{Binding RemoveSelected}"> + <TextBlock Text="{locale:Locale SettingsTabGeneralRemove}" /> + </Button> + <Button + Name="RemoveAllButton" + MinWidth="90" + Margin="5" + Command="{Binding RemoveAll}"> + <TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" /> + </Button> + </DockPanel> + <DockPanel Margin="0" HorizontalAlignment="Right"> + <Button + Name="SaveButton" + MinWidth="90" + Margin="5" + Command="{Binding Save}"> + <TextBlock Text="{locale:Locale SettingsButtonSave}" /> + </Button> + <Button + Name="CancelButton" + MinWidth="90" + Margin="5" + Command="{Binding Close}"> + <TextBlock Text="{locale:Locale InputDialogCancel}" /> + </Button> + </DockPanel> + </DockPanel> + </Grid> +</window:StyleableWindow>
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs new file mode 100644 index 00000000..edc1abcd --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs @@ -0,0 +1,265 @@ +using Avalonia; +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using LibHac.Ns; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Ui.Controls; +using Ryujinx.Ava.Ui.Models; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using Path = System.IO.Path; +using SpanHelpers = LibHac.Common.SpanHelpers; +using LibHac.Tools.FsSystem; + +namespace Ryujinx.Ava.Ui.Windows +{ + public class TitleUpdateWindow : StyleableWindow + { + private readonly string _updateJsonPath; + private TitleUpdateMetadata _titleUpdateWindowData; + + public VirtualFileSystem VirtualFileSystem { get; } + + internal AvaloniaList<TitleUpdateModel> TitleUpdates { get; set; } + public string TitleId { get; } + public string TitleName { get; } + + public string Heading => string.Format(LocaleManager.Instance["GameUpdateWindowHeading"], TitleName, TitleId.ToUpper()); + + public TitleUpdateWindow() + { + DataContext = this; + + InitializeComponent(); + AttachDebugDevTools(); + + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"]; + } + + public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) + { + VirtualFileSystem = virtualFileSystem; + TitleId = titleId; + TitleName = titleName; + + _updateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId, "updates.json"); + + try + { + _titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_updateJsonPath); + } + catch + { + _titleUpdateWindowData = new TitleUpdateMetadata {Selected = "", Paths = new List<string>()}; + } + + DataContext = this; + + InitializeComponent(); + AttachDebugDevTools(); + + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"]; + + LoadUpdates(); + } + + [Conditional("DEBUG")] + private void AttachDebugDevTools() + { + this.AttachDevTools(); + } + + private void InitializeComponent() + { + TitleUpdates = new AvaloniaList<TitleUpdateModel>(); + + AvaloniaXamlLoader.Load(this); + } + + private void LoadUpdates() + { + TitleUpdates.Add(new TitleUpdateModel(default, string.Empty, true)); + + foreach (string path in _titleUpdateWindowData.Paths) + { + AddUpdate(path); + } + + if (_titleUpdateWindowData.Selected == "") + { + TitleUpdates[0].IsEnabled = true; + } + else + { + TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected); + List<TitleUpdateModel> enabled = TitleUpdates.Where(x => x.IsEnabled).ToList(); + + foreach (TitleUpdateModel update in enabled) + { + update.IsEnabled = false; + } + + if (selected != null) + { + selected.IsEnabled = true; + } + } + + SortUpdates(); + } + + private void AddUpdate(string path) + { + if (File.Exists(path) && !TitleUpdates.Any(x => x.Path == path)) + { + using (FileStream file = new(path, FileMode.Open, FileAccess.Read)) + { + PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage()); + + try + { + (Nca patchNca, Nca controlNca) = + ApplicationLoader.GetGameUpdateDataFromPartition(VirtualFileSystem, nsp, TitleId, 0); + + if (controlNca != null && patchNca != null) + { + ApplicationControlProperty controlData = new ApplicationControlProperty(); + + using var nacpFile = new UniqueRef<IFile>(); + + controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None) + .OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read) + .ThrowIfFailure(); + nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None) + .ThrowIfFailure(); + + TitleUpdates.Add(new TitleUpdateModel(controlData, path)); + } + else + { + ContentDialogHelper.CreateErrorDialog(this, + LocaleManager.Instance["DialogUpdateAddUpdateErrorMessage"]); + } + } + catch (Exception ex) + { + ContentDialogHelper.CreateErrorDialog(this, + string.Format(LocaleManager.Instance["DialogDlcLoadNcaErrorMessage"], ex.Message, path)); + } + } + } + } + + private void RemoveUpdates(bool removeSelectedOnly = false) + { + if (removeSelectedOnly) + { + TitleUpdates.RemoveAll(TitleUpdates.Where(x => x.IsEnabled && !x.IsNoUpdate).ToList()); + } + else + { + TitleUpdates.RemoveAll(TitleUpdates.Where(x => !x.IsNoUpdate).ToList()); + } + + TitleUpdates.FirstOrDefault(x => x.IsNoUpdate).IsEnabled = true; + + SortUpdates(); + } + + public void RemoveSelected() + { + RemoveUpdates(true); + } + + public void RemoveAll() + { + RemoveUpdates(); + } + + public async void Add() + { + OpenFileDialog dialog = new OpenFileDialog() { Title = LocaleManager.Instance["SelectUpdateDialogTitle"], AllowMultiple = true }; + + dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } }); + + string[] files = await dialog.ShowAsync(this); + + if (files != null) + { + foreach (string file in files) + { + AddUpdate(file); + } + } + + SortUpdates(); + } + + private void SortUpdates() + { + var list = TitleUpdates.ToList(); + + list.Sort((first, second) => + { + if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString())) + { + return -1; + } + else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString())) + { + return 1; + } + + return Version.Parse(first.Control.DisplayVersionString.ToString()) + .CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1; + }); + + TitleUpdates.Clear(); + + TitleUpdates.AddRange(list); + } + + public void Save() + { + _titleUpdateWindowData.Paths.Clear(); + + _titleUpdateWindowData.Selected = ""; + + foreach (TitleUpdateModel update in TitleUpdates) + { + _titleUpdateWindowData.Paths.Add(update.Path); + + if (update.IsEnabled) + { + _titleUpdateWindowData.Selected = update.Path; + } + } + + using (FileStream dlcJsonStream = File.Create(_updateJsonPath, 4096, FileOptions.WriteThrough)) + { + dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true))); + } + + if (Owner is MainWindow window) + { + window.ViewModel.LoadApplications(); + } + + Close(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/UserProfileWindow.axaml b/Ryujinx.Ava/Ui/Windows/UserProfileWindow.axaml new file mode 100644 index 00000000..4b004206 --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/UserProfileWindow.axaml @@ -0,0 +1,107 @@ +<window:StyleableWindow 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" d:DesignWidth="400" d:DesignHeight="350" + x:Class="Ryujinx.Ava.Ui.Windows.UserProfileWindow" + 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:window="clr-namespace:Ryujinx.Ava.Ui.Windows" + CanResize="False" + Width="850" MinHeight="550" Height="550" + WindowStartupLocation="CenterOwner" + SizeToContent="Manual" + MinWidth="600"> + <Design.DataContext> + <viewModels:UserProfileViewModel /> + </Design.DataContext> + <Window.Resources> + <controls:BitmapArrayValueConverter x:Key="ByteImage" /> + </Window.Resources> + <Grid Margin="15" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Grid Grid.Row="1"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition /> + </Grid.RowDefinitions> + <ContentControl + Focusable="False" + IsVisible="False" + KeyboardNavigation.IsTabStop="False"> + <ui:ContentDialog Name="ContentDialog" + IsPrimaryButtonEnabled="True" + IsSecondaryButtonEnabled="True" + IsVisible="False" /> + </ContentControl> + <TextBlock Text="{Locale:Locale UserProfilesSelectedUserProfile}" /> + <Grid Grid.Row="1" Margin="10"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <Image Height="96" Width="96" + Source="{Binding SelectedProfile.Image, Converter={StaticResource ByteImage}}" /> + <StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" Grid.Column="1" Spacing="10" + Margin="5, 10"> + <TextBox Name="NameBox" Text="{Binding SelectedProfile.Name, Mode=OneWay}" + HorizontalAlignment="Stretch" /> + <TextBlock Text="{Binding SelectedProfile.UserId}" /> + </StackPanel> + <StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" Grid.Column="2" Spacing="10" + Margin="5"> + <Button Content="{Locale:Locale UserProfilesSaveProfileName}" Name="SetNameButton" + Click="SetNameButton_OnClick" /> + <Button Name="SelectProfileImage" Command="{Binding ChooseProfileImage}" + Content="{Locale:Locale UserProfilesChangeProfileImage}" /> + </StackPanel> + </Grid> + </Grid> + <Grid Grid.Row="2"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition /> + </Grid.RowDefinitions> + <TextBlock Text="{Locale:Locale UserProfilesAvailableUserProfiles}" /> + <ListBox Grid.Row="1" Margin="10" Name="ProfilesList" DoubleTapped="ProfilesList_DoubleTapped" + Items="{Binding Profiles}"> + <ListBox.ItemTemplate> + <DataTemplate> + <Grid HorizontalAlignment="Stretch"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <Grid Grid.Column="0" Background="{DynamicResource ThemeAccentColorBrush}" + Grid.ColumnSpan="2" + HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinHeight="5" MinWidth="5" + IsVisible="{Binding IsOpened}" /> + <Image Grid.Column="0" Height="96" Width="96" + Source="{Binding Image, Converter={StaticResource ByteImage}}" /> + <StackPanel Margin="10" Orientation="Vertical" HorizontalAlignment="Stretch" + VerticalAlignment="Center" Grid.Column="1"> + <TextBlock Text="{Binding Name}" /> + <TextBlock Text="{Binding UserId}" /> + </StackPanel> + </Grid> + </DataTemplate> + </ListBox.ItemTemplate> + </ListBox> + </Grid> + <StackPanel Grid.Row="3" Orientation="Horizontal" Margin="10,0" Spacing="10" HorizontalAlignment="Stretch"> + <Button Content="{Locale:Locale UserProfilesAddNewProfile}" Command="{Binding AddUser}" /> + <Button IsEnabled="{Binding IsSelectedProfileDeletable}" + Content="{Locale:Locale UserProfilesDeleteSelectedProfile}" Command="{Binding DeleteUser}" /> + <Button HorizontalAlignment="Right" Content="{Locale:Locale UserProfilesClose}" Click="CloseButton_OnClick" + Name="CloseButton" /> + </StackPanel> + </Grid> +</window:StyleableWindow>
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/UserProfileWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/UserProfileWindow.axaml.cs new file mode 100644 index 00000000..e78e0384 --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/UserProfileWindow.axaml.cs @@ -0,0 +1,102 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Ui.ViewModels; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System.Threading.Tasks; +using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile; + +namespace Ryujinx.Ava.Ui.Windows +{ + public class UserProfileWindow : StyleableWindow + { + private TextBox _nameBox; + + public UserProfileWindow(AccountManager accountManager, ContentManager contentManager, + VirtualFileSystem virtualFileSystem) + { + AccountManager = accountManager; + ContentManager = contentManager; + ViewModel = new UserProfileViewModel(this); + + DataContext = ViewModel; + + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + if (contentManager.GetCurrentFirmwareVersion() != null) + { + Task.Run(() => + { + AvatarProfileViewModel.PreloadAvatars(contentManager, virtualFileSystem); + }); + } + + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UserProfileWindowTitle"]; + } + + public UserProfileWindow() + { + ViewModel = new UserProfileViewModel(); + + DataContext = ViewModel; + + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UserProfileWindowTitle"]; + } + + public AccountManager AccountManager { get; } + public ContentManager ContentManager { get; } + + public UserProfileViewModel ViewModel { get; set; } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + _nameBox = this.FindControl<TextBox>("NameBox"); + } + + 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]; + + AccountManager.OpenUser(ViewModel.SelectedProfile.UserId); + + ViewModel.LoadProfiles(); + + foreach (UserProfile profile in ViewModel.Profiles) + { + profile.UpdateState(); + } + } + } + } + + private void CloseButton_OnClick(object sender, RoutedEventArgs e) + { + Close(); + } + + private void SetNameButton_OnClick(object sender, RoutedEventArgs e) + { + if (!string.IsNullOrWhiteSpace(_nameBox.Text)) + { + ViewModel.SelectedProfile.Name = _nameBox.Text; + AccountManager.SetUserName(ViewModel.SelectedProfile.UserId, _nameBox.Text); + } + } + } +}
\ No newline at end of file |
