aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Ava/UI/Windows
diff options
context:
space:
mode:
authorIsaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com>2022-12-29 14:24:05 +0000
committerGitHub <noreply@github.com>2022-12-29 15:24:05 +0100
commit76671d63d4f3ea18f8ad99e9ce9f0b2ec9a2599d (patch)
tree05013214e4696a9254369d0706173f58877f6a83 /Ryujinx.Ava/UI/Windows
parent3d1a0bf3749afa14da5b5ba1e0666fdb78c99beb (diff)
Ava GUI: Restructure `Ryujinx.Ava` (#4165)
* Restructure `Ryujinx.Ava` * Stylistic consistency * Update Ryujinx.Ava/UI/Controls/UserEditor.axaml.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Controls/UserEditor.axaml.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Controls/UserSelector.axaml.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Controls/SaveManager.axaml.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Controls/SaveManager.axaml.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/ViewModels/UserProfileViewModel.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/ViewModels/UserProfileViewModel.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Fix redundancies * Remove redunancies * Add back elses Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
Diffstat (limited to 'Ryujinx.Ava/UI/Windows')
-rw-r--r--Ryujinx.Ava/UI/Windows/AboutWindow.axaml282
-rw-r--r--Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs78
-rw-r--r--Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml74
-rw-r--r--Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml.cs59
-rw-r--r--Ryujinx.Ava/UI/Windows/AvatarWindow.axaml54
-rw-r--r--Ryujinx.Ava/UI/Windows/AvatarWindow.axaml.cs77
-rw-r--r--Ryujinx.Ava/UI/Windows/CheatWindow.axaml106
-rw-r--r--Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs119
-rw-r--r--Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml29
-rw-r--r--Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml.cs25
-rw-r--r--Ryujinx.Ava/UI/Windows/ControllerSettingsWindow.axaml1169
-rw-r--r--Ryujinx.Ava/UI/Windows/ControllerSettingsWindow.axaml.cs181
-rw-r--r--Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml172
-rw-r--r--Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml.cs314
-rw-r--r--Ryujinx.Ava/UI/Windows/IconColorPicker.cs192
-rw-r--r--Ryujinx.Ava/UI/Windows/MainWindow.axaml770
-rw-r--r--Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs721
-rw-r--r--Ryujinx.Ava/UI/Windows/MotionSettingsWindow.axaml141
-rw-r--r--Ryujinx.Ava/UI/Windows/MotionSettingsWindow.axaml.cs71
-rw-r--r--Ryujinx.Ava/UI/Windows/RumbleSettingsWindow.axaml57
-rw-r--r--Ryujinx.Ava/UI/Windows/RumbleSettingsWindow.axaml.cs57
-rw-r--r--Ryujinx.Ava/UI/Windows/SettingsWindow.axaml980
-rw-r--r--Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs213
-rw-r--r--Ryujinx.Ava/UI/Windows/StyleableWindow.cs39
-rw-r--r--Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml115
-rw-r--r--Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs271
26 files changed, 6366 insertions, 0 deletions
diff --git a/Ryujinx.Ava/UI/Windows/AboutWindow.axaml b/Ryujinx.Ava/UI/Windows/AboutWindow.axaml
new file mode 100644
index 00000000..08d28740
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/AboutWindow.axaml
@@ -0,0 +1,282 @@
+<window:StyleableWindow
+ x:Class="Ryujinx.Ava.UI.Windows.AboutWindow"
+ xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
+ Width="850"
+ Height="550"
+ MinWidth="500"
+ MinHeight="550"
+ d:DesignHeight="350"
+ d:DesignWidth="400"
+ CanResize="False"
+ SizeToContent="Width"
+ WindowStartupLocation="CenterOwner"
+ mc:Ignorable="d"
+ Focusable="True">
+ <Grid
+ Margin="15"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="*" />
+ </Grid.RowDefinitions>
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition Width="*" />
+ </Grid.ColumnDefinitions>
+ <Grid
+ Grid.Row="1"
+ Grid.Column="0"
+ Margin="20"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="*" />
+ <RowDefinition Height="*" />
+ <RowDefinition />
+ <RowDefinition />
+ <RowDefinition Height="Auto" />
+ </Grid.RowDefinitions>
+ <Grid Grid.Row="0">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition />
+ <ColumnDefinition />
+ </Grid.ColumnDefinitions>
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ </Grid.RowDefinitions>
+ <Image
+ Grid.Row="0"
+ Grid.RowSpan="3"
+ Grid.Column="0"
+ Height="110"
+ MinWidth="50"
+ Margin="5,10,20,10"
+ Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
+ <TextBlock
+ Grid.Row="0"
+ Grid.Column="1"
+ Margin="0,20,0,0"
+ FontSize="35"
+ Text="Ryujinx"
+ TextAlignment="Center" />
+ <TextBlock
+ Grid.Row="1"
+ Grid.Column="1"
+ Margin="0,0,0,0"
+ FontSize="16"
+ Text="(REE-YOU-JINX)"
+ TextAlignment="Center" />
+ <Button
+ Grid.Row="2"
+ Grid.Column="1"
+ Margin="0"
+ HorizontalAlignment="Center"
+ Background="Transparent"
+ Click="Button_OnClick"
+ Tag="https://www.ryujinx.org/">
+ <TextBlock
+ Text="www.ryujinx.org"
+ TextAlignment="Center"
+ TextDecorations="Underline"
+ ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}" />
+ </Button>
+ </Grid>
+ <TextBlock
+ Grid.Row="1"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{Binding Version}"
+ TextAlignment="Center" />
+ <TextBlock
+ Grid.Row="2"
+ Margin="20"
+ HorizontalAlignment="Center"
+ MaxLines="2"
+ Text="{locale:Locale AboutDisclaimerMessage}"
+ TextAlignment="Center" />
+ <TextBlock
+ Name="AmiiboLabel"
+ Grid.Row="3"
+ Margin="20"
+ HorizontalAlignment="Center"
+ MaxLines="2"
+ PointerPressed="AmiiboLabel_OnPointerPressed"
+ Text="{locale:Locale AboutAmiiboDisclaimerMessage}"
+ TextAlignment="Center" />
+ <StackPanel
+ Grid.Row="4"
+ HorizontalAlignment="Center"
+ Orientation="Horizontal"
+ Spacing="10">
+ <StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}">
+ <Button
+ Height="65"
+ Background="Transparent"
+ Click="Button_OnClick"
+ Tag="https://www.patreon.com/ryujinx">
+ <Grid>
+ <Grid.RowDefinitions>
+ <RowDefinition />
+ <RowDefinition Height="Auto" />
+ </Grid.RowDefinitions>
+ <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Patreon.png?assembly=Ryujinx.Ui.Common" />
+ <TextBlock
+ Grid.Row="1"
+ Margin="0,5,0,0"
+ HorizontalAlignment="Center"
+ Text="Patreon" />
+ </Grid>
+ </Button>
+ </StackPanel>
+ <StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
+ <Button
+ Height="65"
+ Background="Transparent"
+ Click="Button_OnClick"
+ Tag="https://github.com/Ryujinx/Ryujinx">
+ <Grid>
+ <Grid.RowDefinitions>
+ <RowDefinition />
+ <RowDefinition Height="Auto" />
+ </Grid.RowDefinitions>
+ <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_GitHub.png?assembly=Ryujinx.Ui.Common" />
+ <TextBlock
+ Grid.Row="1"
+ Margin="0,5,0,0"
+ HorizontalAlignment="Center"
+ Text="GitHub" />
+ </Grid>
+ </Button>
+ </StackPanel>
+ <StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
+ <Button
+ Height="65"
+ Background="Transparent"
+ Click="Button_OnClick"
+ Tag="https://discordapp.com/invite/N2FmfVc">
+ <Grid>
+ <Grid.RowDefinitions>
+ <RowDefinition />
+ <RowDefinition Height="Auto" />
+ </Grid.RowDefinitions>
+ <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Discord.png?assembly=Ryujinx.Ui.Common" />
+ <TextBlock
+ Grid.Row="1"
+ Margin="0,5,0,0"
+ HorizontalAlignment="Center"
+ Text="Discord" />
+ </Grid>
+ </Button>
+ </StackPanel>
+ <StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}">
+ <Button
+ Height="65"
+ Background="Transparent"
+ Click="Button_OnClick"
+ Tag="https://twitter.com/RyujinxEmu">
+ <Grid>
+ <Grid.RowDefinitions>
+ <RowDefinition />
+ <RowDefinition Height="Auto" />
+ </Grid.RowDefinitions>
+ <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Twitter.png?assembly=Ryujinx.Ui.Common" />
+ <TextBlock
+ Grid.Row="1"
+ Margin="0,5,0,0"
+ HorizontalAlignment="Center"
+ Text="Twitter" />
+ </Grid>
+ </Button>
+ </StackPanel>
+ </StackPanel>
+ </Grid>
+ <Border
+ Grid.Row="1"
+ Grid.Column="1"
+ Width="2"
+ Margin="5"
+ VerticalAlignment="Stretch"
+ BorderBrush="White"
+ BorderThickness="1,0,0,0">
+ <Separator Width="0" />
+ </Border>
+ <Grid
+ Grid.Row="1"
+ Grid.Column="2"
+ Margin="20"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ </Grid.RowDefinitions>
+ <TextBlock
+ FontWeight="Bold"
+ Text="{locale:Locale AboutRyujinxAboutTitle}"
+ TextDecorations="Underline" />
+ <TextBlock
+ Grid.Row="1"
+ Margin="20,5,5,5"
+ LineHeight="20"
+ Text="{locale:Locale AboutRyujinxAboutContent}" />
+ <TextBlock
+ Grid.Row="2"
+ Margin="0,10,0,0"
+ FontWeight="Bold"
+ Text="{locale:Locale AboutRyujinxMaintainersTitle}"
+ TextDecorations="Underline" />
+ <TextBlock
+ Grid.Row="3"
+ Margin="20,5,5,5"
+ LineHeight="20"
+ Text="{Binding Developers}" />
+ <Button
+ Grid.Row="4"
+ HorizontalAlignment="Right"
+ Background="Transparent"
+ Click="Button_OnClick"
+ Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a">
+ <TextBlock
+ Text="{locale:Locale AboutRyujinxContributorsButtonHeader}"
+ TextAlignment="Right"
+ TextDecorations="Underline"
+ ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" />
+ </Button>
+ <TextBlock
+ Grid.Row="5"
+ Margin="0,0,0,0"
+ FontWeight="Bold"
+ Text="{locale:Locale AboutRyujinxSupprtersTitle}"
+ TextDecorations="Underline" />
+ <Border
+ Grid.Row="6"
+ Width="460"
+ Height="200"
+ Margin="20,5"
+ Padding="5"
+ VerticalAlignment="Stretch"
+ BorderBrush="White"
+ BorderThickness="1">
+ <TextBlock
+ Name="SupportersTextBlock"
+ VerticalAlignment="Top"
+ Text="{Binding Supporters}"
+ TextWrapping="Wrap" />
+ </Border>
+ </Grid>
+ </Grid>
+</window:StyleableWindow>
diff --git a/Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs b/Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs
new file mode 100644
index 00000000..2fb17e3a
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs
@@ -0,0 +1,78 @@
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Threading;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Common.Utilities;
+using Ryujinx.Ui.Common.Helper;
+using System.Net.Http;
+using System.Net.NetworkInformation;
+using System.Threading.Tasks;
+
+namespace Ryujinx.Ava.UI.Windows
+{
+ public partial class AboutWindow : StyleableWindow
+ {
+ public AboutWindow()
+ {
+ if (Program.PreviewerDetached)
+ {
+ Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["MenuBarHelpAbout"];
+ }
+
+ Version = Program.Version;
+
+ DataContext = this;
+
+ InitializeComponent();
+
+ _ = DownloadPatronsJson();
+ }
+
+ public string Supporters { get; set; }
+ public string Version { get; set; }
+
+ public string Developers => string.Format(LocaleManager.Instance["AboutPageDeveloperListMore"], "gdkchan, Ac_K, Thog, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, Xpl0itR, GoffyDude, »jD«");
+
+ private void Button_OnClick(object sender, RoutedEventArgs e)
+ {
+ if (sender is Button button)
+ {
+ OpenHelper.OpenUrl(button.Tag.ToString());
+ }
+ }
+
+ private async Task DownloadPatronsJson()
+ {
+ if (!NetworkInterface.GetIsNetworkAvailable())
+ {
+ Supporters = LocaleManager.Instance["ConnectionError"];
+
+ return;
+ }
+
+ HttpClient httpClient = new();
+
+ try
+ {
+ string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/");
+
+ Supporters = string.Join(", ", JsonHelper.Deserialize<string[]>(patreonJsonString));
+ }
+ catch
+ {
+ Supporters = LocaleManager.Instance["ApiError"];
+ }
+
+ await Dispatcher.UIThread.InvokeAsync(() => SupportersTextBlock.Text = Supporters);
+ }
+
+ private void AmiiboLabel_OnPointerPressed(object sender, PointerPressedEventArgs e)
+ {
+ if (sender is TextBlock)
+ {
+ OpenHelper.OpenUrl("https://amiiboapi.com");
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml b/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml
new file mode 100644
index 00000000..90d47b8e
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml
@@ -0,0 +1,74 @@
+<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"
+ xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
+ xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
+ xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
+ mc:Ignorable="d"
+ d:DesignWidth="400"
+ d:DesignHeight="350"
+ x:Class="Ryujinx.Ava.UI.Windows.AmiiboWindow"
+ CanResize="False"
+ WindowStartupLocation="CenterOwner"
+ Width="800"
+ MinHeight="650"
+ Height="650"
+ SizeToContent="Manual"
+ MinWidth="600"
+ Focusable="True">
+ <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..68d16f35
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml.cs
@@ -0,0 +1,59 @@
+using Avalonia.Interactivity;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.UI.Models;
+using Ryujinx.Ava.UI.ViewModels;
+
+namespace Ryujinx.Ava.UI.Windows
+{
+ public partial class AmiiboWindow : StyleableWindow
+ {
+ public AmiiboWindow(bool showAll, string lastScannedAmiiboId, string titleId)
+ {
+ ViewModel = new AmiiboWindowViewModel(this, lastScannedAmiiboId, titleId);
+
+ ViewModel.ShowAllAmiibo = showAll;
+
+ DataContext = ViewModel;
+
+ InitializeComponent();
+
+ Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["Amiibo"];
+ }
+
+ public AmiiboWindow()
+ {
+ ViewModel = new AmiiboWindowViewModel(this, string.Empty, string.Empty);
+
+ DataContext = ViewModel;
+
+ InitializeComponent();
+
+ 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 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..c90ce022
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/AvatarWindow.axaml
@@ -0,0 +1,54 @@
+<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"
+ xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
+ xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
+ xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
+ mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350"
+ x:Class="Ryujinx.Ava.UI.Windows.AvatarWindow"
+ Margin="0"
+ Padding="0"
+ x:CompileBindings="True"
+ x:DataType="viewModels:AvatarProfileViewModel"
+ Focusable="True">
+ <Design.DataContext>
+ <viewModels:AvatarProfileViewModel />
+ </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" MaxWidth="700" 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 Discard}" Click="CloseButton_OnClick"
+ Name="CloseButton"
+ Width="200" />
+ </StackPanel>
+ </Grid>
+</UserControl> \ 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..e060d65e
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/AvatarWindow.axaml.cs
@@ -0,0 +1,77 @@
+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;
+
+namespace Ryujinx.Ava.UI.Windows
+{
+ public partial class AvatarWindow : UserControl
+ {
+ private NavigationDialogHost _parent;
+ private TempProfile _profile;
+
+ public AvatarWindow(ContentManager contentManager)
+ {
+ ContentManager = contentManager;
+
+ DataContext = ViewModel;
+
+ InitializeComponent();
+ }
+
+ public AvatarWindow()
+ {
+ 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 AvatarProfileViewModel(() => ViewModel.ReloadImages());
+ }
+
+ DataContext = ViewModel;
+ }
+ }
+ }
+
+ public ContentManager ContentManager { get; private set; }
+
+ internal AvatarProfileViewModel ViewModel { get; set; }
+
+ private void CloseButton_OnClick(object sender, RoutedEventArgs e)
+ {
+ ViewModel.Dispose();
+
+ _parent.GoBack();
+ }
+
+ private void ChooseButton_OnClick(object sender, RoutedEventArgs e)
+ {
+ if (ViewModel.SelectedIndex > -1)
+ {
+ _profile.Image = ViewModel.SelectedImage;
+
+ ViewModel.Dispose();
+
+ _parent.GoBack();
+ }
+ }
+ }
+} \ 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..3557ed69
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/CheatWindow.axaml
@@ -0,0 +1,106 @@
+<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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:model="clr-namespace:Ryujinx.Ava.UI.Models"
+ xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
+ Width="500"
+ Height="500"
+ MinWidth="500"
+ MinHeight="500"
+ WindowStartupLocation="CenterOwner"
+ mc:Ignorable="d"
+ Focusable="True">
+ <Window.Styles>
+ <Style Selector="TreeViewItem">
+ <Setter Property="IsExpanded" Value="True" />
+ </Style>
+ </Window.Styles>
+ <Grid Name="CheatGrid" Margin="15">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="*" />
+ <RowDefinition Height="Auto" />
+ </Grid.RowDefinitions>
+ <TextBlock
+ Grid.Row="1"
+ MaxWidth="500"
+ Margin="20,15,20,20"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ LineHeight="18"
+ Text="{Binding Heading}"
+ TextAlignment="Center"
+ TextWrapping="Wrap" />
+ <Border
+ Grid.Row="2"
+ Margin="5"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ BorderBrush="Gray"
+ BorderThickness="1">
+ <TreeView
+ Name="CheatsView"
+ MinHeight="300"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ Items="{Binding LoadedCheats}">
+ <TreeView.Styles>
+ <Styles>
+ <Style Selector="TreeViewItem:empty /template/ ItemsPresenter">
+ <Setter Property="IsVisible" Value="False" />
+ </Style>
+ </Styles>
+ </TreeView.Styles>
+ <TreeView.DataTemplates>
+ <TreeDataTemplate DataType="model:CheatsList" ItemsSource="{Binding}">
+ <StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
+ <CheckBox MinWidth="20" IsChecked="{Binding IsEnabled}" />
+ <TextBlock Width="150" Text="{Binding BuildId}" />
+ <TextBlock Text="{Binding Path}" />
+ </StackPanel>
+ </TreeDataTemplate>
+ <DataTemplate x:DataType="model:CheatModel">
+ <StackPanel
+ Margin="0"
+ HorizontalAlignment="Left"
+ Orientation="Horizontal">
+ <CheckBox
+ MinWidth="20"
+ Margin="5,0"
+ Padding="0"
+ IsChecked="{Binding IsEnabled}" />
+ <TextBlock VerticalAlignment="Center" 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"
+ Command="{Binding Save}"
+ IsVisible="{Binding !NoCheatsFound}">
+ <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..d31212c1
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs
@@ -0,0 +1,119 @@
+using Avalonia.Collections;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.UI.Models;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.HOS;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace Ryujinx.Ava.UI.Windows
+{
+ public partial 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();
+
+ 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();
+
+ 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"];
+ }
+
+ 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/ContentDialogOverlayWindow.axaml b/Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml
new file mode 100644
index 00000000..6cdcae2b
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml
@@ -0,0 +1,29 @@
+<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"
+ xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+ xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
+ mc:Ignorable="d"
+ d:DesignWidth="800"
+ d:DesignHeight="450"
+ x:Class="Ryujinx.Ava.UI.Windows.ContentDialogOverlayWindow"
+ Title="ContentDialogOverlayWindow"
+ Focusable="True">
+ <window:StyleableWindow.Styles>
+ <Style Selector="ui|ContentDialog /template/ Panel#LayoutRoot">
+ <Setter Property="Background"
+ Value="Transparent" />
+ </Style>
+ </window:StyleableWindow.Styles>
+ <ContentControl
+ Focusable="False"
+ IsVisible="False"
+ KeyboardNavigation.IsTabStop="False">
+ <ui:ContentDialog Name="ContentDialog"
+ IsPrimaryButtonEnabled="True"
+ IsSecondaryButtonEnabled="True"
+ IsVisible="False" />
+ </ContentControl>
+</window:StyleableWindow>
diff --git a/Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml.cs b/Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml.cs
new file mode 100644
index 00000000..3f77124d
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml.cs
@@ -0,0 +1,25 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+
+namespace Ryujinx.Ava.UI.Windows
+{
+ public partial class ContentDialogOverlayWindow : StyleableWindow
+ {
+ public ContentDialogOverlayWindow()
+ {
+ InitializeComponent();
+#if DEBUG
+ this.AttachDevTools();
+#endif
+ ExtendClientAreaToDecorationsHint = true;
+ TransparencyLevelHint = WindowTransparencyLevel.Transparent;
+ WindowStartupLocation = WindowStartupLocation.Manual;
+ SystemDecorations = SystemDecorations.None;
+ ExtendClientAreaTitleBarHeightHint = 0;
+ Background = Brushes.Transparent;
+ CanResize = false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Ava/UI/Windows/ControllerSettingsWindow.axaml b/Ryujinx.Ava/UI/Windows/ControllerSettingsWindow.axaml
new file mode 100644
index 00000000..f6bb1aa4
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/ControllerSettingsWindow.axaml
@@ -0,0 +1,1169 @@
+<UserControl
+ x:Class="Ryujinx.Ava.UI.Windows.ControllerSettingsWindow"
+ xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+ xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
+ xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ d:DesignHeight="800"
+ d:DesignWidth="800"
+ x:CompileBindings="False"
+ mc:Ignorable="d"
+ Focusable="True">
+ <Design.DataContext>
+ <viewModels:ControllerSettingsViewModel />
+ </Design.DataContext>
+ <UserControl.Resources>
+ <helpers:KeyValueConverter x:Key="Key" />
+ </UserControl.Resources>
+ <StackPanel
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ Orientation="Vertical">
+ <Grid Margin="2,2,2,5" HorizontalAlignment="Stretch">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition Width="*" />
+ <ColumnDefinition Width="0.5*" />
+ <ColumnDefinition Width="Auto" />
+ </Grid.ColumnDefinitions>
+ <Border
+ Grid.Column="0"
+ Margin="0,0,2,0"
+ BorderBrush="{DynamicResource ThemeControlBorderColor}"
+ BorderThickness="1"
+ Padding="2,0">
+ <StackPanel
+ Margin="2"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Orientation="Vertical">
+ <TextBlock
+ Margin="0,0,0,4"
+ HorizontalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsPlayer}" />
+ <ComboBox
+ Name="PlayerIndexBox"
+ Width="150"
+ SelectionChanged="PlayerIndexBox_OnSelectionChanged"
+ Items="{Binding PlayerIndexes}"
+ SelectedIndex="{Binding PlayerId}">
+ <ComboBox.ItemTemplate>
+ <DataTemplate>
+ <TextBlock Text="{Binding Name}" />
+ </DataTemplate>
+ </ComboBox.ItemTemplate>
+ </ComboBox>
+ </StackPanel>
+ </Border>
+
+ <!-- Main Controller Settings -->
+ <Border
+ Grid.Column="1"
+ Margin="0,0,2,0"
+ BorderBrush="{DynamicResource ThemeControlBorderColor}"
+ BorderThickness="1"
+ Padding="2,0">
+ <StackPanel
+ Margin="2"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Center"
+ Orientation="Vertical">
+ <TextBlock
+ Margin="0,0,0,5"
+ HorizontalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsInputDevice}" />
+ <Grid HorizontalAlignment="Stretch">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition />
+ <ColumnDefinition Width="Auto" />
+ </Grid.ColumnDefinitions>
+ <ComboBox
+ Name="DeviceBox"
+ HorizontalAlignment="Stretch"
+ Items="{Binding DeviceList}"
+ SelectedIndex="{Binding Device}" />
+ <Button
+ Grid.Column="1"
+ MinWidth="0"
+ Margin="5,0,0,0"
+ VerticalAlignment="Center"
+ Command="{Binding LoadDevices}">
+ <ui:SymbolIcon
+ Symbol="Refresh"
+ FontSize="15"
+ Height="20" />
+ </Button>
+ </Grid>
+ </StackPanel>
+ </Border>
+ <Border
+ Grid.Column="2"
+ Margin="0,0,2,0"
+ BorderBrush="{DynamicResource ThemeControlBorderColor}"
+ BorderThickness="1"
+ Padding="2,0">
+ <Grid
+ Margin="2"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Center">
+ <Grid.RowDefinitions>
+ <RowDefinition />
+ <RowDefinition />
+ </Grid.RowDefinitions>
+ <TextBlock
+ Margin="0,0,0,4"
+ HorizontalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsControllerType}" />
+ <ComboBox
+ Grid.Row="1"
+ HorizontalAlignment="Stretch"
+ Items="{Binding Controllers}"
+ SelectedIndex="{Binding Controller}">
+ <ComboBox.ItemTemplate>
+ <DataTemplate>
+ <TextBlock Text="{Binding Name}" />
+ </DataTemplate>
+ </ComboBox.ItemTemplate>
+ </ComboBox>
+ </Grid>
+ </Border>
+ <Border
+ Grid.Column="3"
+ BorderBrush="{DynamicResource ThemeControlBorderColor}"
+ BorderThickness="1"
+ Padding="2,0" >
+ <StackPanel
+ Margin="2"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Orientation="Vertical">
+ <TextBlock
+ Margin="0,0,0,4"
+ HorizontalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsProfile}" />
+ <StackPanel Orientation="Horizontal">
+ <ui:ComboBox
+ IsEditable="True"
+ Name="ProfileBox"
+ Width="100"
+ SelectedIndex="0"
+ Items="{Binding ProfilesList}"
+ Text="{Binding ProfileName}" />
+ <Button
+ MinWidth="0"
+ Margin="5,0,0,0"
+ VerticalAlignment="Center"
+ ToolTip.Tip="{locale:Locale ControllerSettingsLoadProfileToolTip}"
+ Command="{Binding LoadProfile}">
+ <ui:SymbolIcon
+ Symbol="Upload"
+ FontSize="15"
+ Height="20" />
+ </Button>
+ <Button
+ MinWidth="0"
+ Margin="5,0,0,0"
+ VerticalAlignment="Center"
+ ToolTip.Tip="{locale:Locale ControllerSettingsSaveProfileToolTip}"
+ Command="{Binding SaveProfile}">
+ <ui:SymbolIcon
+ Symbol="Save"
+ FontSize="15"
+ Height="20" />
+ </Button>
+ <Button
+ MinWidth="0"
+ Margin="5,0,0,0"
+ VerticalAlignment="Center"
+ ToolTip.Tip="{locale:Locale ControllerSettingsRemoveProfileToolTip}"
+ Command="{Binding RemoveProfile}">
+ <ui:SymbolIcon
+ Symbol="Delete"
+ FontSize="15"
+ Height="20" />
+ </Button>
+ </StackPanel>
+ </StackPanel>
+ </Border>
+ </Grid>
+
+ <!-- Button / JoyStick Settings -->
+ <Grid
+ Name="SettingButtons"
+ MinHeight="450"
+ IsVisible="{Binding ShowSettings}">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition Width="*" />
+ <ColumnDefinition Width="Auto" />
+ </Grid.ColumnDefinitions>
+
+ <!-- Left -->
+ <Grid
+ Margin="0,0,10,0"
+ Grid.Column="0"
+ VerticalAlignment="Stretch"
+ DockPanel.Dock="Left">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ </Grid.RowDefinitions>
+
+ <!-- Left Triggers -->
+ <Border
+ Grid.Row="0"
+ BorderBrush="{DynamicResource ThemeControlBorderColor}"
+ BorderThickness="1"
+ IsVisible="{Binding IsLeft}">
+ <Grid Margin="10" HorizontalAlignment="Stretch">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition />
+ <ColumnDefinition />
+ </Grid.ColumnDefinitions>
+ <Grid.RowDefinitions>
+ <RowDefinition />
+ <RowDefinition />
+ </Grid.RowDefinitions>
+ <StackPanel
+ Margin="0,0,0,4"
+ Grid.Column="0"
+ Grid.Row="0"
+ Background="{DynamicResource ThemeDarkColor}"
+ Orientation="Horizontal">
+ <TextBlock
+ Width="20"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsTriggerZL}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.ButtonZl, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <StackPanel
+ Grid.Column="0"
+ Grid.Row="1"
+ Background="{DynamicResource ThemeDarkColor}"
+ Orientation="Horizontal">
+ <TextBlock
+ Width="20"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsTriggerL}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.ButtonL, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <StackPanel
+ Margin="0,0,0,4"
+ Grid.Column="1"
+ Grid.Row="0"
+ Background="{DynamicResource ThemeDarkColor}"
+ Orientation="Horizontal">
+ <TextBlock
+ Width="20"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsButtonMinus}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.ButtonMinus, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ </Grid>
+ </Border>
+
+ <!-- Left Joystick -->
+ <Border
+ Grid.Row="1"
+ BorderBrush="{DynamicResource ThemeControlBorderColor}"
+ BorderThickness="1"
+ IsVisible="{Binding IsLeft}">
+ <StackPanel Margin="10" Orientation="Vertical">
+ <TextBlock
+ Margin="0,0,0,10"
+ HorizontalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsLStick}" />
+
+ <!-- Left Joystick Keyboard -->
+ <StackPanel IsVisible="{Binding !IsController}" Orientation="Vertical">
+
+ <!-- Left Joystick Button -->
+ <StackPanel Margin="0,0,0,4" Background="{DynamicResource ThemeDarkColor}" Orientation="Horizontal">
+ <TextBlock
+ Margin="0,0,10,0"
+ Width="120"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsLStickButton}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.LeftKeyboardStickButton, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+
+ <!-- Left Joystick Up -->
+ <StackPanel Margin="0,0,0,4" Background="{DynamicResource ThemeDarkColor}" Orientation="Horizontal">
+ <TextBlock
+ Margin="0,0,10,0"
+ Width="120"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsLStickUp}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.LeftStickUp, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+
+ <!-- Left Joystick Down -->
+ <StackPanel Margin="0,0,0,4" Background="{DynamicResource ThemeDarkColor}" Orientation="Horizontal">
+ <TextBlock
+ Margin="0,0,10,0"
+ Width="120"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsLStickDown}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.LeftStickDown, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+
+ <!-- Left Joystick Left -->
+ <StackPanel Margin="0,0,0,4" Background="{DynamicResource ThemeDarkColor}" Orientation="Horizontal">
+ <TextBlock
+ Margin="0,0,10,0"
+ Width="120"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsLStickLeft}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.LeftStickLeft, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+
+ <!-- Left Joystick Right -->
+ <StackPanel Margin="0,0,0,4" Background="{DynamicResource ThemeDarkColor}" Orientation="Horizontal">
+ <TextBlock
+ Margin="0,0,10,0"
+ Width="120"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsLStickRight}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.LeftStickRight, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ </StackPanel>
+
+ <!-- Left Joystick Controller -->
+ <StackPanel IsVisible="{Binding IsController}" Orientation="Vertical">
+
+ <!-- Left Joystick Button -->
+ <StackPanel Background="{DynamicResource ThemeDarkColor}" Orientation="Horizontal">
+ <TextBlock
+ Margin="0,0,10,0"
+ Width="120"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsLStickButton}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.LeftControllerStickButton, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+
+ <!-- Left Joystick Stick -->
+ <StackPanel Margin="0,4,0,4" Background="{DynamicResource ThemeDarkColor}" Orientation="Horizontal">
+ <TextBlock
+ Margin="0,0,10,0"
+ Width="120"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsLStickStick}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch"
+ Tag="stick">
+ <TextBlock
+ Text="{Binding Configuration.LeftJoystick, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <CheckBox IsChecked="{Binding Configuration.LeftInvertStickX}">
+ <TextBlock Text="{locale:Locale ControllerSettingsLStickInvertXAxis}" />
+ </CheckBox>
+ <CheckBox IsChecked="{Binding Configuration.LeftInvertStickY}">
+ <TextBlock Text="{locale:Locale ControllerSettingsLStickInvertYAxis}" />
+ </CheckBox>
+ <CheckBox IsChecked="{Binding Configuration.LeftRotate90}">
+ <TextBlock Text="{locale:Locale ControllerSettingsRotate90}" />
+ </CheckBox>
+ <Separator Margin="0,4,0,4" Height="1" />
+ <StackPanel Orientation="Vertical">
+ <TextBlock Text="{locale:Locale ControllerSettingsLStickDeadzone}" />
+ <StackPanel
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Orientation="Horizontal">
+ <Slider
+ Width="130"
+ Maximum="1"
+ TickFrequency="0.01"
+ IsSnapToTickEnabled="True"
+ Minimum="0"
+ Value="{Binding Configuration.DeadzoneLeft, Mode=TwoWay}" />
+ <TextBlock
+ VerticalAlignment="Center"
+ Text="{Binding Configuration.DeadzoneLeft, StringFormat=\{0:0.00\}}" />
+ </StackPanel>
+ <TextBlock Text="{locale:Locale ControllerSettingsStickRange}" />
+ <StackPanel
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Orientation="Horizontal">
+ <Slider
+ Width="130"
+ Maximum="2"
+ TickFrequency="0.01"
+ IsSnapToTickEnabled="True"
+ Minimum="0"
+ Value="{Binding Configuration.RangeLeft, Mode=TwoWay}" />
+ <TextBlock
+ VerticalAlignment="Center"
+ Text="{Binding Configuration.RangeLeft, StringFormat=\{0:0.00\}}" />
+ </StackPanel>
+ </StackPanel>
+ </StackPanel>
+ </StackPanel>
+ </Border>
+
+ <!-- Left DPad -->
+ <Border
+ Grid.Row="2"
+ BorderBrush="{DynamicResource ThemeControlBorderColor}"
+ BorderThickness="1"
+ VerticalAlignment="Top"
+ IsVisible="{Binding IsLeft}">
+ <StackPanel Margin="10" Orientation="Vertical">
+ <TextBlock
+ Margin="0,0,0,10"
+ HorizontalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsDPad}" />
+ <StackPanel Orientation="Vertical">
+ <!-- Left DPad Up -->
+ <StackPanel Margin="0,0,0,4" Background="{DynamicResource ThemeDarkColor}" Orientation="Horizontal">
+ <TextBlock
+ Margin="0,0,10,0"
+ Width="120"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsDPadUp}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.DpadUp, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+
+ <!-- Left DPad Down -->
+ <StackPanel Margin="0,0,0,4" Background="{DynamicResource ThemeDarkColor}" Orientation="Horizontal">
+ <TextBlock
+ Margin="0,0,10,0"
+ Width="120"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsDPadDown}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.DpadDown, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+
+ <!-- Left DPad Left -->
+ <StackPanel Margin="0,0,0,4" Background="{DynamicResource ThemeDarkColor}" Orientation="Horizontal">
+ <TextBlock
+ Margin="0,0,10,0"
+ Width="120"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsDPadLeft}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.DpadLeft, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+
+ <!-- Left DPad Right -->
+ <StackPanel Margin="0,0,0,4" Background="{DynamicResource ThemeDarkColor}" Orientation="Horizontal">
+ <TextBlock
+ Margin="0,0,10,0"
+ Width="120"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsDPadRight}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.DpadRight, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ </StackPanel>
+ </StackPanel>
+ </Border>
+ </Grid>
+
+ <!-- Triggers And Side Buttons-->
+ <StackPanel Grid.Column="1" HorizontalAlignment="Stretch">
+ <Border
+ BorderBrush="{DynamicResource ThemeControlBorderColor}"
+ BorderThickness="1">
+ <StackPanel Margin="10" Orientation="Vertical">
+ <TextBlock HorizontalAlignment="Center" Text="{locale:Locale ControllerSettingsTriggerThreshold}" />
+ <StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
+ <Slider
+ Width="130"
+ Maximum="1"
+ TickFrequency="0.01"
+ IsSnapToTickEnabled="True"
+ Minimum="0"
+ Value="{Binding Configuration.TriggerThreshold, Mode=TwoWay}" />
+ <TextBlock Text="{Binding Configuration.TriggerThreshold, StringFormat=\{0:0.00\}}" />
+ </StackPanel>
+ <StackPanel
+ Margin="0,4,0,0"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Background="{DynamicResource ThemeDarkColor}"
+ IsVisible="{Binding !IsRight}"
+ Orientation="Horizontal">
+ <TextBlock
+ Width="20"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsLeftSR}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.LeftButtonSr, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <StackPanel
+ Margin="0,4,0,0"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ IsVisible="{Binding !IsRight}"
+ Background="{DynamicResource ThemeDarkColor}"
+ Orientation="Horizontal">
+ <TextBlock
+ Width="20"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsRightSL}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.RightButtonSl, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <StackPanel
+ Margin="0,4,0,0"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ IsVisible="{Binding !IsLeft}"
+ Background="{DynamicResource ThemeDarkColor}"
+ Orientation="Horizontal">
+ <TextBlock
+ Width="20"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsRightSR}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.RightButtonSr, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <StackPanel
+ Margin="0,4,0,0"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ IsVisible="{Binding !IsLeft}"
+ Background="{DynamicResource ThemeDarkColor}"
+ Orientation="Horizontal">
+ <TextBlock
+ Width="20"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsRightSL}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.RightButtonSl, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ </StackPanel>
+ </Border>
+
+ <!-- Controller Picture -->
+ <Image
+ Margin="0,10,0,0"
+ MaxHeight="250"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Top"
+ Source="{Binding Image}" />
+
+ <!-- Motion+Rumble -->
+ <StackPanel Margin="0,10,0,0" Orientation="Vertical" >
+ <Border
+ BorderBrush="{DynamicResource ThemeControlBorderColor}"
+ BorderThickness="1"
+ HorizontalAlignment="Stretch"
+ IsVisible="{Binding IsController}">
+ <Grid>
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="*" />
+ <ColumnDefinition Width="Auto" />
+ </Grid.ColumnDefinitions>
+ <CheckBox
+ Margin="10"
+ MinWidth="0"
+ Grid.Column="0"
+ IsChecked="{Binding Configuration.EnableMotion, Mode=TwoWay}">
+ <TextBlock Text="{locale:Locale ControllerSettingsMotion}" />
+ </CheckBox>
+ <Button Margin="10" Grid.Column="1" Command="{Binding ShowMotionConfig}">
+ <TextBlock Text="{locale:Locale ControllerSettingsConfigureGeneral}" />
+ </Button>
+ </Grid>
+ </Border>
+ <Border
+ BorderBrush="{DynamicResource ThemeControlBorderColor}"
+ BorderThickness="1"
+ HorizontalAlignment="Stretch"
+ IsVisible="{Binding IsController}">
+ <Grid>
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="*" />
+ <ColumnDefinition Width="Auto" />
+ </Grid.ColumnDefinitions>
+ <CheckBox
+ Margin="10"
+ MinWidth="0"
+ Grid.Column="0"
+ IsChecked="{Binding Configuration.EnableRumble, Mode=TwoWay}">
+ <TextBlock Text="{locale:Locale ControllerSettingsRumble}" />
+ </CheckBox>
+ <Button Margin="10" Grid.Column="1" Command="{Binding ShowRumbleConfig}">
+ <TextBlock Text="{locale:Locale ControllerSettingsConfigureGeneral}" />
+ </Button>
+ </Grid>
+ </Border>
+ </StackPanel>
+ </StackPanel>
+
+ <!--Right Controls-->
+ <Grid
+ Margin="10,0,0,0"
+ Grid.Column="2"
+ VerticalAlignment="Top"
+ HorizontalAlignment="Stretch" >
+ <Grid.RowDefinitions>
+ <RowDefinition />
+ <RowDefinition />
+ <RowDefinition />
+ <RowDefinition />
+ </Grid.RowDefinitions>
+ <Border
+ Grid.Row="0"
+ BorderBrush="{DynamicResource ThemeControlBorderColor}"
+ BorderThickness="1"
+ IsVisible="{Binding IsRight}">
+ <StackPanel Margin="10" Orientation="Vertical">
+ <Grid HorizontalAlignment="Stretch">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition />
+ <ColumnDefinition />
+ </Grid.ColumnDefinitions>
+ <Grid.RowDefinitions>
+ <RowDefinition />
+ <RowDefinition />
+ </Grid.RowDefinitions>
+ <StackPanel
+ Margin="0,0,0,4"
+ Grid.Column="1"
+ Grid.Row="0"
+ Background="{DynamicResource ThemeDarkColor}"
+ Orientation="Horizontal">
+ <TextBlock
+ Width="20"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsTriggerZR}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.ButtonZr, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <StackPanel
+ Grid.Column="1"
+ Grid.Row="1"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Background="{DynamicResource ThemeDarkColor}"
+ Orientation="Horizontal">
+ <TextBlock
+ Width="20"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsTriggerR}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.ButtonR, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <StackPanel
+ Margin="0,0,8,4"
+ Grid.Column="0"
+ Grid.Row="0"
+ HorizontalAlignment="Right"
+ VerticalAlignment="Center"
+ Background="{DynamicResource ThemeDarkColor}"
+ Orientation="Horizontal">
+ <TextBlock
+ Width="20"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsButtonPlus}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.ButtonPlus, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ </Grid>
+ </StackPanel>
+ </Border>
+ <Border
+ Grid.Row="1"
+ BorderBrush="{DynamicResource ThemeControlBorderColor}"
+ BorderThickness="1"
+ IsVisible="{Binding IsRight}">
+ <StackPanel Margin="10" Orientation="Vertical">
+ <TextBlock
+ Margin="0,0,0,10"
+ HorizontalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsButtons}" />
+ <Grid HorizontalAlignment="Stretch">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition />
+ <ColumnDefinition />
+ </Grid.ColumnDefinitions>
+ <Grid.RowDefinitions>
+ <RowDefinition />
+ <RowDefinition />
+ </Grid.RowDefinitions>
+ <!-- Right Buttons X -->
+ <StackPanel
+ Margin="0,0,0,4"
+ Grid.Column="0"
+ Grid.Row="0"
+ Background="{DynamicResource ThemeDarkColor}"
+ Orientation="Horizontal">
+ <TextBlock
+ Width="20"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsButtonX}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.ButtonX, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <!-- Right Buttons Y -->
+ <StackPanel
+ Grid.Column="0"
+ Grid.Row="1"
+ Background="{DynamicResource ThemeDarkColor}"
+ Orientation="Horizontal">
+ <TextBlock
+ Width="20"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsButtonY}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.ButtonY, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <!-- Right Buttons A -->
+ <StackPanel
+ Margin="0,0,0,4"
+ Grid.Column="1"
+ Grid.Row="0"
+ Background="{DynamicResource ThemeDarkColor}"
+ Orientation="Horizontal">
+ <TextBlock
+ Width="20"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsButtonA}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.ButtonA, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <!-- Right Buttons B -->
+ <StackPanel
+ Grid.Column="1"
+ Grid.Row="1"
+ Background="{DynamicResource ThemeDarkColor}"
+ Orientation="Horizontal">
+ <TextBlock
+ Width="20"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsButtonB}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.ButtonB, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ </Grid>
+ </StackPanel>
+ </Border>
+ <Border
+ Grid.Row="2"
+ Padding="10"
+ BorderBrush="{DynamicResource ThemeControlBorderColor}"
+ BorderThickness="1"
+ IsVisible="{Binding IsRight}">
+ <StackPanel Orientation="Vertical">
+ <TextBlock
+ Margin="0,0,0,10"
+ HorizontalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsRStick}" />
+
+ <!-- Right Joystick Keyboard -->
+ <StackPanel IsVisible="{Binding !IsController}" Orientation="Vertical">
+
+ <!-- Right Joystick Button -->
+ <StackPanel Margin="0,0,0,4" Background="{DynamicResource ThemeDarkColor}" Orientation="Horizontal">
+ <TextBlock
+ Margin="0,0,10,0"
+ Width="120"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsRStickButton}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.RightKeyboardStickButton, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+
+ <!-- Right Joystick Up -->
+ <StackPanel Margin="0,0,0,4" Background="{DynamicResource ThemeDarkColor}" Orientation="Horizontal">
+ <TextBlock
+ Margin="0,0,10,0"
+ Width="120"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsRStickUp}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.RightStickUp, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+
+ <!-- Right Joystick Down -->
+ <StackPanel Margin="0,0,0,4" Background="{DynamicResource ThemeDarkColor}" Orientation="Horizontal">
+ <TextBlock
+ Margin="0,0,10,0"
+ Width="120"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsRStickDown}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.RightStickDown, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+
+ <!-- Right Joystick Left -->
+ <StackPanel Margin="0,0,0,4" Background="{DynamicResource ThemeDarkColor}" Orientation="Horizontal">
+ <TextBlock
+ Margin="0,0,10,0"
+ Width="120"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsRStickLeft}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.RightStickLeft, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+
+ <!-- Right Joystick Right -->
+ <StackPanel Margin="0,0,0,4" Background="{DynamicResource ThemeDarkColor}" Orientation="Horizontal">
+ <TextBlock
+ Margin="0,0,10,0"
+ Width="120"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsRStickRight}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.RightStickRight, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ </StackPanel>
+
+ <!-- Right Joystick Controller -->
+ <StackPanel IsVisible="{Binding IsController}" Orientation="Vertical">
+
+ <!-- Right Joystick Button -->
+ <StackPanel Background="{DynamicResource ThemeDarkColor}" Orientation="Horizontal">
+ <TextBlock
+ Margin="0,0,10,0"
+ Width="120"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsRStickButton}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch">
+ <TextBlock
+ Text="{Binding Configuration.RightControllerStickButton, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+
+
+ <!-- Right Joystick Stick -->
+ <StackPanel Margin="0,4,0,4" Background="{DynamicResource ThemeDarkColor}" Orientation="Horizontal">
+ <TextBlock
+ Margin="0,0,10,0"
+ Width="120"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsRStickStick}"
+ TextAlignment="Center" />
+ <ToggleButton
+ Width="90"
+ Height="27"
+ HorizontalAlignment="Stretch"
+ Tag="stick">
+ <TextBlock
+ Text="{Binding Configuration.RightJoystick, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <CheckBox IsChecked="{Binding Configuration.RightInvertStickX}">
+ <TextBlock Text="{locale:Locale ControllerSettingsRStickInvertXAxis}" />
+ </CheckBox>
+ <CheckBox IsChecked="{Binding Configuration.RightInvertStickY}">
+ <TextBlock Text="{locale:Locale ControllerSettingsRStickInvertYAxis}" />
+ </CheckBox>
+ <CheckBox IsChecked="{Binding Configuration.RightRotate90}">
+ <TextBlock Text="{locale:Locale ControllerSettingsRotate90}" />
+ </CheckBox>
+ <Separator Margin="0,4,0,4" Height="1" />
+ <StackPanel Orientation="Vertical">
+ <TextBlock Text="{locale:Locale ControllerSettingsRStickDeadzone}" />
+ <StackPanel
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Orientation="Horizontal">
+ <Slider
+ Width="130"
+ Maximum="1"
+ TickFrequency="0.01"
+ IsSnapToTickEnabled="True"
+ Padding="0"
+ VerticalAlignment="Center"
+ Minimum="0"
+ Value="{Binding Configuration.DeadzoneRight, Mode=TwoWay}" />
+ <TextBlock
+ VerticalAlignment="Center"
+ Text="{Binding Configuration.DeadzoneRight, StringFormat=\{0:0.00\}}" />
+ </StackPanel>
+ <TextBlock Text="{locale:Locale ControllerSettingsStickRange}" />
+ <StackPanel
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Orientation="Horizontal">
+ <Slider
+ Width="130"
+ Maximum="2"
+ TickFrequency="0.01"
+ IsSnapToTickEnabled="True"
+ Minimum="0"
+ Value="{Binding Configuration.RangeRight, Mode=TwoWay}" />
+ <TextBlock
+ VerticalAlignment="Center"
+ Text="{Binding Configuration.RangeRight, StringFormat=\{0:0.00\}}" />
+ </StackPanel>
+ </StackPanel>
+ </StackPanel>
+ </StackPanel>
+ </Border>
+ </Grid>
+
+ </Grid>
+
+ </StackPanel>
+
+</UserControl> \ No newline at end of file
diff --git a/Ryujinx.Ava/UI/Windows/ControllerSettingsWindow.axaml.cs b/Ryujinx.Ava/UI/Windows/ControllerSettingsWindow.axaml.cs
new file mode 100644
index 00000000..df083085
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/ControllerSettingsWindow.axaml.cs
@@ -0,0 +1,181 @@
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.LogicalTree;
+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.Common.Configuration.Hid.Controller;
+using Ryujinx.Input;
+using Ryujinx.Input.Assigner;
+using System;
+
+namespace Ryujinx.Ava.UI.Windows
+{
+ public partial class ControllerSettingsWindow : UserControl
+ {
+ private bool _dialogOpen;
+
+ private ButtonKeyAssigner _currentAssigner;
+ internal ControllerSettingsViewModel ViewModel { get; set; }
+
+ public ControllerSettingsWindow()
+ {
+ DataContext = ViewModel = new ControllerSettingsViewModel(this);
+
+ InitializeComponent();
+
+ foreach (ILogical visual in SettingButtons.GetLogicalDescendants())
+ {
+ if (visual is ToggleButton button && !(visual is CheckBox))
+ {
+ button.Checked += Button_Checked;
+ button.Unchecked += Button_Unchecked;
+ }
+ }
+ }
+
+ protected override void OnPointerReleased(PointerReleasedEventArgs e)
+ {
+ base.OnPointerReleased(e);
+
+ if (_currentAssigner != null && _currentAssigner.ToggledButton != null && !_currentAssigner.ToggledButton.IsPointerOver)
+ {
+ _currentAssigner.Cancel();
+ }
+ }
+
+ private void Button_Checked(object sender, RoutedEventArgs e)
+ {
+ if (sender is ToggleButton button)
+ {
+ if (_currentAssigner != null && button == _currentAssigner.ToggledButton)
+ {
+ return;
+ }
+
+ bool isStick = button.Tag != null && button.Tag.ToString() == "stick";
+
+ if (_currentAssigner == null && (bool)button.IsChecked)
+ {
+ _currentAssigner = new ButtonKeyAssigner(button);
+
+ FocusManager.Instance.Focus(this, NavigationMethod.Pointer);
+
+ PointerPressed += MouseClick;
+
+ IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
+ IButtonAssigner assigner = CreateButtonAssigner(isStick);
+
+ _currentAssigner.ButtonAssigned += (sender, e) =>
+ {
+ if (e.IsAssigned)
+ {
+ ViewModel.IsModified = true;
+ }
+ };
+
+ _currentAssigner.GetInputAndAssign(assigner, keyboard);
+ }
+ else
+ {
+ if (_currentAssigner != null)
+ {
+ ToggleButton oldButton = _currentAssigner.ToggledButton;
+
+ _currentAssigner.Cancel();
+ _currentAssigner = null;
+ button.IsChecked = false;
+ }
+ }
+ }
+ }
+
+ public void SaveCurrentProfile()
+ {
+ ViewModel.Save();
+ }
+
+ private IButtonAssigner CreateButtonAssigner(bool forStick)
+ {
+ IButtonAssigner assigner;
+
+ var device = ViewModel.Devices[ViewModel.Device];
+
+ if (device.Type == DeviceType.Keyboard)
+ {
+ assigner = new KeyboardKeyAssigner((IKeyboard)ViewModel.SelectedGamepad);
+ }
+ else if (device.Type == DeviceType.Controller)
+ {
+ assigner = new GamepadButtonAssigner(ViewModel.SelectedGamepad, (ViewModel.Config as StandardControllerInputConfig).TriggerThreshold, forStick);
+ }
+ else
+ {
+ throw new Exception("Controller not supported");
+ }
+
+ return assigner;
+ }
+
+ private void Button_Unchecked(object sender, RoutedEventArgs e)
+ {
+ _currentAssigner?.Cancel();
+ _currentAssigner = null;
+ }
+
+ private void MouseClick(object sender, PointerPressedEventArgs e)
+ {
+ bool shouldUnbind = false;
+
+ if (e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed)
+ {
+ shouldUnbind = true;
+ }
+
+ _currentAssigner?.Cancel(shouldUnbind);
+
+ PointerPressed -= MouseClick;
+ }
+
+ private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (ViewModel.IsModified && !_dialogOpen)
+ {
+ _dialogOpen = true;
+
+ var result = await ContentDialogHelper.CreateConfirmationDialog(
+ LocaleManager.Instance["DialogControllerSettingsModifiedConfirmMessage"],
+ LocaleManager.Instance["DialogControllerSettingsModifiedConfirmSubMessage"],
+ LocaleManager.Instance["InputDialogYes"],
+ LocaleManager.Instance["InputDialogNo"],
+ LocaleManager.Instance["RyujinxConfirm"]);
+
+ if (result == UserResult.Yes)
+ {
+ ViewModel.Save();
+ }
+
+ _dialogOpen = false;
+
+ ViewModel.IsModified = false;
+
+ if (e.AddedItems.Count > 0)
+ {
+ var player = (PlayerModel)e.AddedItems[0];
+ ViewModel.PlayerId = player.Id;
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ _currentAssigner?.Cancel();
+ _currentAssigner = null;
+ ViewModel.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml b/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml
new file mode 100644
index 00000000..e524d6e4
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml
@@ -0,0 +1,172 @@
+<window:StyleableWindow
+ x:Class="Ryujinx.Ava.UI.Windows.DownloadableContentManagerWindow"
+ xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
+ Width="800"
+ Height="500"
+ MinWidth="800"
+ MinHeight="500"
+ MaxWidth="800"
+ MaxHeight="500"
+ SizeToContent="Height"
+ WindowStartupLocation="CenterOwner"
+ mc:Ignorable="d"
+ Focusable="True">
+ <Grid Name="DownloadableContentGrid" Margin="15">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="*" />
+ <RowDefinition Height="Auto" />
+ </Grid.RowDefinitions>
+ <TextBlock
+ Name="Heading"
+ Grid.Row="1"
+ MaxWidth="500"
+ Margin="20,15,20,20"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ LineHeight="18"
+ TextAlignment="Center"
+ TextWrapping="Wrap" />
+ <DockPanel
+ Grid.Row="2"
+ Margin="0"
+ HorizontalAlignment="Left">
+ <Button
+ Name="EnableAllButton"
+ MinWidth="90"
+ Margin="5"
+ Command="{Binding EnableAll}">
+ <TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" />
+ </Button>
+ <Button
+ Name="DisableAllButton"
+ MinWidth="90"
+ Margin="5"
+ Command="{Binding DisableAll}">
+ <TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" />
+ </Button>
+ </DockPanel>
+ <Border
+ Grid.Row="3"
+ Margin="5"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ BorderBrush="Gray"
+ BorderThickness="1">
+ <ScrollViewer
+ VerticalAlignment="Stretch"
+ HorizontalScrollBarVisibility="Auto"
+ VerticalScrollBarVisibility="Auto">
+ <DataGrid
+ Name="DlcDataGrid"
+ MinHeight="200"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ CanUserReorderColumns="False"
+ CanUserResizeColumns="True"
+ CanUserSortColumns="True"
+ HorizontalScrollBarVisibility="Auto"
+ Items="{Binding _downloadableContents}"
+ SelectionMode="Extended"
+ VerticalScrollBarVisibility="Auto">
+ <DataGrid.Styles>
+ <Styles>
+ <Style Selector="DataGridCell:nth-child(3), DataGridCell:nth-child(4)">
+ <Setter Property="HorizontalAlignment" Value="Left" />
+ <Setter Property="HorizontalContentAlignment" Value="Left" />
+ </Style>
+ </Styles>
+ <Styles>
+ <Style Selector="DataGridCell:nth-child(1)">
+ <Setter Property="HorizontalAlignment" Value="Right" />
+ <Setter Property="HorizontalContentAlignment" Value="Right" />
+ </Style>
+ </Styles>
+ </DataGrid.Styles>
+ <DataGrid.Columns>
+ <DataGridTemplateColumn Width="90">
+ <DataGridTemplateColumn.CellTemplate>
+ <DataTemplate>
+ <CheckBox
+ Width="50"
+ MinWidth="40"
+ HorizontalAlignment="Center"
+ IsChecked="{Binding Enabled}" />
+ </DataTemplate>
+ </DataGridTemplateColumn.CellTemplate>
+ <DataGridTemplateColumn.Header>
+ <TextBlock Text="{locale:Locale DlcManagerTableHeadingEnabledLabel}" />
+ </DataGridTemplateColumn.Header>
+ </DataGridTemplateColumn>
+ <DataGridTextColumn Width="140" Binding="{Binding TitleId}">
+ <DataGridTextColumn.Header>
+ <TextBlock Text="{locale:Locale DlcManagerTableHeadingTitleIdLabel}" />
+ </DataGridTextColumn.Header>
+ </DataGridTextColumn>
+ <DataGridTextColumn Width="280" Binding="{Binding FullPath}">
+ <DataGridTextColumn.Header>
+ <TextBlock Text="{locale:Locale DlcManagerTableHeadingFullPathLabel}" />
+ </DataGridTextColumn.Header>
+ </DataGridTextColumn>
+ <DataGridTextColumn Binding="{Binding ContainerPath}">
+ <DataGridTextColumn.Header>
+ <TextBlock Text="{locale:Locale DlcManagerTableHeadingContainerPathLabel}" />
+ </DataGridTextColumn.Header>
+ </DataGridTextColumn>
+ </DataGrid.Columns>
+ </DataGrid>
+ </ScrollViewer>
+ </Border>
+ <DockPanel
+ Grid.Row="4"
+ 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 SaveAndClose}">
+ <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/DownloadableContentManagerWindow.axaml.cs b/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml.cs
new file mode 100644
index 00000000..11be9383
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml.cs
@@ -0,0 +1,314 @@
+using Avalonia.Collections;
+using Avalonia.Controls;
+using Avalonia.Threading;
+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.Helpers;
+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.IO;
+using System.Linq;
+using System.Reactive.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Path = System.IO.Path;
+
+namespace Ryujinx.Ava.UI.Windows
+{
+ public partial class DownloadableContentManagerWindow : StyleableWindow
+ {
+ private readonly List<DownloadableContentContainer> _downloadableContentContainerList;
+ private readonly string _downloadableContentJsonPath;
+
+ private VirtualFileSystem _virtualFileSystem { get; }
+ private AvaloniaList<DownloadableContentModel> _downloadableContents { get; set; }
+
+ private ulong _titleId { get; }
+ private string _titleName { get; }
+
+ public DownloadableContentManagerWindow()
+ {
+ DataContext = this;
+
+ InitializeComponent();
+
+ Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["DlcWindowTitle"]} - {_titleName} ({_titleId:X16})";
+ }
+
+ public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
+ {
+ _virtualFileSystem = virtualFileSystem;
+ _downloadableContents = new AvaloniaList<DownloadableContentModel>();
+
+ _titleId = titleId;
+ _titleName = titleName;
+
+ _downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
+
+ try
+ {
+ _downloadableContentContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(_downloadableContentJsonPath);
+ }
+ catch
+ {
+ _downloadableContentContainerList = new List<DownloadableContentContainer>();
+ }
+
+ DataContext = this;
+
+ InitializeComponent();
+
+ RemoveButton.IsEnabled = false;
+
+ DlcDataGrid.SelectionChanged += DlcDataGrid_SelectionChanged;
+
+ Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["DlcWindowTitle"]} - {_titleName} ({_titleId:X16})";
+
+ LoadDownloadableContents();
+ PrintHeading();
+ }
+
+ private void DlcDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ RemoveButton.IsEnabled = (DlcDataGrid.SelectedItems.Count > 0);
+ }
+
+ private void PrintHeading()
+ {
+ Heading.Text = string.Format(LocaleManager.Instance["DlcWindowHeading"], _downloadableContents.Count, _titleName, _titleId.ToString("X16"));
+ }
+
+ private void LoadDownloadableContents()
+ {
+ foreach (DownloadableContentContainer downloadableContentContainer in _downloadableContentContainerList)
+ {
+ if (File.Exists(downloadableContentContainer.ContainerPath))
+ {
+ using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
+
+ PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
+
+ _virtualFileSystem.ImportTickets(partitionFileSystem);
+
+ foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
+ {
+ using UniqueRef<IFile> ncaFile = new();
+
+ partitionFileSystem.OpenFile(ref ncaFile.Ref(), downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
+ if (nca != null)
+ {
+ _downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"),
+ downloadableContentContainer.ContainerPath,
+ downloadableContentNca.FullPath,
+ downloadableContentNca.Enabled));
+ }
+ }
+ }
+ }
+
+ // NOTE: Save the list again to remove leftovers.
+ Save();
+ }
+
+ private Nca TryOpenNca(IStorage ncaStorage, string containerPath)
+ {
+ try
+ {
+ return new Nca(_virtualFileSystem.KeySet, ncaStorage);
+ }
+ catch (Exception ex)
+ {
+ Dispatcher.UIThread.InvokeAsync(async () =>
+ {
+ await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogDlcLoadNcaErrorMessage"], ex.Message, containerPath));
+ });
+ }
+
+ return null;
+ }
+
+ private async Task AddDownloadableContent(string path)
+ {
+ if (!File.Exists(path) || _downloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
+ {
+ return;
+ }
+
+ using FileStream containerFile = File.OpenRead(path);
+
+ PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
+ bool containsDownloadableContent = false;
+
+ _virtualFileSystem.ImportTickets(partitionFileSystem);
+
+ foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
+ {
+ using var ncaFile = new UniqueRef<IFile>();
+
+ partitionFileSystem.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
+ if (nca == null)
+ {
+ continue;
+ }
+
+ if (nca.Header.ContentType == NcaContentType.PublicData)
+ {
+ if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
+ {
+ break;
+ }
+
+ _downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true));
+
+ containsDownloadableContent = true;
+ }
+ }
+
+ if (!containsDownloadableContent)
+ {
+ await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]);
+ }
+ }
+
+ private void RemoveDownloadableContents(bool removeSelectedOnly = false)
+ {
+ if (removeSelectedOnly)
+ {
+ AvaloniaList<DownloadableContentModel> removedItems = new();
+
+ foreach (var item in DlcDataGrid.SelectedItems)
+ {
+ removedItems.Add(item as DownloadableContentModel);
+ }
+
+ DlcDataGrid.SelectedItems.Clear();
+
+ foreach (var item in removedItems)
+ {
+ _downloadableContents.RemoveAll(_downloadableContents.Where(x => x.TitleId == item.TitleId).ToList());
+ }
+ }
+ else
+ {
+ _downloadableContents.Clear();
+ }
+
+ PrintHeading();
+ }
+
+ public void RemoveSelected()
+ {
+ RemoveDownloadableContents(true);
+ }
+
+ public void RemoveAll()
+ {
+ RemoveDownloadableContents();
+ }
+
+ public void EnableAll()
+ {
+ foreach(var item in _downloadableContents)
+ {
+ item.Enabled = true;
+ }
+ }
+
+ public void DisableAll()
+ {
+ foreach (var item in _downloadableContents)
+ {
+ item.Enabled = false;
+ }
+ }
+
+ 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)
+ {
+ await AddDownloadableContent(file);
+ }
+ }
+
+ PrintHeading();
+ }
+
+ public void Save()
+ {
+ _downloadableContentContainerList.Clear();
+
+ DownloadableContentContainer container = default;
+
+ foreach (DownloadableContentModel downloadableContent in _downloadableContents)
+ {
+ if (container.ContainerPath != downloadableContent.ContainerPath)
+ {
+ if (!string.IsNullOrWhiteSpace(container.ContainerPath))
+ {
+ _downloadableContentContainerList.Add(container);
+ }
+
+ container = new DownloadableContentContainer
+ {
+ ContainerPath = downloadableContent.ContainerPath,
+ DownloadableContentNcaList = new List<DownloadableContentNca>()
+ };
+ }
+
+ container.DownloadableContentNcaList.Add(new DownloadableContentNca
+ {
+ Enabled = downloadableContent.Enabled,
+ TitleId = Convert.ToUInt64(downloadableContent.TitleId, 16),
+ FullPath = downloadableContent.FullPath
+ });
+ }
+
+ if (!string.IsNullOrWhiteSpace(container.ContainerPath))
+ {
+ _downloadableContentContainerList.Add(container);
+ }
+
+ using (FileStream downloadableContentJsonStream = File.Create(_downloadableContentJsonPath, 4096, FileOptions.WriteThrough))
+ {
+ downloadableContentJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_downloadableContentContainerList, true)));
+ }
+ }
+
+ public void SaveAndClose()
+ {
+ Save();
+ Close();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Ava/UI/Windows/IconColorPicker.cs b/Ryujinx.Ava/UI/Windows/IconColorPicker.cs
new file mode 100644
index 00000000..9dca83eb
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/IconColorPicker.cs
@@ -0,0 +1,192 @@
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.PixelFormats;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Ava.UI.Windows
+{
+ static class IconColorPicker
+ {
+ private const int ColorsPerLine = 64;
+ private const int TotalColors = ColorsPerLine * ColorsPerLine;
+
+ private const int UvQuantBits = 3;
+ private const int UvQuantShift = BitsPerComponent - UvQuantBits;
+
+ private const int SatQuantBits = 5;
+ private const int SatQuantShift = BitsPerComponent - SatQuantBits;
+
+ private const int BitsPerComponent = 8;
+
+ private const int CutOffLuminosity = 64;
+
+ private readonly struct PaletteColor
+ {
+ public int Qck { get; }
+ public byte R { get; }
+ public byte G { get; }
+ public byte B { get; }
+
+ public PaletteColor(int qck, byte r, byte g, byte b)
+ {
+ Qck = qck;
+ R = r;
+ G = g;
+ B = b;
+ }
+ }
+
+ public static Color GetFilteredColor(Image<Bgra32> image)
+ {
+ var color = GetColor(image).ToPixel<Bgra32>();
+
+ // We don't want colors that are too dark.
+ // If the color is too dark, make it brighter by reducing the range
+ // and adding a constant color.
+ int luminosity = GetColorApproximateLuminosity(color.R, color.G, color.B);
+ if (luminosity < CutOffLuminosity)
+ {
+ color = Color.FromRgb(
+ (byte)Math.Min(CutOffLuminosity + color.R, byte.MaxValue),
+ (byte)Math.Min(CutOffLuminosity + color.G, byte.MaxValue),
+ (byte)Math.Min(CutOffLuminosity + color.B, byte.MaxValue));
+ }
+
+ return color;
+ }
+
+ public static Color GetColor(Image<Bgra32> image)
+ {
+ var colors = new PaletteColor[TotalColors];
+
+ var dominantColorBin = new Dictionary<int, int>();
+
+ var buffer = GetBuffer(image);
+
+ int w = image.Width;
+
+ int w8 = w << 8;
+ int h8 = image.Height << 8;
+
+ int xStep = w8 / ColorsPerLine;
+ int yStep = h8 / ColorsPerLine;
+
+ int i = 0;
+ int maxHitCount = 0;
+
+ for (int y = 0; y < image.Height; y++)
+ {
+ int yOffset = y * image.Width;
+
+ for (int x = 0; x < image.Width && i < TotalColors; x++)
+ {
+ int offset = x + yOffset;
+
+ byte cb = buffer[offset].B;
+ byte cg = buffer[offset].G;
+ byte cr = buffer[offset].R;
+
+ var qck = GetQuantizedColorKey(cr, cg, cb);
+
+ if (dominantColorBin.TryGetValue(qck, out int hitCount))
+ {
+ dominantColorBin[qck] = hitCount + 1;
+
+ if (maxHitCount < hitCount)
+ {
+ maxHitCount = hitCount;
+ }
+ }
+ else
+ {
+ dominantColorBin.Add(qck, 1);
+ }
+
+ colors[i++] = new PaletteColor(qck, cr, cg, cb);
+ }
+ }
+
+ int highScore = -1;
+ PaletteColor bestCandidate = default;
+
+ for (i = 0; i < TotalColors; i++)
+ {
+ var score = GetColorScore(dominantColorBin, maxHitCount, colors[i]);
+
+ if (highScore < score)
+ {
+ highScore = score;
+ bestCandidate = colors[i];
+ }
+ }
+
+ return Color.FromRgb(bestCandidate.R, bestCandidate.G, bestCandidate.B);
+ }
+
+ public static Bgra32[] GetBuffer(Image<Bgra32> image)
+ {
+ return image.TryGetSinglePixelSpan(out var data) ? data.ToArray() : new Bgra32[0];
+ }
+
+ private static int GetColorScore(Dictionary<int, int> dominantColorBin, int maxHitCount, PaletteColor color)
+ {
+ var hitCount = dominantColorBin[color.Qck];
+ var balancedHitCount = BalanceHitCount(hitCount, maxHitCount);
+ var quantSat = (GetColorSaturation(color) >> SatQuantShift) << SatQuantShift;
+ var value = GetColorValue(color);
+
+ // If the color is rarely used on the image,
+ // then chances are that theres a better candidate, even if the saturation value
+ // is high. By multiplying the saturation value with a weight, we can lower
+ // it if the color is almost never used (hit count is low).
+ var satWeighted = quantSat;
+ var satWeight = balancedHitCount << 5;
+ if (satWeight < 0x100)
+ {
+ satWeighted = (satWeighted * satWeight) >> 8;
+ }
+
+ // Compute score from saturation and dominance of the color.
+ // We prefer more vivid colors over dominant ones, so give more weight to the saturation.
+ var score = ((satWeighted << 1) + balancedHitCount) * value;
+
+ return score;
+ }
+
+ private static int BalanceHitCount(int hitCount, int maxHitCount)
+ {
+ return (hitCount << 8) / maxHitCount;
+ }
+
+ private static int GetColorApproximateLuminosity(byte r, byte g, byte b)
+ {
+ return (r + g + b) / 3;
+ }
+
+ private static int GetColorSaturation(PaletteColor color)
+ {
+ int cMax = Math.Max(Math.Max(color.R, color.G), color.B);
+
+ if (cMax == 0)
+ {
+ return 0;
+ }
+
+ int cMin = Math.Min(Math.Min(color.R, color.G), color.B);
+ int delta = cMax - cMin;
+ return (delta << 8) / cMax;
+ }
+
+ private static int GetColorValue(PaletteColor color)
+ {
+ return Math.Max(Math.Max(color.R, color.G), color.B);
+ }
+
+ private static int GetQuantizedColorKey(byte r, byte g, byte b)
+ {
+ int u = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128;
+ int v = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128;
+ return (v >> UvQuantShift) | ((u >> UvQuantShift) << UvQuantBits);
+ }
+ }
+}
diff --git a/Ryujinx.Ava/UI/Windows/MainWindow.axaml b/Ryujinx.Ava/UI/Windows/MainWindow.axaml
new file mode 100644
index 00000000..1eb42279
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/MainWindow.axaml
@@ -0,0 +1,770 @@
+<window:StyleableWindow
+ x:Class="Ryujinx.Ava.UI.Windows.MainWindow"
+ xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+ xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
+ xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
+ xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
+ Title="Ryujinx"
+ Width="1280"
+ Height="777"
+ MinWidth="1092"
+ MinHeight="672"
+ d:DesignHeight="720"
+ d:DesignWidth="1280"
+ x:CompileBindings="True"
+ x:DataType="viewModels:MainWindowViewModel"
+ WindowStartupLocation="CenterScreen"
+ mc:Ignorable="d"
+ Focusable="True">
+ <Window.Styles>
+ <Style Selector="TitleBar:fullscreen">
+ <Setter Property="Background" Value="#000000" />
+ </Style>
+ </Window.Styles>
+ <Design.DataContext>
+ <viewModels:MainWindowViewModel />
+ </Design.DataContext>
+ <Window.Resources>
+ <helpers:BitmapArrayValueConverter x:Key="ByteImage" />
+ </Window.Resources>
+ <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="*" />
+ </Grid.RowDefinitions>
+ <helpers:OffscreenTextBox Name="HiddenTextBox" Grid.Row="0" />
+ <StackPanel Grid.Row="0" IsVisible="False">
+ <helpers:HotKeyControl Name="FullscreenHotKey" Command="{ReflectionBinding ToggleFullscreen}" />
+ <helpers:HotKeyControl Name="FullscreenHotKey2" Command="{ReflectionBinding ToggleFullscreen}" />
+ <helpers:HotKeyControl Name="DockToggleHotKey" Command="{ReflectionBinding ToggleDockMode}" />
+ <helpers:HotKeyControl Name="ExitHotKey" Command="{ReflectionBinding ExitCurrentState}" />
+ </StackPanel>
+ <Grid
+ Grid.Row="1"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="*" />
+ </Grid.ColumnDefinitions>
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="*" />
+ <RowDefinition Height="Auto" />
+ </Grid.RowDefinitions>
+ <StackPanel
+ Name="MenuBar"
+ MinHeight="35"
+ Grid.Row="0"
+ Margin="0"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ IsVisible="{Binding ShowMenuAndStatusBar}"
+ Orientation="Vertical">
+ <DockPanel HorizontalAlignment="Stretch">
+ <Menu
+ Name="Menu"
+ Height="35"
+ Margin="0"
+ HorizontalAlignment="Left">
+ <Menu.ItemsPanel>
+ <ItemsPanelTemplate>
+ <DockPanel Margin="0" HorizontalAlignment="Stretch" />
+ </ItemsPanelTemplate>
+ </Menu.ItemsPanel>
+ <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarFile}">
+ <MenuItem
+ Command="{ReflectionBinding OpenFile}"
+ Header="{locale:Locale MenuBarFileOpenFromFile}"
+ IsEnabled="{Binding EnableNonGameRunningControls}"
+ ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" />
+ <MenuItem
+ Command="{ReflectionBinding OpenFolder}"
+ Header="{locale:Locale MenuBarFileOpenUnpacked}"
+ IsEnabled="{Binding EnableNonGameRunningControls}"
+ ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" />
+ <MenuItem Header="{locale:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}">
+ <MenuItem
+ Command="{ReflectionBinding OpenMiiApplet}"
+ Header="Mii Edit Applet"
+ ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" />
+ </MenuItem>
+ <Separator />
+ <MenuItem
+ Command="{ReflectionBinding OpenRyujinxFolder}"
+ Header="{locale:Locale MenuBarFileOpenEmuFolder}"
+ ToolTip.Tip="{locale:Locale OpenRyujinxFolderTooltip}" />
+ <MenuItem
+ Command="{ReflectionBinding OpenLogsFolder}"
+ Header="{locale:Locale MenuBarFileOpenLogsFolder}"
+ ToolTip.Tip="{locale:Locale OpenRyujinxLogsTooltip}" />
+ <Separator />
+ <MenuItem
+ Command="{ReflectionBinding CloseWindow}"
+ Header="{locale:Locale MenuBarFileExit}"
+ ToolTip.Tip="{locale:Locale ExitTooltip}" />
+ </MenuItem>
+ <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarOptions}">
+ <MenuItem
+ Command="{ReflectionBinding ToggleFullscreen}"
+ Header="{locale:Locale MenuBarOptionsToggleFullscreen}"
+ InputGesture="F11" />
+ <MenuItem>
+ <MenuItem.Icon>
+ <CheckBox IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}"
+ MinWidth="250">
+ <TextBlock Text="{locale:Locale MenuBarOptionsStartGamesInFullscreen}"/>
+ </CheckBox>
+ </MenuItem.Icon>
+ </MenuItem>
+ <MenuItem IsVisible="{Binding ShowConsoleVisible}">
+ <MenuItem.Icon>
+ <CheckBox IsChecked="{Binding ShowConsole, Mode=TwoWay}"
+ MinWidth="250">
+ <TextBlock Text="{locale:Locale MenuBarOptionsShowConsole}"/>
+ </CheckBox>
+ </MenuItem.Icon>
+ </MenuItem>
+ <Separator />
+ <MenuItem Header="{locale:Locale MenuBarOptionsChangeLanguage}">
+ <MenuItem
+ Command="{ReflectionBinding ChangeLanguage}"
+ CommandParameter="de_DE"
+ Header="Deutsch" />
+ <MenuItem
+ Command="{ReflectionBinding ChangeLanguage}"
+ CommandParameter="en_US"
+ Header="English (US)" />
+ <MenuItem
+ Command="{ReflectionBinding ChangeLanguage}"
+ CommandParameter="es_ES"
+ Header="Español (ES)" />
+ <MenuItem
+ Command="{ReflectionBinding ChangeLanguage}"
+ CommandParameter="fr_FR"
+ Header="Français" />
+ <MenuItem
+ Command="{ReflectionBinding ChangeLanguage}"
+ CommandParameter="it_IT"
+ Header="Italiano" />
+ <MenuItem
+ Command="{ReflectionBinding ChangeLanguage}"
+ CommandParameter="pt_BR"
+ Header="Português (BR)" />
+ <MenuItem
+ Command="{ReflectionBinding ChangeLanguage}"
+ CommandParameter="tr_TR"
+ Header="Türkçe" />
+ <MenuItem
+ Command="{ReflectionBinding ChangeLanguage}"
+ CommandParameter="el_GR"
+ Header="Ελληνικά" />
+ <MenuItem
+ Command="{ReflectionBinding ChangeLanguage}"
+ CommandParameter="pl_PL"
+ Header="Polski" />
+ <MenuItem
+ Command="{ReflectionBinding ChangeLanguage}"
+ CommandParameter="ru_RU"
+ Header="Русский" />
+ <MenuItem
+ Command="{ReflectionBinding ChangeLanguage}"
+ CommandParameter="zh_CN"
+ Header="简体中文" />
+ <MenuItem
+ Command="{ReflectionBinding ChangeLanguage}"
+ CommandParameter="zh_TW"
+ Header="繁體中文" />
+ <MenuItem
+ Command="{ReflectionBinding ChangeLanguage}"
+ CommandParameter="ja_JP"
+ Header="日本語" />
+ <MenuItem
+ Command="{ReflectionBinding ChangeLanguage}"
+ CommandParameter="ko_KR"
+ Header="한국어" />
+ </MenuItem>
+ <Separator />
+ <MenuItem
+ Command="{ReflectionBinding OpenSettings}"
+ Header="{locale:Locale MenuBarOptionsSettings}"
+ ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" />
+ <MenuItem
+ Command="{ReflectionBinding ManageProfiles}"
+ Header="{locale:Locale MenuBarOptionsManageUserProfiles}"
+ IsEnabled="{Binding EnableNonGameRunningControls}"
+ ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" />
+ </MenuItem>
+ <MenuItem
+ Name="ActionsMenuItem"
+ VerticalAlignment="Center"
+ Header="{locale:Locale MenuBarActions}"
+ IsEnabled="{Binding IsGameRunning}">
+ <MenuItem
+ Click="PauseEmulation_Click"
+ Header="{locale:Locale MenuBarOptionsPauseEmulation}"
+ InputGesture="{Binding PauseKey}"
+ IsEnabled="{Binding !IsPaused}"
+ IsVisible="{Binding !IsPaused}" />
+ <MenuItem
+ Click="ResumeEmulation_Click"
+ Header="{locale:Locale MenuBarOptionsResumeEmulation}"
+ InputGesture="{Binding PauseKey}"
+ IsEnabled="{Binding IsPaused}"
+ IsVisible="{Binding IsPaused}" />
+ <MenuItem
+ Click="StopEmulation_Click"
+ Header="{locale:Locale MenuBarOptionsStopEmulation}"
+ InputGesture="Escape"
+ IsEnabled="{Binding IsGameRunning}"
+ ToolTip.Tip="{locale:Locale StopEmulationTooltip}" />
+ <MenuItem Command="{ReflectionBinding SimulateWakeUpMessage}" Header="{locale:Locale MenuBarOptionsSimulateWakeUpMessage}" />
+ <Separator />
+ <MenuItem
+ Name="ScanAmiiboMenuItem"
+ AttachedToVisualTree="ScanAmiiboMenuItem_AttachedToVisualTree"
+ Command="{ReflectionBinding OpenAmiiboWindow}"
+ Header="{locale:Locale MenuBarActionsScanAmiibo}"
+ IsEnabled="{Binding IsAmiiboRequested}" />
+ <MenuItem
+ Command="{ReflectionBinding TakeScreenshot}"
+ Header="{locale:Locale MenuBarFileToolsTakeScreenshot}"
+ InputGesture="{Binding ScreenshotKey}"
+ IsEnabled="{Binding IsGameRunning}" />
+ <MenuItem
+ Command="{ReflectionBinding HideUi}"
+ Header="{locale:Locale MenuBarFileToolsHideUi}"
+ InputGesture="{Binding ShowUiKey}"
+ IsEnabled="{Binding IsGameRunning}" />
+ <MenuItem
+ Command="{ReflectionBinding OpenCheatManagerForCurrentApp}"
+ Header="{locale:Locale GameListContextMenuManageCheat}"
+ IsEnabled="{Binding IsGameRunning}" />
+ </MenuItem>
+ <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarTools}">
+ <MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" IsEnabled="{Binding EnableNonGameRunningControls}">
+ <MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
+ <MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
+ </MenuItem>
+ </MenuItem>
+ <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}">
+ <MenuItem
+ Name="UpdateMenuItem"
+ Command="{ReflectionBinding CheckForUpdates}"
+ Header="{locale:Locale MenuBarHelpCheckForUpdates}"
+ ToolTip.Tip="{locale:Locale CheckUpdatesTooltip}" />
+ <Separator />
+ <MenuItem
+ Command="{ReflectionBinding OpenAboutWindow}"
+ Header="{locale:Locale MenuBarHelpAbout}"
+ ToolTip.Tip="{locale:Locale OpenAboutTooltip}" />
+ </MenuItem>
+ </Menu>
+ </DockPanel>
+ </StackPanel>
+ <ContentControl
+ Name="MainContent"
+ Grid.Row="1"
+ Padding="0"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ BorderBrush="{DynamicResource ThemeControlBorderColor}"
+ BorderThickness="0,0,0,0"
+ DockPanel.Dock="Top"
+ IsVisible="{Binding ShowContent}">
+ <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="*" />
+ </Grid.RowDefinitions>
+ <DockPanel
+ Grid.Row="0"
+ Margin="0,0,0,5"
+ HorizontalAlignment="Stretch">
+ <Button
+ Width="40"
+ MinWidth="40"
+ Margin="5,2,0,2"
+ VerticalAlignment="Stretch"
+ Command="{ReflectionBinding SetListMode}"
+ IsEnabled="{Binding IsGrid}">
+ <ui:FontIcon
+ Margin="0"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Center"
+ FontFamily="avares://FluentAvalonia/Fonts#Symbols"
+ Glyph="{helpers:GlyphValueConverter List}" />
+ </Button>
+ <Button
+ Width="40"
+ MinWidth="40"
+ Margin="5,2,5,2"
+ VerticalAlignment="Stretch"
+ Command="{ReflectionBinding SetGridMode}"
+ IsEnabled="{Binding IsList}">
+ <ui:FontIcon
+ Margin="0"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Center"
+ FontFamily="avares://FluentAvalonia/Fonts#Symbols"
+ Glyph="{helpers:GlyphValueConverter Grid}" />
+ </Button>
+ <TextBlock
+ Margin="10,0"
+ VerticalAlignment="Center"
+ Text="{locale:Locale IconSize}"
+ ToolTip.Tip="{locale:Locale IconSizeTooltip}" />
+ <Slider
+ Width="150"
+ Height="35"
+ Margin="5,-10,5,0"
+ VerticalAlignment="Center"
+ IsSnapToTickEnabled="True"
+ Maximum="4"
+ Minimum="1"
+ TickFrequency="1"
+ ToolTip.Tip="{locale:Locale IconSizeTooltip}"
+ Value="{Binding GridSizeScale}" />
+ <CheckBox
+ Margin="0"
+ VerticalAlignment="Center"
+ IsChecked="{Binding ShowNames, Mode=TwoWay}"
+ IsVisible="{Binding IsGrid}">
+ <TextBlock Margin="5,3,0,0" Text="{locale:Locale CommonShowNames}" />
+ </CheckBox>
+ <TextBox
+ Name="SearchBox"
+ MinWidth="200"
+ Margin="5,0,5,0"
+ HorizontalAlignment="Right"
+ VerticalAlignment="Center"
+ DockPanel.Dock="Right"
+ KeyUp="SearchBox_OnKeyUp"
+ Text="{Binding SearchText}"
+ Watermark="{locale:Locale MenuSearch}" />
+ <ui:DropDownButton
+ Width="150"
+ HorizontalAlignment="Right"
+ VerticalAlignment="Center"
+ Content="{Binding SortName}"
+ DockPanel.Dock="Right">
+ <ui:DropDownButton.Flyout>
+ <Flyout Placement="Bottom">
+ <StackPanel
+ Margin="0"
+ HorizontalAlignment="Stretch"
+ Orientation="Vertical">
+ <StackPanel>
+ <RadioButton
+ Checked="Sort_Checked"
+ Content="{locale:Locale CommonFavorite}"
+ GroupName="Sort"
+ IsChecked="{Binding IsSortedByFavorite, Mode=OneTime}"
+ Tag="Favorite" />
+ <RadioButton
+ Checked="Sort_Checked"
+ Content="{locale:Locale GameListHeaderApplication}"
+ GroupName="Sort"
+ IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
+ Tag="Title" />
+ <RadioButton
+ Checked="Sort_Checked"
+ Content="{locale:Locale GameListHeaderDeveloper}"
+ GroupName="Sort"
+ IsChecked="{Binding IsSortedByDeveloper, Mode=OneTime}"
+ Tag="Developer" />
+ <RadioButton
+ Checked="Sort_Checked"
+ Content="{locale:Locale GameListHeaderTimePlayed}"
+ GroupName="Sort"
+ IsChecked="{Binding IsSortedByTimePlayed, Mode=OneTime}"
+ Tag="TotalTimePlayed" />
+ <RadioButton
+ Checked="Sort_Checked"
+ Content="{locale:Locale GameListHeaderLastPlayed}"
+ GroupName="Sort"
+ IsChecked="{Binding IsSortedByLastPlayed, Mode=OneTime}"
+ Tag="LastPlayed" />
+ <RadioButton
+ Checked="Sort_Checked"
+ Content="{locale:Locale GameListHeaderFileExtension}"
+ GroupName="Sort"
+ IsChecked="{Binding IsSortedByType, Mode=OneTime}"
+ Tag="FileType" />
+ <RadioButton
+ Checked="Sort_Checked"
+ Content="{locale:Locale GameListHeaderFileSize}"
+ GroupName="Sort"
+ IsChecked="{Binding IsSortedBySize, Mode=OneTime}"
+ Tag="FileSize" />
+ <RadioButton
+ Checked="Sort_Checked"
+ Content="{locale:Locale GameListHeaderPath}"
+ GroupName="Sort"
+ IsChecked="{Binding IsSortedByPath, Mode=OneTime}"
+ Tag="Path" />
+ </StackPanel>
+ <Border
+ Width="60"
+ Height="2"
+ Margin="5"
+ HorizontalAlignment="Stretch"
+ BorderBrush="White"
+ BorderThickness="0,1,0,0">
+ <Separator Height="0" HorizontalAlignment="Stretch" />
+ </Border>
+ <RadioButton
+ Checked="Order_Checked"
+ Content="{locale:Locale OrderAscending}"
+ GroupName="Order"
+ IsChecked="{Binding IsAscending, Mode=OneTime}"
+ Tag="Ascending" />
+ <RadioButton
+ Checked="Order_Checked"
+ Content="{locale:Locale OrderDescending}"
+ GroupName="Order"
+ IsChecked="{Binding !IsAscending, Mode=OneTime}"
+ Tag="Descending" />
+ </StackPanel>
+ </Flyout>
+ </ui:DropDownButton.Flyout>
+ </ui:DropDownButton>
+ <TextBlock
+ Margin="10,0"
+ HorizontalAlignment="Right"
+ VerticalAlignment="Center"
+ DockPanel.Dock="Right"
+ Text="{locale:Locale CommonSort}" />
+ </DockPanel>
+ <controls:GameListView
+ x:Name="GameList"
+ Grid.Row="1"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ HorizontalContentAlignment="Stretch"
+ VerticalContentAlignment="Stretch"
+ IsVisible="{Binding IsList}" />
+ <controls:GameGridView
+ x:Name="GameGrid"
+ Grid.Row="1"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ HorizontalContentAlignment="Stretch"
+ VerticalContentAlignment="Stretch"
+ IsVisible="{Binding IsGrid}" />
+ </Grid>
+ </ContentControl>
+ <Grid
+ Grid.Row="1"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ Background="{DynamicResource ThemeContentBackgroundColor}"
+ IsVisible="{Binding ShowLoadProgress}"
+ Name="LoadingView"
+ ZIndex="1000">
+ <Grid
+ Margin="40"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ IsVisible="{Binding ShowLoadProgress}">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition Width="*" />
+ </Grid.ColumnDefinitions>
+ <Border
+ Grid.RowSpan="2"
+ Grid.Column="0"
+ Width="256"
+ Height="256"
+ Margin="10"
+ Padding="4"
+ BorderBrush="Black"
+ BorderThickness="2"
+ BoxShadow="4 4 32 8 #40000000"
+ CornerRadius="3"
+ IsVisible="{Binding ShowLoadProgress}">
+ <Image
+ Width="256"
+ Height="256"
+ IsVisible="{Binding ShowLoadProgress}"
+ Source="{Binding SelectedIcon, Converter={StaticResource ByteImage}}" />
+ </Border>
+ <Grid
+ Grid.Column="1"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Center"
+ IsVisible="{Binding ShowLoadProgress}">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ </Grid.RowDefinitions>
+ <TextBlock
+ Grid.Row="0"
+ Margin="10"
+ FontSize="30"
+ FontWeight="Bold"
+ IsVisible="{Binding ShowLoadProgress}"
+ Text="{Binding LoadHeading}"
+ TextAlignment="Left"
+ TextWrapping="Wrap" />
+ <Border
+ Grid.Row="1"
+ Margin="10"
+ Padding="0"
+ HorizontalAlignment="Stretch"
+ BorderBrush="{Binding ProgressBarBackgroundColor}"
+ BorderThickness="1"
+ ClipToBounds="True"
+ CornerRadius="5"
+ IsVisible="{Binding ShowLoadProgress}">
+ <ProgressBar
+ Height="10"
+ MinWidth="500"
+ Margin="0"
+ Padding="0"
+ HorizontalAlignment="Stretch"
+ Background="{Binding ProgressBarBackgroundColor}"
+ ClipToBounds="True"
+ CornerRadius="5"
+ Foreground="{Binding ProgressBarForegroundColor}"
+ IsIndeterminate="{Binding IsLoadingIndeterminate}"
+ IsVisible="{Binding ShowLoadProgress}"
+ Maximum="{Binding ProgressMaximum}"
+ Minimum="0"
+ Value="{Binding ProgressValue}" />
+ </Border>
+ <TextBlock
+ Grid.Row="2"
+ Margin="10"
+ FontSize="18"
+ IsVisible="{Binding ShowLoadProgress}"
+ Text="{Binding CacheLoadStatus}"
+ TextAlignment="Left" />
+ </Grid>
+ </Grid>
+ </Grid>
+ <Grid
+ Name="StatusBar"
+ Grid.Row="2"
+ Margin="0"
+ MinHeight="22"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Bottom"
+ Background="{DynamicResource ThemeContentBackgroundColor}"
+ DockPanel.Dock="Bottom"
+ IsVisible="{Binding ShowMenuAndStatusBar}">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition Width="*" />
+ <ColumnDefinition Width="Auto" />
+ </Grid.ColumnDefinitions>
+ <StackPanel
+ Grid.Column="0"
+ Margin="5"
+ VerticalAlignment="Center"
+ IsVisible="{Binding EnableNonGameRunningControls}">
+ <Grid Margin="0">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition />
+ </Grid.ColumnDefinitions>
+ <Button
+ Width="25"
+ Height="25"
+ MinWidth="0"
+ Margin="0,0,5,0"
+ VerticalAlignment="Center"
+ Background="Transparent"
+ Command="{ReflectionBinding LoadApplications}">
+ <ui:SymbolIcon
+ Width="50"
+ Height="100"
+ Symbol="Refresh" />
+ </Button>
+ <TextBlock
+ Name="LoadStatus"
+ Grid.Column="1"
+ Margin="0,0,5,0"
+ VerticalAlignment="Center"
+ IsVisible="{Binding EnableNonGameRunningControls}"
+ Text="{locale:Locale StatusBarGamesLoaded}" />
+ <ProgressBar
+ Name="LoadProgressBar"
+ Grid.Column="2"
+ Height="6"
+ VerticalAlignment="Center"
+ Foreground="{DynamicResource HighlightColor}"
+ IsVisible="{Binding EnableNonGameRunningControls}"
+ Maximum="{Binding StatusBarProgressMaximum}"
+ Value="{Binding StatusBarProgressValue}" />
+ </Grid>
+ </StackPanel>
+ <StackPanel
+ Grid.Column="1"
+ Margin="0,2"
+ HorizontalAlignment="Left"
+ VerticalAlignment="Center"
+ IsVisible="{Binding IsGameRunning}"
+ Orientation="Horizontal">
+ <TextBlock
+ Name="VsyncStatus"
+ Margin="5,0,5,0"
+ HorizontalAlignment="Left"
+ VerticalAlignment="Center"
+ Foreground="{Binding VsyncColor}"
+ IsVisible="{Binding !ShowLoadProgress}"
+ PointerReleased="VsyncStatus_PointerReleased"
+ Text="VSync"
+ TextAlignment="Left" />
+ <Border
+ Width="2"
+ Height="12"
+ Margin="0"
+ BorderBrush="Gray"
+ BorderThickness="1"
+ IsVisible="{Binding !ShowLoadProgress}" />
+ <TextBlock
+ Name="DockedStatus"
+ Margin="5,0,5,0"
+ HorizontalAlignment="Left"
+ VerticalAlignment="Center"
+ IsVisible="{Binding !ShowLoadProgress}"
+ PointerReleased="DockedStatus_PointerReleased"
+ Text="{Binding DockedStatusText}"
+ TextAlignment="Left" />
+ <Border
+ Width="2"
+ Height="12"
+ Margin="0"
+ BorderBrush="Gray"
+ BorderThickness="1"
+ IsVisible="{Binding !ShowLoadProgress}" />
+ <TextBlock
+ Name="AspectRatioStatus"
+ Margin="5,0,5,0"
+ HorizontalAlignment="Left"
+ VerticalAlignment="Center"
+ IsVisible="{Binding !ShowLoadProgress}"
+ PointerReleased="AspectRatioStatus_PointerReleased"
+ Text="{Binding AspectRatioStatusText}"
+ TextAlignment="Left" />
+ <Border
+ Width="2"
+ Height="12"
+ Margin="0"
+ BorderBrush="Gray"
+ BorderThickness="1"
+ IsVisible="{Binding !ShowLoadProgress}" />
+ <ui:ToggleSplitButton
+ Name="VolumeStatus"
+ Padding="5,0,5,0"
+ HorizontalAlignment="Left"
+ VerticalAlignment="Center"
+ VerticalContentAlignment="Center"
+ Background="{DynamicResource ThemeContentBackgroundColor}"
+ BorderThickness="0"
+ Content="{Binding VolumeStatusText}"
+ IsChecked="{Binding VolumeMuted}"
+ IsVisible="{Binding !ShowLoadProgress}">
+ <ui:ToggleSplitButton.Flyout>
+ <Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
+ <Grid Margin="0">
+ <Slider
+ MaxHeight="40"
+ Width="150"
+ Margin="0"
+ Padding="0"
+ IsSnapToTickEnabled="True"
+ LargeChange="0.05"
+ Maximum="1"
+ Minimum="0"
+ SmallChange="0.01"
+ TickFrequency="0.05"
+ ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
+ Value="{Binding Volume}" />
+ </Grid>
+ </Flyout>
+ </ui:ToggleSplitButton.Flyout>
+ </ui:ToggleSplitButton>
+ <Border
+ Width="2"
+ Height="12"
+ Margin="0"
+ BorderBrush="Gray"
+ BorderThickness="1"
+ IsVisible="{Binding !ShowLoadProgress}" />
+ <TextBlock
+ Margin="5,0,5,0"
+ HorizontalAlignment="Left"
+ VerticalAlignment="Center"
+ IsVisible="{Binding !ShowLoadProgress}"
+ Text="{Binding GameStatusText}"
+ TextAlignment="Left" />
+ <Border
+ Width="2"
+ Height="12"
+ Margin="0"
+ BorderBrush="Gray"
+ BorderThickness="1"
+ IsVisible="{Binding !ShowLoadProgress}" />
+ <TextBlock
+ Margin="5,0,5,0"
+ HorizontalAlignment="Left"
+ VerticalAlignment="Center"
+ IsVisible="{Binding !ShowLoadProgress}"
+ Text="{Binding FifoStatusText}"
+ TextAlignment="Left" />
+ <Border
+ Width="2"
+ Height="12"
+ Margin="0"
+ BorderBrush="Gray"
+ BorderThickness="1"
+ IsVisible="{Binding !ShowLoadProgress}" />
+ <TextBlock
+ Margin="5,0,5,0"
+ HorizontalAlignment="Left"
+ VerticalAlignment="Center"
+ IsVisible="{Binding !ShowLoadProgress}"
+ Text="{Binding BackendText}"
+ TextAlignment="Left" />
+ <Border
+ Width="2"
+ Height="12"
+ Margin="0"
+ BorderBrush="Gray"
+ BorderThickness="1"
+ IsVisible="{Binding !ShowLoadProgress}" />
+ <TextBlock
+ Margin="5,0,5,0"
+ HorizontalAlignment="Left"
+ VerticalAlignment="Center"
+ IsVisible="{Binding !ShowLoadProgress}"
+ Text="{Binding GpuNameText}"
+ TextAlignment="Left" />
+ </StackPanel>
+ <StackPanel
+ Grid.Column="3"
+ Margin="0,0,5,0"
+ VerticalAlignment="Center"
+ IsVisible="{Binding ShowFirmwareStatus}"
+ Orientation="Horizontal">
+ <TextBlock
+ Name="FirmwareStatus"
+ Margin="0"
+ HorizontalAlignment="Right"
+ VerticalAlignment="Center"
+ Text="{locale:Locale StatusBarSystemVersion}" />
+ </StackPanel>
+ </Grid>
+ </Grid>
+ </Grid>
+</window:StyleableWindow>
diff --git a/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs b/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs
new file mode 100644
index 00000000..08332da8
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs
@@ -0,0 +1,721 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+using Avalonia.Threading;
+using FluentAvalonia.UI.Controls;
+using Ryujinx.Ava.Common;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.Input;
+using Ryujinx.Ava.UI.Applet;
+using Ryujinx.Ava.UI.Controls;
+using Ryujinx.Ava.UI.Helpers;
+using Ryujinx.Ava.UI.Models;
+using Ryujinx.Ava.UI.ViewModels;
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.Gpu;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.HOS;
+using Ryujinx.HLE.HOS.Services.Account.Acc;
+using Ryujinx.Input.SDL2;
+using Ryujinx.Modules;
+using Ryujinx.Ui.App.Common;
+using Ryujinx.Ui.Common;
+using Ryujinx.Ui.Common.Configuration;
+using Ryujinx.Ui.Common.Helper;
+using SixLabors.ImageSharp.PixelFormats;
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using InputManager = Ryujinx.Input.HLE.InputManager;
+
+namespace Ryujinx.Ava.UI.Windows
+{
+ public partial class MainWindow : StyleableWindow
+ {
+ internal static MainWindowViewModel MainWindowViewModel { get; private set; }
+ private bool _canUpdate;
+ private bool _isClosing;
+ private bool _isLoading;
+
+ private Control _mainViewContent;
+
+ private UserChannelPersistence _userChannelPersistence;
+ private static bool _deferLoad;
+ private static string _launchPath;
+ private static bool _startFullscreen;
+ private string _currentEmulatedGamePath;
+ internal readonly AvaHostUiHandler UiHandler;
+ private AutoResetEvent _rendererWaitEvent;
+
+ public VirtualFileSystem VirtualFileSystem { get; private set; }
+ public ContentManager ContentManager { get; private set; }
+ public AccountManager AccountManager { get; private set; }
+
+ public LibHacHorizonManager LibHacHorizonManager { get; private set; }
+
+ internal AppHost AppHost { get; private set; }
+ public InputManager InputManager { get; private set; }
+
+ internal RendererHost RendererControl { get; private set; }
+ internal MainWindowViewModel ViewModel { get; private set; }
+ public SettingsWindow SettingsWindow { get; set; }
+
+ public bool CanUpdate
+ {
+ get => _canUpdate;
+ set
+ {
+ _canUpdate = value;
+
+ Dispatcher.UIThread.InvokeAsync(() => UpdateMenuItem.IsEnabled = _canUpdate);
+ }
+ }
+
+ public static bool ShowKeyErrorOnLoad { get; set; }
+ public ApplicationLibrary ApplicationLibrary { get; set; }
+
+ public MainWindow()
+ {
+ ViewModel = new MainWindowViewModel(this);
+
+ MainWindowViewModel = ViewModel;
+
+ DataContext = ViewModel;
+
+ InitializeComponent();
+ Load();
+
+ UiHandler = new AvaHostUiHandler(this);
+
+ Title = $"Ryujinx {Program.Version}";
+
+ // NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
+ double barHeight = MenuBar.MinHeight + StatusBar.MinHeight;
+ Height = ((Height - barHeight) / Program.WindowScaleFactor) + barHeight;
+ Width /= Program.WindowScaleFactor;
+
+ if (Program.PreviewerDetached)
+ {
+ Initialize();
+
+ ViewModel.Initialize();
+
+ InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver());
+
+ LoadGameList();
+ }
+
+ _rendererWaitEvent = new AutoResetEvent(false);
+ }
+
+ public void LoadGameList()
+ {
+ if (_isLoading)
+ {
+ return;
+ }
+
+ _isLoading = true;
+
+ ViewModel.LoadApplications();
+
+ _isLoading = false;
+ }
+
+ private void Update_StatusBar(object sender, StatusUpdatedEventArgs args)
+ {
+ if (ViewModel.ShowMenuAndStatusBar && !ViewModel.ShowLoadProgress)
+ {
+ Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ if (args.VSyncEnabled)
+ {
+ ViewModel.VsyncColor = new SolidColorBrush(Color.Parse("#ff2eeac9"));
+ }
+ else
+ {
+ ViewModel.VsyncColor = new SolidColorBrush(Color.Parse("#ffff4554"));
+ }
+
+ ViewModel.DockedStatusText = args.DockedMode;
+ ViewModel.AspectRatioStatusText = args.AspectRatio;
+ ViewModel.GameStatusText = args.GameStatus;
+ ViewModel.VolumeStatusText = args.VolumeStatus;
+ ViewModel.FifoStatusText = args.FifoStatus;
+ ViewModel.GpuNameText = args.GpuName;
+ ViewModel.BackendText = args.GpuBackend;
+
+ ViewModel.ShowStatusSeparator = true;
+ });
+ }
+ }
+
+ protected override void HandleScalingChanged(double scale)
+ {
+ Program.DesktopScaleFactor = scale;
+ base.HandleScalingChanged(scale);
+ }
+
+ public void Application_Opened(object sender, ApplicationOpenedEventArgs args)
+ {
+ if (args.Application != null)
+ {
+ ViewModel.SelectedIcon = args.Application.Icon;
+
+ string path = new FileInfo(args.Application.Path).FullName;
+
+ LoadApplication(path);
+ }
+
+ args.Handled = true;
+ }
+
+ public async Task PerformanceCheck()
+ {
+ if (ConfigurationState.Instance.Logger.EnableTrace.Value)
+ {
+ string mainMessage = LocaleManager.Instance["DialogPerformanceCheckLoggingEnabledMessage"];
+ string secondaryMessage = LocaleManager.Instance["DialogPerformanceCheckLoggingEnabledConfirmMessage"];
+
+ UserResult result = await ContentDialogHelper.CreateConfirmationDialog(mainMessage, secondaryMessage,
+ LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"],
+ LocaleManager.Instance["RyujinxConfirm"]);
+
+ if (result != UserResult.Yes)
+ {
+ ConfigurationState.Instance.Logger.EnableTrace.Value = false;
+
+ SaveConfig();
+ }
+ }
+
+ if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value))
+ {
+ string mainMessage = LocaleManager.Instance["DialogPerformanceCheckShaderDumpEnabledMessage"];
+ string secondaryMessage =
+ LocaleManager.Instance["DialogPerformanceCheckShaderDumpEnabledConfirmMessage"];
+
+ UserResult result = await ContentDialogHelper.CreateConfirmationDialog(mainMessage, secondaryMessage,
+ LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"],
+ LocaleManager.Instance["RyujinxConfirm"]);
+
+ if (result != UserResult.Yes)
+ {
+ ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = "";
+
+ SaveConfig();
+ }
+ }
+ }
+
+ internal static void DeferLoadApplication(string launchPathArg, bool startFullscreenArg)
+ {
+ _deferLoad = true;
+ _launchPath = launchPathArg;
+ _startFullscreen = startFullscreenArg;
+ }
+
+#pragma warning disable CS1998
+ public async void LoadApplication(string path, bool startFullscreen = false, string titleName = "")
+#pragma warning restore CS1998
+ {
+ if (AppHost != null)
+ {
+ await ContentDialogHelper.CreateInfoDialog(
+ LocaleManager.Instance["DialogLoadAppGameAlreadyLoadedMessage"],
+ LocaleManager.Instance["DialogLoadAppGameAlreadyLoadedSubMessage"],
+ LocaleManager.Instance["InputDialogOk"],
+ "",
+ LocaleManager.Instance["RyujinxInfo"]);
+
+ return;
+ }
+
+#if RELEASE
+ await PerformanceCheck();
+#endif
+
+ Logger.RestartTime();
+
+ if (ViewModel.SelectedIcon == null)
+ {
+ ViewModel.SelectedIcon = ApplicationLibrary.GetApplicationIcon(path);
+ }
+
+ PrepareLoadScreen();
+
+ _mainViewContent = MainContent.Content as Control;
+
+ RendererControl = new RendererHost(ConfigurationState.Instance.Logger.GraphicsDebugLevel);
+ if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl)
+ {
+ RendererControl.CreateOpenGL();
+ }
+ else
+ {
+ RendererControl.CreateVulkan();
+ }
+
+ AppHost = new AppHost(RendererControl, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this);
+
+ Dispatcher.UIThread.Post(async () =>
+ {
+ if (!await AppHost.LoadGuestApplication())
+ {
+ AppHost.DisposeContext();
+ AppHost = null;
+
+ return;
+ }
+
+ CanUpdate = false;
+ ViewModel.LoadHeading = string.IsNullOrWhiteSpace(titleName) ? string.Format(LocaleManager.Instance["LoadingHeading"], AppHost.Device.Application.TitleName) : titleName;
+ ViewModel.TitleName = string.IsNullOrWhiteSpace(titleName) ? AppHost.Device.Application.TitleName : titleName;
+
+ SwitchToGameControl(startFullscreen);
+
+ _currentEmulatedGamePath = path;
+
+ Thread gameThread = new(InitializeGame)
+ {
+ Name = "GUI.WindowThread"
+ };
+ gameThread.Start();
+ });
+ }
+
+ private void InitializeGame()
+ {
+ RendererControl.RendererInitialized += GlRenderer_Created;
+
+ AppHost.StatusUpdatedEvent += Update_StatusBar;
+ AppHost.AppExit += AppHost_AppExit;
+
+ _rendererWaitEvent.WaitOne();
+
+ AppHost?.Start();
+
+ AppHost.DisposeContext();
+ }
+
+
+ private void HandleRelaunch()
+ {
+ if (_userChannelPersistence.PreviousIndex != -1 && _userChannelPersistence.ShouldRestart)
+ {
+ _userChannelPersistence.ShouldRestart = false;
+
+ Dispatcher.UIThread.Post(() =>
+ {
+ LoadApplication(_currentEmulatedGamePath);
+ });
+ }
+ else
+ {
+ // otherwise, clear state.
+ _userChannelPersistence = new UserChannelPersistence();
+ _currentEmulatedGamePath = null;
+ }
+ }
+
+ public void SwitchToGameControl(bool startFullscreen = false)
+ {
+ ViewModel.ShowLoadProgress = false;
+ ViewModel.ShowContent = true;
+ ViewModel.IsLoadingIndeterminate = false;
+
+ Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ MainContent.Content = RendererControl;
+
+ if (startFullscreen && WindowState != WindowState.FullScreen)
+ {
+ ViewModel.ToggleFullscreen();
+ }
+
+ RendererControl.Focus();
+ });
+ }
+
+ public void ShowLoading(bool startFullscreen = false)
+ {
+ ViewModel.ShowContent = false;
+ ViewModel.ShowLoadProgress = true;
+ ViewModel.IsLoadingIndeterminate = true;
+
+ Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ if (startFullscreen && WindowState != WindowState.FullScreen)
+ {
+ ViewModel.ToggleFullscreen();
+ }
+ });
+ }
+
+ private void GlRenderer_Created(object sender, EventArgs e)
+ {
+ ShowLoading();
+
+ _rendererWaitEvent.Set();
+ }
+
+ private void AppHost_AppExit(object sender, EventArgs e)
+ {
+ if (_isClosing)
+ {
+ return;
+ }
+
+ ViewModel.IsGameRunning = false;
+
+ Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ ViewModel.ShowMenuAndStatusBar = true;
+ ViewModel.ShowContent = true;
+ ViewModel.ShowLoadProgress = false;
+ ViewModel.IsLoadingIndeterminate = false;
+ CanUpdate = true;
+ Cursor = Cursor.Default;
+
+ if (MainContent.Content != _mainViewContent)
+ {
+ MainContent.Content = _mainViewContent;
+ }
+
+ AppHost = null;
+
+ HandleRelaunch();
+ });
+
+ RendererControl.RendererInitialized -= GlRenderer_Created;
+ RendererControl = null;
+
+ ViewModel.SelectedIcon = null;
+
+ Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ Title = $"Ryujinx {Program.Version}";
+ });
+ }
+
+ public void Sort_Checked(object sender, RoutedEventArgs args)
+ {
+ if (sender is RadioButton button)
+ {
+ var sort = Enum.Parse<ApplicationSort>(button.Tag.ToString());
+ ViewModel.Sort(sort);
+ }
+ }
+
+ protected override void HandleWindowStateChanged(WindowState state)
+ {
+ WindowState = state;
+
+ if (state != WindowState.Minimized)
+ {
+ Renderer.Start();
+ }
+ }
+
+ public void Order_Checked(object sender, RoutedEventArgs args)
+ {
+ if (sender is RadioButton button)
+ {
+ var tag = button.Tag.ToString();
+ ViewModel.Sort(tag != "Descending");
+ }
+ }
+
+ private void Initialize()
+ {
+ _userChannelPersistence = new UserChannelPersistence();
+ VirtualFileSystem = VirtualFileSystem.CreateInstance();
+ LibHacHorizonManager = new LibHacHorizonManager();
+ ContentManager = new ContentManager(VirtualFileSystem);
+
+ LibHacHorizonManager.InitializeFsServer(VirtualFileSystem);
+ LibHacHorizonManager.InitializeArpServer();
+ LibHacHorizonManager.InitializeBcatServer();
+ LibHacHorizonManager.InitializeSystemClients();
+
+ ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem);
+
+ // Save data created before we supported extra data in directory save data will not work properly if
+ // given empty extra data. Luckily some of that extra data can be created using the data from the
+ // save data indexer, which should be enough to check access permissions for user saves.
+ // Every single save data's extra data will be checked and fixed if needed each time the emulator is opened.
+ // Consider removing this at some point in the future when we don't need to worry about old saves.
+ VirtualFileSystem.FixExtraData(LibHacHorizonManager.RyujinxClient);
+
+ AccountManager = new AccountManager(LibHacHorizonManager.RyujinxClient, CommandLineState.Profile);
+
+ VirtualFileSystem.ReloadKeySet();
+
+ ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient, this);
+
+ RefreshFirmwareStatus();
+ }
+
+ protected void CheckLaunchState()
+ {
+ if (ShowKeyErrorOnLoad)
+ {
+ ShowKeyErrorOnLoad = false;
+
+ Dispatcher.UIThread.Post(async () => await
+ UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, this));
+ }
+
+ if (_deferLoad)
+ {
+ _deferLoad = false;
+
+ LoadApplication(_launchPath, _startFullscreen);
+ }
+
+ if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false, this))
+ {
+ Updater.BeginParse(this, false).ContinueWith(task =>
+ {
+ Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}");
+ }, TaskContinuationOptions.OnlyOnFaulted);
+ }
+ }
+
+ public void RefreshFirmwareStatus()
+ {
+ SystemVersion version = null;
+ try
+ {
+ version = ContentManager.GetCurrentFirmwareVersion();
+ }
+ catch (Exception) { }
+
+ bool hasApplet = false;
+
+ if (version != null)
+ {
+ LocaleManager.Instance.UpdateDynamicValue("StatusBarSystemVersion",
+ version.VersionString);
+
+ hasApplet = version.Major > 3;
+ }
+ else
+ {
+ LocaleManager.Instance.UpdateDynamicValue("StatusBarSystemVersion", "0.0");
+ }
+
+ ViewModel.IsAppletMenuActive = hasApplet;
+ }
+
+ private void Load()
+ {
+ VolumeStatus.Click += VolumeStatus_CheckedChanged;
+
+ GameGrid.ApplicationOpened += Application_Opened;
+
+ GameGrid.DataContext = ViewModel;
+
+ GameList.ApplicationOpened += Application_Opened;
+
+ GameList.DataContext = ViewModel;
+
+ LoadHotKeys();
+ }
+
+ protected override void OnOpened(EventArgs e)
+ {
+ base.OnOpened(e);
+
+ CheckLaunchState();
+ }
+
+ public static void UpdateGraphicsConfig()
+ {
+ GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1 ? ConfigurationState.Instance.Graphics.ResScaleCustom : ConfigurationState.Instance.Graphics.ResScale;
+ GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
+ GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
+ GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
+ GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression;
+ GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE;
+ }
+
+ public void LoadHotKeys()
+ {
+ HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt));
+ HotKeyManager.SetHotKey(FullscreenHotKey2, new KeyGesture(Key.F11));
+ HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9));
+ HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape));
+ }
+
+ public static void SaveConfig()
+ {
+ ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
+ }
+
+ public void UpdateGameMetadata(string titleId)
+ {
+ ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
+ {
+ if (DateTime.TryParse(appMetadata.LastPlayed, out DateTime lastPlayedDateTime))
+ {
+ double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
+
+ appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
+ }
+ });
+ }
+
+ private void PrepareLoadScreen()
+ {
+ using MemoryStream stream = new MemoryStream(ViewModel.SelectedIcon);
+ using var gameIconBmp = SixLabors.ImageSharp.Image.Load<Bgra32>(stream);
+
+ var dominantColor = IconColorPicker.GetFilteredColor(gameIconBmp).ToPixel<Bgra32>();
+
+ const int ColorDivisor = 4;
+
+ Color progressFgColor = Color.FromRgb(dominantColor.R, dominantColor.G, dominantColor.B);
+ Color progressBgColor = Color.FromRgb(
+ (byte)(dominantColor.R / ColorDivisor),
+ (byte)(dominantColor.G / ColorDivisor),
+ (byte)(dominantColor.B / ColorDivisor));
+
+ ViewModel.ProgressBarForegroundColor = new SolidColorBrush(progressFgColor);
+ ViewModel.ProgressBarBackgroundColor = new SolidColorBrush(progressBgColor);
+ }
+
+ private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)
+ {
+ ViewModel.SearchText = SearchBox.Text;
+ }
+
+ private async void StopEmulation_Click(object sender, RoutedEventArgs e)
+ {
+ if (AppHost != null)
+ {
+ await AppHost.ShowExitPrompt();
+ }
+ }
+
+ private async void PauseEmulation_Click(object sender, RoutedEventArgs e)
+ {
+ await Task.Run(() =>
+ {
+ AppHost?.Pause();
+ });
+ }
+
+ private async void ResumeEmulation_Click(object sender, RoutedEventArgs e)
+ {
+ await Task.Run(() =>
+ {
+ AppHost?.Resume();
+ });
+ }
+
+ private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
+ {
+ if (sender is MenuItem)
+ {
+ ViewModel.IsAmiiboRequested = AppHost.Device.System.SearchingForAmiibo(out _);
+ }
+ }
+
+ private void VsyncStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
+ {
+ AppHost.Device.EnableDeviceVsync = !AppHost.Device.EnableDeviceVsync;
+
+ Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {AppHost.Device.EnableDeviceVsync}");
+ }
+
+ private void DockedStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
+ {
+ ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value;
+ }
+
+ private void AspectRatioStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
+ {
+ AspectRatio aspectRatio = ConfigurationState.Instance.Graphics.AspectRatio.Value;
+
+ ConfigurationState.Instance.Graphics.AspectRatio.Value = (int)aspectRatio + 1 > Enum.GetNames(typeof(AspectRatio)).Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1;
+ }
+
+ private void VolumeStatus_CheckedChanged(object sender, SplitButtonClickEventArgs e)
+ {
+ var volumeSplitButton = sender as ToggleSplitButton;
+ if (ViewModel.IsGameRunning)
+ {
+ if (!volumeSplitButton.IsChecked)
+ {
+ AppHost.Device.SetVolume(ConfigurationState.Instance.System.AudioVolume);
+ }
+ else
+ {
+ AppHost.Device.SetVolume(0);
+ }
+
+ ViewModel.Volume = AppHost.Device.GetVolume();
+ }
+ }
+
+ protected override void OnClosing(CancelEventArgs e)
+ {
+ if (!_isClosing && AppHost != null && ConfigurationState.Instance.ShowConfirmExit)
+ {
+ e.Cancel = true;
+
+ ConfirmExit();
+
+ return;
+ }
+
+ _isClosing = true;
+
+ if (AppHost != null)
+ {
+ AppHost.AppExit -= AppHost_AppExit;
+ AppHost.AppExit += (sender, e) =>
+ {
+ AppHost = null;
+
+ Dispatcher.UIThread.Post(() =>
+ {
+ MainContent = null;
+
+ Close();
+ });
+ };
+ AppHost?.Stop();
+
+ e.Cancel = true;
+
+ return;
+ }
+
+ ApplicationLibrary.CancelLoading();
+ InputManager.Dispose();
+ Program.Exit();
+
+ base.OnClosing(e);
+ }
+
+ private void ConfirmExit()
+ {
+ Dispatcher.UIThread.InvokeAsync(async () =>
+ {
+ _isClosing = await ContentDialogHelper.CreateExitDialog();
+
+ if (_isClosing)
+ {
+ Close();
+ }
+ });
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Ava/UI/Windows/MotionSettingsWindow.axaml b/Ryujinx.Ava/UI/Windows/MotionSettingsWindow.axaml
new file mode 100644
index 00000000..862998ac
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/MotionSettingsWindow.axaml
@@ -0,0 +1,141 @@
+<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"
+ mc:Ignorable="d"
+ x:Class="Ryujinx.Ava.UI.Windows.MotionSettingsWindow"
+ Focusable="True">
+ <Grid Margin="10">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition />
+ </Grid.RowDefinitions>
+ <StackPanel Orientation="Vertical">
+ <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+ <TextBlock
+ Margin="0"
+ HorizontalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsMotionGyroSensitivity}" />
+ <Slider
+ Margin="0,-5,0,-5"
+ Width="150"
+ MaxWidth="150"
+ TickFrequency="0.01"
+ IsSnapToTickEnabled="True"
+ Maximum="100"
+ Minimum="0"
+ Value="{Binding Sensitivity, Mode=TwoWay}" />
+ <TextBlock HorizontalAlignment="Center"
+ Margin="5, 0"
+ Text="{Binding Sensitivity, StringFormat=\{0:0\}%}" />
+ </StackPanel>
+ <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+ <TextBlock
+ Margin="0"
+ HorizontalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsMotionGyroDeadzone}" />
+ <Slider
+ Margin="0,-5,0,-5"
+ Width="150"
+ MaxWidth="150"
+ TickFrequency="0.01"
+ IsSnapToTickEnabled="True"
+ Maximum="100"
+ Minimum="0"
+ Value="{Binding GyroDeadzone, Mode=TwoWay}" />
+ <TextBlock
+ VerticalAlignment="Center"
+ Margin="5, 0"
+ Text="{Binding GyroDeadzone, StringFormat=\{0:0.00\}}" />
+ </StackPanel>
+ <Separator Height="1" Margin="0,5" />
+ <CheckBox Margin="5" IsChecked="{Binding EnableCemuHookMotion}">
+ <TextBlock Margin="0,3,0,0" VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsMotionUseCemuhookCompatibleMotion}" />
+ </CheckBox>
+ </StackPanel>
+ <Border Grid.Row="1"
+ Padding="20,5"
+ BorderBrush="{DynamicResource ThemeControlBorderColor}"
+ BorderThickness="1"
+ HorizontalAlignment="Stretch">
+ <Grid VerticalAlignment="Top">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="*" />
+ </Grid.RowDefinitions>
+ <StackPanel
+ Grid.Row="1"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Orientation="Vertical">
+ <StackPanel
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Orientation="Horizontal">
+ <TextBlock
+ Margin="5"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsMotionServerHost}" />
+ <TextBox
+ Height="30"
+ MinWidth="100"
+ MaxWidth="100"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{Binding DsuServerHost, Mode=TwoWay}" />
+ <TextBlock
+ Margin="5"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text=":" />
+ <TextBox
+ Height="30"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Text="{Binding DsuServerPort, Mode=TwoWay}" />
+ </StackPanel>
+ <StackPanel Orientation="Vertical">
+ <Grid>
+ <Grid.RowDefinitions>
+ <RowDefinition />
+ <RowDefinition />
+ </Grid.RowDefinitions>
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition />
+ <ColumnDefinition />
+ </Grid.ColumnDefinitions>
+ <TextBlock Margin="0,10,0,0" VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsMotionControllerSlot}" />
+ <ui:NumberBox Grid.Row="0" Grid.Column="1"
+ Name="CemuHookSlotUpDown"
+ SmallChange="1"
+ LargeChange="1"
+ Maximum="4"
+ Minimum="0"
+ Value="{Binding Slot}" />
+ <TextBlock Margin="0,10,0,0" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsMotionRightJoyConSlot}" />
+ <ui:NumberBox Grid.Row="1" Grid.Column="1"
+ Name="CemuHookRightJoyConSlotUpDown"
+ SmallChange="1"
+ LargeChange="1"
+ Maximum="4"
+ Minimum="0"
+ Value="{Binding AltSlot}" />
+ </Grid>
+ </StackPanel>
+ <CheckBox HorizontalAlignment="Center"
+ IsChecked="{Binding MirrorInput, Mode=TwoWay}">
+ <TextBlock HorizontalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsMotionMirrorInput}" />
+ </CheckBox>
+ </StackPanel>
+ </Grid>
+ </Border>
+ </Grid>
+</UserControl> \ No newline at end of file
diff --git a/Ryujinx.Ava/UI/Windows/MotionSettingsWindow.axaml.cs b/Ryujinx.Ava/UI/Windows/MotionSettingsWindow.axaml.cs
new file mode 100644
index 00000000..215525fc
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/MotionSettingsWindow.axaml.cs
@@ -0,0 +1,71 @@
+using Avalonia.Controls;
+using FluentAvalonia.UI.Controls;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.UI.Models;
+using Ryujinx.Ava.UI.ViewModels;
+using Ryujinx.Common.Configuration.Hid.Controller;
+using System.Threading.Tasks;
+
+namespace Ryujinx.Ava.UI.Windows
+{
+ public partial class MotionSettingsWindow : UserControl
+ {
+ private readonly InputConfiguration<GamepadInputId, StickInputId> _viewmodel;
+
+ public MotionSettingsWindow()
+ {
+ InitializeComponent();
+ DataContext = _viewmodel;
+ }
+
+ public MotionSettingsWindow(ControllerSettingsViewModel viewmodel)
+ {
+ var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
+
+ _viewmodel = new InputConfiguration<GamepadInputId, StickInputId>()
+ {
+ Slot = config.Slot,
+ AltSlot = config.AltSlot,
+ DsuServerHost = config.DsuServerHost,
+ DsuServerPort = config.DsuServerPort,
+ MirrorInput = config.MirrorInput,
+ EnableMotion = config.EnableMotion,
+ Sensitivity = config.Sensitivity,
+ GyroDeadzone = config.GyroDeadzone,
+ EnableCemuHookMotion = config.EnableCemuHookMotion
+ };
+
+ InitializeComponent();
+ DataContext = _viewmodel;
+ }
+
+ public static async Task Show(ControllerSettingsViewModel viewmodel)
+ {
+ MotionSettingsWindow content = new MotionSettingsWindow(viewmodel);
+
+ ContentDialog contentDialog = new ContentDialog
+ {
+ Title = LocaleManager.Instance["ControllerMotionTitle"],
+ PrimaryButtonText = LocaleManager.Instance["ControllerSettingsSave"],
+ SecondaryButtonText = "",
+ CloseButtonText = LocaleManager.Instance["ControllerSettingsClose"],
+ Content = content
+ };
+ contentDialog.PrimaryButtonClick += (sender, args) =>
+ {
+ var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
+ config.Slot = content._viewmodel.Slot;
+ config.EnableMotion = content._viewmodel.EnableMotion;
+ config.Sensitivity = content._viewmodel.Sensitivity;
+ config.GyroDeadzone = content._viewmodel.GyroDeadzone;
+ config.AltSlot = content._viewmodel.AltSlot;
+ config.DsuServerHost = content._viewmodel.DsuServerHost;
+ config.DsuServerPort = content._viewmodel.DsuServerPort;
+ config.EnableCemuHookMotion = content._viewmodel.EnableCemuHookMotion;
+ config.MirrorInput = content._viewmodel.MirrorInput;
+ };
+
+ await contentDialog.ShowAsync();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Ava/UI/Windows/RumbleSettingsWindow.axaml b/Ryujinx.Ava/UI/Windows/RumbleSettingsWindow.axaml
new file mode 100644
index 00000000..e47cc5bd
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/RumbleSettingsWindow.axaml
@@ -0,0 +1,57 @@
+<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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
+ mc:Ignorable="d"
+ x:Class="Ryujinx.Ava.UI.Windows.RumbleSettingsWindow"
+ Focusable="True">
+ <Grid Margin="10">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition />
+ </Grid.RowDefinitions>
+ <StackPanel Orientation="Vertical">
+ <StackPanel Orientation="Horizontal">
+ <TextBlock
+ Width="100"
+ TextWrapping="WrapWithOverflow"
+ HorizontalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsRumbleStrongMultiplier}" />
+ <Slider
+ Margin="0,-5,0,-5"
+ Width="200"
+ TickFrequency="0.01"
+ IsSnapToTickEnabled="True"
+ Maximum="10"
+ Minimum="0"
+ Value="{Binding StrongRumble, Mode=TwoWay}" />
+ <TextBlock
+ VerticalAlignment="Center"
+ Margin="5,0"
+ Text="{Binding StrongRumble, StringFormat=\{0:0.00\}}" />
+ </StackPanel>
+ <StackPanel Orientation="Horizontal">
+ <TextBlock
+ Width="100"
+ TextWrapping="WrapWithOverflow"
+ HorizontalAlignment="Center"
+ Text="{locale:Locale ControllerSettingsRumbleWeakMultiplier}" />
+ <Slider
+ Margin="0,-5,0,-5"
+ Width="200"
+ MaxWidth="200"
+ Maximum="10"
+ TickFrequency="0.01"
+ IsSnapToTickEnabled="True"
+ Minimum="0"
+ Value="{Binding WeakRumble, Mode=TwoWay}" />
+ <TextBlock
+ VerticalAlignment="Center"
+ Margin="5,0"
+ Text="{Binding WeakRumble, StringFormat=\{0:0.00\}}" />
+ </StackPanel>
+ </StackPanel>
+ </Grid>
+</UserControl> \ No newline at end of file
diff --git a/Ryujinx.Ava/UI/Windows/RumbleSettingsWindow.axaml.cs b/Ryujinx.Ava/UI/Windows/RumbleSettingsWindow.axaml.cs
new file mode 100644
index 00000000..f645ae35
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/RumbleSettingsWindow.axaml.cs
@@ -0,0 +1,57 @@
+using Avalonia.Controls;
+using FluentAvalonia.UI.Controls;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.UI.Models;
+using Ryujinx.Ava.UI.ViewModels;
+using Ryujinx.Common.Configuration.Hid.Controller;
+using System.Threading.Tasks;
+
+namespace Ryujinx.Ava.UI.Windows
+{
+ public partial class RumbleSettingsWindow : UserControl
+ {
+ private readonly InputConfiguration<GamepadInputId, StickInputId> _viewmodel;
+
+ public RumbleSettingsWindow()
+ {
+ InitializeComponent();
+ DataContext = _viewmodel;
+ }
+
+ public RumbleSettingsWindow(ControllerSettingsViewModel viewmodel)
+ {
+ var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
+
+ _viewmodel = new InputConfiguration<GamepadInputId, StickInputId>()
+ {
+ StrongRumble = config.StrongRumble, WeakRumble = config.WeakRumble
+ };
+
+ InitializeComponent();
+ DataContext = _viewmodel;
+ }
+
+ public static async Task Show(ControllerSettingsViewModel viewmodel)
+ {
+ RumbleSettingsWindow content = new RumbleSettingsWindow(viewmodel);
+
+ ContentDialog contentDialog = new ContentDialog
+ {
+ Title = LocaleManager.Instance["ControllerRumbleTitle"],
+ PrimaryButtonText = LocaleManager.Instance["ControllerSettingsSave"],
+ SecondaryButtonText = "",
+ CloseButtonText = LocaleManager.Instance["ControllerSettingsClose"],
+ Content = content,
+ };
+
+ contentDialog.PrimaryButtonClick += (sender, args) =>
+ {
+ var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
+ config.StrongRumble = content._viewmodel.StrongRumble;
+ config.WeakRumble = content._viewmodel.WeakRumble;
+ };
+
+ await contentDialog.ShowAsync();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml b/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml
new file mode 100644
index 00000000..e2550082
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml
@@ -0,0 +1,980 @@
+<window:StyleableWindow
+ x:Class="Ryujinx.Ava.UI.Windows.SettingsWindow"
+ xmlns="https://github.com/avaloniaui"
+ xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+ 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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
+ xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
+ xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
+ Width="1100"
+ Height="768"
+ d:DesignWidth="800"
+ d:DesignHeight="950"
+ MinWidth="800"
+ MinHeight="480"
+ WindowStartupLocation="CenterOwner"
+ x:CompileBindings="True"
+ x:DataType="viewModels:SettingsViewModel"
+ mc:Ignorable="d"
+ Focusable="True">
+ <Design.DataContext>
+ <viewModels:SettingsViewModel />
+ </Design.DataContext>
+ <Window.Resources>
+ <helpers:KeyValueConverter x:Key="Key" />
+ </Window.Resources>
+ <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="600">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition />
+ <RowDefinition Height="Auto" />
+ </Grid.RowDefinitions>
+ <ContentPresenter
+ x:Name="ContentPresenter"
+ Grid.Row="1"
+ IsVisible="False"
+ KeyboardNavigation.IsTabStop="False"/>
+ <Grid Name="Pages" IsVisible="False" Grid.Row="2">
+ <ScrollViewer Name="UiPage"
+ Margin="0,0,2,0"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ HorizontalScrollBarVisibility="Disabled"
+ VerticalScrollBarVisibility="Auto">
+ <Border Classes="settings">
+ <StackPanel
+ Margin="10,5"
+ HorizontalAlignment="Stretch"
+ Orientation="Vertical"
+ Spacing="10">
+ <TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralGeneral}" />
+ <StackPanel Margin="10,0,0,0" Orientation="Vertical">
+ <CheckBox IsChecked="{Binding EnableDiscordIntegration}">
+ <TextBlock VerticalAlignment="Center"
+ ToolTip.Tip="{locale:Locale ToggleDiscordTooltip}"
+ Text="{locale:Locale SettingsTabGeneralEnableDiscordRichPresence}" />
+ </CheckBox>
+ <CheckBox IsChecked="{Binding CheckUpdatesOnStart}">
+ <TextBlock Text="{locale:Locale SettingsTabGeneralCheckUpdatesOnLaunch}" />
+ </CheckBox>
+ <CheckBox IsChecked="{Binding ShowConfirmExit}">
+ <TextBlock Text="{locale:Locale SettingsTabGeneralShowConfirmExitDialog}" />
+ </CheckBox>
+ <CheckBox IsChecked="{Binding HideCursorOnIdle}">
+ <TextBlock Text="{locale:Locale SettingsTabGeneralHideCursorOnIdle}" />
+ </CheckBox>
+ </StackPanel>
+ <Separator Height="1" />
+ <TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralGameDirectories}" />
+ <StackPanel
+ Margin="10,0,0,0"
+ HorizontalAlignment="Stretch"
+ Orientation="Vertical"
+ Spacing="10">
+ <ListBox
+ Name="GameList"
+ MinHeight="250"
+ Items="{Binding GameDirectories}" />
+ <Grid HorizontalAlignment="Stretch">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="*" />
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition Width="Auto" />
+ </Grid.ColumnDefinitions>
+ <TextBox
+ Name="PathBox"
+ Margin="0"
+ ToolTip.Tip="{locale:Locale AddGameDirBoxTooltip}"
+ VerticalAlignment="Stretch" />
+ <Button
+ Name="AddButton"
+ Grid.Column="1"
+ MinWidth="90"
+ Margin="10,0,0,0"
+ ToolTip.Tip="{locale:Locale AddGameDirTooltip}"
+ Click="AddButton_OnClick">
+ <TextBlock HorizontalAlignment="Center"
+ Text="{locale:Locale SettingsTabGeneralAdd}" />
+ </Button>
+ <Button
+ Name="RemoveButton"
+ Grid.Column="2"
+ MinWidth="90"
+ Margin="10,0,0,0"
+ ToolTip.Tip="{locale:Locale RemoveGameDirTooltip}"
+ Click="RemoveButton_OnClick">
+ <TextBlock HorizontalAlignment="Center"
+ Text="{locale:Locale SettingsTabGeneralRemove}" />
+ </Button>
+ </Grid>
+ </StackPanel>
+ <Separator Height="1" />
+ <TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralTheme}" />
+ <Grid Margin="10,0,0,0">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition />
+ <ColumnDefinition Width="Auto" />
+ </Grid.ColumnDefinitions>
+ <Grid.RowDefinitions>
+ <RowDefinition />
+ <RowDefinition />
+ <RowDefinition />
+ </Grid.RowDefinitions>
+ <CheckBox IsChecked="{Binding EnableCustomTheme}"
+ ToolTip.Tip="{locale:Locale CustomThemeCheckTooltip}">
+ <TextBlock Text="{locale:Locale SettingsTabGeneralThemeEnableCustomTheme}" />
+ </CheckBox>
+ <TextBlock VerticalAlignment="Center"
+ Margin="0,10,0,0"
+ Grid.Row="1"
+ Text="{locale:Locale SettingsTabGeneralThemeCustomTheme}"
+ ToolTip.Tip="{locale:Locale CustomThemePathTooltip}" />
+ <TextBox Margin="0,10,0,0"
+ Grid.Row="1"
+ Grid.Column="1"
+ Text="{Binding CustomThemePath}" />
+ <Button Grid.Row="1"
+ Grid.Column="2"
+ Margin="10,10,0,0"
+ Command="{ReflectionBinding BrowseTheme}"
+ ToolTip.Tip="{locale:Locale CustomThemeBrowseTooltip}"
+ Content="{locale:Locale ButtonBrowse}" />
+ <TextBlock VerticalAlignment="Center"
+ Margin="0,10,0,0"
+ Grid.Row="2"
+ Text="{locale:Locale SettingsTabGeneralThemeBaseStyle}" />
+ <ComboBox VerticalAlignment="Center"
+ Margin="0,10,0,0"
+ Grid.Column="1"
+ Grid.Row="2"
+ MinWidth="100"
+ SelectedIndex="{Binding BaseStyleIndex}">
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabGeneralThemeBaseStyleLight}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabGeneralThemeBaseStyleDark}" />
+ </ComboBoxItem>
+ </ComboBox>
+ </Grid>
+ </StackPanel>
+ </Border>
+ </ScrollViewer>
+ <ScrollViewer Name="InputPage"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ Padding="0,0,2,0"
+ HorizontalScrollBarVisibility="Disabled"
+ VerticalScrollBarVisibility="Auto">
+ <Border Classes="settings">
+ <StackPanel Margin="4" Orientation="Vertical">
+ <StackPanel Orientation="Horizontal">
+ <CheckBox Margin="5,0"
+ ToolTip.Tip="{locale:Locale DockModeToggleTooltip}"
+ IsChecked="{Binding EnableDockedMode}">
+ <TextBlock VerticalAlignment="Center"
+ Text="{locale:Locale SettingsTabInputEnableDockedMode}" />
+ </CheckBox>
+ <CheckBox Margin="5,0"
+ ToolTip.Tip="{locale:Locale DirectKeyboardTooltip}"
+ IsChecked="{Binding EnableKeyboard}">
+ <TextBlock Text="{locale:Locale SettingsTabInputDirectKeyboardAccess}" />
+ </CheckBox>
+ <CheckBox Margin="5,0"
+ ToolTip.Tip="{locale:Locale DirectMouseTooltip}"
+ IsChecked="{Binding EnableMouse}">
+ <TextBlock Text="{locale:Locale SettingsTabInputDirectMouseAccess}" />
+ </CheckBox>
+ </StackPanel>
+ <window:ControllerSettingsWindow Name="ControllerSettings" Margin="0,0,0,0" MinHeight="600" />
+ </StackPanel>
+ </Border>
+ </ScrollViewer>
+ <ScrollViewer Name="HotkeysPage"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ HorizontalScrollBarVisibility="Disabled"
+ VerticalScrollBarVisibility="Auto">
+ <Border Classes="settings">
+ <StackPanel Margin="10,5" Orientation="Vertical" Spacing="10">
+ <TextBlock Classes="h1" Text="{locale:Locale SettingsTabHotkeysHotkeys}" />
+ <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysToggleVsyncHotkey}" Width="230" />
+ <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
+ <TextBlock
+ Text="{Binding KeyboardHotkeys.ToggleVsync, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysScreenshotHotkey}" Width="230" />
+ <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
+ <TextBlock
+ Text="{Binding KeyboardHotkeys.Screenshot, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysShowUiHotkey}" Width="230" />
+ <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
+ <TextBlock
+ Text="{Binding KeyboardHotkeys.ShowUi, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysPauseHotkey}" Width="230" />
+ <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
+ <TextBlock
+ Text="{Binding KeyboardHotkeys.Pause, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysToggleMuteHotkey}" Width="230" />
+ <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
+ <TextBlock
+ Text="{Binding KeyboardHotkeys.ToggleMute, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysResScaleUpHotkey}" Width="230" />
+ <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
+ <TextBlock
+ Text="{Binding KeyboardHotkeys.ResScaleUp, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysResScaleDownHotkey}" Width="230" />
+ <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
+ <TextBlock
+ Text="{Binding KeyboardHotkeys.ResScaleDown, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysVolumeUpHotkey}" Width="230" />
+ <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
+ <TextBlock
+ Text="{Binding KeyboardHotkeys.VolumeUp, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysVolumeDownHotkey}" Width="230" />
+ <ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
+ <TextBlock
+ Text="{Binding KeyboardHotkeys.VolumeDown, Mode=TwoWay, Converter={StaticResource Key}}"
+ TextAlignment="Center" />
+ </ToggleButton>
+ </StackPanel>
+ </StackPanel>
+ </Border>
+ </ScrollViewer>
+ <ScrollViewer Name="SystemPage"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ HorizontalScrollBarVisibility="Disabled"
+ VerticalScrollBarVisibility="Auto">
+ <Border Classes="settings">
+ <StackPanel
+ Margin="10,5"
+ HorizontalAlignment="Stretch"
+ Orientation="Vertical"
+ Spacing="10">
+ <TextBlock Classes="h1" Text="{locale:Locale SettingsTabSystemCore}" />
+ <StackPanel Margin="10,0,0,0" Orientation="Vertical">
+ <StackPanel Margin="0,0,0,10" Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center"
+ Text="{locale:Locale SettingsTabSystemSystemRegion}"
+ Width="250" />
+ <ComboBox SelectedIndex="{Binding Region}"
+ ToolTip.Tip="{locale:Locale RegionTooltip}"
+ HorizontalContentAlignment="Left"
+ Width="350">
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionJapan}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionUSA}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionEurope}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionAustralia}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionChina}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionKorea}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionTaiwan}" />
+ </ComboBoxItem>
+ </ComboBox>
+ </StackPanel>
+ <StackPanel Margin="0,0,0,10" Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center"
+ Text="{locale:Locale SettingsTabSystemSystemLanguage}"
+ ToolTip.Tip="{locale:Locale LanguageTooltip}"
+ Width="250" />
+ <ComboBox SelectedIndex="{Binding Language}"
+ ToolTip.Tip="{locale:Locale LanguageTooltip}"
+ HorizontalContentAlignment="Left"
+ Width="350">
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageJapanese}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock
+ Text="{locale:Locale SettingsTabSystemSystemLanguageAmericanEnglish}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageFrench}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageGerman}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageItalian}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageSpanish}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageChinese}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageKorean}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageDutch}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguagePortuguese}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageRussian}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageTaiwanese}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock
+ Text="{locale:Locale SettingsTabSystemSystemLanguageBritishEnglish}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock
+ Text="{locale:Locale SettingsTabSystemSystemLanguageCanadianFrench}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock
+ Text="{locale:Locale SettingsTabSystemSystemLanguageLatinAmericanSpanish}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock
+ Text="{locale:Locale SettingsTabSystemSystemLanguageSimplifiedChinese}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock
+ Text="{locale:Locale SettingsTabSystemSystemLanguageTraditionalChinese}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock
+ Text="{locale:Locale SettingsTabSystemSystemLanguageBrazilianPortuguese}" />
+ </ComboBoxItem>
+ </ComboBox>
+ </StackPanel>
+ <StackPanel Margin="0,0,0,10" Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center"
+ Text="{locale:Locale SettingsTabSystemSystemTimeZone}"
+ ToolTip.Tip="{locale:Locale TimezoneTooltip}"
+ Width="250" />
+ <AutoCompleteBox
+ Name="TimeZoneBox"
+ Width="350"
+ MaxDropDownHeight="500"
+ FilterMode="Contains"
+ Items="{Binding TimeZones}"
+ SelectionChanged="TimeZoneBox_OnSelectionChanged"
+ Text="{Binding Path=TimeZone, Mode=OneWay}"
+ TextChanged="TimeZoneBox_OnTextChanged"
+ ValueMemberBinding="{ReflectionBinding TzMultiBinding}"
+ ToolTip.Tip="{locale:Locale TimezoneTooltip}" />
+ </StackPanel>
+ <StackPanel Margin="0,0,0,10" Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center"
+ Text="{locale:Locale SettingsTabSystemSystemTime}"
+ ToolTip.Tip="{locale:Locale TimeTooltip}"
+ Width="250"/>
+ <DatePicker VerticalAlignment="Center" SelectedDate="{Binding DateOffset}"
+ ToolTip.Tip="{locale:Locale TimeTooltip}"
+ Width="350" />
+ </StackPanel>
+ <StackPanel Margin="250,0,0,10" Orientation="Horizontal">
+ <TimePicker
+ VerticalAlignment="Center"
+ ClockIdentifier="24HourClock"
+ SelectedTime="{Binding TimeOffset}"
+ Width="350"
+ ToolTip.Tip="{locale:Locale TimeTooltip}" />
+ </StackPanel>
+ <CheckBox IsChecked="{Binding EnableVsync}">
+ <TextBlock Text="{locale:Locale SettingsTabSystemEnableVsync}"
+ ToolTip.Tip="{locale:Locale VSyncToggleTooltip}" />
+ </CheckBox>
+ <CheckBox IsChecked="{Binding EnableFsIntegrityChecks}">
+ <TextBlock Text="{locale:Locale SettingsTabSystemEnableFsIntegrityChecks}"
+ ToolTip.Tip="{locale:Locale FsIntegrityToggleTooltip}" />
+ </CheckBox>
+ </StackPanel>
+ <Separator Height="1" />
+ <StackPanel Orientation="Horizontal">
+ <TextBlock Classes="h1" Text="{locale:Locale SettingsTabSystemHacks}" />
+ <TextBlock Text="{locale:Locale SettingsTabSystemHacksNote}" />
+ </StackPanel>
+ <StackPanel
+ Margin="10,0,0,0"
+ HorizontalAlignment="Stretch"
+ Orientation="Vertical">
+ <CheckBox IsChecked="{Binding ExpandDramSize}"
+ ToolTip.Tip="{locale:Locale DRamTooltip}">
+ <TextBlock Text="{locale:Locale SettingsTabSystemExpandDramSize}" />
+ </CheckBox>
+ <CheckBox IsChecked="{Binding IgnoreMissingServices}"
+ ToolTip.Tip="{locale:Locale IgnoreMissingServicesTooltip}">
+ <TextBlock Text="{locale:Locale SettingsTabSystemIgnoreMissingServices}" />
+ </CheckBox>
+ </StackPanel>
+ </StackPanel>
+ </Border>
+ </ScrollViewer>
+ <ScrollViewer
+ Name="CpuPage"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ HorizontalScrollBarVisibility="Disabled"
+ VerticalScrollBarVisibility="Auto">
+ <Border Classes="settings">
+ <StackPanel
+ Margin="10,5"
+ HorizontalAlignment="Stretch"
+ Orientation="Vertical"
+ Spacing="10">
+ <TextBlock Classes="h1" Text="{locale:Locale SettingsTabCpuCache}" />
+ <StackPanel
+ Margin="10,0,0,0"
+ HorizontalAlignment="Stretch"
+ Orientation="Vertical">
+ <CheckBox IsChecked="{Binding EnablePptc}">
+ <TextBlock Text="{locale:Locale SettingsTabSystemEnablePptc}"
+ ToolTip.Tip="{locale:Locale PptcToggleTooltip}" />
+ </CheckBox>
+ </StackPanel>
+ <Separator Height="1" />
+ <TextBlock Classes="h1" Text="{locale:Locale SettingsTabCpuMemory}" />
+ <StackPanel
+ Margin="10,0,0,0"
+ HorizontalAlignment="Stretch"
+ Orientation="Vertical">
+ <StackPanel Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center"
+ Text="{locale:Locale SettingsTabSystemMemoryManagerMode}"
+ ToolTip.Tip="{locale:Locale MemoryManagerTooltip}"
+ Width="250" />
+ <ComboBox SelectedIndex="{Binding MemoryMode}"
+ ToolTip.Tip="{locale:Locale MemoryManagerTooltip}"
+ HorizontalContentAlignment="Left"
+ Width="350">
+ <ComboBoxItem
+ ToolTip.Tip="{locale:Locale MemoryManagerSoftwareTooltip}">
+ <TextBlock
+ Text="{locale:Locale SettingsTabSystemMemoryManagerModeSoftware}" />
+ </ComboBoxItem>
+ <ComboBoxItem
+ ToolTip.Tip="{locale:Locale MemoryManagerHostTooltip}">
+ <TextBlock Text="{locale:Locale SettingsTabSystemMemoryManagerModeHost}" />
+ </ComboBoxItem>
+ <ComboBoxItem
+ ToolTip.Tip="{locale:Locale MemoryManagerUnsafeTooltip}">
+ <TextBlock
+ Text="{locale:Locale SettingsTabSystemMemoryManagerModeHostUnchecked}" />
+ </ComboBoxItem>
+ </ComboBox>
+ </StackPanel>
+ </StackPanel>
+ </StackPanel>
+ </Border>
+ </ScrollViewer>
+ <ScrollViewer
+ Name="GraphicsPage"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ HorizontalScrollBarVisibility="Disabled"
+ VerticalScrollBarVisibility="Auto">
+ <Border Classes="settings">
+ <StackPanel
+ Margin="10,5"
+ HorizontalAlignment="Stretch"
+ Orientation="Vertical"
+ Spacing="10">
+ <TextBlock Classes="h1" Text="{locale:Locale SettingsTabGraphicsAPI}" />
+ <StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10">
+ <StackPanel Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center"
+ ToolTip.Tip="{locale:Locale SettingsTabGraphicsBackendTooltip}"
+ Text="{locale:Locale SettingsTabGraphicsBackend}"
+ Width="250" />
+ <ComboBox Width="350"
+ HorizontalContentAlignment="Left"
+ ToolTip.Tip="{locale:Locale SettingsTabGraphicsBackendTooltip}"
+ SelectedIndex="{Binding GraphicsBackendIndex}">
+ <ComboBoxItem IsVisible="{Binding IsVulkanAvailable}">
+ <TextBlock Text="Vulkan" />
+ </ComboBoxItem>
+ <ComboBoxItem IsEnabled="{Binding IsOpenGLAvailable}">
+ <TextBlock Text="OpenGL" />
+ </ComboBoxItem>
+ </ComboBox>
+ </StackPanel>
+ <StackPanel Orientation="Horizontal" IsVisible="{Binding IsVulkanSelected}">
+ <TextBlock VerticalAlignment="Center"
+ ToolTip.Tip="{locale:Locale SettingsTabGraphicsPreferredGpuTooltip}"
+ Text="{locale:Locale SettingsTabGraphicsPreferredGpu}"
+ Width="250" />
+ <ComboBox Width="350"
+ HorizontalContentAlignment="Left"
+ ToolTip.Tip="{locale:Locale SettingsTabGraphicsPreferredGpuTooltip}"
+ SelectedIndex="{Binding PreferredGpuIndex}"
+ Items="{Binding AvailableGpus}"/>
+ </StackPanel>
+ </StackPanel>
+ <Separator Height="1" />
+ <TextBlock Classes="h1" Text="{locale:Locale SettingsTabGraphicsFeatures}" />
+ <StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10">
+ <StackPanel Orientation="Vertical">
+ <CheckBox IsChecked="{Binding EnableShaderCache}"
+ ToolTip.Tip="{locale:Locale ShaderCacheToggleTooltip}">
+ <TextBlock Text="{locale:Locale SettingsTabGraphicsEnableShaderCache}" />
+ </CheckBox>
+ <CheckBox IsChecked="{Binding EnableTextureRecompression}"
+ ToolTip.Tip="{locale:Locale SettingsEnableTextureRecompressionTooltip}">
+ <TextBlock Text="{locale:Locale SettingsEnableTextureRecompression}" />
+ </CheckBox>
+ <CheckBox IsChecked="{Binding EnableMacroHLE}"
+ ToolTip.Tip="{locale:Locale SettingsEnableMacroHLETooltip}">
+ <TextBlock Text="{locale:Locale SettingsEnableMacroHLE}" />
+ </CheckBox>
+ </StackPanel>
+ <StackPanel Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center"
+ ToolTip.Tip="{locale:Locale ResolutionScaleTooltip}"
+ Text="{locale:Locale SettingsTabGraphicsResolutionScale}"
+ Width="250" />
+ <ComboBox SelectedIndex="{Binding ResolutionScale}"
+ Width="350"
+ HorizontalContentAlignment="Left"
+ ToolTip.Tip="{locale:Locale ResolutionScaleTooltip}">
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleCustom}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleNative}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale2x}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale3x}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale4x}" />
+ </ComboBoxItem>
+ </ComboBox>
+ <ui:NumberBox
+ Margin="10,0,0,0"
+ ToolTip.Tip="{locale:Locale ResolutionScaleEntryTooltip}"
+ MinWidth="150"
+ SmallChange="0.1"
+ LargeChange="1"
+ SimpleNumberFormat="F2"
+ SpinButtonPlacementMode="Inline"
+ IsVisible="{Binding IsCustomResolutionScaleActive}"
+ Maximum="100"
+ Minimum="0.1"
+ Value="{Binding CustomResolutionScale}" />
+ </StackPanel>
+ <StackPanel Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center"
+ ToolTip.Tip="{locale:Locale AnisotropyTooltip}"
+ Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering}"
+ Width="250" />
+ <ComboBox SelectedIndex="{Binding MaxAnisotropy}"
+ Width="350"
+ HorizontalContentAlignment="Left"
+ ToolTip.Tip="{locale:Locale AnisotropyTooltip}">
+ <ComboBoxItem>
+ <TextBlock
+ Text="{locale:Locale SettingsTabGraphicsAnisotropicFilteringAuto}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering2x}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering4x}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering8x}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock
+ Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering16x}" />
+ </ComboBoxItem>
+ </ComboBox>
+ </StackPanel>
+ <StackPanel Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center"
+ ToolTip.Tip="{locale:Locale AspectRatioTooltip}"
+ Text="{locale:Locale SettingsTabGraphicsAspectRatio}"
+ Width="250" />
+ <ComboBox SelectedIndex="{Binding AspectRatio}"
+ Width="350"
+ HorizontalContentAlignment="Left"
+ ToolTip.Tip="{locale:Locale AspectRatioTooltip}">
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio4x3}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio16x9}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio16x10}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio21x9}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio32x9}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatioStretch}" />
+ </ComboBoxItem>
+ </ComboBox>
+ </StackPanel>
+ </StackPanel>
+ <StackPanel
+ Margin="10,0,0,0"
+ HorizontalAlignment="Stretch"
+ Orientation="Vertical"
+ Spacing="10">
+ <StackPanel Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center"
+ ToolTip.Tip="{locale:Locale GraphicsBackendThreadingTooltip}"
+ Text="{locale:Locale SettingsTabGraphicsBackendMultithreading}"
+ Width="250" />
+ <ComboBox Width="350"
+ HorizontalContentAlignment="Left"
+ ToolTip.Tip="{locale:Locale GalThreadingTooltip}"
+ SelectedIndex="{Binding GraphicsBackendMultithreadingIndex}">
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale CommonAuto}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale CommonOff}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale CommonOn}" />
+ </ComboBoxItem>
+ </ComboBox>
+ </StackPanel>
+ </StackPanel>
+ <Separator Height="1" />
+ <TextBlock Classes="h1" Text="{locale:Locale SettingsTabGraphicsDeveloperOptions}" />
+ <StackPanel
+ Margin="10,0,0,0"
+ HorizontalAlignment="Stretch"
+ Orientation="Vertical"
+ Spacing="10">
+ <StackPanel Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center"
+ ToolTip.Tip="{locale:Locale ShaderDumpPathTooltip}"
+ Text="{locale:Locale SettingsTabGraphicsShaderDumpPath}"
+ Width="250" />
+ <TextBox Text="{Binding ShaderDumpPath}"
+ Width="350"
+ ToolTip.Tip="{locale:Locale ShaderDumpPathTooltip}" />
+ </StackPanel>
+ </StackPanel>
+ </StackPanel>
+ </Border>
+ </ScrollViewer>
+ <ScrollViewer
+ Name="AudioPage"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ HorizontalScrollBarVisibility="Disabled"
+ VerticalScrollBarVisibility="Auto">
+ <Border Classes="settings">
+ <StackPanel
+ Margin="10,5"
+ HorizontalAlignment="Stretch"
+ Orientation="Vertical"
+ Spacing="10">
+ <TextBlock Classes="h1" Text="{locale:Locale SettingsTabAudio}" />
+ <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center"
+ Text="{locale:Locale SettingsTabSystemAudioBackend}"
+ ToolTip.Tip="{locale:Locale AudioBackendTooltip}"
+ Width="250" />
+ <ComboBox SelectedIndex="{Binding AudioBackend}"
+ Width="350"
+ HorizontalContentAlignment="Left">
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendDummy}" />
+ </ComboBoxItem>
+ <ComboBoxItem IsEnabled="{Binding IsOpenAlEnabled}">
+ <TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendOpenAL}" />
+ </ComboBoxItem>
+ <ComboBoxItem IsEnabled="{Binding IsSoundIoEnabled}">
+ <TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendSoundIO}" />
+ </ComboBoxItem>
+ <ComboBoxItem IsEnabled="{Binding IsSDL2Enabled}">
+ <TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendSDL2}" />
+ </ComboBoxItem>
+ </ComboBox>
+ </StackPanel>
+ <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center"
+ Text="{locale:Locale SettingsTabSystemAudioVolume}"
+ ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
+ Width="250" />
+ <ui:NumberBox Value="{Binding Volume}"
+ ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
+ Width="350"
+ SmallChange="1"
+ LargeChange="10"
+ SimpleNumberFormat="F0"
+ SpinButtonPlacementMode="Inline"
+ Minimum="0"
+ Maximum="100" />
+ </StackPanel>
+ <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
+ <Slider Value="{Binding Volume}"
+ Margin="250,0,0,0"
+ ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
+ Minimum="0"
+ Maximum="100"
+ SmallChange="5"
+ TickFrequency="5"
+ IsSnapToTickEnabled="True"
+ LargeChange="10"
+ Width="350" />
+ </StackPanel>
+ </StackPanel>
+ </Border>
+ </ScrollViewer>
+ <ScrollViewer
+ Name="NetworkPage"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ HorizontalScrollBarVisibility="Disabled"
+ VerticalScrollBarVisibility="Auto">
+ <Border Classes="settings">
+ <StackPanel
+ Margin="10,5"
+ HorizontalAlignment="Stretch"
+ Orientation="Vertical"
+ Spacing="10">
+ <TextBlock Classes="h1" Text="{locale:Locale SettingsTabNetworkConnection}" />
+ <CheckBox Margin="10,0,0,0" IsChecked="{Binding EnableInternetAccess}">
+ <TextBlock Text="{locale:Locale SettingsTabSystemEnableInternetAccess}"
+ ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" />
+ </CheckBox>
+ </StackPanel>
+ </Border>
+ </ScrollViewer>
+ <ScrollViewer
+ Name="LoggingPage"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ HorizontalScrollBarVisibility="Disabled"
+ VerticalScrollBarVisibility="Auto">
+ <Border Classes="settings">
+ <StackPanel
+ Margin="10,5"
+ HorizontalAlignment="Stretch"
+ Orientation="Vertical"
+ Spacing="10">
+ <TextBlock Classes="h1" Text="{locale:Locale SettingsTabLoggingLogging}" />
+ <StackPanel Margin="10,0,0,0" Orientation="Vertical">
+ <CheckBox IsChecked="{Binding EnableFileLog}"
+ ToolTip.Tip="{locale:Locale FileLogTooltip}">
+ <TextBlock Text="{locale:Locale SettingsTabLoggingEnableLoggingToFile}" />
+ </CheckBox>
+ <CheckBox IsChecked="{Binding EnableStub}"
+ ToolTip.Tip="{locale:Locale StubLogTooltip}">
+ <TextBlock Text="{locale:Locale SettingsTabLoggingEnableStubLogs}" />
+ </CheckBox>
+ <CheckBox IsChecked="{Binding EnableInfo}"
+ ToolTip.Tip="{locale:Locale InfoLogTooltip}">
+ <TextBlock Text="{locale:Locale SettingsTabLoggingEnableInfoLogs}" />
+ </CheckBox>
+ <CheckBox IsChecked="{Binding EnableWarn}"
+ ToolTip.Tip="{locale:Locale WarnLogTooltip}">
+ <TextBlock Text="{locale:Locale SettingsTabLoggingEnableWarningLogs}" />
+ </CheckBox>
+ <CheckBox IsChecked="{Binding EnableError}"
+ ToolTip.Tip="{locale:Locale ErrorLogTooltip}">
+ <TextBlock Text="{locale:Locale SettingsTabLoggingEnableErrorLogs}" />
+ </CheckBox>
+ <CheckBox IsChecked="{Binding EnableTrace}"
+ ToolTip.Tip="{locale:Locale TraceLogTooltip}">
+ <TextBlock Text="{locale:Locale SettingsTabLoggingEnableTraceLogs}" />
+ </CheckBox>
+ <CheckBox IsChecked="{Binding EnableGuest}"
+ ToolTip.Tip="{locale:Locale GuestLogTooltip}">
+ <TextBlock Text="{locale:Locale SettingsTabLoggingEnableGuestLogs}" />
+ </CheckBox>
+ </StackPanel>
+ <Separator Height="1" />
+ <TextBlock Classes="h1" Text="{locale:Locale SettingsTabLoggingDeveloperOptions}" />
+ <StackPanel
+ Margin="10,0,0,0"
+ HorizontalAlignment="Stretch"
+ Orientation="Vertical"
+ Spacing="10">
+ <StackPanel Orientation="Vertical">
+ <CheckBox IsChecked="{Binding EnableDebug}"
+ ToolTip.Tip="{locale:Locale DebugLogTooltip}">
+ <TextBlock Text="{locale:Locale SettingsTabLoggingEnableDebugLogs}" />
+ </CheckBox>
+ <CheckBox IsChecked="{Binding EnableFsAccessLog}"
+ ToolTip.Tip="{locale:Locale FileAccessLogTooltip}">
+ <TextBlock Text="{locale:Locale SettingsTabLoggingEnableFsAccessLogs}" />
+ </CheckBox>
+ <StackPanel Margin="0,10,0,0" Orientation="Horizontal" VerticalAlignment="Stretch">
+ <TextBlock VerticalAlignment="Center"
+ ToolTip.Tip="{locale:Locale FSAccessLogModeTooltip}"
+ Text="{locale:Locale SettingsTabLoggingFsGlobalAccessLogMode}"
+ Width="285" />
+ <ui:NumberBox
+ Maximum="3"
+ Minimum="0"
+ Width="150"
+ SpinButtonPlacementMode="Inline"
+ SmallChange="1"
+ LargeChange="1"
+ Value="{Binding FsGlobalAccessLogMode}" />
+ </StackPanel>
+ <StackPanel Margin="0,10,0,0" Orientation="Horizontal">
+ <TextBlock VerticalAlignment="Center"
+ Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevel}"
+ ToolTip.Tip="{locale:Locale OpenGlLogLevel}"
+ Width="285" />
+ <ComboBox SelectedIndex="{Binding OpenglDebugLevel}"
+ Width="150"
+ HorizontalContentAlignment="Left"
+ ToolTip.Tip="{locale:Locale OpenGlLogLevel}">
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelNone}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelError}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock
+ Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelPerformance}" />
+ </ComboBoxItem>
+ <ComboBoxItem>
+ <TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelAll}" />
+ </ComboBoxItem>
+ </ComboBox>
+ </StackPanel>
+ </StackPanel>
+ </StackPanel>
+ </StackPanel>
+ </Border>
+ </ScrollViewer>
+ </Grid>
+ <ui:NavigationView Grid.Row="1"
+ IsSettingsVisible="False"
+ Name="NavPanel"
+ IsBackEnabled="False"
+ PaneDisplayMode="Left"
+ Margin="2,10,10,0"
+ VerticalAlignment="Stretch"
+ HorizontalAlignment="Stretch"
+ OpenPaneLength="200">
+ <ui:NavigationView.MenuItems>
+ <ui:NavigationViewItem IsSelected="True"
+ Content="{locale:Locale SettingsTabGeneral}"
+ Tag="UiPage"
+ Icon="New" />
+ <ui:NavigationViewItem
+ Content="{locale:Locale SettingsTabInput}"
+ Tag="InputPage"
+ Icon="Games" />
+ <ui:NavigationViewItem
+ Content="{locale:Locale SettingsTabHotkeys}"
+ Tag="HotkeysPage"
+ Icon="Keyboard" />
+ <ui:NavigationViewItem
+ Content="{locale:Locale SettingsTabSystem}"
+ Tag="SystemPage"
+ Icon="Settings" />
+ <ui:NavigationViewItem
+ Content="{locale:Locale SettingsTabCpu}"
+ Tag="CpuPage">
+ <ui:NavigationViewItem.Icon>
+ <ui:FontIcon FontFamily="avares://Ryujinx.Ava/Assets/Fonts#Segoe Fluent Icons"
+ Glyph="{helpers:GlyphValueConverter Chip}" />
+ </ui:NavigationViewItem.Icon>
+ </ui:NavigationViewItem>
+ <ui:NavigationViewItem
+ Content="{locale:Locale SettingsTabGraphics}"
+ Tag="GraphicsPage"
+ Icon="Image" />
+ <ui:NavigationViewItem
+ Content="{locale:Locale SettingsTabAudio}"
+ Icon="Audio"
+ Tag="AudioPage" />
+ <ui:NavigationViewItem
+ Content="{locale:Locale SettingsTabNetwork}"
+ Tag="NetworkPage"
+ Icon="Globe" />
+ <ui:NavigationViewItem
+ Content="{locale:Locale SettingsTabLogging}"
+ Tag="LoggingPage"
+ Icon="Document" />
+ </ui:NavigationView.MenuItems>
+ </ui:NavigationView>
+ <ReversibleStackPanel
+ Grid.Row="2"
+ Margin="10"
+ Spacing="10"
+ Orientation="Horizontal"
+ HorizontalAlignment="Right"
+ ReverseOrder="{ReflectionBinding IsMacOS}">
+ <Button
+ HotKey="Enter"
+ Classes="accent"
+ Content="{locale:Locale SettingsButtonOk}"
+ Command="{ReflectionBinding OkButton}" />
+ <Button
+ HotKey="Escape"
+ Content="{locale:Locale SettingsButtonCancel}"
+ Command="{ReflectionBinding CancelButton}" />
+ <Button
+ Content="{locale:Locale SettingsButtonApply}"
+ Command="{ReflectionBinding ApplyButton}" />
+ </ReversibleStackPanel>
+ </Grid>
+</window:StyleableWindow> \ No newline at end of file
diff --git a/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs b/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs
new file mode 100644
index 00000000..f3aa1d5e
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs
@@ -0,0 +1,213 @@
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Data;
+using Avalonia.Data.Converters;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using FluentAvalonia.Core;
+using FluentAvalonia.UI.Controls;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.UI.Controls;
+using Ryujinx.Ava.UI.Helpers;
+using Ryujinx.Ava.UI.ViewModels;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.Input;
+using Ryujinx.Input.Assigner;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
+
+namespace Ryujinx.Ava.UI.Windows
+{
+ public partial class SettingsWindow : StyleableWindow
+ {
+ private ButtonKeyAssigner _currentAssigner;
+
+ internal SettingsViewModel ViewModel { get; set; }
+
+ public SettingsWindow(VirtualFileSystem virtualFileSystem, ContentManager contentManager)
+ {
+ Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["Settings"]}";
+
+ ViewModel = new SettingsViewModel(virtualFileSystem, contentManager, this);
+ DataContext = ViewModel;
+
+ InitializeComponent();
+ Load();
+
+ FuncMultiValueConverter<string, string> converter = new(parts => string.Format("{0} {1} {2}", parts.ToArray()).Trim());
+ MultiBinding tzMultiBinding = new() { Converter = converter };
+ tzMultiBinding.Bindings.Add(new Binding("UtcDifference"));
+ tzMultiBinding.Bindings.Add(new Binding("Location"));
+ tzMultiBinding.Bindings.Add(new Binding("Abbreviation"));
+
+ TimeZoneBox.ValueMemberBinding = tzMultiBinding;
+ }
+
+ public SettingsWindow()
+ {
+ ViewModel = new SettingsViewModel();
+ DataContext = ViewModel;
+
+ InitializeComponent();
+ Load();
+ }
+
+ private void Load()
+ {
+ Pages.Children.Clear();
+ NavPanel.SelectionChanged += NavPanelOnSelectionChanged;
+ NavPanel.SelectedItem = NavPanel.MenuItems.ElementAt(0);
+ }
+
+ private void Button_Checked(object sender, RoutedEventArgs e)
+ {
+ if (sender is ToggleButton button)
+ {
+ if (_currentAssigner != null && button == _currentAssigner.ToggledButton)
+ {
+ return;
+ }
+
+ if (_currentAssigner == null && (bool)button.IsChecked)
+ {
+ _currentAssigner = new ButtonKeyAssigner(button);
+
+ FocusManager.Instance.Focus(this, NavigationMethod.Pointer);
+
+ PointerPressed += MouseClick;
+
+ IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad(ViewModel.AvaloniaKeyboardDriver.GamepadsIds[0]);
+ IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard);
+
+ _currentAssigner.GetInputAndAssign(assigner);
+ }
+ else
+ {
+ if (_currentAssigner != null)
+ {
+ ToggleButton oldButton = _currentAssigner.ToggledButton;
+
+ _currentAssigner.Cancel();
+ _currentAssigner = null;
+
+ button.IsChecked = false;
+ }
+ }
+ }
+ }
+
+ private void Button_Unchecked(object sender, RoutedEventArgs e)
+ {
+ _currentAssigner?.Cancel();
+ _currentAssigner = null;
+ }
+
+ private void MouseClick(object sender, PointerPressedEventArgs e)
+ {
+ bool shouldUnbind = false;
+
+ if (e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed)
+ {
+ shouldUnbind = true;
+ }
+
+ _currentAssigner?.Cancel(shouldUnbind);
+
+ PointerPressed -= MouseClick;
+ }
+
+ private void NavPanelOnSelectionChanged(object sender, NavigationViewSelectionChangedEventArgs e)
+ {
+ if (e.SelectedItem is NavigationViewItem navitem)
+ {
+ NavPanel.Content = navitem.Tag.ToString() switch
+ {
+ "UiPage" => UiPage,
+ "InputPage" => InputPage,
+ "HotkeysPage" => HotkeysPage,
+ "SystemPage" => SystemPage,
+ "CpuPage" => CpuPage,
+ "GraphicsPage" => GraphicsPage,
+ "AudioPage" => AudioPage,
+ "NetworkPage" => NetworkPage,
+ "LoggingPage" => LoggingPage,
+ _ => throw new NotImplementedException()
+ };
+ }
+ }
+
+ private async void AddButton_OnClick(object sender, RoutedEventArgs e)
+ {
+ string path = PathBox.Text;
+
+ if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !ViewModel.GameDirectories.Contains(path))
+ {
+ ViewModel.GameDirectories.Add(path);
+ ViewModel.DirectoryChanged = true;
+ }
+ else
+ {
+ path = await new OpenFolderDialog().ShowAsync(this);
+
+ if (!string.IsNullOrWhiteSpace(path))
+ {
+ ViewModel.GameDirectories.Add(path);
+ ViewModel.DirectoryChanged = true;
+ }
+ }
+ }
+
+ private void RemoveButton_OnClick(object sender, RoutedEventArgs e)
+ {
+ int oldIndex = GameList.SelectedIndex;
+
+ foreach (string path in new List<string>(GameList.SelectedItems.Cast<string>()))
+ {
+ ViewModel.GameDirectories.Remove(path);
+ ViewModel.DirectoryChanged = true;
+ }
+
+ if (GameList.ItemCount > 0)
+ {
+ GameList.SelectedIndex = oldIndex < GameList.ItemCount ? oldIndex : 0;
+ }
+ }
+
+ private void TimeZoneBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (e.AddedItems != null && e.AddedItems.Count > 0)
+ {
+ if (e.AddedItems[0] is TimeZone timeZone)
+ {
+ e.Handled = true;
+
+ ViewModel.ValidateAndSetTimeZone(timeZone.Location);
+ }
+ }
+ }
+
+ private void TimeZoneBox_OnTextChanged(object sender, EventArgs e)
+ {
+ if (sender is AutoCompleteBox box)
+ {
+ if (box.SelectedItem != null && box.SelectedItem is TimeZone timeZone)
+ {
+ ViewModel.ValidateAndSetTimeZone(timeZone.Location);
+ }
+ }
+ }
+
+ protected override void OnClosed(EventArgs e)
+ {
+ ControllerSettings.Dispose();
+
+ _currentAssigner?.Cancel();
+ _currentAssigner = null;
+
+ base.OnClosed(e);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Ava/UI/Windows/StyleableWindow.cs b/Ryujinx.Ava/UI/Windows/StyleableWindow.cs
new file mode 100644
index 00000000..a157f154
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/StyleableWindow.cs
@@ -0,0 +1,39 @@
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Media.Imaging;
+using Avalonia.Platform;
+using System;
+using System.IO;
+using System.Reflection;
+
+namespace Ryujinx.Ava.UI.Windows
+{
+ public class StyleableWindow : Window
+ {
+ public IBitmap IconImage { get; set; }
+
+ public StyleableWindow()
+ {
+ WindowStartupLocation = WindowStartupLocation.CenterOwner;
+ TransparencyLevelHint = WindowTransparencyLevel.None;
+
+ using Stream stream = Assembly.GetAssembly(typeof(Ryujinx.Ui.Common.Configuration.ConfigurationState)).GetManifestResourceStream("Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png");
+
+ Icon = new WindowIcon(stream);
+ stream.Position = 0;
+ IconImage = new Bitmap(stream);
+ }
+
+ protected override void OnOpened(EventArgs e)
+ {
+ base.OnOpened(e);
+ }
+
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+
+ ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.SystemChrome | ExtendClientAreaChromeHints.OSXThickTitleBar;
+ }
+ }
+} \ 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..5a69be9b
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml
@@ -0,0 +1,115 @@
+<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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
+ Width="600"
+ Height="400"
+ MinWidth="600"
+ MinHeight="400"
+ MaxWidth="600"
+ MaxHeight="400"
+ SizeToContent="Height"
+ WindowStartupLocation="CenterOwner"
+ mc:Ignorable="d"
+ Focusable="True">
+ <Grid Margin="15">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="*" />
+ <RowDefinition Height="Auto" />
+ </Grid.RowDefinitions>
+ <TextBlock
+ Name="Heading"
+ Grid.Row="1"
+ MaxWidth="500"
+ Margin="20,15,20,20"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ LineHeight="18"
+ TextAlignment="Center"
+ TextWrapping="Wrap" />
+ <Border
+ Grid.Row="2"
+ Margin="5"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ BorderBrush="Gray"
+ BorderThickness="1">
+ <ScrollViewer
+ 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}"
+ FontSize="12" />
+ </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..03c2b098
--- /dev/null
+++ b/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs
@@ -0,0 +1,271 @@
+using Avalonia.Collections;
+using Avalonia.Controls;
+using Avalonia.Threading;
+using LibHac.Common;
+using LibHac.Fs;
+using LibHac.Fs.Fsa;
+using LibHac.FsSystem;
+using LibHac.Ns;
+using LibHac.Tools.FsSystem;
+using LibHac.Tools.FsSystem.NcaUtils;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.UI.Controls;
+using Ryujinx.Ava.UI.Helpers;
+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.IO;
+using System.Linq;
+using System.Text;
+using Path = System.IO.Path;
+using SpanHelpers = LibHac.Common.SpanHelpers;
+
+namespace Ryujinx.Ava.UI.Windows
+{
+ public partial class TitleUpdateWindow : StyleableWindow
+ {
+ private readonly string _titleUpdateJsonPath;
+ private TitleUpdateMetadata _titleUpdateWindowData;
+
+ private VirtualFileSystem _virtualFileSystem { get; }
+ private AvaloniaList<TitleUpdateModel> _titleUpdates { get; set; }
+
+ private ulong _titleId { get; }
+ private string _titleName { get; }
+
+ public TitleUpdateWindow()
+ {
+ DataContext = this;
+
+ InitializeComponent();
+
+ Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["UpdateWindowTitle"]} - {_titleName} ({_titleId:X16})";
+ }
+
+ public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
+ {
+ _virtualFileSystem = virtualFileSystem;
+ _titleUpdates = new AvaloniaList<TitleUpdateModel>();
+
+ _titleId = titleId;
+ _titleName = titleName;
+
+ _titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
+
+ try
+ {
+ _titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_titleUpdateJsonPath);
+ }
+ catch
+ {
+ _titleUpdateWindowData = new TitleUpdateMetadata
+ {
+ Selected = "",
+ Paths = new List<string>()
+ };
+ }
+
+ DataContext = this;
+
+ InitializeComponent();
+
+ Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["UpdateWindowTitle"]} - {_titleName} ({_titleId:X16})";
+
+ LoadUpdates();
+ PrintHeading();
+ }
+
+ private void PrintHeading()
+ {
+ Heading.Text = string.Format(LocaleManager.Instance["GameUpdateWindowHeading"], _titleUpdates.Count, _titleName, _titleId.ToString("X16"));
+ }
+
+ 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);
+
+ try
+ {
+ (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
+
+ if (controlNca != null && patchNca != null)
+ {
+ ApplicationControlProperty controlData = new();
+
+ using UniqueRef<IFile> nacpFile = new();
+
+ 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));
+
+ foreach (var update in _titleUpdates)
+ {
+ update.IsEnabled = false;
+ }
+
+ _titleUpdates.Last().IsEnabled = true;
+ }
+ else
+ {
+ Dispatcher.UIThread.Post(async () =>
+ {
+ await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUpdateAddUpdateErrorMessage"]);
+ });
+ }
+ }
+ catch (Exception ex)
+ {
+ Dispatcher.UIThread.Post(async () =>
+ {
+ await ContentDialogHelper.CreateErrorDialog(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();
+ PrintHeading();
+ }
+
+ public void RemoveSelected()
+ {
+ RemoveUpdates(true);
+ }
+
+ public void RemoveAll()
+ {
+ RemoveUpdates();
+ }
+
+ public async void Add()
+ {
+ OpenFileDialog dialog = new()
+ {
+ 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();
+ PrintHeading();
+ }
+
+ 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 titleUpdateJsonStream = File.Create(_titleUpdateJsonPath, 4096, FileOptions.WriteThrough))
+ {
+ titleUpdateJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true)));
+ }
+
+ if (Owner is MainWindow window)
+ {
+ window.ViewModel.LoadApplications();
+ }
+
+ Close();
+ }
+ }
+} \ No newline at end of file