diff --git a/Directory.Packages.props b/Directory.Packages.props
index 7054dcd7d..ab3bc39b8 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -42,7 +42,7 @@
-
+
diff --git a/src/Ryujinx.Common/ReactiveObject.cs b/src/Ryujinx.Common/ReactiveObject.cs
index 8df1e20fe..bb2ece81c 100644
--- a/src/Ryujinx.Common/ReactiveObject.cs
+++ b/src/Ryujinx.Common/ReactiveObject.cs
@@ -53,6 +53,9 @@ namespace Ryujinx.Common
{
public static void LogValueChange(LogClass logClass, ReactiveEventArgs eventArgs, string valueName)
{
+ if (eventArgs.AreValuesEqual)
+ return;
+
string message = string.Create(CultureInfo.InvariantCulture, $"{valueName} set to: {eventArgs.NewValue}");
Logger.Info?.Print(logClass, message);
@@ -65,5 +68,22 @@ namespace Ryujinx.Common
{
public T OldValue { get; } = oldValue;
public T NewValue { get; } = newValue;
+
+ public bool AreValuesEqual
+ {
+ get
+ {
+ if (OldValue == null && NewValue == null)
+ return true;
+
+ if (OldValue == null && NewValue != null)
+ return false;
+
+ if (OldValue != null && NewValue == null)
+ return false;
+
+ return OldValue!.Equals(NewValue);
+ }
+ }
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs
index 910e9aea0..eb0f72af1 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs
@@ -367,7 +367,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
{
try
{
- if (_context.DirtyHacks.IsEnabled(DirtyHack.ShaderTranslationDelay))
+ if (_context.Capabilities.Api == TargetApi.Metal && _context.DirtyHacks.IsEnabled(DirtyHack.ShaderTranslationDelay))
Thread.Sleep(_context.DirtyHacks[DirtyHack.ShaderTranslationDelay]);
AsyncProgramTranslation asyncTranslation = new(guestShaders, specState, programIndex, isCompute);
diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs
index 3d197ac19..ad4cccc44 100644
--- a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs
@@ -15,8 +15,10 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
{
_baseStorage = SharedRef.CreateMove(ref baseStorage);
}
-
- private const string Xc2TitleId = "0100e95004038000";
+
+ private const string Xc2JpTitleId = "0100f3400332c000";
+ private const string Xc2GlobalTitleId = "0100e95004038000";
+ private static bool IsXc2 => TitleIDs.CurrentApplication.Value.OrDefault() is Xc2GlobalTitleId or Xc2JpTitleId;
[CommandCmif(0)]
// Read(u64 offset, u64 length) -> buffer buffer
@@ -39,7 +41,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
using var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true);
Result result = _baseStorage.Get.Read((long)offset, new OutBuffer(region.Memory.Span), (long)size);
- if (context.Device.DirtyHacks.IsEnabled(DirtyHack.Xc2MenuSoftlockFix) && TitleIDs.CurrentApplication.Value == Xc2TitleId)
+ if (context.Device.DirtyHacks.IsEnabled(DirtyHack.Xc2MenuSoftlockFix) && IsXc2)
{
// Add a load-bearing sleep to avoid XC2 softlock
// https://web.archive.org/web/20240728045136/https://github.com/Ryujinx/Ryujinx/issues/2357
diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json
index b3a7a51b8..6f22e7d06 100644
--- a/src/Ryujinx/Assets/locales.json
+++ b/src/Ryujinx/Assets/locales.json
@@ -1235,7 +1235,7 @@
"it_IT": "",
"ja_JP": "",
"ko_KR": "자주 묻는 질문(FAQ) 및 안내",
- "no_NO": "",
+ "no_NO": "Vanlige spørsmål og veiledninger",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "FAQ и Руководства",
@@ -1460,7 +1460,7 @@
"it_IT": "Preferito",
"ja_JP": "お気に入り",
"ko_KR": "즐겨찾기",
- "no_NO": "",
+ "no_NO": "Favoritter",
"pl_PL": "Ulubione",
"pt_BR": "Favorito",
"ru_RU": "Избранное",
@@ -2610,7 +2610,7 @@
"it_IT": "",
"ja_JP": "",
"ko_KR": "펌웨어 버전 : {0}",
- "no_NO": "",
+ "no_NO": "Fastvareversjon: {0}",
"pl_PL": "",
"pt_BR": "Versão do firmware: {0}",
"ru_RU": "Версия прошивки: {0}",
@@ -3460,7 +3460,7 @@
"it_IT": "Corea",
"ja_JP": "韓国",
"ko_KR": "한국",
- "no_NO": "",
+ "no_NO": "Koreansk",
"pl_PL": "",
"pt_BR": "Coreia",
"ru_RU": "Корея",
@@ -4010,7 +4010,7 @@
"it_IT": "",
"ja_JP": "",
"ko_KR": "PC 날짜와 시간에 동기화",
- "no_NO": "",
+ "no_NO": "Resynkroniser til PC-dato og -klokkeslett",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "Повторная синхронизация с датой и временем на компьютере",
@@ -15260,7 +15260,7 @@
"it_IT": "",
"ja_JP": "",
"ko_KR": "시스템 시간을 PC의 현재 날짜 및 시간과 일치하도록 다시 동기화합니다.\n\n이 설정은 활성 설정이 아니므로 여전히 동기화되지 않을 수 있으며, 이 경우 이 버튼을 다시 클릭하면 됩니다.",
- "no_NO": "",
+ "no_NO": "Resynkroniser systemtiden slik at den samsvarer med PC-ens gjeldende dato og klokkeslett. \\Dette er ikke en aktiv innstilling, men den kan likevel komme ut av synkronisering; i så fall er det bare å klikke på denne knappen igjen.",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "Повторно синхронизирует системное время, чтобы оно соответствовало текущей дате и времени вашего компьютера.\n\nЭто не активная настройка, она все еще может рассинхронизироваться; в этом случае просто нажмите эту кнопку еще раз.",
@@ -20535,7 +20535,7 @@
"it_IT": "",
"ja_JP": "",
"ko_KR": "Vulkan을 사용합니다.\nARM 맥에서 해당 플랫폼에서 잘 실행되는 게임을 플레이하는 경우 Metal 후단부를 사용합니다.",
- "no_NO": "",
+ "no_NO": "Bruker Vulkan \nPå en ARM Mac, og når du spiller et spill som kjører bra under den, bruker du Metal-backend.",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "Использует Vulkan.\nНа Mac с ARM процессорами используется Metal, если игра с ним совместима и хорошо работает.",
@@ -22598,4 +22598,4 @@
}
}
]
-}
\ No newline at end of file
+}
diff --git a/src/Ryujinx/Headless/HeadlessRyujinx.cs b/src/Ryujinx/Headless/HeadlessRyujinx.cs
index 3cb0afca3..5730254f7 100644
--- a/src/Ryujinx/Headless/HeadlessRyujinx.cs
+++ b/src/Ryujinx/Headless/HeadlessRyujinx.cs
@@ -52,7 +52,7 @@ namespace Ryujinx.Headless
// Make process DPI aware for proper window sizing on high-res screens.
ForceDpiAware.Windows();
- Console.Title = $"Ryujinx Console {Program.Version} (Headless)";
+ Console.Title = $"HeadlessRyujinx Console {Program.Version}";
if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())
{
@@ -162,6 +162,11 @@ namespace Ryujinx.Headless
}
ReloadConfig();
+
+ if (option.InheritConfig)
+ {
+ option.InheritMainConfigInput(originalArgs, ConfigurationState.Instance);
+ }
_virtualFileSystem = VirtualFileSystem.CreateInstance();
_libHacHorizonManager = new LibHacHorizonManager();
@@ -224,15 +229,7 @@ namespace Ryujinx.Headless
_enableKeyboard = option.EnableKeyboard;
_enableMouse = option.EnableMouse;
- static void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
- {
- InputConfig inputConfig = HandlePlayerConfiguration(inputProfileName, inputId, index);
- if (inputConfig != null)
- {
- _inputConfiguration.Add(inputConfig);
- }
- }
LoadPlayerConfiguration(option.InputProfile1Name, option.InputId1, PlayerIndex.Player1);
LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, PlayerIndex.Player2);
@@ -244,7 +241,6 @@ namespace Ryujinx.Headless
LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, PlayerIndex.Player8);
LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, PlayerIndex.Handheld);
-
if (_inputConfiguration.Count == 0)
{
return;
@@ -306,6 +302,24 @@ namespace Ryujinx.Headless
}
_inputManager.Dispose();
+
+ return;
+
+ void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
+ {
+ if (index == PlayerIndex.Handheld && _inputConfiguration.Count > 0)
+ {
+ Logger.Info?.Print(LogClass.Configuration, "Skipping handheld configuration as there are already other players configured.");
+ return;
+ }
+
+ InputConfig inputConfig = option.InheritedInputConfigs[index] ?? HandlePlayerConfiguration(inputProfileName, inputId, index);
+
+ if (inputConfig != null)
+ {
+ _inputConfiguration.Add(inputConfig);
+ }
+ }
}
private static void SetupProgressHandler()
diff --git a/src/Ryujinx/Headless/Options.cs b/src/Ryujinx/Headless/Options.cs
index c0def95c1..0d7e46285 100644
--- a/src/Ryujinx/Headless/Options.cs
+++ b/src/Ryujinx/Headless/Options.cs
@@ -154,10 +154,37 @@ namespace Ryujinx.Headless
return;
bool NeedsOverride(string argKey) => originalArgs.None(arg => arg.TrimStart('-').EqualsIgnoreCase(OptionName(argKey)));
-
- string OptionName(string propertyName) =>
- typeof(Options)!.GetProperty(propertyName)!.GetCustomAttribute()!.LongName;
}
+
+ public void InheritMainConfigInput(string[] originalArgs, ConfigurationState configurationState)
+ {
+ Dictionary indicesToProperties = new()
+ {
+ { PlayerIndex.Handheld, (nameof(InputIdHandheld), nameof(InputProfileHandheldName)) },
+ { PlayerIndex.Player1, (nameof(InputId1), nameof(InputProfile1Name)) },
+ { PlayerIndex.Player2, (nameof(InputId2), nameof(InputProfile2Name)) },
+ { PlayerIndex.Player3, (nameof(InputId3), nameof(InputProfile3Name)) },
+ { PlayerIndex.Player4, (nameof(InputId4), nameof(InputProfile4Name)) },
+ { PlayerIndex.Player5, (nameof(InputId5), nameof(InputProfile5Name)) },
+ { PlayerIndex.Player6, (nameof(InputId6), nameof(InputProfile6Name)) },
+ { PlayerIndex.Player7, (nameof(InputId7), nameof(InputProfile7Name)) },
+ { PlayerIndex.Player8, (nameof(InputId8), nameof(InputProfile8Name)) }
+ };
+
+ foreach ((PlayerIndex playerIndex, _) in indicesToProperties
+ .Where(it => NeedsOverride(it.Value.InputId) && NeedsOverride(it.Value.InputProfileName)))
+ {
+ configurationState.Hid.InputConfig.Value.FindFirst(x => x.PlayerIndex == playerIndex)
+ .IfPresent(ic => InheritedInputConfigs[playerIndex] = ic);
+ }
+
+ return;
+
+ bool NeedsOverride(string argKey) => originalArgs.None(arg => arg.TrimStart('-').EqualsIgnoreCase(OptionName(argKey)));
+ }
+
+ private static string OptionName(string propertyName) =>
+ typeof(Options)!.GetProperty(propertyName)!.GetCustomAttribute()!.LongName;
// General
@@ -391,5 +418,7 @@ namespace Ryujinx.Headless
[Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)]
public string InputPath { get; set; }
+
+ public SafeDictionary InheritedInputConfigs = new();
}
}
diff --git a/src/Ryujinx/UI/Renderer/RendererHost.axaml b/src/Ryujinx/UI/Renderer/RendererHost.axaml
deleted file mode 100644
index e0b586b45..000000000
--- a/src/Ryujinx/UI/Renderer/RendererHost.axaml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
diff --git a/src/Ryujinx/UI/Renderer/RendererHost.axaml.cs b/src/Ryujinx/UI/Renderer/RendererHost.cs
similarity index 94%
rename from src/Ryujinx/UI/Renderer/RendererHost.axaml.cs
rename to src/Ryujinx/UI/Renderer/RendererHost.cs
index fa9aec0c5..7dfec8d62 100644
--- a/src/Ryujinx/UI/Renderer/RendererHost.axaml.cs
+++ b/src/Ryujinx/UI/Renderer/RendererHost.cs
@@ -1,16 +1,15 @@
-using Avalonia;
+using Avalonia;
using Avalonia.Controls;
-using Gommon;
+using Avalonia.Media;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using System;
-using System.Runtime.InteropServices;
namespace Ryujinx.Ava.UI.Renderer
{
- public partial class RendererHost : UserControl, IDisposable
+ public class RendererHost : UserControl, IDisposable
{
public readonly EmbeddedWindow EmbeddedWindow;
@@ -19,7 +18,8 @@ namespace Ryujinx.Ava.UI.Renderer
public RendererHost()
{
- InitializeComponent();
+ Focusable = true;
+ FlowDirection = FlowDirection.LeftToRight;
EmbeddedWindow = ConfigurationState.Instance.Graphics.GraphicsBackend.Value switch
{
@@ -43,8 +43,6 @@ namespace Ryujinx.Ava.UI.Renderer
public RendererHost(string titleId)
{
- InitializeComponent();
-
switch (TitleIDs.SelectGraphicsBackend(titleId, ConfigurationState.Instance.Graphics.GraphicsBackend))
{
case GraphicsBackend.OpenGl:
@@ -109,3 +107,4 @@ namespace Ryujinx.Ava.UI.Renderer
}
}
}
+
diff --git a/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs
index 6bc1e1f03..979ae8253 100644
--- a/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs
@@ -1,6 +1,7 @@
using Avalonia.Media.Imaging;
using Avalonia.Styling;
using Avalonia.Threading;
+using CommunityToolkit.Mvvm.ComponentModel;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Utilities.Configuration;
@@ -8,42 +9,11 @@ using System;
namespace Ryujinx.Ava.UI.ViewModels
{
- public class AboutWindowViewModel : BaseModel, IDisposable
+ public partial class AboutWindowViewModel : BaseModel, IDisposable
{
- private Bitmap _githubLogo;
- private Bitmap _discordLogo;
-
- private string _version;
-
- public Bitmap GithubLogo
- {
- get => _githubLogo;
- set
- {
- _githubLogo = value;
- OnPropertyChanged();
- }
- }
-
- public Bitmap DiscordLogo
- {
- get => _discordLogo;
- set
- {
- _discordLogo = value;
- OnPropertyChanged();
- }
- }
-
- public string Version
- {
- get => _version;
- set
- {
- _version = value;
- OnPropertyChanged();
- }
- }
+ [ObservableProperty] private Bitmap _githubLogo;
+ [ObservableProperty] private Bitmap _discordLogo;
+ [ObservableProperty] private string _version;
public string Developers => "GreemDev";
diff --git a/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs b/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs
index acc26decb..52f97cf02 100644
--- a/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs
@@ -2,6 +2,7 @@ using Avalonia.Collections;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
+using CommunityToolkit.Mvvm.ComponentModel;
using DynamicData;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
@@ -17,13 +18,13 @@ using Application = Avalonia.Application;
namespace Ryujinx.Ava.UI.ViewModels
{
- public class DownloadableContentManagerViewModel : BaseModel
+ public partial class DownloadableContentManagerViewModel : BaseModel
{
private readonly ApplicationLibrary _applicationLibrary;
private AvaloniaList _downloadableContents = new();
- private AvaloniaList _selectedDownloadableContents = new();
- private AvaloniaList _views = new();
- private bool _showBundledContentNotice = false;
+ [ObservableProperty] private AvaloniaList _selectedDownloadableContents = new();
+ [ObservableProperty] private AvaloniaList _views = new();
+ [ObservableProperty] private bool _showBundledContentNotice = false;
private string _search;
private readonly ApplicationData _applicationData;
@@ -41,26 +42,6 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
- public AvaloniaList Views
- {
- get => _views;
- set
- {
- _views = value;
- OnPropertyChanged();
- }
- }
-
- public AvaloniaList SelectedDownloadableContents
- {
- get => _selectedDownloadableContents;
- set
- {
- _selectedDownloadableContents = value;
- OnPropertyChanged();
- }
- }
-
public string Search
{
get => _search;
@@ -77,16 +58,6 @@ namespace Ryujinx.Ava.UI.ViewModels
get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count);
}
- public bool ShowBundledContentNotice
- {
- get => _showBundledContentNotice;
- set
- {
- _showBundledContentNotice = value;
- OnPropertyChanged();
- }
- }
-
public DownloadableContentManagerViewModel(ApplicationLibrary applicationLibrary, ApplicationData applicationData)
{
_applicationLibrary = applicationLibrary;
@@ -135,9 +106,9 @@ namespace Ryujinx.Ava.UI.ViewModels
// NOTE(jpr): this works around a bug where calling _views.Clear also clears SelectedDownloadableContents for
// some reason. so we save the items here and add them back after
var items = SelectedDownloadableContents.ToArray();
-
- _views.Clear();
- _views.AddRange(view);
+
+ Views.Clear();
+ Views.AddRange(view);
foreach (DownloadableContentModel item in items)
{
diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
index 6df1f76ad..b7a43ccaf 100644
--- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
@@ -182,7 +182,11 @@ namespace Ryujinx.Ava.UI.ViewModels
Applications.ToObservableChangeSet()
.Filter(Filter)
.Sort(GetComparer())
+ .OnItemAdded(_ => OnPropertyChanged(nameof(AppsObservableList)))
+ .OnItemRemoved(_ => OnPropertyChanged(nameof(AppsObservableList)))
+#pragma warning disable MVVMTK0034 // Event to update is fired below
.Bind(out _appsObservableList)
+#pragma warning restore MVVMTK0034
.AsObservableList();
_rendererWaitEvent = new AutoResetEvent(false);
@@ -192,8 +196,8 @@ namespace Ryujinx.Ava.UI.ViewModels
LoadConfigurableHotKeys();
Volume = ConfigurationState.Instance.System.AudioVolume;
+ CustomVSyncInterval = ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value;
}
- CustomVSyncInterval = ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value;
}
public void Initialize(
diff --git a/src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs b/src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs
index 9c26376ce..ce40ce16c 100644
--- a/src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs
@@ -1,8 +1,7 @@
-using Avalonia;
using Avalonia.Collections;
-using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
+using CommunityToolkit.Mvvm.ComponentModel;
using DynamicData;
using Gommon;
using Ryujinx.Ava.Common.Locale;
@@ -18,13 +17,13 @@ using System.Linq;
namespace Ryujinx.Ava.UI.ViewModels
{
- public class ModManagerViewModel : BaseModel
+ public partial class ModManagerViewModel : BaseModel
{
private readonly string _modJsonPath;
private AvaloniaList _mods = new();
- private AvaloniaList _views = new();
- private AvaloniaList _selectedMods = new();
+ [ObservableProperty] private AvaloniaList _views = new();
+ [ObservableProperty] private AvaloniaList _selectedMods = new();
private string _search;
private readonly ulong _applicationId;
@@ -44,26 +43,6 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
- public AvaloniaList Views
- {
- get => _views;
- set
- {
- _views = value;
- OnPropertyChanged();
- }
- }
-
- public AvaloniaList SelectedMods
- {
- get => _selectedMods;
- set
- {
- _selectedMods = value;
- OnPropertyChanged();
- }
- }
-
public string Search
{
get => _search;
@@ -143,8 +122,10 @@ namespace Ryujinx.Ava.UI.ViewModels
.Filter(Filter)
.Bind(out var view).AsObservableList();
+#pragma warning disable MVVMTK0034 // Event to update is fired below
_views.Clear();
_views.AddRange(view);
+#pragma warning restore MVVMTK0034
SelectedMods = new(Views.Where(x => x.Enabled));
diff --git a/src/Ryujinx/UI/ViewModels/SettingsHacksViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsHacksViewModel.cs
index b93cdd6dc..5096a716d 100644
--- a/src/Ryujinx/UI/ViewModels/SettingsHacksViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/SettingsHacksViewModel.cs
@@ -1,9 +1,10 @@
-using Gommon;
+using CommunityToolkit.Mvvm.ComponentModel;
+using Gommon;
using Ryujinx.Ava.Utilities.Configuration;
namespace Ryujinx.Ava.UI.ViewModels
{
- public class SettingsHacksViewModel : BaseModel
+ public partial class SettingsHacksViewModel : BaseModel
{
private readonly SettingsViewModel _baseViewModel;
@@ -14,33 +15,11 @@ namespace Ryujinx.Ava.UI.ViewModels
_baseViewModel = settingsVm;
}
- private bool _xc2MenuSoftlockFix = ConfigurationState.Instance.Hacks.Xc2MenuSoftlockFix;
- private bool _shaderTranslationThreadSleep = ConfigurationState.Instance.Hacks.EnableShaderTranslationDelay;
+ [ObservableProperty] private bool _xc2MenuSoftlockFix = ConfigurationState.Instance.Hacks.Xc2MenuSoftlockFix;
+ [ObservableProperty] private bool _shaderTranslationDelayEnabled = ConfigurationState.Instance.Hacks.EnableShaderTranslationDelay;
private int _shaderTranslationSleepDelay = ConfigurationState.Instance.Hacks.ShaderTranslationDelay;
-
- public bool Xc2MenuSoftlockFixEnabled
- {
- get => _xc2MenuSoftlockFix;
- set
- {
- _xc2MenuSoftlockFix = value;
-
- OnPropertyChanged();
- }
- }
-
- public bool ShaderTranslationDelayEnabled
- {
- get => _shaderTranslationThreadSleep;
- set
- {
- _shaderTranslationThreadSleep = value;
-
- OnPropertyChanged();
- }
- }
- public string ShaderTranslationDelayTooltipText => $"Current value: {ShaderTranslationDelay}";
+ public string ShaderTranslationDelayValueText => $"{ShaderTranslationDelay}ms";
public int ShaderTranslationDelay
{
@@ -49,7 +28,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
_shaderTranslationSleepDelay = value;
- OnPropertiesChanged(nameof(ShaderTranslationDelay), nameof(ShaderTranslationDelayTooltipText));
+ OnPropertiesChanged(nameof(ShaderTranslationDelay), nameof(ShaderTranslationDelayValueText));
}
}
diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
index a5bdd2f88..2678bbf98 100644
--- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
@@ -1,6 +1,7 @@
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Threading;
+using CommunityToolkit.Mvvm.ComponentModel;
using Gommon;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.OpenAL;
@@ -46,9 +47,9 @@ namespace Ryujinx.Ava.UI.ViewModels
private int _resolutionScale;
private int _graphicsBackendMultithreadingIndex;
private float _volume;
- private bool _isVulkanAvailable = true;
- private bool _gameDirectoryChanged;
- private bool _autoloadDirectoryChanged;
+ [ObservableProperty] private bool _isVulkanAvailable = true;
+ [ObservableProperty] private bool _gameDirectoryChanged;
+ [ObservableProperty] private bool _autoloadDirectoryChanged;
private readonly List _gpuIds = new();
private int _graphicsBackendIndex;
private int _scalingFilter;
@@ -63,7 +64,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private int _networkInterfaceIndex;
private int _multiplayerModeIndex;
private string _ldnPassphrase;
- private string _ldnServer;
+ [ObservableProperty] private string _ldnServer;
public SettingsHacksViewModel DirtyHacks { get; }
@@ -111,43 +112,10 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
- public bool IsVulkanAvailable
- {
- get => _isVulkanAvailable;
- set
- {
- _isVulkanAvailable = value;
-
- OnPropertyChanged();
- }
- }
-
public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS();
public bool IsAppleSiliconMac => OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
- public bool GameDirectoryChanged
- {
- get => _gameDirectoryChanged;
- set
- {
- _gameDirectoryChanged = value;
-
- OnPropertyChanged();
- }
- }
-
- public bool AutoloadDirectoryChanged
- {
- get => _autoloadDirectoryChanged;
- set
- {
- _autoloadDirectoryChanged = value;
-
- OnPropertyChanged();
- }
- }
-
public bool IsMacOS => OperatingSystem.IsMacOS();
public bool EnableDiscordIntegration { get; set; }
@@ -182,19 +150,12 @@ namespace Ryujinx.Ava.UI.ViewModels
_customVSyncInterval = newInterval;
_customVSyncIntervalPercentageProxy = value;
OnPropertiesChanged(
- nameof(CustomVSyncInterval),
+ nameof(CustomVSyncInterval),
nameof(CustomVSyncIntervalPercentageText));
}
}
- public string CustomVSyncIntervalPercentageText
- {
- get
- {
- string text = CustomVSyncIntervalPercentageProxy + "%";
- return text;
- }
- }
+ public string CustomVSyncIntervalPercentageText => CustomVSyncIntervalPercentageProxy + "%";
public bool EnableCustomVSyncInterval
{
@@ -356,7 +317,6 @@ namespace Ryujinx.Ava.UI.ViewModels
set
{
_networkInterfaceIndex = value != -1 ? value : 0;
- ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[_networkInterfaceIndex]];
}
}
@@ -366,7 +326,6 @@ namespace Ryujinx.Ava.UI.ViewModels
set
{
_multiplayerModeIndex = value;
- ConfigurationState.Instance.Multiplayer.Mode.Value = (MultiplayerMode)_multiplayerModeIndex;
}
}
@@ -375,16 +334,6 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsInvalidLdnPassphraseVisible { get; set; }
- public string LdnServer
- {
- get => _ldnServer;
- set
- {
- _ldnServer = value;
- OnPropertyChanged();
- }
- }
-
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
{
_virtualFileSystem = virtualFileSystem;
@@ -466,11 +415,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public void MatchSystemTime()
{
- var dto = DateTimeOffset.Now;
-
- CurrentDate = new DateTimeOffset(dto.Year, dto.Month, dto.Day, 0, 0, 0, dto.Offset);
+ (DateTimeOffset dto, TimeSpan timeOfDay) = DateTimeOffset.Now.Extract();
- CurrentTime = dto.TimeOfDay;
+ CurrentDate = dto;
+ CurrentTime = timeOfDay;
OnPropertyChanged(nameof(CurrentDate));
OnPropertyChanged(nameof(CurrentTime));
@@ -648,16 +596,14 @@ namespace Ryujinx.Ava.UI.ViewModels
config.ShowTitleBar.Value = ShowTitleBar;
config.HideCursor.Value = (HideCursorMode)HideCursor;
- if (_gameDirectoryChanged)
+ if (GameDirectoryChanged)
{
- List gameDirs = new(GameDirectories);
- config.UI.GameDirs.Value = gameDirs;
+ config.UI.GameDirs.Value = [..GameDirectories];
}
- if (_autoloadDirectoryChanged)
+ if (AutoloadDirectoryChanged)
{
- List autoloadDirs = new(AutoloadDirectories);
- config.UI.AutoloadDirs.Value = autoloadDirs;
+ config.UI.AutoloadDirs.Value = [..AutoloadDirectories];
}
config.UI.BaseStyle.Value = BaseStyleIndex switch
@@ -756,7 +702,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.Multiplayer.LdnServer.Value = LdnServer;
// Dirty Hacks
- config.Hacks.Xc2MenuSoftlockFix.Value = DirtyHacks.Xc2MenuSoftlockFixEnabled;
+ config.Hacks.Xc2MenuSoftlockFix.Value = DirtyHacks.Xc2MenuSoftlockFix;
config.Hacks.EnableShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelayEnabled;
config.Hacks.ShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelay;
@@ -767,8 +713,8 @@ namespace Ryujinx.Ava.UI.ViewModels
SaveSettingsEvent?.Invoke();
- _gameDirectoryChanged = false;
- _autoloadDirectoryChanged = false;
+ GameDirectoryChanged = false;
+ AutoloadDirectoryChanged = false;
}
private static void RevertIfNotSaved()
diff --git a/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs b/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs
index 0748efeb4..86d59d6b4 100644
--- a/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs
@@ -1,74 +1,32 @@
using Avalonia.Collections;
-using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
+using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Common.Models;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.Utilities.AppLibrary;
-using Ryujinx.HLE.FileSystem;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
-using Application = Avalonia.Application;
namespace Ryujinx.Ava.UI.ViewModels
{
- public record TitleUpdateViewNoUpdateSentinal();
+ public record TitleUpdateViewModelNoUpdate;
- public class TitleUpdateViewModel : BaseModel
+ public partial class TitleUpdateViewModel : BaseModel
{
private ApplicationLibrary ApplicationLibrary { get; }
private ApplicationData ApplicationData { get; }
- private AvaloniaList _titleUpdates = new();
- private AvaloniaList