mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-07-23 08:47:10 +02:00
Custom configuration for each game (#632)
  Now you can make a separate configuration (independent file) for each game. All emulator settings are available except for some UI functionality ones. The configuration file can be changed and deleted from a separate menu. The user configuration menu is available through the context menu on a given application. --------- Co-authored-by: Evan Husted <greem@greemdev.net>
This commit is contained in:
parent
9227cbe5a7
commit
2e4de17472
24 changed files with 1133 additions and 106 deletions
|
@ -19,6 +19,18 @@
|
|||
Header="{ext:Locale GameListContextMenuCreateShortcut}"
|
||||
Icon="{ext:Icon fa-solid fa-bookmark}"
|
||||
ToolTip.Tip="{OnPlatform Default={ext:Locale GameListContextMenuCreateShortcutToolTip}, macOS={ext:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
|
||||
<MenuItem
|
||||
Click="EditGameConfiguration_Click"
|
||||
IsVisible="{Binding SelectedApplication.HasIndependentConfiguration}"
|
||||
Header="{ext:Locale GameListContextMenuEditCustomConfiguration}"
|
||||
Icon="{ext:Icon fa-solid fa-gear}"
|
||||
ToolTip.Tip="{ext:Locale EditCustomConfigurationToolTip}" />
|
||||
<MenuItem
|
||||
Click="EditGameConfiguration_Click"
|
||||
IsVisible="{Binding !SelectedApplication.HasIndependentConfiguration}"
|
||||
Header="{ext:Locale GameListContextMenuCreateCustomConfiguration}"
|
||||
Icon="{ext:Icon fa-solid fa-gear}"
|
||||
ToolTip.Tip="{ext:Locale CreateCustomConfigurationToolTip}" />
|
||||
<MenuItem
|
||||
IsVisible="{Binding HasCompatibilityEntry}"
|
||||
Click="OpenApplicationCompatibility_Click"
|
||||
|
|
|
@ -386,13 +386,26 @@ namespace Ryujinx.Ava.UI.Controls
|
|||
viewModel.SelectedApplication.Icon
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public async void EditGameConfiguration_Click(object sender, RoutedEventArgs args)
|
||||
{
|
||||
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
|
||||
{
|
||||
await new GameSpecificSettingsWindow(viewModel).ShowDialog((Window)viewModel.TopLevel);
|
||||
|
||||
//just checking for file presence
|
||||
viewModel.SelectedApplication.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString,false,false));
|
||||
|
||||
viewModel.RefreshView();
|
||||
}
|
||||
}
|
||||
|
||||
public async void OpenApplicationCompatibility_Click(object sender, RoutedEventArgs args)
|
||||
{
|
||||
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
|
||||
await CompatibilityList.Show(viewModel.SelectedApplication.IdString);
|
||||
}
|
||||
|
||||
|
||||
public async void OpenApplicationData_Click(object sender, RoutedEventArgs args)
|
||||
{
|
||||
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
Focusable="True"
|
||||
|
@ -73,12 +74,18 @@
|
|||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
IsVisible="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Name}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<TextBlock
|
||||
Text="{Binding Name}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
IsVisible="{Binding HasIndependentConfiguration}"
|
||||
Text="{ext:Locale GameSpecificConfigurationHeader}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource Warning}" />
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
@ -86,10 +93,28 @@
|
|||
Margin="5,5,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
FontSize="16"
|
||||
FontSize="18"
|
||||
Foreground="{DynamicResource FavoriteApplicationIconColor}"
|
||||
IsVisible="{Binding Favorite}"
|
||||
Symbol="StarFilled" />
|
||||
<Grid IsVisible="{Binding !$parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}">
|
||||
<Border
|
||||
Margin="15,35,5,15"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Bottom"
|
||||
Width="90"
|
||||
Height="20"
|
||||
CornerRadius="4"
|
||||
IsVisible="{Binding HasIndependentConfiguration}"
|
||||
Background="{DynamicResource ThemeContentBackgroundColor}">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{ext:Locale GameSpecificConfigurationHeader}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
Focusable="True"
|
||||
|
@ -156,6 +157,13 @@
|
|||
Text="{Binding Converter={x:Static helpers:MultiplayerInfoConverter.Instance}}"
|
||||
TextAlignment="Start"
|
||||
TextWrapping="Wrap"/>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
IsVisible="{Binding HasIndependentConfiguration}"
|
||||
Text="{ext:Locale GameSpecificConfigurationHeader}"
|
||||
TextAlignment="Start"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource Warning}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Column="4"
|
||||
|
|
|
@ -356,6 +356,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
_ => null,
|
||||
};
|
||||
}
|
||||
set
|
||||
{
|
||||
ListSelectedApplication = value;
|
||||
GridSelectedApplication = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasCompatibilityEntry => SelectedApplication.HasPlayabilityInfo;
|
||||
|
@ -1085,7 +1090,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
_rendererWaitEvent.WaitOne();
|
||||
|
||||
AppHost?.Start();
|
||||
|
||||
|
||||
AppHost?.DisposeContext();
|
||||
}
|
||||
|
||||
|
@ -1551,8 +1556,50 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
public bool InitializeUserConfig(ApplicationData application)
|
||||
{
|
||||
// Code where conditions will be met before loading the user configuration (Global Config)
|
||||
BackendThreading backendThreadingValue = ConfigurationState.Instance.Graphics.BackendThreading.Value;
|
||||
string BackendThreadingInit = Program.BackendThreadingArg;
|
||||
|
||||
if (BackendThreadingInit is null)
|
||||
{
|
||||
BackendThreadingInit = ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString();
|
||||
}
|
||||
|
||||
// If a configuration is found in the "/games/xxxxxxxxxxxxxx" folder, the program will load the user setting.
|
||||
string idGame = application.IdBaseString;
|
||||
if (ConfigurationFileFormat.TryLoad(Program.GetDirGameUserConfig(idGame), out ConfigurationFileFormat configurationFileFormat))
|
||||
{
|
||||
// Loads the user configuration, having previously changed the global configuration to the user configuration
|
||||
ConfigurationState.Instance.Load(configurationFileFormat, Program.GetDirGameUserConfig(idGame, true, true), idGame);
|
||||
}
|
||||
|
||||
// Code where conditions will be executed after loading user configuration
|
||||
if (ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() != BackendThreadingInit)
|
||||
{
|
||||
|
||||
List<string> Arguments = new List<string>
|
||||
{
|
||||
"--bt", ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() // BackendThreading
|
||||
};
|
||||
|
||||
Rebooter.RebootAppWithGame(application.Path, Arguments);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task LoadApplication(ApplicationData application, bool startFullscreen = false, BlitStruct<ApplicationControlProperty>? customNacpData = null)
|
||||
{
|
||||
|
||||
if (InitializeUserConfig(application))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (AppHost != null)
|
||||
{
|
||||
await ContentDialogHelper.CreateInfoDialog(
|
||||
|
@ -1568,7 +1615,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
#if RELEASE
|
||||
await PerformanceCheck();
|
||||
#endif
|
||||
|
||||
|
||||
Logger.RestartTime();
|
||||
|
||||
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path, ConfigurationState.Instance.System.Language, application.Id);
|
||||
|
@ -1613,6 +1660,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
|
||||
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
|
||||
gameThread.Start();
|
||||
|
||||
}
|
||||
|
||||
public void SwitchToRenderer(bool startFullscreen) =>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
@ -27,6 +28,7 @@ using Ryujinx.HLE.HOS.Services.Time.TimeZone;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -68,6 +70,19 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
|
||||
public SettingsHacksViewModel DirtyHacks { get; }
|
||||
|
||||
private readonly bool _isGameRunning;
|
||||
private Bitmap _gameIcon;
|
||||
private string _gameTitle;
|
||||
private string _gamePath;
|
||||
private string _gameId;
|
||||
public bool IsGameRunning => _isGameRunning;
|
||||
public Bitmap GameIcon => _gameIcon;
|
||||
public string GamePath => _gamePath;
|
||||
public string GameTitle => _gameTitle;
|
||||
public string GameId => _gameId;
|
||||
public bool IsGameTitleNotNull => !string.IsNullOrEmpty(GameTitle);
|
||||
public double PanelOpacity => IsGameTitleNotNull ? 0.5 : 1;
|
||||
|
||||
public int ResolutionScale
|
||||
{
|
||||
get => _resolutionScale;
|
||||
|
@ -335,7 +350,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
|
||||
public bool IsInvalidLdnPassphraseVisible { get; set; }
|
||||
|
||||
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
|
||||
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(false)
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
_contentManager = contentManager;
|
||||
|
@ -348,7 +363,51 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
public SettingsViewModel()
|
||||
public SettingsViewModel(
|
||||
VirtualFileSystem virtualFileSystem,
|
||||
ContentManager contentManager,
|
||||
bool gameRunning,
|
||||
string gamePath,
|
||||
string gameName,
|
||||
string gameId,
|
||||
byte[] gameIconData,
|
||||
bool enableToLoadCustomConfig) : this(enableToLoadCustomConfig)
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
_contentManager = contentManager;
|
||||
|
||||
if (gameIconData != null && gameIconData.Length > 0)
|
||||
{
|
||||
using (var ms = new MemoryStream(gameIconData))
|
||||
{
|
||||
_gameIcon = new Bitmap(ms);
|
||||
}
|
||||
}
|
||||
|
||||
_isGameRunning = gameRunning;
|
||||
_gamePath = gamePath;
|
||||
_gameTitle = gameName;
|
||||
_gameId = gameId;
|
||||
|
||||
if (enableToLoadCustomConfig) // During the game. If there is no user config, then load the global config window
|
||||
{
|
||||
string gameDir = Program.GetDirGameUserConfig(gameId, false, true);
|
||||
if (ConfigurationFileFormat.TryLoad(gameDir, out ConfigurationFileFormat configurationFileFormat))
|
||||
{
|
||||
ConfigurationState.Instance.Load(configurationFileFormat, gameDir, gameId);
|
||||
}
|
||||
|
||||
LoadCurrentConfiguration(); // Needed to load custom configuration
|
||||
}
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
Task.Run(LoadTimeZones);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public SettingsViewModel(bool noLoadGlobalConfig = false)
|
||||
{
|
||||
GameDirectories = [];
|
||||
AutoloadDirectories = [];
|
||||
|
@ -363,7 +422,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
if (Program.PreviewerDetached)
|
||||
{
|
||||
Task.Run(LoadAvailableGpus);
|
||||
LoadCurrentConfiguration();
|
||||
|
||||
// if (!noLoadGlobalConfig)// Default is false, but loading custom config avoids double call
|
||||
LoadCurrentConfiguration();
|
||||
|
||||
DirtyHacks = new SettingsHacksViewModel(this);
|
||||
}
|
||||
|
@ -592,8 +653,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
config.HideCursor.Value = (HideCursorMode)HideCursor;
|
||||
config.UpdateCheckerType.Value = (UpdaterType)UpdateCheckerType;
|
||||
config.FocusLostActionType.Value = (FocusLostType)FocusLostActionType;
|
||||
config.UI.GameDirs.Value = [..GameDirectories];
|
||||
config.UI.AutoloadDirs.Value = [..AutoloadDirectories];
|
||||
config.UI.GameDirs.Value = [.. GameDirectories];
|
||||
config.UI.AutoloadDirs.Value = [.. AutoloadDirectories];
|
||||
|
||||
config.UI.BaseStyle.Value = BaseStyleIndex switch
|
||||
{
|
||||
|
@ -614,10 +675,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
|
||||
// System
|
||||
config.System.Region.Value = (Region)Region;
|
||||
|
||||
|
||||
if (config.System.Language.Value != (Language)Language)
|
||||
GameListNeedsRefresh = true;
|
||||
|
||||
|
||||
config.System.Language.Value = (Language)Language;
|
||||
if (_validTzRegions.Contains(TimeZone))
|
||||
{
|
||||
|
@ -696,7 +757,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
config.Multiplayer.DisableP2p.Value = DisableP2P;
|
||||
config.Multiplayer.LdnPassphrase.Value = LdnPassphrase;
|
||||
config.Multiplayer.LdnServer.Value = LdnServer;
|
||||
|
||||
|
||||
// Dirty Hacks
|
||||
config.Hacks.Xc2MenuSoftlockFix.Value = DirtyHacks.Xc2MenuSoftlockFix;
|
||||
|
||||
|
@ -712,7 +773,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
|
||||
private static void RevertIfNotSaved()
|
||||
{
|
||||
Program.ReloadConfig();
|
||||
// maybe this is an unnecessary check(all options need to be tested)
|
||||
if (string.IsNullOrEmpty(Program.GlobalConfigurationPath))
|
||||
{
|
||||
Program.ReloadConfig();
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyButton()
|
||||
|
@ -720,6 +785,26 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
SaveSettings();
|
||||
}
|
||||
|
||||
public void DeleteConfigGame()
|
||||
{
|
||||
string gameDir = Program.GetDirGameUserConfig(GameId,false,false);
|
||||
|
||||
if (File.Exists(gameDir))
|
||||
{
|
||||
File.Delete(gameDir);
|
||||
}
|
||||
|
||||
RevertIfNotSaved();
|
||||
CloseWindow?.Invoke();
|
||||
}
|
||||
|
||||
public void SaveUserConfig()
|
||||
{
|
||||
SaveSettings();
|
||||
RevertIfNotSaved(); // Revert global configuration after saving user configuration
|
||||
CloseWindow?.Invoke();
|
||||
}
|
||||
|
||||
public void OkButton()
|
||||
{
|
||||
SaveSettings();
|
||||
|
|
|
@ -130,9 +130,26 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||
Window.SettingsWindow = new(Window.VirtualFileSystem, Window.ContentManager);
|
||||
|
||||
Rainbow.Enable();
|
||||
|
||||
await Window.SettingsWindow.ShowDialog(Window);
|
||||
|
||||
|
||||
if (ViewModel.SelectedApplication is null) // Checks if game data exists
|
||||
{
|
||||
await Window.SettingsWindow.ShowDialog(Window);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool userConfigExist = Program.FindGameConfig(Program.GetDirGameUserConfig(ViewModel.SelectedApplication.IdString, false, false));
|
||||
|
||||
if (!ViewModel.IsGameRunning || !userConfigExist)
|
||||
{
|
||||
await Window.SettingsWindow.ShowDialog(Window); // The game is not running, or if the user configuration does not exist
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there is a custom configuration in the folder
|
||||
await new GameSpecificSettingsWindow(ViewModel, userConfigExist).ShowDialog((Window)ViewModel.TopLevel);
|
||||
}
|
||||
}
|
||||
|
||||
Rainbow.Disable();
|
||||
Rainbow.Reset();
|
||||
|
||||
|
|
|
@ -156,6 +156,8 @@
|
|||
ValueMemberBinding="{Binding Mode=OneWay, Converter={x:Static helpers:TimeZoneConverter.Instance}}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
IsEnabled="{Binding !IsGameTitleNotNull}"
|
||||
Opacity="{Binding PanelOpacity}"
|
||||
Margin="0,0,0,10"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock
|
||||
|
@ -169,8 +171,11 @@
|
|||
SelectedDate="{Binding CurrentDate}"
|
||||
ToolTip.Tip="{ext:Locale TimeTooltip}"
|
||||
Width="350" />
|
||||
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
IsEnabled="{Binding !IsGameTitleNotNull}"
|
||||
Opacity="{Binding PanelOpacity}"
|
||||
Margin="250,0,0,10"
|
||||
Orientation="Horizontal">
|
||||
<TimePicker
|
||||
|
@ -180,8 +185,12 @@
|
|||
SelectedTime="{Binding CurrentTime}"
|
||||
Width="350"
|
||||
ToolTip.Tip="{ext:Locale TimeTooltip}" />
|
||||
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<StackPanel
|
||||
IsEnabled="{Binding !IsGameTitleNotNull}"
|
||||
Opacity="{Binding PanelOpacity}"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="{ext:Locale SettingsTabSystemSystemTimeMatch}"
|
||||
|
@ -191,6 +200,7 @@
|
|||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding MatchSystemTime}"
|
||||
ToolTip.Tip="{ext:Locale MatchTimeTooltip}"/>
|
||||
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
|
||||
</StackPanel>
|
||||
<Separator />
|
||||
<StackPanel Margin="0,10,0,10"
|
||||
|
|
|
@ -27,20 +27,42 @@
|
|||
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralGeneral}" />
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Vertical">
|
||||
<CheckBox IsChecked="{Binding EnableDiscordIntegration}">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
ToolTip.Tip="{ext:Locale ToggleDiscordTooltip}"
|
||||
Text="{ext:Locale SettingsTabGeneralEnableDiscordRichPresence}" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
ToolTip.Tip="{ext:Locale ToggleDiscordTooltip}"
|
||||
Text="{ext:Locale SettingsTabGeneralEnableDiscordRichPresence}" />
|
||||
</StackPanel>
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding ShowConfirmExit}">
|
||||
<TextBlock Text="{ext:Locale SettingsTabGeneralShowConfirmExitDialog}" />
|
||||
<CheckBox
|
||||
IsEnabled="{Binding !IsGameTitleNotNull}"
|
||||
Opacity="{Binding PanelOpacity}"
|
||||
IsChecked="{Binding ShowConfirmExit}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{ext:Locale SettingsTabGeneralShowConfirmExitDialog}" />
|
||||
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}" />
|
||||
</StackPanel>
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding RememberWindowState}">
|
||||
<TextBlock Text="{ext:Locale SettingsTabGeneralRememberWindowState}" />
|
||||
<CheckBox
|
||||
IsEnabled="{Binding !IsGameTitleNotNull}"
|
||||
Opacity="{Binding PanelOpacity}"
|
||||
IsChecked="{Binding RememberWindowState}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{ext:Locale SettingsTabGeneralRememberWindowState}" />
|
||||
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}" />
|
||||
</StackPanel>
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding ShowTitleBar}" IsVisible="{x:Static helper:RunningPlatform.IsWindows}">
|
||||
<TextBlock Text="{ext:Locale SettingsTabGeneralShowTitleBar}" />
|
||||
<CheckBox
|
||||
IsEnabled="{Binding !IsGameTitleNotNull}"
|
||||
Opacity="{Binding PanelOpacity}"
|
||||
IsChecked="{Binding ShowTitleBar}" IsVisible="{x:Static helper:RunningPlatform.IsWindows}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{ext:Locale SettingsTabGeneralShowTitleBar}" />
|
||||
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}" />
|
||||
</StackPanel>
|
||||
</CheckBox>
|
||||
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
|
||||
<StackPanel
|
||||
Margin="0, 15, 0, 0"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{ext:Locale SettingsTabGeneralFocusLossType}"
|
||||
Width="150" />
|
||||
|
@ -64,7 +86,11 @@
|
|||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
|
||||
<StackPanel
|
||||
IsEnabled="{Binding !IsGameTitleNotNull}"
|
||||
Opacity="{Binding PanelOpacity}"
|
||||
Margin="0, 15, 0, 0"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunch}"
|
||||
Width="150" />
|
||||
|
@ -81,8 +107,11 @@
|
|||
<TextBlock Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunchBackground}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
|
||||
<StackPanel
|
||||
Margin="0, 15, 0, 0"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{ext:Locale SettingsTabGeneralHideCursor}"
|
||||
Width="150" />
|
||||
|
@ -100,7 +129,11 @@
|
|||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0, 15, 0, 10" Orientation="Horizontal">
|
||||
<StackPanel
|
||||
IsEnabled="{Binding !IsGameTitleNotNull}"
|
||||
Opacity="{Binding PanelOpacity}"
|
||||
Margin="0, 15, 0, 10"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="{ext:Locale SettingsTabGeneralTheme}"
|
||||
|
@ -118,11 +151,17 @@
|
|||
<TextBlock Text="{ext:Locale SettingsTabGeneralThemeDark}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<Separator Height="1" />
|
||||
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralGameDirectories}" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralGameDirectories}" />
|
||||
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
IsEnabled="{Binding !IsGameTitleNotNull}"
|
||||
Opacity="{Binding PanelOpacity}"
|
||||
Margin="10,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
|
@ -172,10 +211,15 @@
|
|||
</StackPanel>
|
||||
<Separator Height="1" />
|
||||
<StackPanel Orientation="Vertical" Spacing="5">
|
||||
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralAutoloadDirectories}" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralAutoloadDirectories}" />
|
||||
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
|
||||
</StackPanel>
|
||||
<TextBlock Foreground="{DynamicResource SecondaryTextColor}" Text="{ext:Locale SettingsTabGeneralAutoloadNote}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
IsEnabled="{Binding !IsGameTitleNotNull}"
|
||||
Opacity="{Binding PanelOpacity}"
|
||||
Margin="10,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
|
|
155
src/Ryujinx/UI/Windows/GameSpecificSettingsWindow.axaml
Normal file
155
src/Ryujinx/UI/Windows/GameSpecificSettingsWindow.axaml
Normal file
|
@ -0,0 +1,155 @@
|
|||
<window:StyleableAppWindow
|
||||
x:Class="Ryujinx.Ava.UI.Windows.GameSpecificSettingsWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||
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:settings="clr-namespace:Ryujinx.Ava.UI.Views.Settings"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
|
||||
Width="1100"
|
||||
Height="768"
|
||||
MinWidth="800"
|
||||
MinHeight="480"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
x:DataType="viewModels:SettingsViewModel"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Design.DataContext>
|
||||
<viewModels:SettingsViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="600" RowDefinitions="Auto,*,Auto">
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
Grid.Row="1"
|
||||
IsVisible="False"
|
||||
KeyboardNavigation.IsTabStop="False"/>
|
||||
<Grid Name="Pages" IsVisible="False" Grid.Row="2">
|
||||
<settings:SettingsUiView Name="UiPage" />
|
||||
<settings:SettingsInputView Name="InputPage" />
|
||||
<settings:SettingsSystemView Name="SystemPage" />
|
||||
<settings:SettingsCPUView Name="CpuPage" />
|
||||
<settings:SettingsGraphicsView Name="GraphicsPage" />
|
||||
<settings:SettingsAudioView Name="AudioPage" />
|
||||
<settings:SettingsNetworkView Name="NetworkPage" />
|
||||
<settings:SettingsLoggingView Name="LoggingPage" />
|
||||
<settings:SettingsHacksView Name="HacksPage" />
|
||||
</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"
|
||||
IsPaneToggleButtonVisible="False">
|
||||
|
||||
<!-- For image -->
|
||||
<ui:NavigationView.PaneHeader>
|
||||
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="5"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Text="{Binding GameId}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,0,10"
|
||||
TextAlignment="Center" Grid.Row="0" />
|
||||
<Image Source="{Binding GameIcon}"
|
||||
Width="160"
|
||||
Height="160"
|
||||
Grid.Row="1"
|
||||
Margin="0,0,0,10"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock Text="{Binding GameTitle}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,0,10"
|
||||
TextAlignment="Center" Grid.Row="2" />
|
||||
<Separator Height="1" Grid.Row="3" Margin="0,0,0,10" HorizontalAlignment="Stretch"/>
|
||||
</Grid>
|
||||
</ui:NavigationView.PaneHeader>
|
||||
|
||||
<ui:NavigationView.MenuItems>
|
||||
<ui:NavigationViewItem
|
||||
IsSelected="True"
|
||||
Content="{ext:Locale SettingsTabGeneral}"
|
||||
Tag="UiPage"
|
||||
IconSource="New" />
|
||||
<ui:NavigationViewItem
|
||||
Content="{ext:Locale SettingsTabInput}"
|
||||
Tag="InputPage"
|
||||
IconSource="Games" />
|
||||
<ui:NavigationViewItem
|
||||
Content="{ext:Locale SettingsTabSystem}"
|
||||
Tag="SystemPage"
|
||||
IconSource="Settings" />
|
||||
<ui:NavigationViewItem
|
||||
Content="{ext:Locale SettingsTabCpu}"
|
||||
Tag="CpuPage">
|
||||
<ui:NavigationViewItem.IconSource>
|
||||
<ui:FontIconSource
|
||||
FontFamily="avares://Ryujinx/Assets/Fonts#Segoe Fluent Icons"
|
||||
Glyph="{helpers:GlyphValueConverter Chip}" />
|
||||
</ui:NavigationViewItem.IconSource>
|
||||
</ui:NavigationViewItem>
|
||||
<ui:NavigationViewItem
|
||||
Content="{ext:Locale SettingsTabGraphics}"
|
||||
Tag="GraphicsPage"
|
||||
IconSource="Image" />
|
||||
<ui:NavigationViewItem
|
||||
Content="{ext:Locale SettingsTabAudio}"
|
||||
IconSource="Audio"
|
||||
Tag="AudioPage" />
|
||||
<ui:NavigationViewItem
|
||||
Content="{ext:Locale SettingsTabNetwork}"
|
||||
Tag="NetworkPage"
|
||||
IconSource="Globe" />
|
||||
<ui:NavigationViewItem
|
||||
Content="{ext:Locale SettingsTabLogging}"
|
||||
Tag="LoggingPage"
|
||||
IconSource="Document" />
|
||||
<ui:NavigationViewItem
|
||||
IsVisible="{Binding ShowDirtyHacks}"
|
||||
Content="Dirty Hacks"
|
||||
Tag="HacksPage"
|
||||
IconSource="Code" />
|
||||
</ui:NavigationView.MenuItems>
|
||||
</ui:NavigationView>
|
||||
|
||||
<ReversibleStackPanel
|
||||
Grid.Row="2"
|
||||
Margin="10"
|
||||
Spacing="10"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
ReverseOrder="{x:Static helper:RunningPlatform.IsMacOS}">
|
||||
<Button
|
||||
Content="{ext:Locale SettingsButtonSave}"
|
||||
Command="{Binding SaveUserConfig}" />
|
||||
<Button
|
||||
HotKey="Escape"
|
||||
Content="{ext:Locale SettingsButtonClose}"
|
||||
Command="{Binding CancelButton}" />
|
||||
<Button
|
||||
IsVisible="{Binding IsGameRunning}"
|
||||
Content="{ext:Locale SettingsButtonApply}"
|
||||
Command="{Binding ApplyButton}" />
|
||||
<Button
|
||||
IsVisible="{Binding !IsGameRunning}"
|
||||
Content="{ext:Locale UserProfilesDelete}"
|
||||
Command="{Binding DeleteConfigGame}"
|
||||
Classes="red"/>
|
||||
</ReversibleStackPanel>
|
||||
</Grid>
|
||||
</window:StyleableAppWindow>
|
121
src/Ryujinx/UI/Windows/GameSpecificSettingsWindow.axaml.cs
Normal file
121
src/Ryujinx/UI/Windows/GameSpecificSettingsWindow.axaml.cs
Normal file
|
@ -0,0 +1,121 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Shapes;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Media.Imaging;
|
||||
using FluentAvalonia.Core;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Projektanker.Icons.Avalonia;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ava.UI.ViewModels.Input;
|
||||
using Ryujinx.Ava.Utilities;
|
||||
using Ryujinx.Ava.Utilities.Configuration;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.SystemState;
|
||||
using Ryujinx.Input;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Key = Avalonia.Input.Key;
|
||||
|
||||
|
||||
namespace Ryujinx.Ava.UI.Windows
|
||||
{
|
||||
public partial class GameSpecificSettingsWindow : StyleableAppWindow
|
||||
{
|
||||
internal readonly SettingsViewModel ViewModel;
|
||||
|
||||
public GameSpecificSettingsWindow(MainWindowViewModel viewModel, bool findUserConfigDir = true)
|
||||
{
|
||||
Title = string.Format(LocaleManager.Instance[LocaleKeys.SettingsWithInfo], viewModel.SelectedApplication.Name, viewModel.SelectedApplication.IdString);
|
||||
|
||||
DataContext = ViewModel = new SettingsViewModel(
|
||||
viewModel.VirtualFileSystem,
|
||||
viewModel.ContentManager,
|
||||
viewModel.IsGameRunning,
|
||||
viewModel.SelectedApplication.Path,
|
||||
viewModel.SelectedApplication.Name,
|
||||
viewModel.SelectedApplication.IdString,
|
||||
viewModel.SelectedApplication.Icon,
|
||||
findUserConfigDir);
|
||||
|
||||
ViewModel.CloseWindow += Close;
|
||||
ViewModel.SaveSettingsEvent += SaveSettings;
|
||||
|
||||
InitializeComponent();
|
||||
Load();
|
||||
|
||||
#if DEBUG
|
||||
this.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Alt));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
InputPage.InputView?.SaveCurrentProfile();
|
||||
}
|
||||
|
||||
|
||||
private void Load()
|
||||
{
|
||||
Pages.Children.Clear();
|
||||
NavPanel.SelectionChanged += NavPanelOnSelectionChanged;
|
||||
NavPanel.SelectedItem = NavPanel.MenuItems.ElementAt(0);
|
||||
|
||||
}
|
||||
|
||||
private void NavPanelOnSelectionChanged(object sender, NavigationViewSelectionChangedEventArgs e)
|
||||
{
|
||||
|
||||
if (e.SelectedItem is NavigationViewItem navItem && navItem.Tag is not null)
|
||||
{
|
||||
switch (navItem.Tag.ToString())
|
||||
{
|
||||
case nameof(UiPage):
|
||||
UiPage.ViewModel = ViewModel;
|
||||
NavPanel.Content = UiPage;
|
||||
break;
|
||||
case nameof(InputPage):
|
||||
NavPanel.Content = InputPage;
|
||||
break;
|
||||
case nameof(SystemPage):
|
||||
SystemPage.ViewModel = ViewModel;
|
||||
NavPanel.Content = SystemPage;
|
||||
break;
|
||||
case nameof(CpuPage):
|
||||
NavPanel.Content = CpuPage;
|
||||
break;
|
||||
case nameof(GraphicsPage):
|
||||
NavPanel.Content = GraphicsPage;
|
||||
break;
|
||||
case nameof(AudioPage):
|
||||
NavPanel.Content = AudioPage;
|
||||
break;
|
||||
case nameof(NetworkPage):
|
||||
NetworkPage.ViewModel = ViewModel;
|
||||
NavPanel.Content = NetworkPage;
|
||||
break;
|
||||
case nameof(LoggingPage):
|
||||
NavPanel.Content = LoggingPage;
|
||||
break;
|
||||
case nameof(HacksPage):
|
||||
HacksPage.DataContext = ViewModel;
|
||||
NavPanel.Content = HacksPage;
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnClosing(WindowClosingEventArgs e)
|
||||
{
|
||||
InputPage.Dispose(); // You need to unload the gamepad settings, otherwise the controls will be blocked
|
||||
base.OnClosing(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -234,7 +234,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||
_deferLoad = true;
|
||||
_launchPath = launchPathArg;
|
||||
_launchApplicationId = launchApplicationId;
|
||||
_startFullscreen = startFullscreenArg;
|
||||
_startFullscreen = startFullscreenArg;
|
||||
}
|
||||
|
||||
public void SwitchToGameControl(bool startFullscreen = false)
|
||||
|
@ -385,6 +385,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||
|
||||
if (applicationData != null)
|
||||
{
|
||||
ViewModel.SelectedApplication = applicationData;
|
||||
await ViewModel.LoadApplication(applicationData, _startFullscreen);
|
||||
}
|
||||
else
|
||||
|
@ -396,6 +397,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||
else
|
||||
{
|
||||
applicationData = applications[0];
|
||||
ViewModel.SelectedApplication = applicationData;
|
||||
await ViewModel.LoadApplication(applicationData, _startFullscreen);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue