diff --git a/docs/compatibility.csv b/docs/compatibility.csv
index a3c9aa619..7b3f814c5 100644
--- a/docs/compatibility.csv
+++ b/docs/compatibility.csv
@@ -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
diff --git a/src/Ryujinx/Common/ApplicationHelper.cs b/src/Ryujinx/Common/ApplicationHelper.cs
index f4f76d0d3..86e5ee310 100644
--- a/src/Ryujinx/Common/ApplicationHelper.cs
+++ b/src/Ryujinx/Common/ApplicationHelper.cs
@@ -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;
diff --git a/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml b/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml
index d929cc501..20d466031 100644
--- a/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml
+++ b/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml
@@ -17,12 +17,8 @@
-
-
-
-
-
-
+
+
{
- public ProfileSelectorDialogViewModel ViewModel { get; set; }
-
public ProfileSelectorDialog(ProfileSelectorDialogViewModel viewModel)
{
DataContext = ViewModel = viewModel;
diff --git a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs
index b2beb23e0..cd6700aea 100644
--- a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs
+++ b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs
@@ -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();
diff --git a/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml.cs b/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml.cs
index fdfc588e2..12c6a9daf 100644
--- a/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml.cs
+++ b/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml.cs
@@ -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
{
public AccountManager AccountManager { get; }
public ContentManager ContentManager { get; }
public VirtualFileSystem VirtualFileSystem { get; }
public HorizonClient HorizonClient { get; }
- public UserProfileViewModel ViewModel { get; set; }
public NavigationDialogHost()
{
diff --git a/src/Ryujinx/UI/Controls/RyujinxControl.cs b/src/Ryujinx/UI/Controls/RyujinxControl.cs
new file mode 100644
index 000000000..748d7ed94
--- /dev/null
+++ b/src/Ryujinx/UI/Controls/RyujinxControl.cs
@@ -0,0 +1,24 @@
+using Avalonia.Controls;
+using Gommon;
+using Ryujinx.Ava.UI.ViewModels;
+using System;
+
+namespace Ryujinx.Ava.UI.Controls
+{
+ public class RyujinxControl : 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;
+ }
+ }
+}
diff --git a/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs b/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs
index b523e1143..0fd290b13 100644
--- a/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs
+++ b/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs
@@ -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;
diff --git a/src/Ryujinx/UI/Helpers/MiniCommand.cs b/src/Ryujinx/UI/Helpers/MiniCommand.cs
deleted file mode 100644
index 9782aa69d..000000000
--- a/src/Ryujinx/UI/Helpers/MiniCommand.cs
+++ /dev/null
@@ -1,72 +0,0 @@
-using System;
-using System.Threading.Tasks;
-using System.Windows.Input;
-
-namespace Ryujinx.Ava.UI.Helpers
-{
- public sealed class MiniCommand : MiniCommand, ICommand
- {
- private readonly Action _callback;
- private bool _busy;
- private readonly Func _asyncCallback;
-
- public MiniCommand(Action callback)
- {
- _callback = callback;
- }
-
- public MiniCommand(Func 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