Compare commits

..

9 Commits

Author SHA1 Message Date
Evan Husted
ed5cb82aa8 feature: Turbo Mode
Adds an elapsed tick multiplier feature which speeds up games which are built upon delta time.
More information: https://web.archive.org/web/20240713135029/https://github.com/Ryujinx/Ryujinx/pull/6456
2025-03-03 02:33:28 -06:00
Evan Husted
c48a2e6ba0 join assignment and declaration for this local variable in ipc service 2025-03-03 02:30:26 -06:00
Evan Husted
90f2b089eb fix nullref when switching vsync mode with turbo mode 2025-03-02 23:51:56 -06:00
Evan Husted
c410474d83 misc: chore: Remove MiniCommand 2025-03-02 21:49:58 -06:00
Evan Husted
ffdc419417 misc: chore: [ci skip] small Avalonia project restructure
Moved the Views that existed in the Controls namespace into the Ryujinx.Ava.UI.Views.Misc namespace
Moved UpdateWaitWindow to Ryujinx.Ava.UI.Windows
2025-03-02 21:42:25 -06:00
Evan Husted
da3f4e1d3a misc: Created generic type RyujinxControl to allow for more unified control view model definitions 2025-03-02 21:24:39 -06:00
Evan Husted
69d79322bb misc: chore: remove old title ID constructor for RendererHost 2025-03-02 21:23:36 -06:00
Evan Husted
c3af1dbf1a Stick Visualizer (#579)
![](https://i.imgur.com/iSaXRMr.png)

---------

Co-authored-by: MutantAura <domw0401@gmail.com>
2025-03-02 20:43:31 -06:00
Piplup
10ac381525 compat: Updates (#742)
These are branches i have on my private repo that i been meaning to push
Bluey The Videogame - compatibility/Bluey
Grand Theft Auto: III – The Definitive Edition -
compatibility/gta-definitiveedition
Grand Theft Auto: Vice City – The Definitive Edition -
compatibility/gta-definitiveedition
Grand Theft Auto: San Andreas – The Definitive Edition -
compatibility/gta-definitiveedition
SpongeBob SquarePants: The Cosmic Shake - compatibility/TheCosmicShake

p.s i didn't mess up one of the commit names i swear
2025-03-02 18:39:32 -06:00
59 changed files with 574 additions and 306 deletions

View File

@@ -631,6 +631,7 @@
010030D012FF6000,"Bus Driver Simulator",,playable,2022-10-17 13:55:27
0100A9101418C000,"BUSTAFELLOWS",nvdec,playable,2020-10-17 20:04:41
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
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
@@ -1382,6 +1383,9 @@
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
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
01007E100EFA8000,"Habroxia",,playable,2020-06-16 23:04:42
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
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
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
0100C6100D75E000,"Spooky Ghosts Dot Com",,playable,2021-06-15 15:16:11
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

@@ -13,5 +13,7 @@ namespace Ryujinx.Common.Configuration.Hid
public Key VolumeDown { get; set; }
public Key CustomVSyncIntervalIncrement { get; set; }
public Key CustomVSyncIntervalDecrement { get; set; }
public Key TurboMode { get; set; }
public bool TurboModeWhileHeld { get; set; }
}
}

View File

@@ -12,6 +12,11 @@ namespace Ryujinx.Cpu
/// Time elapsed since the counter was created.
/// </summary>
TimeSpan ElapsedTime { get; }
/// <summary>
/// Clock tick scalar, in percent points (100 = 1.0).
/// </summary>
long TickScalar { get; set; }
/// <summary>
/// Time elapsed since the counter was created, in seconds.

View File

@@ -14,12 +14,37 @@ namespace Ryujinx.Cpu
/// <inheritdoc/>
public ulong Counter => (ulong)(ElapsedSeconds * Frequency);
public long TickScalar { get; set; }
private static long _acumElapsedTicks;
private static long _lastElapsedTicks;
private long ElapsedTicks
{
get
{
long elapsedTicks = _tickCounter.ElapsedTicks;
_acumElapsedTicks += (elapsedTicks - _lastElapsedTicks) * TickScalar / 100;
_lastElapsedTicks = elapsedTicks;
return _acumElapsedTicks;
}
}
/// <inheritdoc/>
public TimeSpan ElapsedTime => _tickCounter.Elapsed;
public TimeSpan ElapsedTime => Stopwatch.GetElapsedTime(0, ElapsedTicks);
/// <inheritdoc/>
public double ElapsedSeconds => _tickCounter.ElapsedTicks * _hostTickFreq;
public double ElapsedSeconds => ElapsedTicks * _hostTickFreq;
public TickSource(ulong frequency)
{

View File

@@ -1065,7 +1065,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
private void UpdateIndexBufferState()
{
IndexBufferState indexBuffer = _state.State.IndexBufferState;
IndexBufferState? indexBufferNullable = _state?.State.IndexBufferState;
if (!indexBufferNullable.HasValue)
{
return;
}
IndexBufferState indexBuffer = indexBufferNullable.Value;
if (_drawState.IndexCount == 0)
{

View File

@@ -102,6 +102,11 @@ namespace Ryujinx.HLE
/// Control if the Profiled Translation Cache (PTC) should be used.
/// </summary>
internal readonly bool EnablePtc;
/// <summary>
/// Control the arbitrary scalar applied to emulated CPU tick timing.
/// </summary>
public long TickScalar { get; set; }
/// <summary>
/// Control if the guest application should be told that there is a Internet connection available.
@@ -225,6 +230,7 @@ namespace Ryujinx.HLE
string multiplayerLdnPassphrase,
string multiplayerLdnServer,
int customVSyncInterval,
long tickScalar,
EnabledDirtyHack[] dirtyHacks = null)
{
VirtualFileSystem = virtualFileSystem;
@@ -257,6 +263,7 @@ namespace Ryujinx.HLE
MultiplayerDisableP2p = multiplayerDisableP2p;
MultiplayerLdnPassphrase = multiplayerLdnPassphrase;
MultiplayerLdnServer = multiplayerLdnServer;
TickScalar = tickScalar;
Hacks = dirtyHacks ?? [];
}
}

View File

@@ -127,10 +127,7 @@ namespace Ryujinx.HLE.HOS.Services
}
else
{
string serviceName;
serviceName = (service is not DummyService dummyService) ? service.GetType().FullName : dummyService.ServiceName;
string serviceName = (service is not DummyService dummyService) ? service.GetType().FullName : dummyService.ServiceName;
Logger.Warning?.Print(LogClass.KernelIpc, $"Missing service {serviceName}: {commandId} ignored");
}

View File

@@ -2,6 +2,7 @@ using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.PreciseSleep;
using Ryujinx.Cpu;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
@@ -89,7 +90,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
}
else
{
_ticksPerFrame = Stopwatch.Frequency / _device.TargetVSyncInterval;
_ticksPerFrame = ((Stopwatch.Frequency / _device.TargetVSyncInterval) * 100) / _device.TickScalar;
_targetVSyncInterval = _device.TargetVSyncInterval;
}
}

View File

@@ -6,6 +6,8 @@ namespace Ryujinx.HLE
{
public class PerformanceStatistics
{
private readonly Switch _device;
private const int FrameTypeGame = 0;
private const int PercentTypeFifo = 0;
@@ -28,8 +30,10 @@ namespace Ryujinx.HLE
private readonly System.Timers.Timer _resetTimer;
public PerformanceStatistics()
public PerformanceStatistics(Switch device)
{
_device = device;
_frameRate = new double[1];
_accumulatedFrameTime = new double[1];
_previousFrameTime = new double[1];
@@ -166,8 +170,11 @@ namespace Ryujinx.HLE
{
double frameRate = GetGameFrameRate();
double frameTime = GetGameFrameTime();
string turboSuffix = _device.TurboMode
? $" Turbo ({_device.TickScalar}%)"
: string.Empty;
return $"{frameRate:00.00} FPS ({frameTime:00.00}ms)";
return $"{frameRate:00.00} FPS ({frameTime:00.00}ms){turboSuffix}";
}
public string FormatFifoPercent()

View File

@@ -26,18 +26,26 @@ namespace Ryujinx.HLE
public GpuContext Gpu { get; }
public VirtualFileSystem FileSystem { get; }
public HOS.Horizon System { get; }
public bool TurboMode = false;
public long TickScalar
{
get => System?.TickSource?.TickScalar ?? 100;
set => System.TickSource.TickScalar = value;
}
public ProcessLoader Processes { get; }
public PerformanceStatistics Statistics { get; }
public Hid Hid { get; }
public TamperMachine TamperMachine { get; }
public IHostUIHandler UIHandler { get; }
public int CpuCoresCount = 4; //Switch 1 has 4 cores
public int CpuCoresCount = 4; // Switch has a quad-core Tegra X1 SoC
public VSyncMode VSyncMode { get; set; }
public bool CustomVSyncIntervalEnabled { get; set; }
public int CustomVSyncInterval { get; set; }
public long TargetVSyncInterval { get; set; } = 60;
public bool IsFrameAvailable => Gpu.Window.IsFrameAvailable;
@@ -64,7 +72,7 @@ namespace Ryujinx.HLE
Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), memoryAllocationFlags);
Gpu = new GpuContext(Configuration.GpuRenderer, DirtyHacks);
System = new HOS.Horizon(this);
Statistics = new PerformanceStatistics();
Statistics = new PerformanceStatistics(this);
Hid = new Hid(this, System.HidStorage);
Processes = new ProcessLoader(this);
TamperMachine = new TamperMachine();
@@ -75,6 +83,7 @@ namespace Ryujinx.HLE
VSyncMode = Configuration.VSyncMode;
CustomVSyncInterval = Configuration.CustomVSyncInterval;
TickScalar = TurboMode ? Configuration.TickScalar : 100;
System.State.DockedMode = Configuration.EnableDockedMode;
System.PerformanceState.PerformanceMode = System.State.DockedMode ? PerformanceMode.Boost : PerformanceMode.Default;
System.EnablePtc = Configuration.EnablePtc;
@@ -122,6 +131,12 @@ namespace Ryujinx.HLE
}
}
public void ToggleTurbo()
{
TurboMode = !TurboMode;
TickScalar = TurboMode ? Configuration.TickScalar : 100;
}
public bool LoadCart(string exeFsDir, string romFsFile = null) => Processes.LoadUnpackedNca(exeFsDir, romFsFile);
public bool LoadXci(string xciFile, ulong applicationId = 0) => Processes.LoadXci(xciFile, applicationId);
public bool LoadNca(string ncaFile, BlitStruct<ApplicationControlProperty>? customNacpData = null) => Processes.LoadNca(ncaFile, customNacpData);

View File

@@ -947,7 +947,8 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Multiplayer.DisableP2p,
ConfigurationState.Instance.Multiplayer.LdnPassphrase,
ConfigurationState.Instance.Multiplayer.GetLdnServer(),
ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value,
ConfigurationState.Instance.Graphics.CustomVSyncInterval,
ConfigurationState.Instance.System.TickScalar,
ConfigurationState.Instance.Hacks.ShowDirtyHacks ? ConfigurationState.Instance.Hacks.EnabledHacks : null));
}
@@ -1249,6 +1250,12 @@ namespace Ryujinx.Ava
if (currentHotkeyState != _prevHotkeyState)
{
if (ConfigurationState.Instance.Hid.Hotkeys.Value.TurboModeWhileHeld &&
_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.TurboMode) != Device.TurboMode)
{
Device.ToggleTurbo();
}
switch (currentHotkeyState)
{
case KeyboardHotkeyState.ToggleVSyncMode:
@@ -1262,6 +1269,12 @@ namespace Ryujinx.Ava
Device.IncrementCustomVSyncInterval();
_viewModel.CustomVSyncInterval += 1;
break;
case KeyboardHotkeyState.TurboMode:
if (!ConfigurationState.Instance.Hid.Hotkeys.Value.TurboModeWhileHeld)
{
Device.ToggleTurbo();
}
break;
case KeyboardHotkeyState.Screenshot:
ScreenshotRequested = true;
break;
@@ -1391,6 +1404,10 @@ namespace Ryujinx.Ava
{
state = KeyboardHotkeyState.CustomVSyncIntervalDecrement;
}
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.TurboMode))
{
state = KeyboardHotkeyState.TurboMode;
}
return state;
}

View File

@@ -4897,6 +4897,81 @@
"zh_TW": "低功耗 PPTC"
}
},
{
"ID": "SettingsTabSystemTurboMultiplierValue",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Turbo multiplier:",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SettingsTabSystemTurboMultiplierSliderToolTip",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "The Turbo mode multiplier target value.\n\nLeave at 100 if unsure.",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SettingsTabSystemTurboMultiplierValueToolTip",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "The Turbo mode multiplier, as a percentage of the normal Switch clock speed.\n\nLeave at 100 if unsure.",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SettingsTabSystemEnableFsIntegrityChecks",
"Translations": {
@@ -5097,6 +5172,31 @@
"zh_TW": "可能導致模擬器不穩定"
}
},
{
"ID": "SettingsTabCpuHacksNote",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Can and will cause crashing, slow/too fast games, etc. Use with caution.",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SettingsTabSystemDramSize",
"Translations": {
@@ -23797,6 +23897,56 @@
"zh_TW": "降低自訂的重新整理頻率"
}
},
{
"ID": "SettingsTabHotkeysTurboMode",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Turbo mode:",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SettingsTabHotkeysOnlyWhilePressed",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Only while pressed",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "CompatibilityListLastUpdated",
"Translations": {

View File

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

View File

@@ -14,5 +14,6 @@ namespace Ryujinx.Ava.Common
VolumeDown,
CustomVSyncIntervalIncrement,
CustomVSyncIntervalDecrement,
TurboMode,
}
}

View File

@@ -352,7 +352,8 @@ namespace Ryujinx.Headless
false,
string.Empty,
string.Empty,
options.CustomVSyncInterval);
options.CustomVSyncInterval,
100);
return new Switch(configuration);
}

View File

@@ -9,17 +9,14 @@ using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading.Tasks;
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
using UserProfileSft = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
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)
{
DataContext = ViewModel = viewModel;

View File

@@ -9,6 +9,7 @@ using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Common.Models;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Views.Misc;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.AppLibrary;
@@ -26,6 +27,7 @@ namespace Ryujinx.Ava.UI.Controls
{
public class ApplicationContextMenu : MenuFlyout
{
public ApplicationContextMenu()
{
InitializeComponent();

View File

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

@@ -40,19 +40,19 @@ namespace Ryujinx.Ava.UI.Helpers
SecondaryButtonText = secondaryButton,
CloseButtonText = closeButton,
Content = content,
PrimaryButtonCommand = MiniCommand.Create(() =>
PrimaryButtonCommand = Commands.Create(() =>
{
result = primaryButtonResult;
})
};
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
contentDialog.SecondaryButtonCommand = Commands.Create(() =>
{
result = UserResult.No;
contentDialog.PrimaryButtonClick -= deferCloseAction;
});
contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
contentDialog.CloseButtonCommand = Commands.Create(() =>
{
result = UserResult.Cancel;
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

@@ -28,6 +28,10 @@ namespace Ryujinx.Ava.UI.Models.Input
[ObservableProperty] private Key _customVSyncIntervalDecrement;
[ObservableProperty] private Key _turboMode;
[ObservableProperty] private bool _turboModeWhileHeld;
public HotkeyConfig(KeyboardHotkeys config)
{
if (config == null)
@@ -44,6 +48,8 @@ namespace Ryujinx.Ava.UI.Models.Input
VolumeDown = config.VolumeDown;
CustomVSyncIntervalIncrement = config.CustomVSyncIntervalIncrement;
CustomVSyncIntervalDecrement = config.CustomVSyncIntervalDecrement;
TurboMode = config.TurboMode;
TurboModeWhileHeld = config.TurboModeWhileHeld;
}
public KeyboardHotkeys GetConfig() =>
@@ -60,6 +66,8 @@ namespace Ryujinx.Ava.UI.Models.Input
VolumeDown = VolumeDown,
CustomVSyncIntervalIncrement = CustomVSyncIntervalIncrement,
CustomVSyncIntervalDecrement = CustomVSyncIntervalDecrement,
TurboMode = TurboMode,
TurboModeWhileHeld = TurboModeWhileHeld
};
}
}

View File

@@ -2,9 +2,7 @@
using Avalonia.Controls;
using Avalonia.Media;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using System;
namespace Ryujinx.Ava.UI.Renderer
@@ -38,32 +36,6 @@ namespace Ryujinx.Ava.UI.Renderer
EmbeddedWindowOpenGL => GraphicsBackend.OpenGl,
_ => 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()

View File

@@ -60,6 +60,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private bool _enableCustomVSyncInterval;
private int _customVSyncIntervalPercentageProxy;
private VSyncMode _vSyncMode;
private long _turboModeMultiplier;
public event Action CloseWindow;
public event Action SaveSettingsEvent;
@@ -207,6 +208,25 @@ namespace Ryujinx.Ava.UI.ViewModels
}
public bool EnablePptc { get; set; }
public bool EnableLowPowerPptc { get; set; }
public long TurboMultiplier
{
get => _turboModeMultiplier;
set
{
if (_turboModeMultiplier != value)
{
_turboModeMultiplier = value;
OnPropertyChanged();
OnPropertyChanged((nameof(TurboMultiplierPercentageText)));
}
}
}
public string TurboMultiplierPercentageText => $"{TurboMultiplier}%";
public bool EnableInternetAccess { get; set; }
public bool EnableFsIntegrityChecks { get; set; }
public bool IgnoreMissingServices { get; set; }
@@ -594,6 +614,7 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableLowPowerPptc = config.System.EnableLowPowerPtc;
MemoryMode = (int)config.System.MemoryManagerMode.Value;
UseHypervisor = config.System.UseHypervisor;
TurboMultiplier = config.System.TickScalar;
// Graphics
GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value;
@@ -697,6 +718,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.System.EnableLowPowerPtc.Value = EnableLowPowerPptc;
config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode;
config.System.UseHypervisor.Value = UseHypervisor;
config.System.TickScalar.Value = TurboMultiplier;
// Graphics
config.Graphics.VSyncMode.Value = VSyncMode;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,16 +3,15 @@ using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
using System;
namespace Ryujinx.Ava.UI.Views.Main
{
public partial class MainViewControls : UserControl
public partial class MainViewControls : RyujinxControl<MainWindowViewModel>
{
public MainWindowViewModel ViewModel;
public MainViewControls()
{
InitializeComponent();
@@ -24,7 +23,7 @@ namespace Ryujinx.Ava.UI.Views.Main
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
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">
<StackPanel Orientation="Horizontal">
<Image Margin="0"

View File

@@ -4,6 +4,7 @@ using Avalonia.Interactivity;
using Avalonia.Styling;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
@@ -12,9 +13,9 @@ using Ryujinx.Ava.Utilities.Compat;
using System.Linq;
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)
{
@@ -25,7 +26,7 @@ namespace Ryujinx.Ava.UI.Controls
SecondaryButtonText = string.Empty,
CloseButtonText = LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
MinWidth = 256,
Content = new ApplicationDataView { DataContext = new ApplicationDataViewModel(appData) }
Content = new ApplicationDataView { ViewModel = new ApplicationDataViewModel(appData) }
};
Style closeButton = new(x => x.Name("CloseButton"));

View File

@@ -1,5 +1,5 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Controls.ApplicationGridView"
x:Class="Ryujinx.Ava.UI.Views.Misc.ApplicationGridView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"

View File

@@ -1,13 +1,15 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities.AppLibrary;
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 =
RoutedEvent.Register<ApplicationGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble);

View File

@@ -1,5 +1,5 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Controls.ApplicationListView"
x:Class="Ryujinx.Ava.UI.Views.Misc.ApplicationListView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

View File

@@ -2,6 +2,7 @@ using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities.AppLibrary;
@@ -9,9 +10,9 @@ using Ryujinx.Ava.Utilities.Compat;
using System;
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 =
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)
{
if (DataContext is not MainWindowViewModel mwvm)
return;
if (sender is not Button { Content: TextBlock playabilityLabel })
return;
@@ -43,16 +41,13 @@ namespace Ryujinx.Ava.UI.Controls
private async void IdString_OnClick(object sender, RoutedEventArgs e)
{
if (DataContext is not MainWindowViewModel mwvm)
return;
if (sender is not Button { Content: TextBlock idText })
return;
if (!RyujinxApp.IsClipboardAvailable(out IClipboard clipboard))
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)
return;

View File

@@ -7,7 +7,7 @@
xmlns:models="using:Ryujinx.Ava.Common.Models"
xmlns:viewModels="using:Ryujinx.Ava.UI.ViewModels"
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">
<Grid RowDefinitions="*,Auto,*">
<TextBlock

View File

@@ -3,14 +3,15 @@ using Avalonia.Styling;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Common.Models;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities.AppLibrary;
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()
{
@@ -28,7 +29,7 @@ namespace Ryujinx.Ava.UI.Controls
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue],
SecondaryButtonText = string.Empty,
CloseButtonText = string.Empty,
Content = new DlcSelectView { DataContext = viewModel }
Content = new DlcSelectView { ViewModel = viewModel }
};
Style closeButton = new(x => x.Name("CloseButton"));

View File

@@ -7,6 +7,7 @@
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d"
x:DataType="viewModels:SettingsViewModel">
<Design.DataContext>
@@ -76,6 +77,56 @@
ToolTip.Tip="{ext:Locale UseHypervisorTooltip}" />
</CheckBox>
</StackPanel>
<Separator Height="1" />
<StackPanel
Orientation="Vertical"
Spacing="5">
<TextBlock
Classes="h1"
Text="{ext:Locale SettingsTabSystemHacks}" />
<TextBlock
Foreground="{DynamicResource SecondaryTextColor}"
TextDecorations="Underline"
Text="{ext:Locale SettingsTabCpuHacksNote}" />
</StackPanel>
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<StackPanel Margin="0,0,0,10"
Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
Text="{ext:Locale SettingsTabSystemTurboMultiplierValue}"
ToolTip.Tip="{ext:Locale SettingsTabSystemTurboMultiplierValueToolTip}"
Width="250" />
<ui:NumberBox ToolTip.Tip="{ext:Locale SettingsTabSystemTurboMultiplierValueToolTip}"
Value="{Binding TurboMultiplier}"
Width="165"
SmallChange="1.0"
LargeChange="10"
SimpleNumberFormat="F0"
SpinButtonPlacementMode="Hidden"
Minimum="50"
Maximum="500" />
<Slider Value="{Binding TurboMultiplier}"
ToolTip.Tip="{ext:Locale SettingsTabSystemTurboMultiplierSliderToolTip}"
MinWidth="175"
Margin="10,-3,0,0"
Height="32"
Padding="0,-5"
TickFrequency="1"
IsSnapToTickEnabled="True"
LargeChange="10"
SmallChange="1"
VerticalAlignment="Center"
Minimum="50"
Maximum="500" />
<TextBlock Margin="5,0"
Width="40"
Text="{Binding TurboMultiplierPercentageText}"/>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>

View File

@@ -19,7 +19,7 @@
<Setter Property="Margin" Value="10, 0, 0, 0" />
<Setter Property="Orientation" Value="Horizontal" />
</Style>
<Style Selector="StackPanel > StackPanel > TextBlock">
<Style Selector="StackPanel > StackPanel > TextBlock.settingHeader">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Width" Value="230" />
</Style>
@@ -47,71 +47,79 @@
Classes="h1"
Text="{ext:Locale SettingsTabHotkeysHotkeys}" />
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysToggleVSyncModeHotkey}" />
<TextBlock Text="{ext:Locale SettingsTabHotkeysToggleVSyncModeHotkey}" Classes="settingHeader" />
<ToggleButton Name="ToggleVSyncMode">
<TextBlock Text="{Binding KeyboardHotkey.ToggleVSyncMode, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysScreenshotHotkey}" />
<TextBlock Text="{ext:Locale SettingsTabHotkeysScreenshotHotkey}" Classes="settingHeader" />
<ToggleButton Name="Screenshot">
<TextBlock Text="{Binding KeyboardHotkey.Screenshot, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysShowUiHotkey}" />
<TextBlock Text="{ext:Locale SettingsTabHotkeysShowUiHotkey}" Classes="settingHeader" />
<ToggleButton Name="ShowUI">
<TextBlock Text="{Binding KeyboardHotkey.ShowUI, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysPauseHotkey}" />
<TextBlock Text="{ext:Locale SettingsTabHotkeysPauseHotkey}" Classes="settingHeader" />
<ToggleButton Name="Pause">
<TextBlock Text="{Binding KeyboardHotkey.Pause, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysToggleMuteHotkey}" />
<TextBlock Text="{ext:Locale SettingsTabHotkeysToggleMuteHotkey}" Classes="settingHeader" />
<ToggleButton Name="ToggleMute">
<TextBlock Text="{Binding KeyboardHotkey.ToggleMute, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysResScaleUpHotkey}" />
<TextBlock Text="{ext:Locale SettingsTabHotkeysResScaleUpHotkey}" Classes="settingHeader" />
<ToggleButton Name="ResScaleUp">
<TextBlock Text="{Binding KeyboardHotkey.ResScaleUp, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysResScaleDownHotkey}" />
<TextBlock Text="{ext:Locale SettingsTabHotkeysResScaleDownHotkey}" Classes="settingHeader" />
<ToggleButton Name="ResScaleDown">
<TextBlock Text="{Binding KeyboardHotkey.ResScaleDown, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysVolumeUpHotkey}" />
<TextBlock Text="{ext:Locale SettingsTabHotkeysVolumeUpHotkey}" Classes="settingHeader" />
<ToggleButton Name="VolumeUp">
<TextBlock Text="{Binding KeyboardHotkey.VolumeUp, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysVolumeDownHotkey}" />
<TextBlock Text="{ext:Locale SettingsTabHotkeysVolumeDownHotkey}" Classes="settingHeader" />
<ToggleButton Name="VolumeDown">
<TextBlock Text="{Binding KeyboardHotkey.VolumeDown, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock Text="{ext:Locale SettingsTabHotkeysIncrementCustomVSyncIntervalHotkey}" />
<TextBlock Text="{ext:Locale SettingsTabHotkeysIncrementCustomVSyncIntervalHotkey}" Classes="settingHeader" />
<ToggleButton Name="CustomVSyncIntervalIncrement">
<TextBlock Text="{Binding KeyboardHotkey.CustomVSyncIntervalIncrement, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock Text="{ext:Locale SettingsTabHotkeysDecrementCustomVSyncIntervalHotkey}" />
<TextBlock Text="{ext:Locale SettingsTabHotkeysDecrementCustomVSyncIntervalHotkey}" Classes="settingHeader" />
<ToggleButton Name="CustomVSyncIntervalDecrement">
<TextBlock Text="{Binding KeyboardHotkey.CustomVSyncIntervalDecrement, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{ext:Locale SettingsTabHotkeysTurboMode}" Classes="settingHeader" />
<ToggleButton Name="TurboMode">
<TextBlock Text="{Binding KeyboardHotkey.TurboMode, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
<TextBlock Text="{ext:Locale SettingsTabHotkeysOnlyWhilePressed}" Margin="10,0" />
<CheckBox IsChecked="{Binding KeyboardHotkey.TurboModeWhileHeld}" />
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>

View File

@@ -116,6 +116,9 @@ namespace Ryujinx.Ava.UI.Views.Settings
case "CustomVSyncIntervalDecrement":
viewModel.KeyboardHotkey.CustomVSyncIntervalDecrement = buttonValue.AsHidType<Key>();
break;
case "TurboMode":
viewModel.KeyboardHotkey.TurboMode = buttonValue.AsHidType<Key>();
break;
}
}
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,10 +23,8 @@ using UserId = LibHac.Fs.UserId;
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 HorizonClient _horizonClient;
private VirtualFileSystem _virtualFileSystem;
@@ -66,7 +64,7 @@ namespace Ryujinx.Ava.UI.Views.User
public void LoadSaves()
{
ViewModel.Saves.Clear();
Dispatcher.UIThread.Post(() => ViewModel.Saves.Clear());
ObservableCollection<SaveModel> saves = [];
SaveDataFilter saveDataFilter = SaveDataFilter.Make(
programId: default,

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
<Window
x:Class="Ryujinx.Ava.UI.Controls.UpdateWaitWindow"
x:Class="Ryujinx.Ava.UI.Windows.UpdateWaitWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

View File

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

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// <summary>
/// The current version of the file format
/// </summary>
public const int CurrentVersion = 67;
public const int CurrentVersion = 68;
/// <summary>
/// Version of the configuration file format
@@ -258,6 +258,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// Enables or disables low-power profiled translation cache persistency loading
/// </summary>
public bool EnableLowPowerPtc { get; set; }
/// <summary>
/// Clock tick scalar, in percent points (100 = 1.0).
/// </summary>
public long TickScalar { get; set; }
/// <summary>
/// Enables or disables guest Internet access

View File

@@ -94,6 +94,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
System.EnableDockedMode.Value = cff.DockedMode;
System.EnablePtc.Value = cff.EnablePtc;
System.EnableLowPowerPtc.Value = cff.EnableLowPowerPtc;
System.TickScalar.Value = cff.TickScalar;
System.EnableInternetAccess.Value = cff.EnableInternetAccess;
System.EnableFsIntegrityChecks.Value = cff.EnableFsIntegrityChecks;
System.FsGlobalAccessLogMode.Value = cff.FsGlobalAccessLogMode;
@@ -441,7 +442,27 @@ namespace Ryujinx.Ava.Utilities.Configuration
(64, static cff => cff.LoggingEnableAvalonia = false),
(65, static cff => cff.UpdateCheckerType = cff.CheckUpdatesOnStart ? UpdaterType.PromptAtStartup : UpdaterType.Off),
(66, static cff => cff.DisableInputWhenOutOfFocus = false),
(67, static cff => cff.FocusLostActionType = cff.DisableInputWhenOutOfFocus ? FocusLostType.BlockInput : FocusLostType.DoNothing)
(67, static cff => cff.FocusLostActionType = cff.DisableInputWhenOutOfFocus ? FocusLostType.BlockInput : FocusLostType.DoNothing),
(68, static cff =>
{
cff.TickScalar = 200;
cff.Hotkeys = new KeyboardHotkeys
{
ToggleVSyncMode = cff.Hotkeys.ToggleVSyncMode,
Screenshot = cff.Hotkeys.Screenshot,
ShowUI = cff.Hotkeys.ShowUI,
Pause = cff.Hotkeys.Pause,
ToggleMute = cff.Hotkeys.ToggleMute,
ResScaleUp = cff.Hotkeys.ResScaleUp,
ResScaleDown = cff.Hotkeys.ResScaleDown,
VolumeUp = cff.Hotkeys.VolumeUp,
VolumeDown = cff.Hotkeys.VolumeDown,
CustomVSyncIntervalIncrement = cff.Hotkeys.CustomVSyncIntervalIncrement,
CustomVSyncIntervalDecrement = cff.Hotkeys.CustomVSyncIntervalDecrement,
TurboMode = Key.Unbound,
TurboModeWhileHeld = false
};
})
);
}
}

View File

@@ -335,6 +335,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// Enables or disables persistent profiled translation cache
/// </summary>
public ReactiveObject<bool> EnablePtc { get; private set; }
/// <summary>
/// Clock tick scalar, in percent points (100 = 1.0).
/// </summary>
public ReactiveObject<long> TickScalar { get; set; }
/// <summary>
/// Enables or disables low-power persistent profiled translation cache loading
@@ -411,6 +416,15 @@ namespace Ryujinx.Ava.Utilities.Configuration
EnableLowPowerPtc.LogChangesToValue(nameof(EnableLowPowerPtc));
EnableLowPowerPtc.Event += (_, evnt)
=> Optimizations.LowPower = evnt.NewValue;
TickScalar = new ReactiveObject<long>();
TickScalar.LogChangesToValue(nameof(TickScalar));
TickScalar.Event += (_, evnt) =>
{
if (Switch.Shared is null)
return;
Switch.Shared.Configuration.TickScalar = evnt.NewValue;
};
EnableInternetAccess = new ReactiveObject<bool>();
EnableInternetAccess.LogChangesToValue(nameof(EnableInternetAccess));
EnableFsIntegrityChecks = new ReactiveObject<bool>();

View File

@@ -73,6 +73,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
EnableColorSpacePassthrough = Graphics.EnableColorSpacePassthrough,
EnablePtc = System.EnablePtc,
EnableLowPowerPtc = System.EnableLowPowerPtc,
TickScalar = System.TickScalar,
EnableInternetAccess = System.EnableInternetAccess,
EnableFsIntegrityChecks = System.EnableFsIntegrityChecks,
FsGlobalAccessLogMode = System.FsGlobalAccessLogMode,
@@ -261,6 +262,10 @@ namespace Ryujinx.Ava.Utilities.Configuration
ResScaleDown = Key.Unbound,
VolumeUp = Key.Unbound,
VolumeDown = Key.Unbound,
CustomVSyncIntervalIncrement = Key.Unbound,
CustomVSyncIntervalDecrement = Key.Unbound,
TurboMode = Key.Unbound,
TurboModeWhileHeld = false
};
Hid.RainbowSpeed.Value = 1f;
Hid.InputConfig.Value =
@@ -327,5 +332,5 @@ namespace Ryujinx.Ava.Utilities.Configuration
return GraphicsBackend.OpenGl;
}
}
}
}
}