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(_ => callback()); - public static MiniCommand Create(Action callback) => new MiniCommand(callback); - public static MiniCommand CreateFromTask(Func callback) => new MiniCommand(_ => callback()); - public static MiniCommand CreateFromTask(Func callback) => new MiniCommand(callback); - - public abstract bool CanExecute(object parameter); - public abstract void Execute(object parameter); - public abstract event EventHandler CanExecuteChanged; - } -} diff --git a/src/Ryujinx/UI/Models/Input/StickVisualizer.cs b/src/Ryujinx/UI/Models/Input/StickVisualizer.cs new file mode 100644 index 000000000..b7e9ec331 --- /dev/null +++ b/src/Ryujinx/UI/Models/Input/StickVisualizer.cs @@ -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); + } + } +} diff --git a/src/Ryujinx/UI/Renderer/RendererHost.cs b/src/Ryujinx/UI/Renderer/RendererHost.cs index 69275ecb2..7352dbdb5 100644 --- a/src/Ryujinx/UI/Renderer/RendererHost.cs +++ b/src/Ryujinx/UI/Renderer/RendererHost.cs @@ -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() diff --git a/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs index 4ff088d7b..2aaddb6d7 100644 --- a/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs @@ -1,5 +1,9 @@ using Avalonia.Svg.Skia; 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.Views.Input; using Ryujinx.Common.Utilities; @@ -10,8 +14,30 @@ namespace Ryujinx.Ava.UI.ViewModels.Input { 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; public bool IsLeft { @@ -37,14 +63,15 @@ namespace Ryujinx.Ava.UI.ViewModels.Input } public bool HasSides => IsLeft ^ IsRight; - + [ObservableProperty] private SvgImage _image; - + public InputViewModel ParentModel { get; } - - public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config) + + public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config, StickVisualizer visualizer) { ParentModel = model; + Visualizer = visualizer; model.NotifyChangesEvent += OnParentModelChanged; OnParentModelChanged(); config.PropertyChanged += (_, args) => diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index f56a154ea..1b0291fb9 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -49,7 +49,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input private int _controller; private string _controllerImage; private int _device; - [ObservableProperty] private object _configViewModel; + private object _configViewModel; [ObservableProperty] private string _profileName; private bool _isLoaded; @@ -74,6 +74,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input OnPropertiesChanged(nameof(HasLed), nameof(CanClearLed)); } } + public StickVisualizer VisualStick { get; private set; } public ObservableCollection PlayerIndexes { get; set; } public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; } @@ -106,6 +107,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public event Action NotifyChangesEvent; + public string _profileChoose; public string ProfileChoose { @@ -121,6 +123,19 @@ namespace Ryujinx.Ava.UI.ViewModels.Input } } + public object ConfigViewModel + { + get => _configViewModel; + set + { + _configViewModel = value; + + VisualStick.UpdateConfig(value); + + OnPropertyChanged(); + } + } + public PlayerIndex PlayerIdChoose { get => _playerIdChoose; @@ -319,6 +334,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input Devices = []; ProfilesList = []; DeviceList = []; + VisualStick = new StickVisualizer(this); ControllerImage = ProControllerResource; @@ -339,12 +355,12 @@ namespace Ryujinx.Ava.UI.ViewModels.Input 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) { - ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig)); + ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig), VisualStick); } FindPairedDevice(); @@ -1049,6 +1065,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input _mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates(); + VisualStick.Dispose(); + SelectedGamepad?.Dispose(); AvaloniaKeyboardDriver.Dispose(); diff --git a/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs index 5ff9bb578..bab8db7ce 100644 --- a/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs @@ -6,7 +6,29 @@ namespace Ryujinx.Ava.UI.ViewModels.Input { 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; public bool IsLeft @@ -38,9 +60,10 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public readonly InputViewModel ParentModel; - public KeyboardInputViewModel(InputViewModel model, KeyboardInputConfig config) + public KeyboardInputViewModel(InputViewModel model, KeyboardInputConfig config, StickVisualizer visualizer) { ParentModel = model; + Visualizer = visualizer; model.NotifyChangesEvent += OnParentModelChanged; OnParentModelChanged(); Config = config; diff --git a/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs b/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs index cc5b35dc6..6b4d7795d 100644 --- a/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs @@ -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 _avatarStore = new(); diff --git a/src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs b/src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs index 36a9a62f9..f9f9ca2f5 100644 --- a/src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs @@ -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; } diff --git a/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml index 49c2cfd4c..555ded9fc 100644 --- a/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml +++ b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml @@ -34,12 +34,7 @@ - - - - - + MinHeight="450" ColumnDefinitions="Auto,*,Auto"> - - - - - - - - + HorizontalAlignment="Stretch" ColumnDefinitions="*,*" RowDefinitions="*,*"> - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -345,8 +414,8 @@ Minimum="0" Value="{Binding Config.TriggerThreshold, Mode=TwoWay}" /> + Width="25" + Text="{Binding Config.TriggerThreshold, StringFormat=\{0:0.00\}}" /> @@ -438,11 +507,7 @@ CornerRadius="5" VerticalAlignment="Bottom" HorizontalAlignment="Stretch"> - - - - - + - - - - - + - - - - - + - - - - - - - - + HorizontalAlignment="Stretch" ColumnDefinitions="*,*" RowDefinitions="*,*"> { private ButtonKeyAssigner _currentAssigner; @@ -224,20 +225,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); diff --git a/src/Ryujinx/UI/Views/Input/InputView.axaml b/src/Ryujinx/UI/Views/Input/InputView.axaml index 89b2146a9..a8c453912 100644 --- a/src/Ryujinx/UI/Views/Input/InputView.axaml +++ b/src/Ryujinx/UI/Views/Input/InputView.axaml @@ -35,25 +35,15 @@ Margin="0 0 0 5" Orientation="Vertical" Spacing="5"> - - - - - - + - - - - - + VerticalAlignment="Center" ColumnDefinitions="Auto,*,Auto"> - - - - - - - + VerticalAlignment="Center" ColumnDefinitions="Auto,*,Auto,Auto,Auto"> - - - - - - + - - - - - + HorizontalAlignment="Stretch" ColumnDefinitions="Auto,*,Auto"> - - - - + VerticalAlignment="Center" ColumnDefinitions="Auto,*"> { private bool _dialogOpen; - private InputViewModel ViewModel { get; set; } public InputView() { - DataContext = ViewModel = new InputViewModel(this); + ViewModel = new InputViewModel(this); InitializeComponent(); } diff --git a/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml b/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml index e830412c1..22fd369d9 100644 --- a/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml +++ b/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml @@ -32,12 +32,7 @@ - - - - - + MinHeight="450" ColumnDefinitions="Auto,*,Auto"> - - - - - - - - + HorizontalAlignment="Stretch" ColumnDefinitions="*,*" RowDefinitions="*,*"> - + MinHeight="90"> + + + + + + + + + + + + + + + + + + + - - - - - - - - + HorizontalAlignment="Stretch" ColumnDefinitions="*,*" RowDefinitions="*,*"> { private ButtonKeyAssigner _currentAssigner; @@ -62,106 +63,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; - FlagInputConfigChanged(); + Button buttonValue = be.ButtonValue.Value; + ViewModel.ParentModel.IsModified = true; switch (button.Name) { case "ButtonZl": - viewModel.Config.ButtonZl = buttonValue.AsHidType(); + ViewModel.Config.ButtonZl = buttonValue.AsHidType(); break; case "ButtonL": - viewModel.Config.ButtonL = buttonValue.AsHidType(); + ViewModel.Config.ButtonL = buttonValue.AsHidType(); break; case "ButtonMinus": - viewModel.Config.ButtonMinus = buttonValue.AsHidType(); + ViewModel.Config.ButtonMinus = buttonValue.AsHidType(); break; case "LeftStickButton": - viewModel.Config.LeftStickButton = buttonValue.AsHidType(); + ViewModel.Config.LeftStickButton = buttonValue.AsHidType(); break; case "LeftStickUp": - viewModel.Config.LeftStickUp = buttonValue.AsHidType(); + ViewModel.Config.LeftStickUp = buttonValue.AsHidType(); break; case "LeftStickDown": - viewModel.Config.LeftStickDown = buttonValue.AsHidType(); + ViewModel.Config.LeftStickDown = buttonValue.AsHidType(); break; case "LeftStickRight": - viewModel.Config.LeftStickRight = buttonValue.AsHidType(); + ViewModel.Config.LeftStickRight = buttonValue.AsHidType(); break; case "LeftStickLeft": - viewModel.Config.LeftStickLeft = buttonValue.AsHidType(); + ViewModel.Config.LeftStickLeft = buttonValue.AsHidType(); break; case "DpadUp": - viewModel.Config.DpadUp = buttonValue.AsHidType(); + ViewModel.Config.DpadUp = buttonValue.AsHidType(); break; case "DpadDown": - viewModel.Config.DpadDown = buttonValue.AsHidType(); + ViewModel.Config.DpadDown = buttonValue.AsHidType(); break; case "DpadLeft": - viewModel.Config.DpadLeft = buttonValue.AsHidType(); + ViewModel.Config.DpadLeft = buttonValue.AsHidType(); break; case "DpadRight": - viewModel.Config.DpadRight = buttonValue.AsHidType(); + ViewModel.Config.DpadRight = buttonValue.AsHidType(); break; case "LeftButtonSr": - viewModel.Config.LeftButtonSr = buttonValue.AsHidType(); + ViewModel.Config.LeftButtonSr = buttonValue.AsHidType(); break; case "LeftButtonSl": - viewModel.Config.LeftButtonSl = buttonValue.AsHidType(); + ViewModel.Config.LeftButtonSl = buttonValue.AsHidType(); break; case "RightButtonSr": - viewModel.Config.RightButtonSr = buttonValue.AsHidType(); + ViewModel.Config.RightButtonSr = buttonValue.AsHidType(); break; case "RightButtonSl": - viewModel.Config.RightButtonSl = buttonValue.AsHidType(); + ViewModel.Config.RightButtonSl = buttonValue.AsHidType(); break; case "ButtonZr": - viewModel.Config.ButtonZr = buttonValue.AsHidType(); + ViewModel.Config.ButtonZr = buttonValue.AsHidType(); break; case "ButtonR": - viewModel.Config.ButtonR = buttonValue.AsHidType(); + ViewModel.Config.ButtonR = buttonValue.AsHidType(); break; case "ButtonPlus": - viewModel.Config.ButtonPlus = buttonValue.AsHidType(); + ViewModel.Config.ButtonPlus = buttonValue.AsHidType(); break; case "ButtonA": - viewModel.Config.ButtonA = buttonValue.AsHidType(); + ViewModel.Config.ButtonA = buttonValue.AsHidType(); break; case "ButtonB": - viewModel.Config.ButtonB = buttonValue.AsHidType(); + ViewModel.Config.ButtonB = buttonValue.AsHidType(); break; case "ButtonX": - viewModel.Config.ButtonX = buttonValue.AsHidType(); + ViewModel.Config.ButtonX = buttonValue.AsHidType(); break; case "ButtonY": - viewModel.Config.ButtonY = buttonValue.AsHidType(); + ViewModel.Config.ButtonY = buttonValue.AsHidType(); break; case "RightStickButton": - viewModel.Config.RightStickButton = buttonValue.AsHidType(); + ViewModel.Config.RightStickButton = buttonValue.AsHidType(); break; case "RightStickUp": - viewModel.Config.RightStickUp = buttonValue.AsHidType(); + ViewModel.Config.RightStickUp = buttonValue.AsHidType(); break; case "RightStickDown": - viewModel.Config.RightStickDown = buttonValue.AsHidType(); + ViewModel.Config.RightStickDown = buttonValue.AsHidType(); break; case "RightStickRight": - viewModel.Config.RightStickRight = buttonValue.AsHidType(); + ViewModel.Config.RightStickRight = buttonValue.AsHidType(); break; case "RightStickLeft": - viewModel.Config.RightStickLeft = buttonValue.AsHidType(); + ViewModel.Config.RightStickLeft = buttonValue.AsHidType(); break; } } diff --git a/src/Ryujinx/UI/Views/Input/LedInputView.axaml.cs b/src/Ryujinx/UI/Views/Input/LedInputView.axaml.cs index ce28f6c1c..3252ee7e2 100644 --- a/src/Ryujinx/UI/Views/Input/LedInputView.axaml.cs +++ b/src/Ryujinx/UI/Views/Input/LedInputView.axaml.cs @@ -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 { - 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(); diff --git a/src/Ryujinx/UI/Views/Input/MotionInputView.axaml b/src/Ryujinx/UI/Views/Input/MotionInputView.axaml index 9096a06d1..abab04285 100644 --- a/src/Ryujinx/UI/Views/Input/MotionInputView.axaml +++ b/src/Ryujinx/UI/Views/Input/MotionInputView.axaml @@ -11,11 +11,7 @@ x:Class="Ryujinx.Ava.UI.Views.Input.MotionInputView" x:DataType="viewModels:MotionInputViewModel" Focusable="True"> - - - - - + - - - - - + - - - - - - - - - + { - 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(); diff --git a/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml b/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml index 5f6cde5b5..98489aab0 100644 --- a/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml +++ b/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml @@ -10,11 +10,7 @@ x:Class="Ryujinx.Ava.UI.Views.Input.RumbleInputView" x:DataType="viewModels:RumbleInputViewModel" Focusable="True"> - - - - - + { - 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(); diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs index 2cab0915d..6c2cf6cae 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs @@ -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 { 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; diff --git a/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs index 78747013c..5a023910c 100644 --- a/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs +++ b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs @@ -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 { 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) diff --git a/src/Ryujinx/UI/Views/Main/MainViewControls.axaml.cs b/src/Ryujinx/UI/Views/Main/MainViewControls.axaml.cs index 7a83a36de..8ebc89f26 100644 --- a/src/Ryujinx/UI/Views/Main/MainViewControls.axaml.cs +++ b/src/Ryujinx/UI/Views/Main/MainViewControls.axaml.cs @@ -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 { - 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; } } diff --git a/src/Ryujinx/UI/Controls/ApplicationDataView.axaml b/src/Ryujinx/UI/Views/Misc/ApplicationDataView.axaml similarity index 99% rename from src/Ryujinx/UI/Controls/ApplicationDataView.axaml rename to src/Ryujinx/UI/Views/Misc/ApplicationDataView.axaml index 92e4d1ac3..a36b637ec 100644 --- a/src/Ryujinx/UI/Controls/ApplicationDataView.axaml +++ b/src/Ryujinx/UI/Views/Misc/ApplicationDataView.axaml @@ -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"> { 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")); diff --git a/src/Ryujinx/UI/Controls/ApplicationGridView.axaml b/src/Ryujinx/UI/Views/Misc/ApplicationGridView.axaml similarity index 93% rename from src/Ryujinx/UI/Controls/ApplicationGridView.axaml rename to src/Ryujinx/UI/Views/Misc/ApplicationGridView.axaml index bd20e2969..4d1db1507 100644 --- a/src/Ryujinx/UI/Controls/ApplicationGridView.axaml +++ b/src/Ryujinx/UI/Views/Misc/ApplicationGridView.axaml @@ -1,5 +1,5 @@ - - - - + - - - - - + { public static readonly RoutedEvent ApplicationOpenedEvent = RoutedEvent.Register(nameof(ApplicationOpened), RoutingStrategies.Bubble); diff --git a/src/Ryujinx/UI/Controls/ApplicationListView.axaml b/src/Ryujinx/UI/Views/Misc/ApplicationListView.axaml similarity index 98% rename from src/Ryujinx/UI/Controls/ApplicationListView.axaml rename to src/Ryujinx/UI/Views/Misc/ApplicationListView.axaml index 5ed7acc20..c9926301b 100644 --- a/src/Ryujinx/UI/Controls/ApplicationListView.axaml +++ b/src/Ryujinx/UI/Views/Misc/ApplicationListView.axaml @@ -1,5 +1,5 @@ - - - - + { public static readonly RoutedEvent ApplicationOpenedEvent = RoutedEvent.Register(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; diff --git a/src/Ryujinx/UI/Controls/DlcSelectView.axaml b/src/Ryujinx/UI/Views/Misc/DlcSelectView.axaml similarity index 98% rename from src/Ryujinx/UI/Controls/DlcSelectView.axaml rename to src/Ryujinx/UI/Views/Misc/DlcSelectView.axaml index 790c6dd3b..f44bc3261 100644 --- a/src/Ryujinx/UI/Controls/DlcSelectView.axaml +++ b/src/Ryujinx/UI/Views/Misc/DlcSelectView.axaml @@ -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"> { 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")); diff --git a/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml index b0edc51a5..0a77c8863 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml @@ -21,12 +21,7 @@ - - - - - - + diff --git a/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml index 2a46dcf49..7dd5211a7 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml @@ -177,12 +177,7 @@ - - - - - - + - - - - - - + - - - - - - - - - + { 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) diff --git a/src/Ryujinx/UI/Views/User/UserFirmwareAvatarSelectorView.axaml b/src/Ryujinx/UI/Views/User/UserFirmwareAvatarSelectorView.axaml index b6442918b..c22624fd5 100644 --- a/src/Ryujinx/UI/Views/User/UserFirmwareAvatarSelectorView.axaml +++ b/src/Ryujinx/UI/Views/User/UserFirmwareAvatarSelectorView.axaml @@ -20,13 +20,7 @@ - - - - - - + VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto,Auto"> { 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(); diff --git a/src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml b/src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml index 26e27176b..03aebec8f 100644 --- a/src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml +++ b/src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml @@ -17,12 +17,7 @@ - - - - - + VerticalAlignment="Center" RowDefinitions="Auto,70,Auto"> { private ContentManager _contentManager; private NavigationDialogHost _parent; private TempProfile _profile; - internal UserProfileImageSelectorViewModel ViewModel { get; private set; } - public UserProfileImageSelectorView() { InitializeComponent(); diff --git a/src/Ryujinx/UI/Views/User/UserRecovererView.axaml b/src/Ryujinx/UI/Views/User/UserRecovererView.axaml index f49444642..43d84787d 100644 --- a/src/Ryujinx/UI/Views/User/UserRecovererView.axaml +++ b/src/Ryujinx/UI/Views/User/UserRecovererView.axaml @@ -18,11 +18,7 @@ - - - - + VerticalAlignment="Stretch" RowDefinitions="*,Auto"> - - - - - + { private NavigationDialogHost _parent; diff --git a/src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml b/src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml index 576b15731..f04b8a7a3 100644 --- a/src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml +++ b/src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml @@ -19,19 +19,10 @@ - - - - - - + - - - - + HorizontalAlignment="Stretch" ColumnDefinitions="Auto,*"> - - - - + Margin="10,0, 0, 0" ColumnDefinitions="Auto,*">