misc: Created generic type RyujinxControl to allow for more unified control view model definitions

This commit is contained in:
Evan Husted 2025-03-02 21:24:39 -06:00
parent 69d79322bb
commit da3f4e1d3a
25 changed files with 136 additions and 155 deletions

View File

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

View File

@ -26,6 +26,7 @@ namespace Ryujinx.Ava.UI.Controls
{ {
public class ApplicationContextMenu : MenuFlyout public class ApplicationContextMenu : MenuFlyout
{ {
public ApplicationContextMenu() public ApplicationContextMenu()
{ {
InitializeComponent(); InitializeComponent();

View File

@ -14,7 +14,7 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Controls namespace Ryujinx.Ava.UI.Controls
{ {
public partial class ApplicationDataView : UserControl public partial class ApplicationDataView : RyujinxControl<ApplicationDataViewModel>
{ {
public static async Task Show(ApplicationData appData) public static async Task Show(ApplicationData appData)
{ {
@ -25,7 +25,7 @@ namespace Ryujinx.Ava.UI.Controls
SecondaryButtonText = string.Empty, SecondaryButtonText = string.Empty,
CloseButtonText = LocaleManager.Instance[LocaleKeys.SettingsButtonClose], CloseButtonText = LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
MinWidth = 256, MinWidth = 256,
Content = new ApplicationDataView { DataContext = new ApplicationDataViewModel(appData) } Content = new ApplicationDataView { ViewModel = new ApplicationDataViewModel(appData) }
}; };
Style closeButton = new(x => x.Name("CloseButton")); Style closeButton = new(x => x.Name("CloseButton"));

View File

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

View File

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

View File

@ -10,7 +10,7 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Controls namespace Ryujinx.Ava.UI.Controls
{ {
public partial class DlcSelectView : UserControl public partial class DlcSelectView : RyujinxControl<DlcSelectViewModel>
{ {
public DlcSelectView() public DlcSelectView()
{ {
@ -28,7 +28,7 @@ namespace Ryujinx.Ava.UI.Controls
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue], PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue],
SecondaryButtonText = string.Empty, SecondaryButtonText = string.Empty,
CloseButtonText = string.Empty, CloseButtonText = string.Empty,
Content = new DlcSelectView { DataContext = viewModel } Content = new DlcSelectView { ViewModel = viewModel }
}; };
Style closeButton = new(x => x.Name("CloseButton")); Style closeButton = new(x => x.Name("CloseButton"));

View File

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

View File

@ -0,0 +1,18 @@
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 => (TViewModel)DataContext ?? throw new InvalidOperationException(
$"Underlying DataContext is not of type {typeof(TViewModel).AsPrettyString()}; " +
$"Actual type is {DataContext?.GetType().AsPrettyString()}");
set => DataContext = value;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ using Gommon;
using LibHac.Common; using LibHac.Common;
using LibHac.Ns; using LibHac.Ns;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
@ -25,10 +26,9 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Views.Main namespace Ryujinx.Ava.UI.Views.Main
{ {
public partial class MainMenuBarView : UserControl public partial class MainMenuBarView : RyujinxControl<MainWindowViewModel>
{ {
public MainWindow Window { get; private set; } public MainWindow Window { get; private set; }
public MainWindowViewModel ViewModel { get; private set; }
public MainMenuBarView() public MainMenuBarView()
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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