Merge branch 'master' into master

This commit is contained in:
Evan Husted 2025-01-01 21:19:02 -06:00 committed by GitHub
commit 4e77bcb55a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 222 additions and 413 deletions

View File

@ -42,7 +42,7 @@
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" /> <PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" /> <PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" /> <PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
<PackageVersion Include="Gommon" Version="2.7.0" /> <PackageVersion Include="Gommon" Version="2.7.0.1" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" /> <PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" /> <PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpMetal" Version="1.0.0-preview21" /> <PackageVersion Include="SharpMetal" Version="1.0.0-preview21" />

View File

@ -53,6 +53,9 @@ namespace Ryujinx.Common
{ {
public static void LogValueChange<T>(LogClass logClass, ReactiveEventArgs<T> eventArgs, string valueName) public static void LogValueChange<T>(LogClass logClass, ReactiveEventArgs<T> eventArgs, string valueName)
{ {
if (eventArgs.AreValuesEqual)
return;
string message = string.Create(CultureInfo.InvariantCulture, $"{valueName} set to: {eventArgs.NewValue}"); string message = string.Create(CultureInfo.InvariantCulture, $"{valueName} set to: {eventArgs.NewValue}");
Logger.Info?.Print(logClass, message); Logger.Info?.Print(logClass, message);
@ -65,5 +68,22 @@ namespace Ryujinx.Common
{ {
public T OldValue { get; } = oldValue; public T OldValue { get; } = oldValue;
public T NewValue { get; } = newValue; 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);
}
}
} }
} }

View File

@ -367,7 +367,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
{ {
try try
{ {
if (_context.DirtyHacks.IsEnabled(DirtyHack.ShaderTranslationDelay)) if (_context.Capabilities.Api == TargetApi.Metal && _context.DirtyHacks.IsEnabled(DirtyHack.ShaderTranslationDelay))
Thread.Sleep(_context.DirtyHacks[DirtyHack.ShaderTranslationDelay]); Thread.Sleep(_context.DirtyHacks[DirtyHack.ShaderTranslationDelay]);
AsyncProgramTranslation asyncTranslation = new(guestShaders, specState, programIndex, isCompute); AsyncProgramTranslation asyncTranslation = new(guestShaders, specState, programIndex, isCompute);

View File

@ -15,8 +15,10 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
{ {
_baseStorage = SharedRef<LibHac.FsSrv.Sf.IStorage>.CreateMove(ref baseStorage); _baseStorage = SharedRef<LibHac.FsSrv.Sf.IStorage>.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)] [CommandCmif(0)]
// Read(u64 offset, u64 length) -> buffer<u8, 0x46, 0> buffer // Read(u64 offset, u64 length) -> buffer<u8, 0x46, 0> buffer
@ -39,7 +41,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
using var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true); using var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true);
Result result = _baseStorage.Get.Read((long)offset, new OutBuffer(region.Memory.Span), (long)size); 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 // Add a load-bearing sleep to avoid XC2 softlock
// https://web.archive.org/web/20240728045136/https://github.com/Ryujinx/Ryujinx/issues/2357 // https://web.archive.org/web/20240728045136/https://github.com/Ryujinx/Ryujinx/issues/2357

View File

@ -1235,7 +1235,7 @@
"it_IT": "", "it_IT": "",
"ja_JP": "", "ja_JP": "",
"ko_KR": "자주 묻는 질문(FAQ) 및 안내", "ko_KR": "자주 묻는 질문(FAQ) 및 안내",
"no_NO": "", "no_NO": "Vanlige spørsmål og veiledninger",
"pl_PL": "", "pl_PL": "",
"pt_BR": "", "pt_BR": "",
"ru_RU": "FAQ и Руководства", "ru_RU": "FAQ и Руководства",
@ -1460,7 +1460,7 @@
"it_IT": "Preferito", "it_IT": "Preferito",
"ja_JP": "お気に入り", "ja_JP": "お気に入り",
"ko_KR": "즐겨찾기", "ko_KR": "즐겨찾기",
"no_NO": "", "no_NO": "Favoritter",
"pl_PL": "Ulubione", "pl_PL": "Ulubione",
"pt_BR": "Favorito", "pt_BR": "Favorito",
"ru_RU": "Избранное", "ru_RU": "Избранное",
@ -2610,7 +2610,7 @@
"it_IT": "", "it_IT": "",
"ja_JP": "", "ja_JP": "",
"ko_KR": "펌웨어 버전 : {0}", "ko_KR": "펌웨어 버전 : {0}",
"no_NO": "", "no_NO": "Fastvareversjon: {0}",
"pl_PL": "", "pl_PL": "",
"pt_BR": "Versão do firmware: {0}", "pt_BR": "Versão do firmware: {0}",
"ru_RU": "Версия прошивки: {0}", "ru_RU": "Версия прошивки: {0}",
@ -3460,7 +3460,7 @@
"it_IT": "Corea", "it_IT": "Corea",
"ja_JP": "韓国", "ja_JP": "韓国",
"ko_KR": "한국", "ko_KR": "한국",
"no_NO": "", "no_NO": "Koreansk",
"pl_PL": "", "pl_PL": "",
"pt_BR": "Coreia", "pt_BR": "Coreia",
"ru_RU": "Корея", "ru_RU": "Корея",
@ -4010,7 +4010,7 @@
"it_IT": "", "it_IT": "",
"ja_JP": "", "ja_JP": "",
"ko_KR": "PC 날짜와 시간에 동기화", "ko_KR": "PC 날짜와 시간에 동기화",
"no_NO": "", "no_NO": "Resynkroniser til PC-dato og -klokkeslett",
"pl_PL": "", "pl_PL": "",
"pt_BR": "", "pt_BR": "",
"ru_RU": "Повторная синхронизация с датой и временем на компьютере", "ru_RU": "Повторная синхронизация с датой и временем на компьютере",
@ -15260,7 +15260,7 @@
"it_IT": "", "it_IT": "",
"ja_JP": "", "ja_JP": "",
"ko_KR": "시스템 시간을 PC의 현재 날짜 및 시간과 일치하도록 다시 동기화합니다.\n\n이 설정은 활성 설정이 아니므로 여전히 동기화되지 않을 수 있으며, 이 경우 이 버튼을 다시 클릭하면 됩니다.", "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": "", "pl_PL": "",
"pt_BR": "", "pt_BR": "",
"ru_RU": "Повторно синхронизирует системное время, чтобы оно соответствовало текущей дате и времени вашего компьютера.\n\nЭто не активная настройка, она все еще может рассинхронизироваться; в этом случае просто нажмите эту кнопку еще раз.", "ru_RU": "Повторно синхронизирует системное время, чтобы оно соответствовало текущей дате и времени вашего компьютера.\n\nЭто не активная настройка, она все еще может рассинхронизироваться; в этом случае просто нажмите эту кнопку еще раз.",
@ -20535,7 +20535,7 @@
"it_IT": "", "it_IT": "",
"ja_JP": "", "ja_JP": "",
"ko_KR": "Vulkan을 사용합니다.\nARM 맥에서 해당 플랫폼에서 잘 실행되는 게임을 플레이하는 경우 Metal 후단부를 사용합니다.", "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": "", "pl_PL": "",
"pt_BR": "", "pt_BR": "",
"ru_RU": "Использует Vulkan.\nНа Mac с ARM процессорами используется Metal, если игра с ним совместима и хорошо работает.", "ru_RU": "Использует Vulkan.\nНа Mac с ARM процессорами используется Metal, если игра с ним совместима и хорошо работает.",
@ -22598,4 +22598,4 @@
} }
} }
] ]
} }

View File

@ -52,7 +52,7 @@ namespace Ryujinx.Headless
// Make process DPI aware for proper window sizing on high-res screens. // Make process DPI aware for proper window sizing on high-res screens.
ForceDpiAware.Windows(); ForceDpiAware.Windows();
Console.Title = $"Ryujinx Console {Program.Version} (Headless)"; Console.Title = $"HeadlessRyujinx Console {Program.Version}";
if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux()) if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())
{ {
@ -162,6 +162,11 @@ namespace Ryujinx.Headless
} }
ReloadConfig(); ReloadConfig();
if (option.InheritConfig)
{
option.InheritMainConfigInput(originalArgs, ConfigurationState.Instance);
}
_virtualFileSystem = VirtualFileSystem.CreateInstance(); _virtualFileSystem = VirtualFileSystem.CreateInstance();
_libHacHorizonManager = new LibHacHorizonManager(); _libHacHorizonManager = new LibHacHorizonManager();
@ -224,15 +229,7 @@ namespace Ryujinx.Headless
_enableKeyboard = option.EnableKeyboard; _enableKeyboard = option.EnableKeyboard;
_enableMouse = option.EnableMouse; _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.InputProfile1Name, option.InputId1, PlayerIndex.Player1);
LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, PlayerIndex.Player2); LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, PlayerIndex.Player2);
@ -244,7 +241,6 @@ namespace Ryujinx.Headless
LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, PlayerIndex.Player8); LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, PlayerIndex.Player8);
LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, PlayerIndex.Handheld); LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, PlayerIndex.Handheld);
if (_inputConfiguration.Count == 0) if (_inputConfiguration.Count == 0)
{ {
return; return;
@ -306,6 +302,24 @@ namespace Ryujinx.Headless
} }
_inputManager.Dispose(); _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() private static void SetupProgressHandler()

View File

@ -154,10 +154,37 @@ namespace Ryujinx.Headless
return; return;
bool NeedsOverride(string argKey) => originalArgs.None(arg => arg.TrimStart('-').EqualsIgnoreCase(OptionName(argKey))); bool NeedsOverride(string argKey) => originalArgs.None(arg => arg.TrimStart('-').EqualsIgnoreCase(OptionName(argKey)));
string OptionName(string propertyName) =>
typeof(Options)!.GetProperty(propertyName)!.GetCustomAttribute<OptionAttribute>()!.LongName;
} }
public void InheritMainConfigInput(string[] originalArgs, ConfigurationState configurationState)
{
Dictionary<PlayerIndex, (string InputId, string InputProfileName)> 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<OptionAttribute>()!.LongName;
// General // General
@ -391,5 +418,7 @@ namespace Ryujinx.Headless
[Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)] [Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)]
public string InputPath { get; set; } public string InputPath { get; set; }
public SafeDictionary<PlayerIndex, InputConfig> InheritedInputConfigs = new();
} }
} }

View File

@ -1,12 +0,0 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="450"
x:Class="Ryujinx.Ava.UI.Renderer.RendererHost"
FlowDirection="LeftToRight"
Focusable="True">
</UserControl>

View File

@ -1,16 +1,15 @@
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Gommon; using Avalonia.Media;
using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using System; using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Ava.UI.Renderer namespace Ryujinx.Ava.UI.Renderer
{ {
public partial class RendererHost : UserControl, IDisposable public class RendererHost : UserControl, IDisposable
{ {
public readonly EmbeddedWindow EmbeddedWindow; public readonly EmbeddedWindow EmbeddedWindow;
@ -19,7 +18,8 @@ namespace Ryujinx.Ava.UI.Renderer
public RendererHost() public RendererHost()
{ {
InitializeComponent(); Focusable = true;
FlowDirection = FlowDirection.LeftToRight;
EmbeddedWindow = ConfigurationState.Instance.Graphics.GraphicsBackend.Value switch EmbeddedWindow = ConfigurationState.Instance.Graphics.GraphicsBackend.Value switch
{ {
@ -43,8 +43,6 @@ namespace Ryujinx.Ava.UI.Renderer
public RendererHost(string titleId) public RendererHost(string titleId)
{ {
InitializeComponent();
switch (TitleIDs.SelectGraphicsBackend(titleId, ConfigurationState.Instance.Graphics.GraphicsBackend)) switch (TitleIDs.SelectGraphicsBackend(titleId, ConfigurationState.Instance.Graphics.GraphicsBackend))
{ {
case GraphicsBackend.OpenGl: case GraphicsBackend.OpenGl:
@ -109,3 +107,4 @@ namespace Ryujinx.Ava.UI.Renderer
} }
} }
} }

View File

@ -1,6 +1,7 @@
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Threading; using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using Ryujinx.Ava.Common; using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Ava.Utilities.Configuration;
@ -8,42 +9,11 @@ using System;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
{ {
public class AboutWindowViewModel : BaseModel, IDisposable public partial class AboutWindowViewModel : BaseModel, IDisposable
{ {
private Bitmap _githubLogo; [ObservableProperty] private Bitmap _githubLogo;
private Bitmap _discordLogo; [ObservableProperty] private Bitmap _discordLogo;
[ObservableProperty] private string _version;
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();
}
}
public string Developers => "GreemDev"; public string Developers => "GreemDev";

View File

@ -2,6 +2,7 @@ using Avalonia.Collections;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using Avalonia.Threading; using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using DynamicData; using DynamicData;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
@ -17,13 +18,13 @@ using Application = Avalonia.Application;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
{ {
public class DownloadableContentManagerViewModel : BaseModel public partial class DownloadableContentManagerViewModel : BaseModel
{ {
private readonly ApplicationLibrary _applicationLibrary; private readonly ApplicationLibrary _applicationLibrary;
private AvaloniaList<DownloadableContentModel> _downloadableContents = new(); private AvaloniaList<DownloadableContentModel> _downloadableContents = new();
private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new(); [ObservableProperty] private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new();
private AvaloniaList<DownloadableContentModel> _views = new(); [ObservableProperty] private AvaloniaList<DownloadableContentModel> _views = new();
private bool _showBundledContentNotice = false; [ObservableProperty] private bool _showBundledContentNotice = false;
private string _search; private string _search;
private readonly ApplicationData _applicationData; private readonly ApplicationData _applicationData;
@ -41,26 +42,6 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public AvaloniaList<DownloadableContentModel> Views
{
get => _views;
set
{
_views = value;
OnPropertyChanged();
}
}
public AvaloniaList<DownloadableContentModel> SelectedDownloadableContents
{
get => _selectedDownloadableContents;
set
{
_selectedDownloadableContents = value;
OnPropertyChanged();
}
}
public string Search public string Search
{ {
get => _search; get => _search;
@ -77,16 +58,6 @@ namespace Ryujinx.Ava.UI.ViewModels
get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count); get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count);
} }
public bool ShowBundledContentNotice
{
get => _showBundledContentNotice;
set
{
_showBundledContentNotice = value;
OnPropertyChanged();
}
}
public DownloadableContentManagerViewModel(ApplicationLibrary applicationLibrary, ApplicationData applicationData) public DownloadableContentManagerViewModel(ApplicationLibrary applicationLibrary, ApplicationData applicationData)
{ {
_applicationLibrary = applicationLibrary; _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 // 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 // some reason. so we save the items here and add them back after
var items = SelectedDownloadableContents.ToArray(); var items = SelectedDownloadableContents.ToArray();
_views.Clear(); Views.Clear();
_views.AddRange(view); Views.AddRange(view);
foreach (DownloadableContentModel item in items) foreach (DownloadableContentModel item in items)
{ {

View File

@ -182,7 +182,11 @@ namespace Ryujinx.Ava.UI.ViewModels
Applications.ToObservableChangeSet() Applications.ToObservableChangeSet()
.Filter(Filter) .Filter(Filter)
.Sort(GetComparer()) .Sort(GetComparer())
.OnItemAdded(_ => OnPropertyChanged(nameof(AppsObservableList)))
.OnItemRemoved(_ => OnPropertyChanged(nameof(AppsObservableList)))
#pragma warning disable MVVMTK0034 // Event to update is fired below
.Bind(out _appsObservableList) .Bind(out _appsObservableList)
#pragma warning restore MVVMTK0034
.AsObservableList(); .AsObservableList();
_rendererWaitEvent = new AutoResetEvent(false); _rendererWaitEvent = new AutoResetEvent(false);
@ -192,8 +196,8 @@ namespace Ryujinx.Ava.UI.ViewModels
LoadConfigurableHotKeys(); LoadConfigurableHotKeys();
Volume = ConfigurationState.Instance.System.AudioVolume; Volume = ConfigurationState.Instance.System.AudioVolume;
CustomVSyncInterval = ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value;
} }
CustomVSyncInterval = ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value;
} }
public void Initialize( public void Initialize(

View File

@ -1,8 +1,7 @@
using Avalonia;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using Avalonia.Threading; using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using DynamicData; using DynamicData;
using Gommon; using Gommon;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
@ -18,13 +17,13 @@ using System.Linq;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
{ {
public class ModManagerViewModel : BaseModel public partial class ModManagerViewModel : BaseModel
{ {
private readonly string _modJsonPath; private readonly string _modJsonPath;
private AvaloniaList<ModModel> _mods = new(); private AvaloniaList<ModModel> _mods = new();
private AvaloniaList<ModModel> _views = new(); [ObservableProperty] private AvaloniaList<ModModel> _views = new();
private AvaloniaList<ModModel> _selectedMods = new(); [ObservableProperty] private AvaloniaList<ModModel> _selectedMods = new();
private string _search; private string _search;
private readonly ulong _applicationId; private readonly ulong _applicationId;
@ -44,26 +43,6 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public AvaloniaList<ModModel> Views
{
get => _views;
set
{
_views = value;
OnPropertyChanged();
}
}
public AvaloniaList<ModModel> SelectedMods
{
get => _selectedMods;
set
{
_selectedMods = value;
OnPropertyChanged();
}
}
public string Search public string Search
{ {
get => _search; get => _search;
@ -143,8 +122,10 @@ namespace Ryujinx.Ava.UI.ViewModels
.Filter(Filter) .Filter(Filter)
.Bind(out var view).AsObservableList(); .Bind(out var view).AsObservableList();
#pragma warning disable MVVMTK0034 // Event to update is fired below
_views.Clear(); _views.Clear();
_views.AddRange(view); _views.AddRange(view);
#pragma warning restore MVVMTK0034
SelectedMods = new(Views.Where(x => x.Enabled)); SelectedMods = new(Views.Where(x => x.Enabled));

View File

@ -1,9 +1,10 @@
using Gommon; using CommunityToolkit.Mvvm.ComponentModel;
using Gommon;
using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Ava.Utilities.Configuration;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
{ {
public class SettingsHacksViewModel : BaseModel public partial class SettingsHacksViewModel : BaseModel
{ {
private readonly SettingsViewModel _baseViewModel; private readonly SettingsViewModel _baseViewModel;
@ -14,33 +15,11 @@ namespace Ryujinx.Ava.UI.ViewModels
_baseViewModel = settingsVm; _baseViewModel = settingsVm;
} }
private bool _xc2MenuSoftlockFix = ConfigurationState.Instance.Hacks.Xc2MenuSoftlockFix; [ObservableProperty] private bool _xc2MenuSoftlockFix = ConfigurationState.Instance.Hacks.Xc2MenuSoftlockFix;
private bool _shaderTranslationThreadSleep = ConfigurationState.Instance.Hacks.EnableShaderTranslationDelay; [ObservableProperty] private bool _shaderTranslationDelayEnabled = ConfigurationState.Instance.Hacks.EnableShaderTranslationDelay;
private int _shaderTranslationSleepDelay = ConfigurationState.Instance.Hacks.ShaderTranslationDelay; 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 public int ShaderTranslationDelay
{ {
@ -49,7 +28,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
_shaderTranslationSleepDelay = value; _shaderTranslationSleepDelay = value;
OnPropertiesChanged(nameof(ShaderTranslationDelay), nameof(ShaderTranslationDelayTooltipText)); OnPropertiesChanged(nameof(ShaderTranslationDelay), nameof(ShaderTranslationDelayValueText));
} }
} }

View File

@ -1,6 +1,7 @@
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Threading; using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using Gommon; using Gommon;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.OpenAL; using Ryujinx.Audio.Backends.OpenAL;
@ -46,9 +47,9 @@ namespace Ryujinx.Ava.UI.ViewModels
private int _resolutionScale; private int _resolutionScale;
private int _graphicsBackendMultithreadingIndex; private int _graphicsBackendMultithreadingIndex;
private float _volume; private float _volume;
private bool _isVulkanAvailable = true; [ObservableProperty] private bool _isVulkanAvailable = true;
private bool _gameDirectoryChanged; [ObservableProperty] private bool _gameDirectoryChanged;
private bool _autoloadDirectoryChanged; [ObservableProperty] private bool _autoloadDirectoryChanged;
private readonly List<string> _gpuIds = new(); private readonly List<string> _gpuIds = new();
private int _graphicsBackendIndex; private int _graphicsBackendIndex;
private int _scalingFilter; private int _scalingFilter;
@ -63,7 +64,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private int _networkInterfaceIndex; private int _networkInterfaceIndex;
private int _multiplayerModeIndex; private int _multiplayerModeIndex;
private string _ldnPassphrase; private string _ldnPassphrase;
private string _ldnServer; [ObservableProperty] private string _ldnServer;
public SettingsHacksViewModel DirtyHacks { get; } 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 IsOpenGLAvailable => !OperatingSystem.IsMacOS();
public bool IsAppleSiliconMac => OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64; 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 IsMacOS => OperatingSystem.IsMacOS();
public bool EnableDiscordIntegration { get; set; } public bool EnableDiscordIntegration { get; set; }
@ -182,19 +150,12 @@ namespace Ryujinx.Ava.UI.ViewModels
_customVSyncInterval = newInterval; _customVSyncInterval = newInterval;
_customVSyncIntervalPercentageProxy = value; _customVSyncIntervalPercentageProxy = value;
OnPropertiesChanged( OnPropertiesChanged(
nameof(CustomVSyncInterval), nameof(CustomVSyncInterval),
nameof(CustomVSyncIntervalPercentageText)); nameof(CustomVSyncIntervalPercentageText));
} }
} }
public string CustomVSyncIntervalPercentageText public string CustomVSyncIntervalPercentageText => CustomVSyncIntervalPercentageProxy + "%";
{
get
{
string text = CustomVSyncIntervalPercentageProxy + "%";
return text;
}
}
public bool EnableCustomVSyncInterval public bool EnableCustomVSyncInterval
{ {
@ -356,7 +317,6 @@ namespace Ryujinx.Ava.UI.ViewModels
set set
{ {
_networkInterfaceIndex = value != -1 ? value : 0; _networkInterfaceIndex = value != -1 ? value : 0;
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[_networkInterfaceIndex]];
} }
} }
@ -366,7 +326,6 @@ namespace Ryujinx.Ava.UI.ViewModels
set set
{ {
_multiplayerModeIndex = value; _multiplayerModeIndex = value;
ConfigurationState.Instance.Multiplayer.Mode.Value = (MultiplayerMode)_multiplayerModeIndex;
} }
} }
@ -375,16 +334,6 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsInvalidLdnPassphraseVisible { get; set; } public bool IsInvalidLdnPassphraseVisible { get; set; }
public string LdnServer
{
get => _ldnServer;
set
{
_ldnServer = value;
OnPropertyChanged();
}
}
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this() public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
{ {
_virtualFileSystem = virtualFileSystem; _virtualFileSystem = virtualFileSystem;
@ -466,11 +415,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public void MatchSystemTime() public void MatchSystemTime()
{ {
var dto = DateTimeOffset.Now; (DateTimeOffset dto, TimeSpan timeOfDay) = DateTimeOffset.Now.Extract();
CurrentDate = new DateTimeOffset(dto.Year, dto.Month, dto.Day, 0, 0, 0, dto.Offset);
CurrentTime = dto.TimeOfDay; CurrentDate = dto;
CurrentTime = timeOfDay;
OnPropertyChanged(nameof(CurrentDate)); OnPropertyChanged(nameof(CurrentDate));
OnPropertyChanged(nameof(CurrentTime)); OnPropertyChanged(nameof(CurrentTime));
@ -648,16 +596,14 @@ namespace Ryujinx.Ava.UI.ViewModels
config.ShowTitleBar.Value = ShowTitleBar; config.ShowTitleBar.Value = ShowTitleBar;
config.HideCursor.Value = (HideCursorMode)HideCursor; config.HideCursor.Value = (HideCursorMode)HideCursor;
if (_gameDirectoryChanged) if (GameDirectoryChanged)
{ {
List<string> gameDirs = new(GameDirectories); config.UI.GameDirs.Value = [..GameDirectories];
config.UI.GameDirs.Value = gameDirs;
} }
if (_autoloadDirectoryChanged) if (AutoloadDirectoryChanged)
{ {
List<string> autoloadDirs = new(AutoloadDirectories); config.UI.AutoloadDirs.Value = [..AutoloadDirectories];
config.UI.AutoloadDirs.Value = autoloadDirs;
} }
config.UI.BaseStyle.Value = BaseStyleIndex switch config.UI.BaseStyle.Value = BaseStyleIndex switch
@ -756,7 +702,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.Multiplayer.LdnServer.Value = LdnServer; config.Multiplayer.LdnServer.Value = LdnServer;
// Dirty Hacks // Dirty Hacks
config.Hacks.Xc2MenuSoftlockFix.Value = DirtyHacks.Xc2MenuSoftlockFixEnabled; config.Hacks.Xc2MenuSoftlockFix.Value = DirtyHacks.Xc2MenuSoftlockFix;
config.Hacks.EnableShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelayEnabled; config.Hacks.EnableShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelayEnabled;
config.Hacks.ShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelay; config.Hacks.ShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelay;
@ -767,8 +713,8 @@ namespace Ryujinx.Ava.UI.ViewModels
SaveSettingsEvent?.Invoke(); SaveSettingsEvent?.Invoke();
_gameDirectoryChanged = false; GameDirectoryChanged = false;
_autoloadDirectoryChanged = false; AutoloadDirectoryChanged = false;
} }
private static void RevertIfNotSaved() private static void RevertIfNotSaved()

View File

@ -1,74 +1,32 @@
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using Avalonia.Threading; using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Common.Models; using Ryujinx.Ava.Common.Models;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.HLE.FileSystem;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Application = Avalonia.Application;
namespace Ryujinx.Ava.UI.ViewModels 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 ApplicationLibrary ApplicationLibrary { get; }
private ApplicationData ApplicationData { get; } private ApplicationData ApplicationData { get; }
private AvaloniaList<TitleUpdateModel> _titleUpdates = new(); [ObservableProperty] private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
private AvaloniaList<object> _views = new(); [ObservableProperty] private AvaloniaList<object> _views = new();
private object _selectedUpdate = new TitleUpdateViewNoUpdateSentinal(); [ObservableProperty] private object _selectedUpdate = new TitleUpdateViewModelNoUpdate();
private bool _showBundledContentNotice = false; [ObservableProperty] private bool _showBundledContentNotice;
public AvaloniaList<TitleUpdateModel> TitleUpdates private readonly IStorageProvider _storageProvider;
{
get => _titleUpdates;
set
{
_titleUpdates = value;
OnPropertyChanged();
}
}
public AvaloniaList<object> Views
{
get => _views;
set
{
_views = value;
OnPropertyChanged();
}
}
public object SelectedUpdate
{
get => _selectedUpdate;
set
{
_selectedUpdate = value;
OnPropertyChanged();
}
}
public bool ShowBundledContentNotice
{
get => _showBundledContentNotice;
set
{
_showBundledContentNotice = value;
OnPropertyChanged();
}
}
public IStorageProvider StorageProvider;
public TitleUpdateViewModel(ApplicationLibrary applicationLibrary, ApplicationData applicationData) public TitleUpdateViewModel(ApplicationLibrary applicationLibrary, ApplicationData applicationData)
{ {
@ -76,7 +34,7 @@ namespace Ryujinx.Ava.UI.ViewModels
ApplicationData = applicationData; ApplicationData = applicationData;
StorageProvider = RyujinxApp.MainWindow.StorageProvider; _storageProvider = RyujinxApp.MainWindow.StorageProvider;
LoadUpdates(); LoadUpdates();
} }
@ -87,7 +45,7 @@ namespace Ryujinx.Ava.UI.ViewModels
.Where(it => it.TitleUpdate.TitleIdBase == ApplicationData.IdBase); .Where(it => it.TitleUpdate.TitleIdBase == ApplicationData.IdBase);
bool hasBundledContent = false; bool hasBundledContent = false;
SelectedUpdate = new TitleUpdateViewNoUpdateSentinal(); SelectedUpdate = new TitleUpdateViewModelNoUpdate();
foreach ((TitleUpdateModel update, bool isSelected) in updates) foreach ((TitleUpdateModel update, bool isSelected) in updates)
{ {
TitleUpdates.Add(update); TitleUpdates.Add(update);
@ -113,12 +71,12 @@ namespace Ryujinx.Ava.UI.ViewModels
var selected = SelectedUpdate; var selected = SelectedUpdate;
Views.Clear(); Views.Clear();
Views.Add(new TitleUpdateViewNoUpdateSentinal()); Views.Add(new TitleUpdateViewModelNoUpdate());
Views.AddRange(sortedUpdates); Views.AddRange(sortedUpdates);
SelectedUpdate = selected; SelectedUpdate = selected;
if (SelectedUpdate is TitleUpdateViewNoUpdateSentinal) if (SelectedUpdate is TitleUpdateViewModelNoUpdate)
{ {
SelectedUpdate = Views[0]; SelectedUpdate = Views[0];
} }
@ -176,7 +134,7 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
else if (update == SelectedUpdate as TitleUpdateModel) else if (update == SelectedUpdate as TitleUpdateModel)
{ {
SelectedUpdate = new TitleUpdateViewNoUpdateSentinal(); SelectedUpdate = new TitleUpdateViewModelNoUpdate();
} }
SortUpdates(); SortUpdates();
@ -184,7 +142,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public async Task Add() public async Task Add()
{ {
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions var result = await _storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{ {
AllowMultiple = true, AllowMultiple = true,
FileTypeFilter = new List<FilePickerFileType> FileTypeFilter = new List<FilePickerFileType>

View File

@ -1,4 +1,5 @@
using Avalonia.Media; using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using LibHac.Common; using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
@ -20,12 +21,12 @@ using Image = SkiaSharp.SKImage;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
{ {
internal class UserFirmwareAvatarSelectorViewModel : BaseModel internal partial class UserFirmwareAvatarSelectorViewModel : BaseModel
{ {
private static readonly Dictionary<string, byte[]> _avatarStore = new(); private static readonly Dictionary<string, byte[]> _avatarStore = new();
private ObservableCollection<ProfileImageModel> _images; [ObservableProperty] private ObservableCollection<ProfileImageModel> _images;
private Color _backgroundColor = Colors.White; [ObservableProperty] private Color _backgroundColor = Colors.White;
private int _selectedIndex; private int _selectedIndex;
@ -34,27 +35,11 @@ namespace Ryujinx.Ava.UI.ViewModels
_images = new ObservableCollection<ProfileImageModel>(); _images = new ObservableCollection<ProfileImageModel>();
LoadImagesFromStore(); LoadImagesFromStore();
} PropertyChanged += (_, args) =>
public Color BackgroundColor
{
get => _backgroundColor;
set
{ {
_backgroundColor = value; if (args.PropertyName == nameof(BackgroundColor))
OnPropertyChanged(); ChangeImageBackground();
ChangeImageBackground(); };
}
}
public ObservableCollection<ProfileImageModel> Images
{
get => _images;
set
{
_images = value;
OnPropertyChanged();
}
} }
public int SelectedIndex public int SelectedIndex
@ -70,7 +55,7 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
else else
{ {
SelectedImage = _images[_selectedIndex].Data; SelectedImage = Images[_selectedIndex].Data;
} }
OnPropertyChanged(); OnPropertyChanged();

View File

@ -1,18 +1,9 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
{ {
internal class UserProfileImageSelectorViewModel : BaseModel internal partial class UserProfileImageSelectorViewModel : BaseModel
{ {
private bool _firmwareFound; [ObservableProperty] private bool _firmwareFound;
public bool FirmwareFound
{
get => _firmwareFound;
set
{
_firmwareFound = value;
OnPropertyChanged();
}
}
} }
} }

View File

@ -1,3 +1,4 @@
using CommunityToolkit.Mvvm.ComponentModel;
using DynamicData; using DynamicData;
using DynamicData.Binding; using DynamicData.Binding;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
@ -8,74 +9,31 @@ using System.Collections.ObjectModel;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
{ {
public class UserSaveManagerViewModel : BaseModel public partial class UserSaveManagerViewModel : BaseModel
{ {
private int _sortIndex; [ObservableProperty] private int _sortIndex;
private int _orderIndex; [ObservableProperty] private int _orderIndex;
private string _search; [ObservableProperty] private string _search;
private ObservableCollection<SaveModel> _saves = new(); [ObservableProperty] private ObservableCollection<SaveModel> _saves = new();
private ObservableCollection<SaveModel> _views = new(); [ObservableProperty] private ObservableCollection<SaveModel> _views = new();
private readonly AccountManager _accountManager; private readonly AccountManager _accountManager;
public string SaveManagerHeading => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SaveManagerHeading, _accountManager.LastOpenedUser.Name, _accountManager.LastOpenedUser.UserId); public string SaveManagerHeading => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SaveManagerHeading, _accountManager.LastOpenedUser.Name, _accountManager.LastOpenedUser.UserId);
public int SortIndex
{
get => _sortIndex;
set
{
_sortIndex = value;
OnPropertyChanged();
Sort();
}
}
public int OrderIndex
{
get => _orderIndex;
set
{
_orderIndex = value;
OnPropertyChanged();
Sort();
}
}
public string Search
{
get => _search;
set
{
_search = value;
OnPropertyChanged();
Sort();
}
}
public ObservableCollection<SaveModel> Saves
{
get => _saves;
set
{
_saves = value;
OnPropertyChanged();
Sort();
}
}
public ObservableCollection<SaveModel> Views
{
get => _views;
set
{
_views = value;
OnPropertyChanged();
}
}
public UserSaveManagerViewModel(AccountManager accountManager) public UserSaveManagerViewModel(AccountManager accountManager)
{ {
_accountManager = accountManager; _accountManager = accountManager;
PropertyChanged += (_, evt) =>
{
if (evt.PropertyName is
nameof(SortIndex) or
nameof(OrderIndex) or
nameof(Search) or
nameof(Saves))
{
Sort();
}
};
} }
public void Sort() public void Sort()
@ -85,8 +43,10 @@ namespace Ryujinx.Ava.UI.ViewModels
.Sort(GetComparer()) .Sort(GetComparer())
.Bind(out var view).AsObservableList(); .Bind(out var view).AsObservableList();
#pragma warning disable MVVMTK0034
_views.Clear(); _views.Clear();
_views.AddRange(view); _views.AddRange(view);
#pragma warning restore MVVMTK0034
OnPropertyChanged(nameof(Views)); OnPropertyChanged(nameof(Views));
} }
@ -94,7 +54,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
if (arg is SaveModel save) if (arg is SaveModel save)
{ {
return string.IsNullOrWhiteSpace(_search) || save.Title.ToLower().Contains(_search.ToLower()); return string.IsNullOrWhiteSpace(Search) || save.Title.ToLower().Contains(Search.ToLower());
} }
return false; return false;

View File

@ -247,6 +247,7 @@
Header="{ext:Locale MenuBarActionsScanAmiiboBin}" Header="{ext:Locale MenuBarActionsScanAmiiboBin}"
Icon="{ext:Icon mdi-cube-scan}" Icon="{ext:Icon mdi-cube-scan}"
IsVisible="{Binding CanScanAmiiboBinaries}" IsVisible="{Binding CanScanAmiiboBinaries}"
InputGesture="Ctrl + B"
IsEnabled="{Binding IsAmiiboBinRequested}" /> IsEnabled="{Binding IsAmiiboBinRequested}" />
<MenuItem <MenuItem
Command="{Binding TakeScreenshot}" Command="{Binding TakeScreenshot}"

View File

@ -37,7 +37,7 @@
ToolTip.Tip="{Binding DirtyHacks.Xc2MenuFixTooltip}"> ToolTip.Tip="{Binding DirtyHacks.Xc2MenuFixTooltip}">
<CheckBox <CheckBox
Margin="0" Margin="0"
IsChecked="{Binding DirtyHacks.Xc2MenuSoftlockFixEnabled}"/> IsChecked="{Binding DirtyHacks.Xc2MenuSoftlockFix}"/>
<TextBlock <TextBlock
VerticalAlignment="Center" VerticalAlignment="Center"
Text="Xenoblade Chronicles 2 Menu Softlock Fix" /> Text="Xenoblade Chronicles 2 Menu Softlock Fix" />
@ -54,21 +54,27 @@
<TextBlock VerticalAlignment="Center" <TextBlock VerticalAlignment="Center"
Text="Arbitrary Delay on Shader Translation"/> Text="Arbitrary Delay on Shader Translation"/>
</StackPanel> </StackPanel>
<Slider IsVisible="{Binding DirtyHacks.ShaderTranslationDelayEnabled}" <StackPanel
HorizontalAlignment="Center" IsVisible="{Binding DirtyHacks.ShaderTranslationDelayEnabled}"
Value="{Binding DirtyHacks.ShaderTranslationDelay}" Margin="0,10,0,0"
ToolTip.Tip="{Binding DirtyHacks.ShaderTranslationDelayTooltipText}" Orientation="Horizontal"
Width="175" HorizontalAlignment="Center">
Margin="0,-3,0,0" <Slider HorizontalAlignment="Center"
Height="32" Value="{Binding DirtyHacks.ShaderTranslationDelay}"
Padding="0,-5" Width="175"
TickFrequency="1" Margin="0,-3,0,0"
IsSnapToTickEnabled="True" Height="32"
LargeChange="10" Padding="0,-5"
SmallChange="1" TickFrequency="1"
VerticalAlignment="Center" IsSnapToTickEnabled="True"
Minimum="1" LargeChange="10"
Maximum="1000" /> SmallChange="1"
VerticalAlignment="Center"
Minimum="1"
Maximum="1000" />
<TextBlock Margin="5,0"
Text="{Binding DirtyHacks.ShaderTranslationDelayValueText}"/>
</StackPanel>
<Separator/> <Separator/>
</StackPanel> </StackPanel>
</Border> </Border>

View File

@ -40,6 +40,7 @@
<KeyBinding Gesture="F9" Command="{Binding ToggleDockMode}" /> <KeyBinding Gesture="F9" Command="{Binding ToggleDockMode}" />
<KeyBinding Gesture="Escape" Command="{Binding ExitCurrentState}" /> <KeyBinding Gesture="Escape" Command="{Binding ExitCurrentState}" />
<KeyBinding Gesture="Ctrl+A" Command="{Binding OpenAmiiboWindow}" /> <KeyBinding Gesture="Ctrl+A" Command="{Binding OpenAmiiboWindow}" />
<KeyBinding Gesture="Ctrl+B" Command="{Binding OpenBinFile}" />
</Window.KeyBindings> </Window.KeyBindings>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RowDefinitions="*"> <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RowDefinitions="*">
<helpers:OffscreenTextBox IsEnabled="False" Opacity="0" Name="HiddenTextBox" IsHitTestVisible="False" IsTabStop="False" /> <helpers:OffscreenTextBox IsEnabled="False" Opacity="0" Name="HiddenTextBox" IsHitTestVisible="False" IsTabStop="False" />

View File

@ -93,6 +93,7 @@
Padding="10" Padding="10"
MinWidth="0" MinWidth="0"
MinHeight="0" MinHeight="0"
ToolTip.Tip="{Binding Path}"
Click="OpenLocation"> Click="OpenLocation">
<ui:SymbolIcon <ui:SymbolIcon
Symbol="OpenFolder" Symbol="OpenFolder"

View File

@ -95,7 +95,7 @@
</Panel> </Panel>
</DataTemplate> </DataTemplate>
<DataTemplate <DataTemplate
DataType="viewModels:TitleUpdateViewNoUpdateSentinal"> DataType="viewModels:TitleUpdateViewModelNoUpdate">
<Panel <Panel
Height="33" Height="33"
Margin="10"> Margin="10">

View File

@ -645,6 +645,9 @@ namespace Ryujinx.Ava.Utilities.Configuration
private void HackChanged(object sender, ReactiveEventArgs<bool> rxe) private void HackChanged(object sender, ReactiveEventArgs<bool> rxe)
{ {
if (!ShowDirtyHacks)
return;
var newHacks = EnabledHacks.Select(x => x.Hack) var newHacks = EnabledHacks.Select(x => x.Hack)
.JoinToString(", "); .JoinToString(", ");