Merge branch 'master' into feature/native_aot

This commit is contained in:
Evan Husted 2025-03-04 14:52:14 -06:00
commit 206452e655
72 changed files with 907 additions and 764 deletions

View File

@ -106,7 +106,7 @@ If you are planning to contribute or just want to learn more about this project
- **Input** - **Input**
We currently have support for keyboard, mouse, touch input, JoyCon input support, and nearly all controllers. We currently have support for keyboard, mouse, touch input, Joy-Con input support, and nearly all controllers.
Motion controls are natively supported in most cases; for dual-JoyCon motion support, DS4Windows or BetterJoy are currently required. Motion controls are natively supported in most cases; for dual-JoyCon motion support, DS4Windows or BetterJoy are currently required.
In all scenarios, you can set up everything inside the input configuration menu. In all scenarios, you can set up everything inside the input configuration menu.

View File

@ -631,6 +631,7 @@
010030D012FF6000,"Bus Driver Simulator",,playable,2022-10-17 13:55:27 010030D012FF6000,"Bus Driver Simulator",,playable,2022-10-17 13:55:27
0100A9101418C000,"BUSTAFELLOWS",nvdec,playable,2020-10-17 20:04:41 0100A9101418C000,"BUSTAFELLOWS",nvdec,playable,2020-10-17 20:04:41
0100177005C8A000,"BUTCHER",,playable,2021-01-11 18:50:17 0100177005C8A000,"BUTCHER",,playable,2021-01-11 18:50:17
01008c2019598000,"Bluey: The Videogame",,playable,2025-02-11 04:38:00
01000B900D8B0000,"Cadence of Hyrule: Crypt of the NecroDancer Featuring The Legend of Zelda",slow;nvdec,playable,2024-04-01 22:43:40 01000B900D8B0000,"Cadence of Hyrule: Crypt of the NecroDancer Featuring The Legend of Zelda",slow;nvdec,playable,2024-04-01 22:43:40
010065700EE06000,"Cadence of Hyrule: Crypt of the NecroDancer Featuring The Legend of Zelda Demo",demo;gpu;nvdec,ingame,2021-02-14 21:48:15 010065700EE06000,"Cadence of Hyrule: Crypt of the NecroDancer Featuring The Legend of Zelda Demo",demo;gpu;nvdec,ingame,2021-02-14 21:48:15
01005C00117A8000,"Café Enchanté",,playable,2020-11-13 14:54:25 01005C00117A8000,"Café Enchanté",,playable,2020-11-13 14:54:25
@ -1382,6 +1383,9 @@
0100763015C2E000,"Gunvolt Chronicles: Luminous Avenger iX 2",crash;Needs Update,nothing,2022-04-29 15:34:34 0100763015C2E000,"Gunvolt Chronicles: Luminous Avenger iX 2",crash;Needs Update,nothing,2022-04-29 15:34:34
01002C8018554000,"Gurimugurimoa OnceMore Demo",,playable,2022-07-29 22:07:31 01002C8018554000,"Gurimugurimoa OnceMore Demo",,playable,2022-07-29 22:07:31
0100AC601DCA8000,"GYLT",crash,ingame,2024-03-18 20:16:51 0100AC601DCA8000,"GYLT",crash,ingame,2024-03-18 20:16:51
0100c3c012718000,"Grand Theft Auto: III The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52
0100182014022000,"Grand Theft Auto: Vice City The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52
010065a014024000,"Grand Theft Auto: San Andreas The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52
0100822012D76000,"HAAK",gpu,ingame,2023-02-19 14:31:05 0100822012D76000,"HAAK",gpu,ingame,2023-02-19 14:31:05
01007E100EFA8000,"Habroxia",,playable,2020-06-16 23:04:42 01007E100EFA8000,"Habroxia",,playable,2020-06-16 23:04:42
0100535012974000,"Hades",vulkan,playable,2022-10-05 10:45:21 0100535012974000,"Hades",vulkan,playable,2022-10-05 10:45:21
@ -2729,7 +2733,7 @@
0100C2500FC20000,"Splatoon™ 3",ldn-works;opengl-backend-bug;LAN;amd-vendor-bug,playable,2024-08-04 23:49:11 0100C2500FC20000,"Splatoon™ 3",ldn-works;opengl-backend-bug;LAN;amd-vendor-bug,playable,2024-08-04 23:49:11
0100BA0018500000,"Splatoon™ 3: Splatfest World Premiere",gpu;online-broken;demo,ingame,2022-09-19 03:17:12 0100BA0018500000,"Splatoon™ 3: Splatfest World Premiere",gpu;online-broken;demo,ingame,2022-09-19 03:17:12
010062800D39C000,"SpongeBob SquarePants: Battle for Bikini Bottom - Rehydrated",online-broken;UE4;ldn-broken;vulkan-backend-bug,playable,2023-08-01 19:29:34 010062800D39C000,"SpongeBob SquarePants: Battle for Bikini Bottom - Rehydrated",online-broken;UE4;ldn-broken;vulkan-backend-bug,playable,2023-08-01 19:29:34
01009FB0172F4000,"SpongeBob SquarePants: The Cosmic Shake",gpu;UE4,ingame,2023-08-01 19:29:53 01009FB0172F4000,"SpongeBob SquarePants: The Cosmic Shake",gpu;UE4,ingame,2024-03-04 16:35:00
010097C01336A000,"Spooky Chase",,playable,2022-11-04 12:17:44 010097C01336A000,"Spooky Chase",,playable,2022-11-04 12:17:44
0100C6100D75E000,"Spooky Ghosts Dot Com",,playable,2021-06-15 15:16:11 0100C6100D75E000,"Spooky Ghosts Dot Com",,playable,2021-06-15 15:16:11
0100DE9005170000,"Sports Party",nvdec,playable,2021-03-05 13:40:42 0100DE9005170000,"Sports Party",nvdec,playable,2021-03-05 13:40:42

1 title_id game_name labels status last_updated
631 010030D012FF6000 Bus Driver Simulator playable 2022-10-17 13:55:27
632 0100A9101418C000 BUSTAFELLOWS nvdec playable 2020-10-17 20:04:41
633 0100177005C8A000 BUTCHER playable 2021-01-11 18:50:17
634 01008c2019598000 Bluey: The Videogame playable 2025-02-11 04:38:00
635 01000B900D8B0000 Cadence of Hyrule: Crypt of the NecroDancer Featuring The Legend of Zelda slow;nvdec playable 2024-04-01 22:43:40
636 010065700EE06000 Cadence of Hyrule: Crypt of the NecroDancer Featuring The Legend of Zelda Demo demo;gpu;nvdec ingame 2021-02-14 21:48:15
637 01005C00117A8000 Café Enchanté playable 2020-11-13 14:54:25
1383 0100763015C2E000 Gunvolt Chronicles: Luminous Avenger iX 2 crash;Needs Update nothing 2022-04-29 15:34:34
1384 01002C8018554000 Gurimugurimoa OnceMore Demo playable 2022-07-29 22:07:31
1385 0100AC601DCA8000 GYLT crash ingame 2024-03-18 20:16:51
1386 0100c3c012718000 Grand Theft Auto: III – The Definitive Edition gpu;UE4 ingame 2022-10-31 20:13:52
1387 0100182014022000 Grand Theft Auto: Vice City – The Definitive Edition gpu;UE4 ingame 2022-10-31 20:13:52
1388 010065a014024000 Grand Theft Auto: San Andreas – The Definitive Edition gpu;UE4 ingame 2022-10-31 20:13:52
1389 0100822012D76000 HAAK gpu ingame 2023-02-19 14:31:05
1390 01007E100EFA8000 Habroxia playable 2020-06-16 23:04:42
1391 0100535012974000 Hades vulkan playable 2022-10-05 10:45:21
2733 0100C2500FC20000 Splatoon™ 3 ldn-works;opengl-backend-bug;LAN;amd-vendor-bug playable 2024-08-04 23:49:11
2734 0100BA0018500000 Splatoon™ 3: Splatfest World Premiere gpu;online-broken;demo ingame 2022-09-19 03:17:12
2735 010062800D39C000 SpongeBob SquarePants: Battle for Bikini Bottom - Rehydrated online-broken;UE4;ldn-broken;vulkan-backend-bug playable 2023-08-01 19:29:34
2736 01009FB0172F4000 SpongeBob SquarePants: The Cosmic Shake gpu;UE4 ingame 2023-08-01 19:29:53 2024-03-04 16:35:00
2737 010097C01336A000 Spooky Chase playable 2022-11-04 12:17:44
2738 0100C6100D75E000 Spooky Ghosts Dot Com playable 2021-06-15 15:16:11
2739 0100DE9005170000 Sports Party nvdec playable 2021-03-05 13:40:42

View File

@ -1,4 +1,6 @@
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL.Multithreading;
using System; using System;
using System.Threading; using System.Threading;
@ -10,6 +12,20 @@ namespace Ryujinx.Graphics.GAL
bool PreferThreading { get; } bool PreferThreading { get; }
public IRenderer TryMakeThreaded(BackendThreading backendThreading = BackendThreading.Auto)
{
if (backendThreading is BackendThreading.On ||
(backendThreading is BackendThreading.Auto && PreferThreading))
{
Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({backendThreading}): True");
return new ThreadedRenderer(this);
}
Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({backendThreading}): False");
return this;
}
IPipeline Pipeline { get; } IPipeline Pipeline { get; }
IWindow Window { get; } IWindow Window { get; }

View File

@ -19,7 +19,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm
private readonly LanDiscovery _lanDiscovery; private readonly LanDiscovery _lanDiscovery;
public LdnMitmClient(HLEConfiguration config) public LdnMitmClient(HleConfiguration config)
{ {
UnicastIPAddressInformation localIpInterface = NetworkHelpers.GetLocalInterface(config.MultiplayerLanInterfaceId).Item2; UnicastIPAddressInformation localIpInterface = NetworkHelpers.GetLocalInterface(config.MultiplayerLanInterfaceId).Item2;

View File

@ -51,13 +51,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
private string _passphrase; private string _passphrase;
private byte[] _gameVersion = new byte[0x10]; private byte[] _gameVersion = new byte[0x10];
private readonly HLEConfiguration _config; private readonly HleConfiguration _config;
public event EventHandler<NetworkChangeEventArgs> NetworkChange; public event EventHandler<NetworkChangeEventArgs> NetworkChange;
public ProxyConfig Config { get; private set; } public ProxyConfig Config { get; private set; }
public LdnMasterProxyClient(string address, int port, HLEConfiguration config) : base(address, port) public LdnMasterProxyClient(string address, int port, HleConfiguration config) : base(address, port)
{ {
if (ProxyHelpers.SupportsNoDelay()) if (ProxyHelpers.SupportsNoDelay())
{ {

View File

@ -15,55 +15,55 @@ namespace Ryujinx.HLE
/// <summary> /// <summary>
/// HLE configuration. /// HLE configuration.
/// </summary> /// </summary>
public class HLEConfiguration public class HleConfiguration
{ {
/// <summary> /// <summary>
/// The virtual file system used by the FS service. /// The virtual file system used by the FS service.
/// </summary> /// </summary>
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks> /// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
internal readonly VirtualFileSystem VirtualFileSystem; internal VirtualFileSystem VirtualFileSystem { get; private set; }
/// <summary> /// <summary>
/// The manager for handling a LibHac Horizon instance. /// The manager for handling a LibHac Horizon instance.
/// </summary> /// </summary>
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks> /// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
internal readonly LibHacHorizonManager LibHacHorizonManager; internal LibHacHorizonManager LibHacHorizonManager { get; private set; }
/// <summary> /// <summary>
/// The account manager used by the account service. /// The account manager used by the account service.
/// </summary> /// </summary>
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks> /// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
internal readonly AccountManager AccountManager; internal AccountManager AccountManager { get; private set; }
/// <summary> /// <summary>
/// The content manager used by the NCM service. /// The content manager used by the NCM service.
/// </summary> /// </summary>
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks> /// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
internal readonly ContentManager ContentManager; internal ContentManager ContentManager { get; private set; }
/// <summary> /// <summary>
/// The persistent information between run for multi-application capabilities. /// The persistent information between run for multi-application capabilities.
/// </summary> /// </summary>
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks> /// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
public readonly UserChannelPersistence UserChannelPersistence; public UserChannelPersistence UserChannelPersistence { get; private set; }
/// <summary> /// <summary>
/// The GPU renderer to use for all GPU operations. /// The GPU renderer to use for all GPU operations.
/// </summary> /// </summary>
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks> /// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
internal readonly IRenderer GpuRenderer; internal IRenderer GpuRenderer { get; private set; }
/// <summary> /// <summary>
/// The audio device driver to use for all audio operations. /// The audio device driver to use for all audio operations.
/// </summary> /// </summary>
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks> /// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
internal readonly IHardwareDeviceDriver AudioDeviceDriver; internal IHardwareDeviceDriver AudioDeviceDriver { get; private set; }
/// <summary> /// <summary>
/// The handler for various UI related operations needed outside of HLE. /// The handler for various UI related operations needed outside of HLE.
/// </summary> /// </summary>
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks> /// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
internal readonly IHostUIHandler HostUIHandler; internal IHostUIHandler HostUIHandler { get; private set; }
/// <summary> /// <summary>
/// Control the memory configuration used by the emulation context. /// Control the memory configuration used by the emulation context.
@ -195,15 +195,7 @@ namespace Ryujinx.HLE
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks> /// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
public EnabledDirtyHack[] Hacks { internal get; set; } public EnabledDirtyHack[] Hacks { internal get; set; }
public HLEConfiguration(VirtualFileSystem virtualFileSystem, public HleConfiguration(MemoryConfiguration memoryConfiguration,
LibHacHorizonManager libHacHorizonManager,
ContentManager contentManager,
AccountManager accountManager,
UserChannelPersistence userChannelPersistence,
IRenderer gpuRenderer,
IHardwareDeviceDriver audioDeviceDriver,
MemoryConfiguration memoryConfiguration,
IHostUIHandler hostUIHandler,
SystemLanguage systemLanguage, SystemLanguage systemLanguage,
RegionCode region, RegionCode region,
VSyncMode vSyncMode, VSyncMode vSyncMode,
@ -227,15 +219,7 @@ namespace Ryujinx.HLE
int customVSyncInterval, int customVSyncInterval,
EnabledDirtyHack[] dirtyHacks = null) EnabledDirtyHack[] dirtyHacks = null)
{ {
VirtualFileSystem = virtualFileSystem;
LibHacHorizonManager = libHacHorizonManager;
AccountManager = accountManager;
ContentManager = contentManager;
UserChannelPersistence = userChannelPersistence;
GpuRenderer = gpuRenderer;
AudioDeviceDriver = audioDeviceDriver;
MemoryConfiguration = memoryConfiguration; MemoryConfiguration = memoryConfiguration;
HostUIHandler = hostUIHandler;
SystemLanguage = systemLanguage; SystemLanguage = systemLanguage;
Region = region; Region = region;
VSyncMode = vSyncMode; VSyncMode = vSyncMode;
@ -259,5 +243,30 @@ namespace Ryujinx.HLE
MultiplayerLdnServer = multiplayerLdnServer; MultiplayerLdnServer = multiplayerLdnServer;
Hacks = dirtyHacks ?? []; Hacks = dirtyHacks ?? [];
} }
/// <summary>
/// Set the pre-configured services to use for this <see cref="HleConfiguration"/> instance.
/// </summary>
public HleConfiguration Configure(
VirtualFileSystem virtualFileSystem,
LibHacHorizonManager libHacHorizonManager,
ContentManager contentManager,
AccountManager accountManager,
UserChannelPersistence userChannelPersistence,
IRenderer gpuRenderer,
IHardwareDeviceDriver audioDeviceDriver,
IHostUIHandler hostUIHandler
)
{
VirtualFileSystem = virtualFileSystem;
LibHacHorizonManager = libHacHorizonManager;
AccountManager = accountManager;
ContentManager = contentManager;
UserChannelPersistence = userChannelPersistence;
GpuRenderer = gpuRenderer;
AudioDeviceDriver = audioDeviceDriver;
HostUIHandler = hostUIHandler;
return this;
}
} }
} }

View File

@ -20,7 +20,7 @@ namespace Ryujinx.HLE
{ {
public static Switch Shared { get; private set; } public static Switch Shared { get; private set; }
public HLEConfiguration Configuration { get; } public HleConfiguration Configuration { get; }
public IHardwareDeviceDriver AudioDeviceDriver { get; } public IHardwareDeviceDriver AudioDeviceDriver { get; }
public MemoryBlock Memory { get; } public MemoryBlock Memory { get; }
public GpuContext Gpu { get; } public GpuContext Gpu { get; }
@ -44,7 +44,7 @@ namespace Ryujinx.HLE
public DirtyHacks DirtyHacks { get; } public DirtyHacks DirtyHacks { get; }
public Switch(HLEConfiguration configuration) public Switch(HleConfiguration configuration)
{ {
ArgumentNullException.ThrowIfNull(configuration.GpuRenderer); ArgumentNullException.ThrowIfNull(configuration.GpuRenderer);
ArgumentNullException.ThrowIfNull(configuration.AudioDeviceDriver); ArgumentNullException.ThrowIfNull(configuration.AudioDeviceDriver);
@ -94,16 +94,20 @@ namespace Ryujinx.HLE
Gpu.GPFifo.DispatchCalls(); Gpu.GPFifo.DispatchCalls();
} }
public void IncrementCustomVSyncInterval() public int IncrementCustomVSyncInterval()
{ {
CustomVSyncInterval += 1; CustomVSyncInterval += 1;
UpdateVSyncInterval(); UpdateVSyncInterval();
return CustomVSyncInterval;
} }
public void DecrementCustomVSyncInterval() public int DecrementCustomVSyncInterval()
{ {
CustomVSyncInterval -= 1; CustomVSyncInterval -= 1;
UpdateVSyncInterval(); UpdateVSyncInterval();
return CustomVSyncInterval;
} }
public void UpdateVSyncInterval() public void UpdateVSyncInterval()

View File

@ -902,53 +902,19 @@ namespace Ryujinx.Ava
_ => new OpenGLRenderer() _ => new OpenGLRenderer()
}; };
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
bool isGALThreaded = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading);
if (isGALThreaded)
{
renderer = new ThreadedRenderer(renderer);
}
Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({threadingMode}): {isGALThreaded}");
// Initialize Configuration. // Initialize Configuration.
MemoryConfiguration memoryConfiguration = ConfigurationState.Instance.System.DramSize.Value; Device = new Switch(ConfigurationState.Instance.CreateHleConfiguration()
.Configure(
Device = new Switch(new HLEConfiguration(
VirtualFileSystem, VirtualFileSystem,
_viewModel.LibHacHorizonManager, _viewModel.LibHacHorizonManager,
ContentManager, ContentManager,
_accountManager, _accountManager,
_userChannelPersistence, _userChannelPersistence,
renderer, renderer.TryMakeThreaded(ConfigurationState.Instance.Graphics.BackendThreading),
InitializeAudio(), InitializeAudio(),
memoryConfiguration, _viewModel.UiHandler
_viewModel.UiHandler, )
(SystemLanguage)ConfigurationState.Instance.System.Language.Value, );
(RegionCode)ConfigurationState.Instance.System.Region.Value,
ConfigurationState.Instance.Graphics.VSyncMode,
ConfigurationState.Instance.System.EnableDockedMode,
ConfigurationState.Instance.System.EnablePtc,
ConfigurationState.Instance.System.EnableInternetAccess,
ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
ConfigurationState.Instance.System.FsGlobalAccessLogMode,
ConfigurationState.Instance.System.MatchSystemTime
? 0
: ConfigurationState.Instance.System.SystemTimeOffset,
ConfigurationState.Instance.System.TimeZone,
ConfigurationState.Instance.System.MemoryManagerMode,
ConfigurationState.Instance.System.IgnoreMissingServices,
ConfigurationState.Instance.Graphics.AspectRatio,
ConfigurationState.Instance.System.AudioVolume,
ConfigurationState.Instance.System.UseHypervisor,
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value,
ConfigurationState.Instance.Multiplayer.Mode,
ConfigurationState.Instance.Multiplayer.DisableP2p,
ConfigurationState.Instance.Multiplayer.LdnPassphrase,
ConfigurationState.Instance.Multiplayer.GetLdnServer(),
ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value,
ConfigurationState.Instance.Hacks.ShowDirtyHacks ? ConfigurationState.Instance.Hacks.EnabledHacks : null));
} }
private static IHardwareDeviceDriver InitializeAudio() private static IHardwareDeviceDriver InitializeAudio()
@ -1182,6 +1148,9 @@ namespace Ryujinx.Ava
private void UpdateShaderCount() private void UpdateShaderCount()
{ {
if (_displayCount is 0 && _renderer.ProgramCount is 0)
return;
// If there is a mismatch between total program compile and previous count // If there is a mismatch between total program compile and previous count
// this means new shaders have been compiled and should be displayed. // this means new shaders have been compiled and should be displayed.
if (_renderer.ProgramCount != _previousCount) if (_renderer.ProgramCount != _previousCount)
@ -1255,12 +1224,10 @@ namespace Ryujinx.Ava
VSyncModeToggle(); VSyncModeToggle();
break; break;
case KeyboardHotkeyState.CustomVSyncIntervalDecrement: case KeyboardHotkeyState.CustomVSyncIntervalDecrement:
Device.DecrementCustomVSyncInterval(); _viewModel.CustomVSyncInterval = Device.DecrementCustomVSyncInterval();
_viewModel.CustomVSyncInterval -= 1;
break; break;
case KeyboardHotkeyState.CustomVSyncIntervalIncrement: case KeyboardHotkeyState.CustomVSyncIntervalIncrement:
Device.IncrementCustomVSyncInterval(); _viewModel.CustomVSyncInterval = Device.IncrementCustomVSyncInterval();
_viewModel.CustomVSyncInterval += 1;
break; break;
case KeyboardHotkeyState.Screenshot: case KeyboardHotkeyState.Screenshot:
ScreenshotRequested = true; ScreenshotRequested = true;

View File

@ -2763,7 +2763,7 @@
"no_NO": "", "no_NO": "",
"pl_PL": "", "pl_PL": "",
"pt_BR": "", "pt_BR": "",
"ru_RU": "", "ru_RU": "Создать пользовательскую конфигурацию",
"sv_SE": "", "sv_SE": "",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
@ -2788,7 +2788,7 @@
"no_NO": "", "no_NO": "",
"pl_PL": "", "pl_PL": "",
"pt_BR": "", "pt_BR": "",
"ru_RU": "", "ru_RU": "Изменить пользовательскую конфигурацию",
"sv_SE": "", "sv_SE": "",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
@ -2863,7 +2863,7 @@
"no_NO": "", "no_NO": "",
"pl_PL": "", "pl_PL": "",
"pt_BR": "", "pt_BR": "",
"ru_RU": "", "ru_RU": "Отредактировать существующую независимую конфигурацию для выбранной игры.",
"sv_SE": "", "sv_SE": "",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
@ -5013,7 +5013,7 @@
"no_NO": "Lyd Inn/Ut", "no_NO": "Lyd Inn/Ut",
"pl_PL": "", "pl_PL": "",
"pt_BR": "", "pt_BR": "",
"ru_RU": "", "ru_RU": "Выход/Вход звука",
"sv_SE": "", "sv_SE": "",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
@ -6563,7 +6563,7 @@
"no_NO": "", "no_NO": "",
"pl_PL": "", "pl_PL": "",
"pt_BR": "", "pt_BR": "",
"ru_RU": "", "ru_RU": "Ок",
"sv_SE": "Ok", "sv_SE": "Ok",
"th_TH": "ตกลง", "th_TH": "ตกลง",
"tr_TR": "Tamam", "tr_TR": "Tamam",
@ -7013,7 +7013,7 @@
"no_NO": "", "no_NO": "",
"pl_PL": "Pro Kontroler", "pl_PL": "Pro Kontroler",
"pt_BR": "", "pt_BR": "",
"ru_RU": "", "ru_RU": "Pro контроллер",
"sv_SE": "", "sv_SE": "",
"th_TH": "โปรคอนโทรลเลอร์", "th_TH": "โปรคอนโทรลเลอร์",
"tr_TR": "Profesyonel Kumanda", "tr_TR": "Profesyonel Kumanda",
@ -20013,7 +20013,7 @@
"no_NO": "", "no_NO": "",
"pl_PL": "", "pl_PL": "",
"pt_BR": "", "pt_BR": "",
"ru_RU": "", "ru_RU": "Амибо",
"sv_SE": "", "sv_SE": "",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",

View File

@ -13,7 +13,7 @@ using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.Utilities; using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Ava.Utilities.Configuration;

View File

@ -312,26 +312,10 @@ namespace Ryujinx.Headless
return new OpenGLRenderer(); return new OpenGLRenderer();
} }
private static Switch InitializeEmulationContext(WindowBase window, IRenderer renderer, Options options) private static Switch InitializeEmulationContext(WindowBase window, IRenderer renderer, Options options) =>
{ new(
BackendThreading threadingMode = options.BackendThreading; new HleConfiguration(
bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading);
if (threadedGAL)
{
renderer = new ThreadedRenderer(renderer);
}
HLEConfiguration configuration = new(_virtualFileSystem,
_libHacHorizonManager,
_contentManager,
_accountManager,
_userChannelPersistence,
renderer,
new SDL2HardwareDeviceDriver(),
options.DramSize, options.DramSize,
window,
options.SystemLanguage, options.SystemLanguage,
options.SystemRegion, options.SystemRegion,
options.VSyncMode, options.VSyncMode,
@ -352,9 +336,18 @@ namespace Ryujinx.Headless
false, false,
string.Empty, string.Empty,
string.Empty, string.Empty,
options.CustomVSyncInterval); options.CustomVSyncInterval
)
return new Switch(configuration); .Configure(
} _virtualFileSystem,
_libHacHorizonManager,
_contentManager,
_accountManager,
_userChannelPersistence,
renderer.TryMakeThreaded(options.BackendThreading),
new SDL2HardwareDeviceDriver(),
window
)
);
} }
} }

View File

@ -17,11 +17,7 @@
<viewModels:ProfileSelectorDialogViewModel /> <viewModels:ProfileSelectorDialogViewModel />
</Design.DataContext> </Design.DataContext>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RowDefinitions="*,Auto">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border <Border
CornerRadius="5" CornerRadius="5"

View File

@ -9,17 +9,14 @@ using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading.Tasks; using System.Threading.Tasks;
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile; using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
using UserProfileSft = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile; using UserProfileSft = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
namespace Ryujinx.Ava.UI.Applet namespace Ryujinx.Ava.UI.Applet
{ {
public partial class ProfileSelectorDialog : UserControl public partial class ProfileSelectorDialog : RyujinxControl<ProfileSelectorDialogViewModel>
{ {
public ProfileSelectorDialogViewModel ViewModel { get; set; }
public ProfileSelectorDialog(ProfileSelectorDialogViewModel viewModel) public ProfileSelectorDialog(ProfileSelectorDialogViewModel viewModel)
{ {
DataContext = ViewModel = viewModel; DataContext = ViewModel = viewModel;

View File

@ -9,6 +9,7 @@ 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.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Views.Misc;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Ava.Utilities; using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.AppLibrary;
@ -26,6 +27,7 @@ namespace Ryujinx.Ava.UI.Controls
{ {
public class ApplicationContextMenu : MenuFlyout public class ApplicationContextMenu : MenuFlyout
{ {
public ApplicationContextMenu() public ApplicationContextMenu()
{ {
InitializeComponent(); InitializeComponent();

View File

@ -23,13 +23,12 @@ using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
namespace Ryujinx.Ava.UI.Controls namespace Ryujinx.Ava.UI.Controls
{ {
public partial class NavigationDialogHost : UserControl public partial class NavigationDialogHost : RyujinxControl<UserProfileViewModel>
{ {
public AccountManager AccountManager { get; } public AccountManager AccountManager { get; }
public ContentManager ContentManager { get; } public ContentManager ContentManager { get; }
public VirtualFileSystem VirtualFileSystem { get; } public VirtualFileSystem VirtualFileSystem { get; }
public HorizonClient HorizonClient { get; } public HorizonClient HorizonClient { get; }
public UserProfileViewModel ViewModel { get; set; }
public NavigationDialogHost() public NavigationDialogHost()
{ {

View File

@ -0,0 +1,24 @@
using Avalonia.Controls;
using Gommon;
using Ryujinx.Ava.UI.ViewModels;
using System;
namespace Ryujinx.Ava.UI.Controls
{
public class RyujinxControl<TViewModel> : UserControl where TViewModel : BaseModel
{
public TViewModel ViewModel
{
get
{
if (DataContext is not TViewModel viewModel)
throw new InvalidOperationException(
$"Underlying DataContext is not of type {typeof(TViewModel).AsPrettyString()}; " +
$"Actual type is {DataContext?.GetType().AsPrettyString()}");
return viewModel;
}
set => DataContext = value;
}
}
}

View File

@ -4,6 +4,7 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Threading; using Avalonia.Threading;
using FluentAvalonia.Core; using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
@ -21,6 +22,23 @@ namespace Ryujinx.Ava.UI.Helpers
private static bool _isChoiceDialogOpen; private static bool _isChoiceDialogOpen;
private static ContentDialogOverlayWindow _contentDialogOverlayWindow; private static ContentDialogOverlayWindow _contentDialogOverlayWindow;
public static ContentDialog ApplyStyles(
this ContentDialog contentDialog,
double closeButtonWidth = 80,
HorizontalAlignment buttonSpaceAlignment = HorizontalAlignment.Right)
{
Style closeButton = new(x => x.Name("CloseButton"));
closeButton.Setters.Add(new Setter(Layoutable.WidthProperty, closeButtonWidth));
Style closeButtonParent = new(x => x.Name("CommandSpace"));
closeButtonParent.Setters.Add(new Setter(Layoutable.HorizontalAlignmentProperty, buttonSpaceAlignment));
contentDialog.Styles.Add(closeButton);
contentDialog.Styles.Add(closeButtonParent);
return contentDialog;
}
private async static Task<UserResult> ShowContentDialog( private async static Task<UserResult> ShowContentDialog(
string title, string title,
object content, object content,
@ -40,19 +58,19 @@ namespace Ryujinx.Ava.UI.Helpers
SecondaryButtonText = secondaryButton, SecondaryButtonText = secondaryButton,
CloseButtonText = closeButton, CloseButtonText = closeButton,
Content = content, Content = content,
PrimaryButtonCommand = MiniCommand.Create(() => PrimaryButtonCommand = Commands.Create(() =>
{ {
result = primaryButtonResult; result = primaryButtonResult;
}) })
}; };
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() => contentDialog.SecondaryButtonCommand = Commands.Create(() =>
{ {
result = UserResult.No; result = UserResult.No;
contentDialog.PrimaryButtonClick -= deferCloseAction; contentDialog.PrimaryButtonClick -= deferCloseAction;
}); });
contentDialog.CloseButtonCommand = MiniCommand.Create(() => contentDialog.CloseButtonCommand = Commands.Create(() =>
{ {
result = UserResult.Cancel; result = UserResult.Cancel;
contentDialog.PrimaryButtonClick -= deferCloseAction; contentDialog.PrimaryButtonClick -= deferCloseAction;

View File

@ -1,72 +0,0 @@
using System;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Ryujinx.Ava.UI.Helpers
{
public sealed class MiniCommand<T> : MiniCommand, ICommand
{
private readonly Action<T> _callback;
private bool _busy;
private readonly Func<T, Task> _asyncCallback;
public MiniCommand(Action<T> callback)
{
_callback = callback;
}
public MiniCommand(Func<T, Task> callback)
{
_asyncCallback = callback;
}
private bool Busy
{
get => _busy;
set
{
_busy = value;
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
public override event EventHandler CanExecuteChanged;
public override bool CanExecute(object parameter) => !_busy;
public override async void Execute(object parameter)
{
if (Busy)
{
return;
}
try
{
Busy = true;
if (_callback != null)
{
_callback((T)parameter);
}
else
{
await _asyncCallback((T)parameter);
}
}
finally
{
Busy = false;
}
}
}
public abstract class MiniCommand : ICommand
{
public static MiniCommand Create(Action callback) => new MiniCommand<object>(_ => callback());
public static MiniCommand Create<TArg>(Action<TArg> callback) => new MiniCommand<TArg>(callback);
public static MiniCommand CreateFromTask(Func<Task> callback) => new MiniCommand<object>(_ => callback());
public static MiniCommand CreateFromTask<TArg>(Func<TArg, Task> callback) => new MiniCommand<TArg>(callback);
public abstract bool CanExecute(object parameter);
public abstract void Execute(object parameter);
public abstract event EventHandler CanExecuteChanged;
}
}

View File

@ -0,0 +1,260 @@
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Input;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Models.Input
{
public class StickVisualizer : BaseModel, IDisposable
{
public const int DrawStickPollRate = 50; // Milliseconds per poll.
public const int DrawStickCircumference = 5;
public const float DrawStickScaleFactor = DrawStickCanvasCenter;
public const int DrawStickCanvasSize = 100;
public const int DrawStickBorderSize = DrawStickCanvasSize + 5;
public const float DrawStickCanvasCenter = (DrawStickCanvasSize - DrawStickCircumference) / 2;
public const float MaxVectorLength = DrawStickCanvasSize / 2;
public CancellationTokenSource PollTokenSource;
public CancellationToken PollToken;
private static float _vectorLength;
private static float _vectorMultiplier;
private bool disposedValue;
private DeviceType _type;
public DeviceType Type
{
get => _type;
set
{
_type = value;
OnPropertyChanged();
}
}
private GamepadInputConfig _gamepadConfig;
public GamepadInputConfig GamepadConfig
{
get => _gamepadConfig;
set
{
_gamepadConfig = value;
OnPropertyChanged();
}
}
private KeyboardInputConfig _keyboardConfig;
public KeyboardInputConfig KeyboardConfig
{
get => _keyboardConfig;
set
{
_keyboardConfig = value;
OnPropertyChanged();
}
}
private (float, float) _uiStickLeft;
public (float, float) UiStickLeft
{
get => (_uiStickLeft.Item1 * DrawStickScaleFactor, _uiStickLeft.Item2 * DrawStickScaleFactor);
set
{
_uiStickLeft = value;
OnPropertyChanged();
OnPropertyChanged(nameof(UiStickRightX));
OnPropertyChanged(nameof(UiStickRightY));
OnPropertyChanged(nameof(UiDeadzoneRight));
}
}
private (float, float) _uiStickRight;
public (float, float) UiStickRight
{
get => (_uiStickRight.Item1 * DrawStickScaleFactor, _uiStickRight.Item2 * DrawStickScaleFactor);
set
{
_uiStickRight = value;
OnPropertyChanged();
OnPropertyChanged(nameof(UiStickLeftX));
OnPropertyChanged(nameof(UiStickLeftY));
OnPropertyChanged(nameof(UiDeadzoneLeft));
}
}
public float UiStickLeftX => ClampVector(UiStickLeft).Item1;
public float UiStickLeftY => ClampVector(UiStickLeft).Item2;
public float UiStickRightX => ClampVector(UiStickRight).Item1;
public float UiStickRightY => ClampVector(UiStickRight).Item2;
public int UiStickCircumference => DrawStickCircumference;
public int UiCanvasSize => DrawStickCanvasSize;
public int UiStickBorderSize => DrawStickBorderSize;
public float? UiDeadzoneLeft => _gamepadConfig?.DeadzoneLeft * DrawStickCanvasSize - DrawStickCircumference;
public float? UiDeadzoneRight => _gamepadConfig?.DeadzoneRight * DrawStickCanvasSize - DrawStickCircumference;
private InputViewModel Parent;
public StickVisualizer(InputViewModel parent)
{
Parent = parent;
PollTokenSource = new CancellationTokenSource();
PollToken = PollTokenSource.Token;
Task.Run(Initialize, PollToken);
}
public void UpdateConfig(object config)
{
if (config is ControllerInputViewModel padConfig)
{
GamepadConfig = padConfig.Config;
Type = DeviceType.Controller;
return;
}
else if (config is KeyboardInputViewModel keyConfig)
{
KeyboardConfig = keyConfig.Config;
Type = DeviceType.Keyboard;
return;
}
Type = DeviceType.None;
}
public async Task Initialize()
{
(float, float) leftBuffer;
(float, float) rightBuffer;
while (!PollToken.IsCancellationRequested)
{
leftBuffer = (0f, 0f);
rightBuffer = (0f, 0f);
switch (Type)
{
case DeviceType.Keyboard:
IKeyboard keyboard = (IKeyboard)Parent.AvaloniaKeyboardDriver.GetGamepad("0");
if (keyboard != null)
{
KeyboardStateSnapshot snapshot = keyboard.GetKeyboardStateSnapshot();
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickRight))
{
leftBuffer.Item1 += 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickLeft))
{
leftBuffer.Item1 -= 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickUp))
{
leftBuffer.Item2 += 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickDown))
{
leftBuffer.Item2 -= 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickRight))
{
rightBuffer.Item1 += 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickLeft))
{
rightBuffer.Item1 -= 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickUp))
{
rightBuffer.Item2 += 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickDown))
{
rightBuffer.Item2 -= 1;
}
UiStickLeft = leftBuffer;
UiStickRight = rightBuffer;
}
break;
case DeviceType.Controller:
IGamepad controller = Parent.SelectedGamepad;
if (controller != null)
{
leftBuffer = controller.GetStick((StickInputId)GamepadConfig.LeftJoystick);
rightBuffer = controller.GetStick((StickInputId)GamepadConfig.RightJoystick);
}
break;
case DeviceType.None:
break;
default:
throw new ArgumentException($"Unable to poll device type \"{Type}\"");
}
UiStickLeft = leftBuffer;
UiStickRight = rightBuffer;
await Task.Delay(DrawStickPollRate, PollToken);
}
PollTokenSource.Dispose();
}
public static (float, float) ClampVector((float, float) vect)
{
_vectorMultiplier = 1;
_vectorLength = MathF.Sqrt((vect.Item1 * vect.Item1) + (vect.Item2 * vect.Item2));
if (_vectorLength > MaxVectorLength)
{
_vectorMultiplier = MaxVectorLength / _vectorLength;
}
vect.Item1 = vect.Item1 * _vectorMultiplier + DrawStickCanvasCenter;
vect.Item2 = vect.Item2 * _vectorMultiplier + DrawStickCanvasCenter;
return vect;
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
PollTokenSource.Cancel();
}
KeyboardConfig = null;
GamepadConfig = null;
Parent = null;
disposedValue = true;
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@ -2,9 +2,7 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media; using Avalonia.Media;
using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using System; using System;
namespace Ryujinx.Ava.UI.Renderer namespace Ryujinx.Ava.UI.Renderer
@ -39,32 +37,6 @@ namespace Ryujinx.Ava.UI.Renderer
_ => throw new NotImplementedException() _ => throw new NotImplementedException()
}; };
public RendererHost(string titleId)
{
Focusable = true;
FlowDirection = FlowDirection.LeftToRight;
EmbeddedWindow =
#pragma warning disable CS8524
ConfigurationState.Instance.Graphics.GraphicsBackend.Value switch
#pragma warning restore CS8524
{
GraphicsBackend.OpenGl => new EmbeddedWindowOpenGL(),
GraphicsBackend.Vulkan => new EmbeddedWindowVulkan(),
};
string backendText = EmbeddedWindow switch
{
EmbeddedWindowVulkan => "Vulkan",
EmbeddedWindowOpenGL => "OpenGL",
_ => throw new NotImplementedException()
};
Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend ({ConfigurationState.Instance.Graphics.GraphicsBackend.Value}): {backendText}");
Initialize();
}
private void Initialize() private void Initialize()
{ {

View File

@ -1,5 +1,9 @@
using Avalonia.Svg.Skia; using Avalonia.Svg.Skia;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.Views.Input; using Ryujinx.Ava.UI.Views.Input;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
@ -10,7 +14,29 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
public partial class ControllerInputViewModel : BaseModel public partial class ControllerInputViewModel : BaseModel
{ {
[ObservableProperty] private GamepadInputConfig _config; private GamepadInputConfig _config;
public GamepadInputConfig Config
{
get => _config;
set
{
_config = value;
OnPropertyChanged();
}
}
private StickVisualizer _visualizer;
public StickVisualizer Visualizer
{
get => _visualizer;
set
{
_visualizer = value;
OnPropertyChanged();
}
}
private bool _isLeft; private bool _isLeft;
public bool IsLeft public bool IsLeft
@ -42,9 +68,10 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public InputViewModel ParentModel { get; } public InputViewModel ParentModel { get; }
public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config) public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config, StickVisualizer visualizer)
{ {
ParentModel = model; ParentModel = model;
Visualizer = visualizer;
model.NotifyChangesEvent += OnParentModelChanged; model.NotifyChangesEvent += OnParentModelChanged;
OnParentModelChanged(); OnParentModelChanged();
config.PropertyChanged += (_, args) => config.PropertyChanged += (_, args) =>

View File

@ -49,7 +49,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
private int _controller; private int _controller;
private string _controllerImage; private string _controllerImage;
private int _device; private int _device;
[ObservableProperty] private object _configViewModel; private object _configViewModel;
[ObservableProperty] private string _profileName; [ObservableProperty] private string _profileName;
private bool _isLoaded; private bool _isLoaded;
@ -74,6 +74,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
OnPropertiesChanged(nameof(HasLed), nameof(CanClearLed)); OnPropertiesChanged(nameof(HasLed), nameof(CanClearLed));
} }
} }
public StickVisualizer VisualStick { get; private set; }
public ObservableCollection<PlayerModel> PlayerIndexes { get; set; } public ObservableCollection<PlayerModel> PlayerIndexes { get; set; }
public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; } public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; }
@ -94,6 +95,19 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public bool IsModified { get; set; } public bool IsModified { get; set; }
public event Action NotifyChangesEvent; public event Action NotifyChangesEvent;
public object ConfigViewModel
{
get => _configViewModel;
set
{
_configViewModel = value;
VisualStick.UpdateConfig(value);
OnPropertyChanged();
}
}
public PlayerIndex PlayerIdChoose public PlayerIndex PlayerIdChoose
{ {
get => _playerIdChoose; get => _playerIdChoose;
@ -269,6 +283,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
Devices = []; Devices = [];
ProfilesList = []; ProfilesList = [];
DeviceList = []; DeviceList = [];
VisualStick = new StickVisualizer(this);
ControllerImage = ProControllerResource; ControllerImage = ProControllerResource;
@ -289,12 +304,12 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
if (Config is StandardKeyboardInputConfig keyboardInputConfig) if (Config is StandardKeyboardInputConfig keyboardInputConfig)
{ {
ConfigViewModel = new KeyboardInputViewModel(this, new KeyboardInputConfig(keyboardInputConfig)); ConfigViewModel = new KeyboardInputViewModel(this, new KeyboardInputConfig(keyboardInputConfig), VisualStick);
} }
if (Config is StandardControllerInputConfig controllerInputConfig) if (Config is StandardControllerInputConfig controllerInputConfig)
{ {
ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig)); ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig), VisualStick);
} }
} }
@ -893,6 +908,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
_mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates(); _mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates();
VisualStick.Dispose();
SelectedGamepad?.Dispose(); SelectedGamepad?.Dispose();
AvaloniaKeyboardDriver.Dispose(); AvaloniaKeyboardDriver.Dispose();

View File

@ -6,7 +6,29 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
public partial class KeyboardInputViewModel : BaseModel public partial class KeyboardInputViewModel : BaseModel
{ {
[ObservableProperty] private KeyboardInputConfig _config; private KeyboardInputConfig _config;
public KeyboardInputConfig Config
{
get => _config;
set
{
_config = value;
OnPropertyChanged();
}
}
private StickVisualizer _visualizer;
public StickVisualizer Visualizer
{
get => _visualizer;
set
{
_visualizer = value;
OnPropertyChanged();
}
}
private bool _isLeft; private bool _isLeft;
public bool IsLeft public bool IsLeft
@ -38,9 +60,10 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public readonly InputViewModel ParentModel; public readonly InputViewModel ParentModel;
public KeyboardInputViewModel(InputViewModel model, KeyboardInputConfig config) public KeyboardInputViewModel(InputViewModel model, KeyboardInputConfig config, StickVisualizer visualizer)
{ {
ParentModel = model; ParentModel = model;
Visualizer = visualizer;
model.NotifyChangesEvent += OnParentModelChanged; model.NotifyChangesEvent += OnParentModelChanged;
OnParentModelChanged(); OnParentModelChanged();
Config = config; Config = config;

View File

@ -21,7 +21,7 @@ using Image = SkiaSharp.SKImage;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
{ {
internal partial class UserFirmwareAvatarSelectorViewModel : BaseModel public partial class UserFirmwareAvatarSelectorViewModel : BaseModel
{ {
private static readonly Dictionary<string, byte[]> _avatarStore = new(); private static readonly Dictionary<string, byte[]> _avatarStore = new();

View File

@ -2,7 +2,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
{ {
internal partial class UserProfileImageSelectorViewModel : BaseModel public partial class UserProfileImageSelectorViewModel : BaseModel
{ {
[ObservableProperty] private bool _firmwareFound; [ObservableProperty] private bool _firmwareFound;
} }

View File

@ -34,12 +34,7 @@
<!-- Button / JoyStick Settings --> <!-- Button / JoyStick Settings -->
<Grid <Grid
Name="SettingButtons" Name="SettingButtons"
MinHeight="450"> MinHeight="450" ColumnDefinitions="Auto,*,Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Left Controls --> <!-- Left Controls -->
<StackPanel <StackPanel
Orientation="Vertical" Orientation="Vertical"
@ -54,15 +49,7 @@
CornerRadius="5"> CornerRadius="5">
<Grid <Grid
Margin="10" Margin="10"
HorizontalAlignment="Stretch"> HorizontalAlignment="Stretch" ColumnDefinitions="*,*" RowDefinitions="*,*">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel <StackPanel
Grid.Column="0" Grid.Column="0"
Grid.Row="0" Grid.Row="0"
@ -316,17 +303,99 @@
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> VerticalAlignment="Stretch">
<!-- Controller Picture --> <!-- Controller Picture -->
<Image
Margin="0,10"
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{Binding Image}" />
<Border <Border
BorderBrush="{DynamicResource ThemeControlBorderColor}" BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1" BorderThickness="1"
CornerRadius="5" CornerRadius="5"
Margin="0,0, 0, 5"
MinHeight="90"> MinHeight="90">
<StackPanel Orientation="Vertical">
<Image
Margin="5,10"
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{Binding Image}" />
<StackPanel
Margin="10"
Orientation="Horizontal"
Spacing="20"
HorizontalAlignment="Center">
<Border
BorderBrush="Transparent"
Height="{Binding Visualizer.UiStickBorderSize}"
Width="{Binding Visualizer.UiStickBorderSize}"
IsVisible="{Binding IsLeft}">
<Canvas
Background="{DynamicResource ThemeBackgroundColor}"
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}">
<Grid
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}"
Background="{DynamicResource ThemeBackgroundColor}">
<Ellipse
HorizontalAlignment="Center"
Stroke="{DynamicResource ThemeControlBorderColor}"
StrokeThickness="1"
Width="{Binding Visualizer.UiCanvasSize}"
Height="{Binding Visualizer.UiCanvasSize}" />
<Ellipse
HorizontalAlignment="Center"
Fill="Black"
Opacity="100"
Height="{Binding Visualizer.UiDeadzoneLeft}"
Width="{Binding Visualizer.UiDeadzoneLeft}" />
</Grid>
<Ellipse
Fill="{DynamicResource Warning}"
Width="{Binding Visualizer.UiStickCircumference}"
Height="{Binding Visualizer.UiStickCircumference}"
Canvas.Bottom="{Binding Visualizer.UiStickLeftY}"
Canvas.Left="{Binding Visualizer.UiStickLeftX}" />
</Canvas>
</Border>
<Border
BorderBrush="Transparent"
Height="{Binding Visualizer.UiStickBorderSize}"
Width="{Binding Visualizer.UiStickBorderSize}"
IsVisible="{Binding IsRight}">
<Canvas
Background="{DynamicResource ThemeBackgroundColor}"
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}">
<Grid
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}"
Background="{DynamicResource ThemeBackgroundColor}">
<Ellipse
HorizontalAlignment="Center"
Stroke="{DynamicResource ThemeControlBorderColor}"
StrokeThickness="1"
Width="{Binding Visualizer.UiCanvasSize}"
Height="{Binding Visualizer.UiCanvasSize}" />
<Ellipse
HorizontalAlignment="Center"
Fill="Black"
Opacity="100"
Height="{Binding Visualizer.UiDeadzoneRight}"
Width="{Binding Visualizer.UiDeadzoneRight}" />
</Grid>
<Ellipse
Fill="{DynamicResource Warning}"
Width="{Binding Visualizer.UiStickCircumference}"
Height="{Binding Visualizer.UiStickCircumference}"
Canvas.Bottom="{Binding Visualizer.UiStickRightY}"
Canvas.Left="{Binding Visualizer.UiStickRightX}" />
</Canvas>
</Border>
</StackPanel>
</StackPanel>
</Border>
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5">
<StackPanel <StackPanel
Margin="8" Margin="8"
Orientation="Vertical"> Orientation="Vertical">
@ -428,7 +497,7 @@
</Border> </Border>
<!-- Motion, Rumble, LED --> <!-- Motion, Rumble, LED -->
<StackPanel <StackPanel
Margin="0,10,0,0" Margin="0,5,0,0"
Spacing="5" Spacing="5"
Orientation="Vertical" Orientation="Vertical"
VerticalAlignment="Bottom"> VerticalAlignment="Bottom">
@ -438,11 +507,7 @@
CornerRadius="5" CornerRadius="5"
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"> HorizontalAlignment="Stretch">
<Grid> <Grid ColumnDefinitions="*,Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<CheckBox <CheckBox
Margin="10" Margin="10"
MinWidth="0" MinWidth="0"
@ -464,11 +529,7 @@
CornerRadius="5" CornerRadius="5"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Margin="0,-1,0,0"> Margin="0,-1,0,0">
<Grid> <Grid ColumnDefinitions="*,Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<CheckBox <CheckBox
Margin="10" Margin="10"
MinWidth="0" MinWidth="0"
@ -490,11 +551,7 @@
CornerRadius="5" CornerRadius="5"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Margin="0,-1,0,0"> Margin="0,-1,0,0">
<Grid IsVisible="{Binding ParentModel.HasLed}"> <Grid IsVisible="{Binding ParentModel.HasLed}" ColumnDefinitions="*,Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<CheckBox <CheckBox
Margin="10, 10, 5, 10" Margin="10, 10, 5, 10"
MinWidth="0" MinWidth="0"
@ -526,15 +583,7 @@
CornerRadius="5"> CornerRadius="5">
<Grid <Grid
Margin="10" Margin="10"
HorizontalAlignment="Stretch"> HorizontalAlignment="Stretch" ColumnDefinitions="*,*" RowDefinitions="*,*">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel <StackPanel
Grid.Column="1" Grid.Column="1"
Grid.Row="0" Grid.Row="0"

View File

@ -4,6 +4,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels.Input; using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
@ -14,7 +15,7 @@ using StickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
namespace Ryujinx.Ava.UI.Views.Input namespace Ryujinx.Ava.UI.Views.Input
{ {
public partial class ControllerInputView : UserControl public partial class ControllerInputView : RyujinxControl<ControllerInputViewModel>
{ {
private ButtonKeyAssigner _currentAssigner; private ButtonKeyAssigner _currentAssigner;
@ -217,20 +218,12 @@ namespace Ryujinx.Ava.UI.Views.Input
PointerPressed -= MouseClick; PointerPressed -= MouseClick;
} }
private IButtonAssigner CreateButtonAssigner(bool forStick) private IButtonAssigner CreateButtonAssigner(bool forStick) =>
{ new GamepadButtonAssigner(
IButtonAssigner assigner; ViewModel.ParentModel.SelectedGamepad,
(ViewModel.ParentModel.Config as StandardControllerInputConfig).TriggerThreshold,
ControllerInputViewModel controllerInputViewModel = DataContext as ControllerInputViewModel;
assigner = new GamepadButtonAssigner(
controllerInputViewModel.ParentModel.SelectedGamepad,
(controllerInputViewModel.ParentModel.Config as StandardControllerInputConfig).TriggerThreshold,
forStick); forStick);
return assigner;
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnDetachedFromVisualTree(e); base.OnDetachedFromVisualTree(e);

View File

@ -35,22 +35,13 @@
Margin="0 0 0 5" Margin="0 0 0 5"
Orientation="Vertical" Orientation="Vertical"
Spacing="5"> Spacing="5">
<Grid> <Grid ColumnDefinitions="*,10,*">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Player Selection --> <!-- Player Selection -->
<Grid <Grid
Grid.Column="0" Grid.Column="0"
Margin="2" Margin="2"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Center"> VerticalAlignment="Center" ColumnDefinitions="Auto,*">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock <TextBlock
Margin="5,0,10,0" Margin="5,0,10,0"
Width="90" Width="90"
@ -77,14 +68,7 @@
Grid.Column="2" Grid.Column="2"
Margin="2" Margin="2"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Center"> VerticalAlignment="Center" ColumnDefinitions="Auto,*,Auto,Auto,Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock <TextBlock
Margin="5,0,10,0" Margin="5,0,10,0"
Width="90" Width="90"
@ -139,22 +123,12 @@
</Grid> </Grid>
</Grid> </Grid>
<Separator /> <Separator />
<Grid> <Grid ColumnDefinitions="*,10,*">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Input Device --> <!-- Input Device -->
<Grid <Grid
Grid.Column="0" Grid.Column="0"
Margin="2" Margin="2"
HorizontalAlignment="Stretch"> HorizontalAlignment="Stretch" ColumnDefinitions="Auto,*,Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock <TextBlock
Grid.Column="0" Grid.Column="0"
Margin="5,0,10,0" Margin="5,0,10,0"
@ -186,11 +160,7 @@
Grid.Column="2" Grid.Column="2"
Margin="2" Margin="2"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Center"> VerticalAlignment="Center" ColumnDefinitions="Auto,*">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock <TextBlock
Margin="5,0,10,0" Margin="5,0,10,0"
Width="90" Width="90"

View File

@ -1,19 +1,19 @@
using Avalonia.Controls; using Avalonia.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels.Input; using Ryujinx.Ava.UI.ViewModels.Input;
namespace Ryujinx.Ava.UI.Views.Input namespace Ryujinx.Ava.UI.Views.Input
{ {
public partial class InputView : UserControl public partial class InputView : RyujinxControl<InputViewModel>
{ {
private bool _dialogOpen; private bool _dialogOpen;
private InputViewModel ViewModel { get; set; }
public InputView() public InputView()
{ {
DataContext = ViewModel = new InputViewModel(this); ViewModel = new InputViewModel(this);
InitializeComponent(); InitializeComponent();
} }

View File

@ -32,12 +32,7 @@
<!-- Button / JoyStick Settings --> <!-- Button / JoyStick Settings -->
<Grid <Grid
Name="SettingButtons" Name="SettingButtons"
MinHeight="450"> MinHeight="450" ColumnDefinitions="Auto,*,Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Left Controls --> <!-- Left Controls -->
<StackPanel <StackPanel
Orientation="Vertical" Orientation="Vertical"
@ -52,15 +47,7 @@
CornerRadius="5"> CornerRadius="5">
<Grid <Grid
Margin="10" Margin="10"
HorizontalAlignment="Stretch"> HorizontalAlignment="Stretch" ColumnDefinitions="*,*" RowDefinitions="*,*">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel <StackPanel
Grid.Column="0" Grid.Column="0"
Grid.Row="0" Grid.Row="0"
@ -309,12 +296,79 @@
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> VerticalAlignment="Stretch">
<!-- Controller Picture --> <!-- Controller Picture -->
<Image <Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
Margin="0,10" Margin="0,10"
MaxHeight="300" MinHeight="90">
HorizontalAlignment="Stretch" <StackPanel
VerticalAlignment="Stretch" Margin="10"
Source="{Binding Image}" /> Orientation="Horizontal"
Spacing="20"
HorizontalAlignment="Center">
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
Height="{Binding Visualizer.UiStickBorderSize}"
Width="{Binding Visualizer.UiStickBorderSize}"
IsVisible="{Binding IsLeft}">
<Canvas
Background="{DynamicResource ThemeBackgroundColor}"
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}">
<Grid
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}"
Background="{DynamicResource ThemeBackgroundColor}">
<Ellipse
HorizontalAlignment="Center"
Stroke="Black"
StrokeThickness="1"
Width="{Binding Visualizer.UiCanvasSize}"
Height="{Binding Visualizer.UiCanvasSize}"/>
</Grid>
<Ellipse
Fill="Red"
Width="{Binding Visualizer.UiStickCircumference}"
Height="{Binding Visualizer.UiStickCircumference}"
Canvas.Bottom="{Binding Visualizer.UiStickLeftY}"
Canvas.Left="{Binding Visualizer.UiStickLeftX}" />
</Canvas>
</Border>
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
Height="{Binding Visualizer.UiStickBorderSize}"
Width="{Binding Visualizer.UiStickBorderSize}"
IsVisible="{Binding IsRight}">
<Canvas
Background="{DynamicResource ThemeBackgroundColor}"
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}">
<Grid
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}"
Background="{DynamicResource ThemeBackgroundColor}">
<Ellipse
HorizontalAlignment="Center"
Stroke="Black"
StrokeThickness="1"
Width="{Binding Visualizer.UiCanvasSize}"
Height="{Binding Visualizer.UiCanvasSize}"/>
</Grid>
<Ellipse
Fill="Red"
Width="{Binding Visualizer.UiStickCircumference}"
Height="{Binding Visualizer.UiStickCircumference}"
Canvas.Bottom="{Binding Visualizer.UiStickRightY}"
Canvas.Left="{Binding Visualizer.UiStickRightX}" />
</Canvas>
</Border>
</StackPanel>
</Border>
<Border <Border
BorderBrush="{DynamicResource ThemeControlBorderColor}" BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1" BorderThickness="1"
@ -413,15 +467,7 @@
CornerRadius="5"> CornerRadius="5">
<Grid <Grid
Margin="10" Margin="10"
HorizontalAlignment="Stretch"> HorizontalAlignment="Stretch" ColumnDefinitions="*,*" RowDefinitions="*,*">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel <StackPanel
Grid.Column="1" Grid.Column="1"
Grid.Row="0" Grid.Row="0"

View File

@ -4,6 +4,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels.Input; using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Input; using Ryujinx.Input;
@ -13,7 +14,7 @@ using Key = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Ava.UI.Views.Input namespace Ryujinx.Ava.UI.Views.Input
{ {
public partial class KeyboardInputView : UserControl public partial class KeyboardInputView : RyujinxControl<KeyboardInputViewModel>
{ {
private ButtonKeyAssigner _currentAssigner; private ButtonKeyAssigner _currentAssigner;
@ -60,106 +61,103 @@ namespace Ryujinx.Ava.UI.Views.Input
PointerPressed += MouseClick; PointerPressed += MouseClick;
if (DataContext is not KeyboardInputViewModel viewModel)
return;
IKeyboard keyboard = IKeyboard keyboard =
(IKeyboard)viewModel.ParentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations. (IKeyboard)ViewModel.ParentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
IButtonAssigner assigner = IButtonAssigner assigner =
new KeyboardKeyAssigner((IKeyboard)viewModel.ParentModel.SelectedGamepad); new KeyboardKeyAssigner((IKeyboard)ViewModel.ParentModel.SelectedGamepad);
_currentAssigner.ButtonAssigned += (_, e) => _currentAssigner.ButtonAssigned += (_, be) =>
{ {
if (e.ButtonValue.HasValue) if (be.ButtonValue.HasValue)
{ {
Button buttonValue = e.ButtonValue.Value; Button buttonValue = be.ButtonValue.Value;
viewModel.ParentModel.IsModified = true; ViewModel.ParentModel.IsModified = true;
switch (button.Name) switch (button.Name)
{ {
case "ButtonZl": case "ButtonZl":
viewModel.Config.ButtonZl = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonZl = buttonValue.AsHidType<Key>();
break; break;
case "ButtonL": case "ButtonL":
viewModel.Config.ButtonL = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonL = buttonValue.AsHidType<Key>();
break; break;
case "ButtonMinus": case "ButtonMinus":
viewModel.Config.ButtonMinus = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonMinus = buttonValue.AsHidType<Key>();
break; break;
case "LeftStickButton": case "LeftStickButton":
viewModel.Config.LeftStickButton = buttonValue.AsHidType<Key>(); ViewModel.Config.LeftStickButton = buttonValue.AsHidType<Key>();
break; break;
case "LeftStickUp": case "LeftStickUp":
viewModel.Config.LeftStickUp = buttonValue.AsHidType<Key>(); ViewModel.Config.LeftStickUp = buttonValue.AsHidType<Key>();
break; break;
case "LeftStickDown": case "LeftStickDown":
viewModel.Config.LeftStickDown = buttonValue.AsHidType<Key>(); ViewModel.Config.LeftStickDown = buttonValue.AsHidType<Key>();
break; break;
case "LeftStickRight": case "LeftStickRight":
viewModel.Config.LeftStickRight = buttonValue.AsHidType<Key>(); ViewModel.Config.LeftStickRight = buttonValue.AsHidType<Key>();
break; break;
case "LeftStickLeft": case "LeftStickLeft":
viewModel.Config.LeftStickLeft = buttonValue.AsHidType<Key>(); ViewModel.Config.LeftStickLeft = buttonValue.AsHidType<Key>();
break; break;
case "DpadUp": case "DpadUp":
viewModel.Config.DpadUp = buttonValue.AsHidType<Key>(); ViewModel.Config.DpadUp = buttonValue.AsHidType<Key>();
break; break;
case "DpadDown": case "DpadDown":
viewModel.Config.DpadDown = buttonValue.AsHidType<Key>(); ViewModel.Config.DpadDown = buttonValue.AsHidType<Key>();
break; break;
case "DpadLeft": case "DpadLeft":
viewModel.Config.DpadLeft = buttonValue.AsHidType<Key>(); ViewModel.Config.DpadLeft = buttonValue.AsHidType<Key>();
break; break;
case "DpadRight": case "DpadRight":
viewModel.Config.DpadRight = buttonValue.AsHidType<Key>(); ViewModel.Config.DpadRight = buttonValue.AsHidType<Key>();
break; break;
case "LeftButtonSr": case "LeftButtonSr":
viewModel.Config.LeftButtonSr = buttonValue.AsHidType<Key>(); ViewModel.Config.LeftButtonSr = buttonValue.AsHidType<Key>();
break; break;
case "LeftButtonSl": case "LeftButtonSl":
viewModel.Config.LeftButtonSl = buttonValue.AsHidType<Key>(); ViewModel.Config.LeftButtonSl = buttonValue.AsHidType<Key>();
break; break;
case "RightButtonSr": case "RightButtonSr":
viewModel.Config.RightButtonSr = buttonValue.AsHidType<Key>(); ViewModel.Config.RightButtonSr = buttonValue.AsHidType<Key>();
break; break;
case "RightButtonSl": case "RightButtonSl":
viewModel.Config.RightButtonSl = buttonValue.AsHidType<Key>(); ViewModel.Config.RightButtonSl = buttonValue.AsHidType<Key>();
break; break;
case "ButtonZr": case "ButtonZr":
viewModel.Config.ButtonZr = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonZr = buttonValue.AsHidType<Key>();
break; break;
case "ButtonR": case "ButtonR":
viewModel.Config.ButtonR = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonR = buttonValue.AsHidType<Key>();
break; break;
case "ButtonPlus": case "ButtonPlus":
viewModel.Config.ButtonPlus = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonPlus = buttonValue.AsHidType<Key>();
break; break;
case "ButtonA": case "ButtonA":
viewModel.Config.ButtonA = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonA = buttonValue.AsHidType<Key>();
break; break;
case "ButtonB": case "ButtonB":
viewModel.Config.ButtonB = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonB = buttonValue.AsHidType<Key>();
break; break;
case "ButtonX": case "ButtonX":
viewModel.Config.ButtonX = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonX = buttonValue.AsHidType<Key>();
break; break;
case "ButtonY": case "ButtonY":
viewModel.Config.ButtonY = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonY = buttonValue.AsHidType<Key>();
break; break;
case "RightStickButton": case "RightStickButton":
viewModel.Config.RightStickButton = buttonValue.AsHidType<Key>(); ViewModel.Config.RightStickButton = buttonValue.AsHidType<Key>();
break; break;
case "RightStickUp": case "RightStickUp":
viewModel.Config.RightStickUp = buttonValue.AsHidType<Key>(); ViewModel.Config.RightStickUp = buttonValue.AsHidType<Key>();
break; break;
case "RightStickDown": case "RightStickDown":
viewModel.Config.RightStickDown = buttonValue.AsHidType<Key>(); ViewModel.Config.RightStickDown = buttonValue.AsHidType<Key>();
break; break;
case "RightStickRight": case "RightStickRight":
viewModel.Config.RightStickRight = buttonValue.AsHidType<Key>(); ViewModel.Config.RightStickRight = buttonValue.AsHidType<Key>();
break; break;
case "RightStickLeft": case "RightStickLeft":
viewModel.Config.RightStickLeft = buttonValue.AsHidType<Key>(); ViewModel.Config.RightStickLeft = buttonValue.AsHidType<Key>();
break; break;
} }
} }

View File

@ -2,19 +2,18 @@
using Avalonia.Controls; using Avalonia.Controls;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.ViewModels.Input; using Ryujinx.Ava.UI.ViewModels.Input;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ryujinx.UI.Views.Input namespace Ryujinx.UI.Views.Input
{ {
public partial class LedInputView : UserControl public partial class LedInputView : RyujinxControl<LedInputViewModel>
{ {
private readonly LedInputViewModel _viewModel;
public LedInputView(ControllerInputViewModel viewModel) public LedInputView(ControllerInputViewModel viewModel)
{ {
DataContext = _viewModel = new LedInputViewModel ViewModel = new LedInputViewModel
{ {
ParentModel = viewModel.ParentModel, ParentModel = viewModel.ParentModel,
TurnOffLed = viewModel.Config.TurnOffLed, TurnOffLed = viewModel.Config.TurnOffLed,
@ -29,20 +28,18 @@ namespace Ryujinx.UI.Views.Input
private void ColorPickerButton_OnColorChanged(ColorPickerButton sender, ColorButtonColorChangedEventArgs args) private void ColorPickerButton_OnColorChanged(ColorPickerButton sender, ColorButtonColorChangedEventArgs args)
{ {
if (!args.NewColor.HasValue) return; if (!args.NewColor.HasValue) return;
if (DataContext is not LedInputViewModel lvm) return; if (!ViewModel.EnableLedChanging) return;
if (!lvm.EnableLedChanging) return; if (ViewModel.TurnOffLed) return;
if (lvm.TurnOffLed) return;
lvm.ParentModel.SelectedGamepad.SetLed(args.NewColor.Value.ToUInt32()); ViewModel.ParentModel.SelectedGamepad.SetLed(args.NewColor.Value.ToUInt32());
} }
private void ColorPickerButton_OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) private void ColorPickerButton_OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{ {
if (DataContext is not LedInputViewModel lvm) return; if (!ViewModel.EnableLedChanging) return;
if (!lvm.EnableLedChanging) return; if (ViewModel.TurnOffLed) return;
if (lvm.TurnOffLed) return;
lvm.ParentModel.SelectedGamepad.SetLed(lvm.LedColor.ToUInt32()); ViewModel.ParentModel.SelectedGamepad.SetLed(ViewModel.LedColor.ToUInt32());
} }
public static async Task Show(ControllerInputViewModel viewModel) public static async Task Show(ControllerInputViewModel viewModel)
@ -57,13 +54,13 @@ namespace Ryujinx.UI.Views.Input
CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose], CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose],
Content = content, Content = content,
}; };
contentDialog.PrimaryButtonClick += (sender, args) => contentDialog.PrimaryButtonClick += (_, _) =>
{ {
GamepadInputConfig config = viewModel.Config; GamepadInputConfig config = viewModel.Config;
config.EnableLedChanging = content._viewModel.EnableLedChanging; config.EnableLedChanging = content.ViewModel.EnableLedChanging;
config.LedColor = content._viewModel.LedColor; config.LedColor = content.ViewModel.LedColor;
config.UseRainbowLed = content._viewModel.UseRainbowLed; config.UseRainbowLed = content.ViewModel.UseRainbowLed;
config.TurnOffLed = content._viewModel.TurnOffLed; config.TurnOffLed = content.ViewModel.TurnOffLed;
}; };
await contentDialog.ShowAsync(); await contentDialog.ShowAsync();

View File

@ -11,11 +11,7 @@
x:Class="Ryujinx.Ava.UI.Views.Input.MotionInputView" x:Class="Ryujinx.Ava.UI.Views.Input.MotionInputView"
x:DataType="viewModels:MotionInputViewModel" x:DataType="viewModels:MotionInputViewModel"
Focusable="True"> Focusable="True">
<Grid Margin="10"> <Grid Margin="10" RowDefinitions="Auto,*">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<StackPanel <StackPanel
Orientation="Horizontal" Orientation="Horizontal"
@ -80,11 +76,7 @@
BorderThickness="1" BorderThickness="1"
CornerRadius="5" CornerRadius="5"
HorizontalAlignment="Stretch"> HorizontalAlignment="Stretch">
<Grid VerticalAlignment="Top"> <Grid VerticalAlignment="Top" RowDefinitions="Auto,*">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel <StackPanel
Grid.Row="1" Grid.Row="1"
HorizontalAlignment="Center" HorizontalAlignment="Center"
@ -118,15 +110,7 @@
Text="{Binding DsuServerPort, Mode=TwoWay}" /> Text="{Binding DsuServerPort, Mode=TwoWay}" />
</StackPanel> </StackPanel>
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<Grid> <Grid RowDefinitions="*,*" ColumnDefinitions="*,*">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock <TextBlock
Margin="0,10,0,0" Margin="0,10,0,0"
VerticalAlignment="Center" VerticalAlignment="Center"

View File

@ -1,16 +1,15 @@
using Avalonia.Controls; using Avalonia.Controls;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.ViewModels.Input; using Ryujinx.Ava.UI.ViewModels.Input;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Views.Input namespace Ryujinx.Ava.UI.Views.Input
{ {
public partial class MotionInputView : UserControl public partial class MotionInputView : RyujinxControl<MotionInputViewModel>
{ {
private readonly MotionInputViewModel _viewModel;
public MotionInputView() public MotionInputView()
{ {
InitializeComponent(); InitializeComponent();
@ -20,7 +19,7 @@ namespace Ryujinx.Ava.UI.Views.Input
{ {
GamepadInputConfig config = viewModel.Config; GamepadInputConfig config = viewModel.Config;
_viewModel = new MotionInputViewModel ViewModel = new MotionInputViewModel
{ {
Slot = config.Slot, Slot = config.Slot,
AltSlot = config.AltSlot, AltSlot = config.AltSlot,
@ -33,7 +32,6 @@ namespace Ryujinx.Ava.UI.Views.Input
}; };
InitializeComponent(); InitializeComponent();
DataContext = _viewModel;
} }
public static async Task Show(ControllerInputViewModel viewModel) public static async Task Show(ControllerInputViewModel viewModel)
@ -48,17 +46,17 @@ namespace Ryujinx.Ava.UI.Views.Input
CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose], CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose],
Content = content, Content = content,
}; };
contentDialog.PrimaryButtonClick += (sender, args) => contentDialog.PrimaryButtonClick += (_, _) =>
{ {
GamepadInputConfig config = viewModel.Config; GamepadInputConfig config = viewModel.Config;
config.Slot = content._viewModel.Slot; config.Slot = content.ViewModel.Slot;
config.Sensitivity = content._viewModel.Sensitivity; config.Sensitivity = content.ViewModel.Sensitivity;
config.GyroDeadzone = content._viewModel.GyroDeadzone; config.GyroDeadzone = content.ViewModel.GyroDeadzone;
config.AltSlot = content._viewModel.AltSlot; config.AltSlot = content.ViewModel.AltSlot;
config.DsuServerHost = content._viewModel.DsuServerHost; config.DsuServerHost = content.ViewModel.DsuServerHost;
config.DsuServerPort = content._viewModel.DsuServerPort; config.DsuServerPort = content.ViewModel.DsuServerPort;
config.EnableCemuHookMotion = content._viewModel.EnableCemuHookMotion; config.EnableCemuHookMotion = content.ViewModel.EnableCemuHookMotion;
config.MirrorInput = content._viewModel.MirrorInput; config.MirrorInput = content.ViewModel.MirrorInput;
}; };
await contentDialog.ShowAsync(); await contentDialog.ShowAsync();

View File

@ -10,11 +10,7 @@
x:Class="Ryujinx.Ava.UI.Views.Input.RumbleInputView" x:Class="Ryujinx.Ava.UI.Views.Input.RumbleInputView"
x:DataType="viewModels:RumbleInputViewModel" x:DataType="viewModels:RumbleInputViewModel"
Focusable="True"> Focusable="True">
<Grid Margin="10"> <Grid Margin="10" RowDefinitions="Auto,*">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock <TextBlock

View File

@ -1,16 +1,15 @@
using Avalonia.Controls; using Avalonia.Controls;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.ViewModels.Input; using Ryujinx.Ava.UI.ViewModels.Input;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Views.Input namespace Ryujinx.Ava.UI.Views.Input
{ {
public partial class RumbleInputView : UserControl public partial class RumbleInputView : RyujinxControl<RumbleInputViewModel>
{ {
private readonly RumbleInputViewModel _viewModel;
public RumbleInputView() public RumbleInputView()
{ {
InitializeComponent(); InitializeComponent();
@ -20,15 +19,13 @@ namespace Ryujinx.Ava.UI.Views.Input
{ {
GamepadInputConfig config = viewModel.Config; GamepadInputConfig config = viewModel.Config;
_viewModel = new RumbleInputViewModel ViewModel = new RumbleInputViewModel
{ {
StrongRumble = config.StrongRumble, StrongRumble = config.StrongRumble,
WeakRumble = config.WeakRumble, WeakRumble = config.WeakRumble,
}; };
InitializeComponent(); InitializeComponent();
DataContext = _viewModel;
} }
public static async Task Show(ControllerInputViewModel viewModel) public static async Task Show(ControllerInputViewModel viewModel)
@ -44,11 +41,11 @@ namespace Ryujinx.Ava.UI.Views.Input
Content = content, Content = content,
}; };
contentDialog.PrimaryButtonClick += (sender, args) => contentDialog.PrimaryButtonClick += (_, _) =>
{ {
GamepadInputConfig config = viewModel.Config; GamepadInputConfig config = viewModel.Config;
config.StrongRumble = content._viewModel.StrongRumble; config.StrongRumble = content.ViewModel.StrongRumble;
config.WeakRumble = content._viewModel.WeakRumble; config.WeakRumble = content.ViewModel.WeakRumble;
}; };
await contentDialog.ShowAsync(); await contentDialog.ShowAsync();

View File

@ -6,6 +6,7 @@ using Gommon;
using LibHac.Common; using LibHac.Common;
using LibHac.Ns; using LibHac.Ns;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
@ -25,10 +26,9 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Views.Main namespace Ryujinx.Ava.UI.Views.Main
{ {
public partial class MainMenuBarView : UserControl public partial class MainMenuBarView : RyujinxControl<MainWindowViewModel>
{ {
public MainWindow Window { get; private set; } public MainWindow Window { get; private set; }
public MainWindowViewModel ViewModel { get; private set; }
public MainMenuBarView() public MainMenuBarView()
{ {
@ -73,7 +73,7 @@ namespace Ryujinx.Ava.UI.Views.Main
{ {
Content = $".{it.FileName}", Content = $".{it.FileName}",
IsChecked = it.FileType.GetConfigValue(ConfigurationState.Instance.UI.ShownFileTypes), IsChecked = it.FileType.GetConfigValue(ConfigurationState.Instance.UI.ShownFileTypes),
Command = MiniCommand.Create(() => Window.ToggleFileType(it.FileName)) Command = Commands.Create(() => Window.ToggleFileType(it.FileName))
} }
); );
@ -108,7 +108,7 @@ namespace Ryujinx.Ava.UI.Views.Main
Margin = new Thickness(3, 0, 3, 0), Margin = new Thickness(3, 0, 3, 0),
HorizontalAlignment = HorizontalAlignment.Stretch, HorizontalAlignment = HorizontalAlignment.Stretch,
Header = languageName, Header = languageName,
Command = MiniCommand.Create(() => MainWindowViewModel.ChangeLanguage(language)) Command = Commands.Create(() => MainWindowViewModel.ChangeLanguage(language))
}; };
yield return menuItem; yield return menuItem;

View File

@ -4,6 +4,8 @@ using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Threading; using Avalonia.Threading;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common; using Ryujinx.Common;
@ -13,7 +15,7 @@ using System;
namespace Ryujinx.Ava.UI.Views.Main namespace Ryujinx.Ava.UI.Views.Main
{ {
public partial class MainStatusBarView : UserControl public partial class MainStatusBarView : RyujinxControl<MainWindowViewModel>
{ {
public MainWindow Window; public MainWindow Window;
@ -29,7 +31,7 @@ namespace Ryujinx.Ava.UI.Views.Main
if (VisualRoot is MainWindow window) if (VisualRoot is MainWindow window)
{ {
Window = window; Window = window;
DataContext = window.ViewModel; ViewModel = window.ViewModel;
LocaleManager.Instance.LocaleChanged += () => Dispatcher.UIThread.Post(() => LocaleManager.Instance.LocaleChanged += () => Dispatcher.UIThread.Post(() =>
{ {
if (Window.ViewModel.EnableNonGameRunningControls) if (Window.ViewModel.EnableNonGameRunningControls)

View File

@ -3,16 +3,15 @@ using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Ryujinx.Ava.Common; using Ryujinx.Ava.Common;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using System; using System;
namespace Ryujinx.Ava.UI.Views.Main namespace Ryujinx.Ava.UI.Views.Main
{ {
public partial class MainViewControls : UserControl public partial class MainViewControls : RyujinxControl<MainWindowViewModel>
{ {
public MainWindowViewModel ViewModel;
public MainViewControls() public MainViewControls()
{ {
InitializeComponent(); InitializeComponent();
@ -24,7 +23,7 @@ namespace Ryujinx.Ava.UI.Views.Main
if (VisualRoot is MainWindow window) if (VisualRoot is MainWindow window)
{ {
DataContext = ViewModel = window.ViewModel; ViewModel = window.ViewModel;
} }
} }

View File

@ -7,7 +7,7 @@
xmlns:ui="using:FluentAvalonia.UI.Controls" xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.UI.Controls.ApplicationDataView" x:Class="Ryujinx.Ava.UI.Views.Misc.ApplicationDataView"
x:DataType="viewModels:ApplicationDataViewModel"> x:DataType="viewModels:ApplicationDataViewModel">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<Image Margin="0" <Image Margin="0"

View File

@ -1,9 +1,11 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Styling; using Avalonia.Styling;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
@ -12,9 +14,9 @@ using Ryujinx.Ava.Utilities.Compat;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Controls namespace Ryujinx.Ava.UI.Views.Misc
{ {
public partial class ApplicationDataView : UserControl public partial class ApplicationDataView : RyujinxControl<ApplicationDataViewModel>
{ {
public static async Task Show(ApplicationData appData) public static async Task Show(ApplicationData appData)
{ {
@ -25,20 +27,10 @@ namespace Ryujinx.Ava.UI.Controls
SecondaryButtonText = string.Empty, SecondaryButtonText = string.Empty,
CloseButtonText = LocaleManager.Instance[LocaleKeys.SettingsButtonClose], CloseButtonText = LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
MinWidth = 256, MinWidth = 256,
Content = new ApplicationDataView { DataContext = new ApplicationDataViewModel(appData) } Content = new ApplicationDataView { ViewModel = new ApplicationDataViewModel(appData) }
}; };
Style closeButton = new(x => x.Name("CloseButton")); await ContentDialogHelper.ShowAsync(contentDialog.ApplyStyles(160, HorizontalAlignment.Center));
closeButton.Setters.Add(new Setter(WidthProperty, 160d));
Style closeButtonParent = new(x => x.Name("CommandSpace"));
closeButtonParent.Setters.Add(new Setter(HorizontalAlignmentProperty,
Avalonia.Layout.HorizontalAlignment.Center));
contentDialog.Styles.Add(closeButton);
contentDialog.Styles.Add(closeButtonParent);
await ContentDialogHelper.ShowAsync(contentDialog);
} }
public ApplicationDataView() public ApplicationDataView()

View File

@ -1,5 +1,5 @@
<UserControl <UserControl
x:Class="Ryujinx.Ava.UI.Controls.ApplicationGridView" x:Class="Ryujinx.Ava.UI.Views.Misc.ApplicationGridView"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
@ -14,10 +14,7 @@
mc:Ignorable="d" mc:Ignorable="d"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
x:DataType="viewModels:MainWindowViewModel"> x:DataType="viewModels:MainWindowViewModel">
<Grid> <Grid RowDefinitions="*">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListBox <ListBox
Grid.Row="0" Grid.Row="0"
Padding="8" Padding="8"
@ -57,11 +54,7 @@
Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}" Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}"
ClipToBounds="True" ClipToBounds="True"
CornerRadius="4"> CornerRadius="4">
<Grid> <Grid RowDefinitions="Auto,Auto">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image <Image
Grid.Row="0" Grid.Row="0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"

View File

@ -1,13 +1,15 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.AppLibrary;
using System; using System;
namespace Ryujinx.Ava.UI.Controls namespace Ryujinx.Ava.UI.Views.Misc
{ {
public partial class ApplicationGridView : UserControl public partial class ApplicationGridView : RyujinxControl<MainWindowViewModel>
{ {
public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent = public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent =
RoutedEvent.Register<ApplicationGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble); RoutedEvent.Register<ApplicationGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble);

View File

@ -1,5 +1,5 @@
<UserControl <UserControl
x:Class="Ryujinx.Ava.UI.Controls.ApplicationListView" x:Class="Ryujinx.Ava.UI.Views.Misc.ApplicationListView"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@ -13,10 +13,7 @@
mc:Ignorable="d" mc:Ignorable="d"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
x:DataType="viewModels:MainWindowViewModel"> x:DataType="viewModels:MainWindowViewModel">
<Grid> <Grid RowDefinitions="*">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListBox <ListBox
Name="GameListBox" Name="GameListBox"
Grid.Row="0" Grid.Row="0"

View File

@ -2,6 +2,7 @@ using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.AppLibrary;
@ -9,9 +10,9 @@ using Ryujinx.Ava.Utilities.Compat;
using System; using System;
using System.Linq; using System.Linq;
namespace Ryujinx.Ava.UI.Controls namespace Ryujinx.Ava.UI.Views.Misc
{ {
public partial class ApplicationListView : UserControl public partial class ApplicationListView : RyujinxControl<MainWindowViewModel>
{ {
public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent = public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent =
RoutedEvent.Register<ApplicationListView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble); RoutedEvent.Register<ApplicationListView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble);
@ -32,9 +33,6 @@ namespace Ryujinx.Ava.UI.Controls
private async void PlayabilityStatus_OnClick(object sender, RoutedEventArgs e) private async void PlayabilityStatus_OnClick(object sender, RoutedEventArgs e)
{ {
if (DataContext is not MainWindowViewModel mwvm)
return;
if (sender is not Button { Content: TextBlock playabilityLabel }) if (sender is not Button { Content: TextBlock playabilityLabel })
return; return;
@ -43,16 +41,13 @@ namespace Ryujinx.Ava.UI.Controls
private async void IdString_OnClick(object sender, RoutedEventArgs e) private async void IdString_OnClick(object sender, RoutedEventArgs e)
{ {
if (DataContext is not MainWindowViewModel mwvm)
return;
if (sender is not Button { Content: TextBlock idText }) if (sender is not Button { Content: TextBlock idText })
return; return;
if (!RyujinxApp.IsClipboardAvailable(out IClipboard clipboard)) if (!RyujinxApp.IsClipboardAvailable(out IClipboard clipboard))
return; return;
ApplicationData appData = mwvm.Applications.FirstOrDefault(it => it.IdString == idText.Text); ApplicationData appData = ViewModel.Applications.FirstOrDefault(it => it.IdString == idText.Text);
if (appData is null) if (appData is null)
return; return;

View File

@ -7,7 +7,7 @@
xmlns:models="using:Ryujinx.Ava.Common.Models" xmlns:models="using:Ryujinx.Ava.Common.Models"
xmlns:viewModels="using:Ryujinx.Ava.UI.ViewModels" xmlns:viewModels="using:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.UI.Controls.DlcSelectView" x:Class="Ryujinx.Ava.UI.Views.Misc.DlcSelectView"
x:DataType="viewModels:DlcSelectViewModel"> x:DataType="viewModels:DlcSelectViewModel">
<Grid RowDefinitions="*,Auto,*"> <Grid RowDefinitions="*,Auto,*">
<TextBlock <TextBlock

View File

@ -3,14 +3,15 @@ using Avalonia.Styling;
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.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.AppLibrary;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Controls namespace Ryujinx.Ava.UI.Views.Misc
{ {
public partial class DlcSelectView : UserControl public partial class DlcSelectView : RyujinxControl<DlcSelectViewModel>
{ {
public DlcSelectView() public DlcSelectView()
{ {
@ -28,20 +29,10 @@ namespace Ryujinx.Ava.UI.Controls
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue], PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue],
SecondaryButtonText = string.Empty, SecondaryButtonText = string.Empty,
CloseButtonText = string.Empty, CloseButtonText = string.Empty,
Content = new DlcSelectView { DataContext = viewModel } Content = new DlcSelectView { ViewModel = viewModel }
}; };
Style closeButton = new(x => x.Name("CloseButton")); await ContentDialogHelper.ShowAsync(contentDialog.ApplyStyles());
closeButton.Setters.Add(new Setter(WidthProperty, 80d));
Style closeButtonParent = new(x => x.Name("CommandSpace"));
closeButtonParent.Setters.Add(new Setter(HorizontalAlignmentProperty,
Avalonia.Layout.HorizontalAlignment.Right));
contentDialog.Styles.Add(closeButton);
contentDialog.Styles.Add(closeButtonParent);
await ContentDialogHelper.ShowAsync(contentDialog);
return viewModel.SelectedDlc; return viewModel.SelectedDlc;
} }

View File

@ -21,12 +21,7 @@
<Border Classes="settings"> <Border Classes="settings">
<Panel <Panel
Margin="10"> Margin="10">
<Grid> <Grid RowDefinitions="Auto,*,Auto">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<views:InputView <views:InputView
Grid.Row="0" Grid.Row="0"
Name="InputView" /> Name="InputView" />

View File

@ -177,12 +177,7 @@
</Style> </Style>
</ListBox.Styles> </ListBox.Styles>
</ListBox> </ListBox>
<Grid HorizontalAlignment="Stretch"> <Grid HorizontalAlignment="Stretch" ColumnDefinitions="*,Auto,Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox <TextBox
Name="GameDirPathBox" Name="GameDirPathBox"
Margin="0" Margin="0"
@ -235,12 +230,7 @@
</Style> </Style>
</ListBox.Styles> </ListBox.Styles>
</ListBox> </ListBox>
<Grid HorizontalAlignment="Stretch"> <Grid HorizontalAlignment="Stretch" ColumnDefinitions="*,Auto,Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox <TextBox
Name="AutoloadDirPathBox" Name="AutoloadDirPathBox"
Margin="0" Margin="0"

View File

@ -14,15 +14,7 @@
mc:Ignorable="d" mc:Ignorable="d"
Focusable="True" Focusable="True"
x:DataType="models:TempProfile"> x:DataType="models:TempProfile">
<Grid Margin="0"> <Grid Margin="0" ColumnDefinitions="Auto,*" RowDefinitions="*,Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel <StackPanel
Grid.Row="0" Grid.Row="0"
Grid.Column="0" Grid.Column="0"

View File

@ -13,13 +13,12 @@ using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
namespace Ryujinx.Ava.UI.Views.User namespace Ryujinx.Ava.UI.Views.User
{ {
public partial class UserEditorView : UserControl public partial class UserEditorView : RyujinxControl<TempProfile>
{ {
private NavigationDialogHost _parent; private NavigationDialogHost _parent;
private UserProfile _profile; private UserProfile _profile;
private bool _isNewUser; private bool _isNewUser;
public TempProfile TempProfile { get; set; }
public static uint MaxProfileNameLength => 0x20; public static uint MaxProfileNameLength => 0x20;
public bool IsDeletable => _profile.UserId != AccountManager.DefaultUserId; public bool IsDeletable => _profile.UserId != AccountManager.DefaultUserId;
@ -42,7 +41,7 @@ namespace Ryujinx.Ava.UI.Views.User
(NavigationDialogHost parent, UserProfile profile, bool isNewUser) = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter; (NavigationDialogHost parent, UserProfile profile, bool isNewUser) = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
_isNewUser = isNewUser; _isNewUser = isNewUser;
_profile = profile; _profile = profile;
TempProfile = new TempProfile(_profile); ViewModel = new TempProfile(_profile);
_parent = parent; _parent = parent;
break; break;
@ -51,8 +50,6 @@ namespace Ryujinx.Ava.UI.Views.User
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - " + ((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - " +
$"{(_isNewUser ? LocaleManager.Instance[LocaleKeys.UserEditorTitleCreate] : LocaleManager.Instance[LocaleKeys.UserEditorTitle])}"; $"{(_isNewUser ? LocaleManager.Instance[LocaleKeys.UserEditorTitleCreate] : LocaleManager.Instance[LocaleKeys.UserEditorTitle])}";
DataContext = TempProfile;
AddPictureButton.IsVisible = _isNewUser; AddPictureButton.IsVisible = _isNewUser;
ChangePictureButton.IsVisible = !_isNewUser; ChangePictureButton.IsVisible = !_isNewUser;
IdLabel.IsVisible = _profile != null; IdLabel.IsVisible = _profile != null;
@ -72,7 +69,7 @@ namespace Ryujinx.Ava.UI.Views.User
{ {
if (_isNewUser) if (_isNewUser)
{ {
if (TempProfile.Name != String.Empty || TempProfile.Image != null) if (ViewModel.Name != string.Empty || ViewModel.Image != null)
{ {
if (await ContentDialogHelper.CreateChoiceDialog( if (await ContentDialogHelper.CreateChoiceDialog(
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle], LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle],
@ -89,7 +86,7 @@ namespace Ryujinx.Ava.UI.Views.User
} }
else else
{ {
if (_profile.Name != TempProfile.Name || _profile.Image != TempProfile.Image) if (_profile.Name != ViewModel.Name || _profile.Image != ViewModel.Image)
{ {
if (await ContentDialogHelper.CreateChoiceDialog( if (await ContentDialogHelper.CreateChoiceDialog(
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle], LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle],
@ -115,31 +112,31 @@ namespace Ryujinx.Ava.UI.Views.User
{ {
DataValidationErrors.ClearErrors(NameBox); DataValidationErrors.ClearErrors(NameBox);
if (string.IsNullOrWhiteSpace(TempProfile.Name)) if (string.IsNullOrWhiteSpace(ViewModel.Name))
{ {
DataValidationErrors.SetError(NameBox, new DataValidationException(LocaleManager.Instance[LocaleKeys.UserProfileEmptyNameError])); DataValidationErrors.SetError(NameBox, new DataValidationException(LocaleManager.Instance[LocaleKeys.UserProfileEmptyNameError]));
return; return;
} }
if (TempProfile.Image == null) if (ViewModel.Image == null)
{ {
_parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, TempProfile)); _parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, ViewModel));
return; return;
} }
if (_profile != null && !_isNewUser) if (_profile != null && !_isNewUser)
{ {
_profile.Name = TempProfile.Name; _profile.Name = ViewModel.Name;
_profile.Image = TempProfile.Image; _profile.Image = ViewModel.Image;
_profile.UpdateState(); _profile.UpdateState();
_parent.AccountManager.SetUserName(_profile.UserId, _profile.Name); _parent.AccountManager.SetUserName(_profile.UserId, _profile.Name);
_parent.AccountManager.SetUserImage(_profile.UserId, _profile.Image); _parent.AccountManager.SetUserImage(_profile.UserId, _profile.Image);
} }
else if (_isNewUser) else if (_isNewUser)
{ {
_parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image, TempProfile.UserId); _parent.AccountManager.AddUser(ViewModel.Name, ViewModel.Image, ViewModel.UserId);
} }
else else
{ {
@ -151,7 +148,7 @@ namespace Ryujinx.Ava.UI.Views.User
public void SelectProfileImage() public void SelectProfileImage()
{ {
_parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, TempProfile)); _parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, ViewModel));
} }
private void ChangePictureButton_Click(object sender, RoutedEventArgs e) private void ChangePictureButton_Click(object sender, RoutedEventArgs e)

View File

@ -20,13 +20,7 @@
<Grid <Grid
Margin="0" Margin="0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto,Auto">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox <ListBox
Grid.Row="1" Grid.Row="1"
BorderThickness="0" BorderThickness="0"

View File

@ -1,4 +1,3 @@
using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation; using FluentAvalonia.UI.Navigation;
@ -11,7 +10,7 @@ using System.IO;
namespace Ryujinx.Ava.UI.Views.User namespace Ryujinx.Ava.UI.Views.User
{ {
public partial class UserFirmwareAvatarSelectorView : UserControl public partial class UserFirmwareAvatarSelectorView : RyujinxControl<UserFirmwareAvatarSelectorViewModel>
{ {
private NavigationDialogHost _parent; private NavigationDialogHost _parent;
private TempProfile _profile; private TempProfile _profile;
@ -20,8 +19,6 @@ namespace Ryujinx.Ava.UI.Views.User
{ {
ContentManager = contentManager; ContentManager = contentManager;
DataContext = ViewModel;
InitializeComponent(); InitializeComponent();
} }
@ -55,8 +52,6 @@ namespace Ryujinx.Ava.UI.Views.User
public ContentManager ContentManager { get; private set; } public ContentManager ContentManager { get; private set; }
internal UserFirmwareAvatarSelectorViewModel ViewModel { get; set; }
private void GoBack(object sender, RoutedEventArgs e) private void GoBack(object sender, RoutedEventArgs e)
{ {
_parent.GoBack(); _parent.GoBack();

View File

@ -17,12 +17,7 @@
</Design.DataContext> </Design.DataContext>
<Grid <Grid
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Center"> VerticalAlignment="Center" RowDefinitions="Auto,70,Auto">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="70" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock <TextBlock
Grid.Row="0" Grid.Row="0"
TextWrapping="Wrap" TextWrapping="Wrap"

View File

@ -15,14 +15,12 @@ using System.IO;
namespace Ryujinx.Ava.UI.Views.User namespace Ryujinx.Ava.UI.Views.User
{ {
public partial class UserProfileImageSelectorView : UserControl public partial class UserProfileImageSelectorView : RyujinxControl<UserProfileImageSelectorViewModel>
{ {
private ContentManager _contentManager; private ContentManager _contentManager;
private NavigationDialogHost _parent; private NavigationDialogHost _parent;
private TempProfile _profile; private TempProfile _profile;
internal UserProfileImageSelectorViewModel ViewModel { get; private set; }
public UserProfileImageSelectorView() public UserProfileImageSelectorView()
{ {
InitializeComponent(); InitializeComponent();

View File

@ -18,11 +18,7 @@
<viewModels:UserProfileViewModel /> <viewModels:UserProfileViewModel />
</Design.DataContext> </Design.DataContext>
<Grid HorizontalAlignment="Stretch" <Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> VerticalAlignment="Stretch" RowDefinitions="*,Auto">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border <Border
CornerRadius="5" CornerRadius="5"
BorderBrush="{DynamicResource AppListHoverBackgroundColor}" BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
@ -41,11 +37,7 @@
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
ClipToBounds="True" ClipToBounds="True"
CornerRadius="5"> CornerRadius="5">
<Grid Margin="0"> <Grid Margin="0" ColumnDefinitions="*,Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock <TextBlock
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Text="{Binding UserId}" Text="{Binding UserId}"

View File

@ -4,10 +4,11 @@ using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation; using FluentAvalonia.UI.Navigation;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls; using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.ViewModels;
namespace Ryujinx.Ava.UI.Views.User namespace Ryujinx.Ava.UI.Views.User
{ {
public partial class UserRecovererView : UserControl public partial class UserRecovererView : RyujinxControl<UserProfileViewModel>
{ {
private NavigationDialogHost _parent; private NavigationDialogHost _parent;

View File

@ -19,19 +19,10 @@
<Design.DataContext> <Design.DataContext>
<viewModels:UserSaveManagerViewModel /> <viewModels:UserSaveManagerViewModel />
</Design.DataContext> </Design.DataContext>
<Grid> <Grid RowDefinitions="Auto,*,Auto">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid <Grid
Grid.Row="0" Grid.Row="0"
HorizontalAlignment="Stretch"> HorizontalAlignment="Stretch" ColumnDefinitions="Auto,*">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel <StackPanel
Spacing="10" Spacing="10"
Orientation="Horizontal" Orientation="Horizontal"
@ -80,11 +71,7 @@
<Grid <Grid
Grid.Column="1" Grid.Column="1"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Margin="10,0, 0, 0"> Margin="10,0, 0, 0" ColumnDefinitions="Auto,*">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="{ext:Locale Search}" VerticalAlignment="Center" /> <Label Content="{ext:Locale Search}" VerticalAlignment="Center" />
<TextBox <TextBox
Margin="5,0,0,0" Margin="5,0,0,0"
@ -118,11 +105,7 @@
</ListBox.Styles> </ListBox.Styles>
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate x:DataType="models:SaveModel"> <DataTemplate x:DataType="models:SaveModel">
<Grid HorizontalAlignment="Stretch"> <Grid HorizontalAlignment="Stretch" ColumnDefinitions="*,Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel <StackPanel
Grid.Column="0" Grid.Column="0"
Orientation="Horizontal" Orientation="Horizontal"

View File

@ -23,10 +23,8 @@ using UserId = LibHac.Fs.UserId;
namespace Ryujinx.Ava.UI.Views.User namespace Ryujinx.Ava.UI.Views.User
{ {
public partial class UserSaveManagerView : UserControl public partial class UserSaveManagerView : RyujinxControl<UserSaveManagerViewModel>
{ {
internal UserSaveManagerViewModel ViewModel { get; private set; }
private AccountManager _accountManager; private AccountManager _accountManager;
private HorizonClient _horizonClient; private HorizonClient _horizonClient;
private VirtualFileSystem _virtualFileSystem; private VirtualFileSystem _virtualFileSystem;
@ -66,7 +64,7 @@ namespace Ryujinx.Ava.UI.Views.User
public void LoadSaves() public void LoadSaves()
{ {
ViewModel.Saves.Clear(); Dispatcher.UIThread.Post(() => ViewModel.Saves.Clear());
ObservableCollection<SaveModel> saves = []; ObservableCollection<SaveModel> saves = [];
SaveDataFilter saveDataFilter = SaveDataFilter.Make( SaveDataFilter saveDataFilter = SaveDataFilter.Make(
programId: default, programId: default,

View File

@ -18,11 +18,7 @@
<Design.DataContext> <Design.DataContext>
<viewModels:UserProfileViewModel /> <viewModels:UserProfileViewModel />
</Design.DataContext> </Design.DataContext>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RowDefinitions="*,Auto">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border <Border
CornerRadius="5" CornerRadius="5"
BorderBrush="{DynamicResource AppListHoverBackgroundColor}" BorderBrush="{DynamicResource AppListHoverBackgroundColor}"

View File

@ -11,12 +11,10 @@ using Button = Avalonia.Controls.Button;
namespace Ryujinx.Ava.UI.Views.User namespace Ryujinx.Ava.UI.Views.User
{ {
public partial class UserSelectorViews : UserControl public partial class UserSelectorViews : RyujinxControl<UserProfileViewModel>
{ {
private NavigationDialogHost _parent; private NavigationDialogHost _parent;
public UserProfileViewModel ViewModel { get; set; }
public UserSelectorViews() public UserSelectorViews()
{ {
InitializeComponent(); InitializeComponent();

View File

@ -5,6 +5,7 @@ using Avalonia.Layout;
using Avalonia.Styling; using Avalonia.Styling;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common; using Ryujinx.Common;
@ -14,7 +15,7 @@ using Button = Avalonia.Controls.Button;
namespace Ryujinx.Ava.UI.Windows namespace Ryujinx.Ava.UI.Windows
{ {
public partial class AboutWindow : UserControl public partial class AboutWindow : RyujinxControl<AboutWindowViewModel>
{ {
public AboutWindow() public AboutWindow()
{ {
@ -33,19 +34,10 @@ namespace Ryujinx.Ava.UI.Windows
PrimaryButtonText = string.Empty, PrimaryButtonText = string.Empty,
SecondaryButtonText = string.Empty, SecondaryButtonText = string.Empty,
CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose], CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose],
Content = new AboutWindow { DataContext = viewModel } Content = new AboutWindow { ViewModel = viewModel }
}; };
Style closeButton = new(x => x.Name("CloseButton")); await ContentDialogHelper.ShowAsync(contentDialog.ApplyStyles());
closeButton.Setters.Add(new Setter(WidthProperty, 80d));
Style closeButtonParent = new(x => x.Name("CommandSpace"));
closeButtonParent.Setters.Add(new Setter(HorizontalAlignmentProperty, HorizontalAlignment.Right));
contentDialog.Styles.Add(closeButton);
contentDialog.Styles.Add(closeButtonParent);
await ContentDialogHelper.ShowAsync(contentDialog);
} }
private void Button_OnClick(object sender, RoutedEventArgs e) private void Button_OnClick(object sender, RoutedEventArgs e)

View File

@ -53,13 +53,7 @@
<!-- For image --> <!-- For image -->
<ui:NavigationView.PaneHeader> <ui:NavigationView.PaneHeader>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center"> <Grid HorizontalAlignment="Center" VerticalAlignment="Center" RowDefinitions="Auto,Auto,Auto,5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="5"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding GameId}" <TextBlock Text="{Binding GameId}"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"

View File

@ -9,6 +9,7 @@
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:main="clr-namespace:Ryujinx.Ava.UI.Views.Main" xmlns:main="clr-namespace:Ryujinx.Ava.UI.Views.Main"
xmlns:viewsMisc="clr-namespace:Ryujinx.Ava.UI.Views.Misc"
Cursor="{Binding Cursor}" Cursor="{Binding Cursor}"
Title="{Binding Title}" Title="{Binding Title}"
WindowState="{Binding WindowState}" WindowState="{Binding WindowState}"
@ -73,7 +74,7 @@
<main:MainViewControls <main:MainViewControls
Name="ViewControls" Name="ViewControls"
Grid.Row="0"/> Grid.Row="0"/>
<controls:ApplicationListView <viewsMisc:ApplicationListView
x:Name="ApplicationList" x:Name="ApplicationList"
Grid.Row="1" Grid.Row="1"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
@ -81,7 +82,7 @@
HorizontalContentAlignment="Stretch" HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch" VerticalContentAlignment="Stretch"
IsVisible="{Binding IsList}" /> IsVisible="{Binding IsList}" />
<controls:ApplicationGridView <viewsMisc:ApplicationGridView
x:Name="ApplicationGrid" x:Name="ApplicationGrid"
Grid.Row="1" Grid.Row="1"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"

View File

@ -70,11 +70,7 @@
<DataTemplate <DataTemplate
DataType="models:ModModel"> DataType="models:ModModel">
<Panel Margin="10"> <Panel Margin="10">
<Grid> <Grid ColumnDefinitions="*,Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock <TextBlock
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"

View File

@ -12,7 +12,7 @@
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common" xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
Width="1100" Width="1100"
Height="918" Height="927"
MinWidth="800" MinWidth="800"
MinHeight="480" MinHeight="480"
WindowStartupLocation="CenterOwner" WindowStartupLocation="CenterOwner"

View File

@ -1,5 +1,5 @@
<Window <Window
x:Class="Ryujinx.Ava.UI.Controls.UpdateWaitWindow" x:Class="Ryujinx.Ava.UI.Windows.UpdateWaitWindow"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@ -13,15 +13,7 @@
<Grid <Grid
Margin="20" Margin="20"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> VerticalAlignment="Stretch" ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Image <Image
Grid.Row="1" Grid.Row="1"
Height="70" Height="70"

View File

@ -1,8 +1,7 @@
using Avalonia.Controls; using Avalonia.Controls;
using Ryujinx.Ava.UI.Windows;
using System.Threading; using System.Threading;
namespace Ryujinx.Ava.UI.Controls namespace Ryujinx.Ava.UI.Windows
{ {
public partial class UpdateWaitWindow : StyleableWindow public partial class UpdateWaitWindow : StyleableWindow
{ {

View File

@ -13,14 +13,7 @@
x:DataType="viewModels:XCITrimmerViewModel" x:DataType="viewModels:XCITrimmerViewModel"
Focusable="True" Focusable="True"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid Margin="20 0 20 0"> <Grid Margin="20 0 20 0" RowDefinitions="Auto,Auto,*,Auto,Auto">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Panel <Panel
Margin="10 10 10 10" Margin="10 10 10 10"
Grid.Row="0"> Grid.Row="0">
@ -30,12 +23,7 @@
Margin="0 0 10 10" Margin="0 0 10 10"
IsVisible="{Binding !Processing}" IsVisible="{Binding !Processing}"
Grid.Row="1"> Grid.Row="1">
<Grid> <Grid ColumnDefinitions="Auto,*,Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel <StackPanel
Grid.Column="0" Grid.Column="0"
Orientation="Horizontal"> Orientation="Horizontal">
@ -145,11 +133,7 @@
<DataTemplate <DataTemplate
DataType="models:XCITrimmerFileModel"> DataType="models:XCITrimmerFileModel">
<Panel Margin="10"> <Panel Margin="10">
<Grid> <Grid ColumnDefinitions="65*,35*">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="65*" />
<ColumnDefinition Width="35*" />
</Grid.ColumnDefinitions>
<TextBlock <TextBlock
Grid.Column="0" Grid.Column="0"
Margin="10 0 10 0" Margin="10 0 10 0"
@ -160,11 +144,7 @@
TextTrimming="CharacterEllipsis" TextTrimming="CharacterEllipsis"
Text="{Binding Name}"> Text="{Binding Name}">
</TextBlock> </TextBlock>
<Grid Grid.Column="1"> <Grid Grid.Column="1" ColumnDefinitions="45*,55*">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="45*" />
<ColumnDefinition Width="55*" />
</Grid.ColumnDefinitions>
<ProgressBar <ProgressBar
Height="10" Height="10"
Margin="10 0 10 0" Margin="10 0 10 0"
@ -226,15 +206,7 @@
BorderThickness="1" BorderThickness="1"
CornerRadius="5" CornerRadius="5"
Padding="2.5"> Padding="2.5">
<Grid> <Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock <TextBlock
Grid.Column="0" Grid.Column="0"
Grid.Row="0" Grid.Row="0"
@ -274,11 +246,7 @@
<Panel <Panel
Grid.Row="4" Grid.Row="4"
HorizontalAlignment="Stretch"> HorizontalAlignment="Stretch">
<Grid> <Grid ColumnDefinitions="*,Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel <StackPanel
Grid.Column="0" Grid.Column="0"
Orientation="Horizontal" Orientation="Horizontal"

View File

@ -25,16 +25,7 @@ namespace Ryujinx.Ava.Utilities.Compat
} }
}; };
Style closeButton = new(x => x.Name("CloseButton")); await ContentDialogHelper.ShowAsync(contentDialog.ApplyStyles());
closeButton.Setters.Add(new Setter(WidthProperty, 80d));
Style closeButtonParent = new(x => x.Name("CommandSpace"));
closeButtonParent.Setters.Add(new Setter(HorizontalAlignmentProperty, Avalonia.Layout.HorizontalAlignment.Right));
contentDialog.Styles.Add(closeButton);
contentDialog.Styles.Add(closeButtonParent);
await ContentDialogHelper.ShowAsync(contentDialog);
} }
public CompatibilityList() public CompatibilityList()

View File

@ -1,6 +1,6 @@
using ARMeilleure; using ARMeilleure;
using Gommon; using Gommon;
using Ryujinx.Ava.Utilities.AppLibrary; using LibHac.Tools.FsSystem;
using Ryujinx.Ava.Utilities.Configuration.System; using Ryujinx.Ava.Utilities.Configuration.System;
using Ryujinx.Ava.Utilities.Configuration.UI; using Ryujinx.Ava.Utilities.Configuration.UI;
using Ryujinx.Common; using Ryujinx.Common;
@ -11,6 +11,7 @@ using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.HLE; using Ryujinx.HLE;
using Ryujinx.HLE.HOS.SystemState;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using RyuLogger = Ryujinx.Common.Logging.Logger; using RyuLogger = Ryujinx.Common.Logging.Logger;
@ -838,5 +839,35 @@ namespace Ryujinx.Ava.Utilities.Configuration
EnableHardwareAcceleration = new ReactiveObject<bool>(); EnableHardwareAcceleration = new ReactiveObject<bool>();
HideCursor = new ReactiveObject<HideCursorMode>(); HideCursor = new ReactiveObject<HideCursorMode>();
} }
public HleConfiguration CreateHleConfiguration() =>
new(
System.DramSize,
(SystemLanguage)System.Language.Value,
(RegionCode)System.Region.Value,
Graphics.VSyncMode,
System.EnableDockedMode,
System.EnablePtc,
System.EnableInternetAccess,
System.EnableFsIntegrityChecks
? IntegrityCheckLevel.ErrorOnInvalid
: IntegrityCheckLevel.None,
System.FsGlobalAccessLogMode,
System.MatchSystemTime
? 0
: System.SystemTimeOffset,
System.TimeZone,
System.MemoryManagerMode,
System.IgnoreMissingServices,
Graphics.AspectRatio,
System.AudioVolume,
System.UseHypervisor,
Multiplayer.LanInterfaceId,
Multiplayer.Mode,
Multiplayer.DisableP2p,
Multiplayer.LdnPassphrase,
Instance.Multiplayer.GetLdnServer(),
Instance.Graphics.CustomVSyncInterval,
Instance.Hacks.ShowDirtyHacks ? Instance.Hacks.EnabledHacks : null);
} }
} }

View File

@ -328,4 +328,4 @@ namespace Ryujinx.Ava.Utilities.Configuration
return GraphicsBackend.OpenGl; return GraphicsBackend.OpenGl;
} }
} }
} }