Merge branch 'Master_PR' into 'master'

Improved interaction with "Input" settings.

See merge request ryubing/ryujinx!4
This commit is contained in:
Goodfeat 2025-03-27 06:51:30 +00:00
commit 6b7946377c
13 changed files with 520 additions and 83 deletions

View file

@ -21,6 +21,11 @@ namespace Ryujinx.Common.Configuration.Hid
/// </summary> /// </summary>
public string Id { get; set; } public string Id { get; set; }
/// <summary>
/// Controller name
/// </summary>
public string Name { get; set; }
/// <summary> /// <summary>
/// Controller's Type /// Controller's Type
/// </summary> /// </summary>

View file

@ -49,7 +49,6 @@
<TextBlock <TextBlock
Classes="globalConfigMarker"/> Classes="globalConfigMarker"/>
</StackPanel> </StackPanel>
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
<Style Selector="DropDownButton"> <Style Selector="DropDownButton">

View file

@ -6997,6 +6997,31 @@
"zh_TW": "輸入裝置" "zh_TW": "輸入裝置"
} }
}, },
{
"ID": "ControllerSettingsWaitingConnectDevice",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Configuration found:\n\nName:\t{0}\nGUID:\t{1}\n\n Waiting for controller connection...",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{ {
"ID": "ControllerSettingsRefresh", "ID": "ControllerSettingsRefresh",
"Translations": { "Translations": {
@ -7297,6 +7322,81 @@
"zh_TW": "新增" "zh_TW": "新增"
} }
}, },
{
"ID": "ControllerSettingsModifiedNotification",
"Translations": {
"ar_SA": "(تم التعديل!)",
"de_DE": "(modifiziert!)",
"el_GR": "(τροποποιημένο!)",
"en_US": "(Modified!)",
"es_ES": "(modificado!)",
"fr_FR": "(modifié!)",
"he_IL": "(שונה!)",
"it_IT": "(modificato!)",
"ja_JP": "(変更済み!)",
"ko_KR": "(수정됨!)",
"no_NO": "(modifisert!)",
"pl_PL": "(zmodyfikowane!)",
"pt_BR": "(modificado!)",
"ru_RU": "(изменено!)",
"sv_SE": "(ändrad!)",
"th_TH": "(แก้ไขแล้ว!)",
"tr_TR": "(değiştirildi!)",
"uk_UA": "(модифіковано!)",
"zh_CN": "(已修改!)",
"zh_TW": "(已修改!)"
}
},
{
"ID": "ControllerSettingsDisableDeviceForSaving",
"Translations": {
"ar_SA": "تم إعداد التحكم.\n\nفي انتظار اتصال وحدة التحكم...",
"de_DE": "Steuerung konfiguriert.\n\nWarten auf die Verbindung des Controllers...",
"el_GR": "Η διαχείριση έχει ρυθμιστεί.\n\nΑναμένεται σύνδεση του χειριστηρίου...",
"en_US": "Control configured.\n\nWaiting for controller connection...",
"es_ES": "Control configurado.\n\nEsperando la conexión del controlador...",
"fr_FR": "Contrôle configuré.\n\nEn attente de la connexion du contrôleur...",
"he_IL": "השליטה הוגדרה.\n\nממתין לחיבור הבקר...",
"it_IT": "Controllo configurato.\n\nIn attesa della connessione del controller...",
"ja_JP": "コントロールが設定されました。\n\nコントローラーの接続を待っています...",
"ko_KR": "제어가 설정되었습니다.\n\n컨트롤러 연결 대기 중...",
"no_NO": "Kontroll konfigurert.\n\nVenter på tilkobling av kontroller...",
"pl_PL": "Sterowanie skonfigurowane.\n\nOczekiwanie na połączenie kontrolera...",
"pt_BR": "Controle configurado.\n\nAguardando conexão do controle...",
"ru_RU": "Управление настроено.\n\nОжидается подключение контроллера...",
"sv_SE": "Kontroll konfigurerad.\n\nVäntar på anslutning av kontrollen...",
"th_TH": "การควบคุมได้รับการตั้งค่าแล้ว\n\nกำลังรอการเชื่อมต่อคอนโทรลเลอร์...",
"tr_TR": "Kontrol yapılandırıldı.\n\nKontrolcü bağlantısı bekleniyor...",
"uk_UA": "Керування налаштовано.\n\nОчікується підключення контролера...",
"zh_CN": "控制已配置。\n\n等待控制器连接...",
"zh_TW": "控制已設定。\n\n等待控制器連接..."
}
},
{
"ID": "ControllerSettingsUnlink",
"Translations": {
"ar_SA": "إلغاء الربط",
"de_DE": "Entkoppeln",
"el_GR": "Αποσύνδεση",
"en_US": "Unlink",
"es_ES": "Desvincular",
"fr_FR": "Dissocier",
"he_IL": "ניתוק קישור",
"it_IT": "Scollega",
"ja_JP": "リンク解除",
"ko_KR": "연결 해제",
"no_NO": "Frakoble",
"pl_PL": "Odłącz",
"pt_BR": "Desvincular",
"ru_RU": "Отвязать",
"sv_SE": "Koppla från",
"th_TH": "ยกเลิกการเชื่อมโยง",
"tr_TR": "Bağlantıyı Kes",
"uk_UA": "Відв'язати",
"zh_CN": "解除绑定",
"zh_TW": "解除綁定"
}
},
{ {
"ID": "ControllerSettingsRemove", "ID": "ControllerSettingsRemove",
"Translations": { "Translations": {
@ -11947,6 +12047,31 @@
"zh_TW": "儲存設定檔" "zh_TW": "儲存設定檔"
} }
}, },
{
"ID": "ControllerSettingsCancelCurrentChangesToolTip",
"Translations": {
"ar_SA": "إلغاء التغييرات الحالية",
"de_DE": "Aktuelle Änderungen abbrechen",
"el_GR": "Ακύρωση τρεχουσών αλλαγών",
"en_US": "Cancel current changes",
"es_ES": "Cancelar los cambios actuales",
"fr_FR": "Annuler les modifications en cours",
"he_IL": "ביטול השינויים הנוכחיים",
"it_IT": "Annulla le modifiche correnti",
"ja_JP": "現在の変更をキャンセル",
"ko_KR": "현재 변경 취소",
"no_NO": "Avbryt gjeldende endringer",
"pl_PL": "Anuluj bieżące zmiany",
"pt_BR": "Cancelar alterações atuais",
"ru_RU": "Отменить текущие изменения",
"sv_SE": "Avbryt aktuella ändringar",
"th_TH": "ยกเลิกการเปลี่ยนแปลงปัจจุบัน",
"tr_TR": "Geçerli değişiklikleri iptal et",
"uk_UA": "Скасувати поточні зміни",
"zh_CN": "取消当前更改",
"zh_TW": "取消當前變更"
}
},
{ {
"ID": "MenuBarFileToolsTakeScreenshot", "ID": "MenuBarFileToolsTakeScreenshot",
"Translations": { "Translations": {

View file

@ -274,6 +274,7 @@ namespace Ryujinx.Ava.Systems.Configuration
Version = InputConfig.CurrentVersion, Version = InputConfig.CurrentVersion,
Backend = InputBackendType.WindowKeyboard, Backend = InputBackendType.WindowKeyboard,
Id = "0", Id = "0",
Name = "Keyboard",
PlayerIndex = PlayerIndex.Player1, PlayerIndex = PlayerIndex.Player1,
ControllerType = ControllerType.ProController, ControllerType = ControllerType.ProController,
LeftJoycon = new LeftJoyconCommonConfig<Key> LeftJoycon = new LeftJoyconCommonConfig<Key>

View file

@ -22,6 +22,8 @@ namespace Ryujinx.Ava.UI.Models.Input
public float StrongRumble { get; set; } public float StrongRumble { get; set; }
public string Id { get; set; } public string Id { get; set; }
public string Name { get; set; }
public ControllerType ControllerType { get; set; } public ControllerType ControllerType { get; set; }
public PlayerIndex PlayerIndex { get; set; } public PlayerIndex PlayerIndex { get; set; }
@ -111,6 +113,7 @@ namespace Ryujinx.Ava.UI.Models.Input
if (config != null) if (config != null)
{ {
Id = config.Id; Id = config.Id;
Name = config.Name;
ControllerType = config.ControllerType; ControllerType = config.ControllerType;
PlayerIndex = config.PlayerIndex; PlayerIndex = config.PlayerIndex;
@ -201,6 +204,7 @@ namespace Ryujinx.Ava.UI.Models.Input
StandardControllerInputConfig config = new() StandardControllerInputConfig config = new()
{ {
Id = Id, Id = Id,
Name = Name,
Backend = InputBackendType.GamepadSDL2, Backend = InputBackendType.GamepadSDL2,
PlayerIndex = PlayerIndex, PlayerIndex = PlayerIndex,
ControllerType = ControllerType, ControllerType = ControllerType,

View file

@ -2,12 +2,14 @@ using CommunityToolkit.Mvvm.ComponentModel;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.Common.Configuration.Hid.Keyboard;
using System.Xml.Linq;
namespace Ryujinx.Ava.UI.Models.Input namespace Ryujinx.Ava.UI.Models.Input
{ {
public partial class KeyboardInputConfig : BaseModel public partial class KeyboardInputConfig : BaseModel
{ {
public string Id { get; set; } public string Id { get; set; }
public string Name { get; set; }
public ControllerType ControllerType { get; set; } public ControllerType ControllerType { get; set; }
public PlayerIndex PlayerIndex { get; set; } public PlayerIndex PlayerIndex { get; set; }
@ -53,6 +55,7 @@ namespace Ryujinx.Ava.UI.Models.Input
if (config != null) if (config != null)
{ {
Id = config.Id; Id = config.Id;
Name = config.Name;
ControllerType = config.ControllerType; ControllerType = config.ControllerType;
PlayerIndex = config.PlayerIndex; PlayerIndex = config.PlayerIndex;
@ -100,6 +103,7 @@ namespace Ryujinx.Ava.UI.Models.Input
StandardKeyboardInputConfig config = new() StandardKeyboardInputConfig config = new()
{ {
Id = Id, Id = Id,
Name = Name,
Backend = InputBackendType.WindowKeyboard, Backend = InputBackendType.WindowKeyboard,
PlayerIndex = PlayerIndex, PlayerIndex = PlayerIndex,
ControllerType = ControllerType, ControllerType = ControllerType,

View file

@ -93,16 +93,19 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public async void ShowMotionConfig() public async void ShowMotionConfig()
{ {
await MotionInputView.Show(this); await MotionInputView.Show(this);
ParentModel.IsModified = true;
} }
public async void ShowRumbleConfig() public async void ShowRumbleConfig()
{ {
await RumbleInputView.Show(this); await RumbleInputView.Show(this);
ParentModel.IsModified = true;
} }
public async void ShowLedConfig() public async void ShowLedConfig()
{ {
await LedInputView.Show(this); await LedInputView.Show(this);
ParentModel.IsModified = true;
} }
public void OnParentModelChanged() public void OnParentModelChanged()

View file

@ -51,6 +51,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
private int _device; private int _device;
private object _configViewModel; private object _configViewModel;
[ObservableProperty] private string _profileName; [ObservableProperty] private string _profileName;
[ObservableProperty] private bool _notificationIsVisible; // Automatically call the NotificationView property with OnPropertyChanged()
[ObservableProperty] private string _notificationText; // Automatically call the NotificationText property with OnPropertyChanged()
private bool _isLoaded; private bool _isLoaded;
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
@ -88,13 +90,40 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public bool IsKeyboard => !IsController; public bool IsKeyboard => !IsController;
public bool IsRight { get; set; } public bool IsRight { get; set; }
public bool IsLeft { get; set; } public bool IsLeft { get; set; }
public string RevertDeviceId { get; set; }
public bool HasLed => SelectedGamepad.Features.HasFlag(GamepadFeaturesFlag.Led); public bool HasLed => SelectedGamepad.Features.HasFlag(GamepadFeaturesFlag.Led);
public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense"); public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense");
public bool IsModified { get; set; } public bool _isChangeTrackingActive;
public bool _isModified;
public bool IsModified
{
get => _isModified;
set
{
_isModified = value;
OnPropertyChanged();
}
}
public event Action NotifyChangesEvent; public event Action NotifyChangesEvent;
public string _profileChoose;
public string ProfileChoose
{
get => _profileChoose;
set
{
// When you select a profile, the settings from the profile will be applied.
// To save the settings, you still need to click the apply button
_profileChoose = value;
LoadProfile();
OnPropertyChanged();
}
}
public object ConfigViewModel public object ConfigViewModel
{ {
get => _configViewModel; get => _configViewModel;
@ -121,13 +150,13 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
if (IsModified) if (IsModified)
{ {
_playerIdChoose = value; _playerIdChoose = value;
return; return;
} }
IsModified = false; IsModified = false;
_playerId = value; _playerId = value;
_isChangeTrackingActive = false;
if (!Enum.IsDefined<PlayerIndex>(_playerId)) if (!Enum.IsDefined<PlayerIndex>(_playerId))
{ {
@ -135,13 +164,13 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
} }
_isLoaded = false; _isLoaded = false;
LoadConfiguration(); LoadConfiguration();
LoadDevice(); LoadDevice();
LoadProfiles(); LoadProfiles();
RevertDeviceId = Devices[Device].Id;
_isLoaded = true; _isLoaded = true;
_isChangeTrackingActive = true;
OnPropertyChanged(); OnPropertyChanged();
} }
} }
@ -151,6 +180,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
get => _controller; get => _controller;
set set
{ {
MarkAsChanged();
_controller = value; _controller = value;
if (_controller == -1) if (_controller == -1)
@ -229,6 +260,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
get => _device; get => _device;
set set
{ {
MarkAsChanged();
_device = value < 0 ? 0 : value; _device = value < 0 ? 0 : value;
if (_device >= Devices.Count) if (_device >= Devices.Count)
@ -248,11 +281,13 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
} }
} }
FindPairedDeviceInConfigFile();
OnPropertyChanged(); OnPropertyChanged();
NotifyChanges(); NotifyChanges();
} }
} }
public InputConfig Config { get; set; } public InputConfig Config { get; set; }
public InputViewModel(UserControl owner) : this() public InputViewModel(UserControl owner) : this()
@ -274,6 +309,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
PlayerId = PlayerIndex.Player1; PlayerId = PlayerIndex.Player1;
} }
_isChangeTrackingActive = true;
} }
public InputViewModel() public InputViewModel()
@ -311,8 +348,50 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig), VisualStick); ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig), VisualStick);
} }
FindPairedDeviceInConfigFile();
} }
private void FindPairedDeviceInConfigFile()
{
// This function allows you to output a message about the device configuration found in the file
// NOTE: if the configuration is found, we display the message "Waiting for controller connection",
// but only if the id gamepad belongs to the selected player
NotificationIsVisible = Config != null && Devices.FirstOrDefault(d => d.Id == Config.Id).Id != Config.Id && Config.PlayerIndex == PlayerId;
if (NotificationIsVisible)
{
if (string.IsNullOrEmpty(Config.Name))
{
NotificationText = $"{LocaleManager.Instance[LocaleKeys.ControllerSettingsWaitingConnectDevice].Format("No information", Config.Id)}";
}
else
{
NotificationText = $"{LocaleManager.Instance[LocaleKeys.ControllerSettingsWaitingConnectDevice].Format(Config.Name, Config.Id)}";
}
}
}
private void MarkAsChanged()
{
//If tracking is active, then allow changing the modifier
if (!IsModified && _isChangeTrackingActive)
{
RevertDeviceId = Devices[Device].Id; // Remember the device to undo changes
IsModified = true;
}
}
public void UnlinkDevice()
{
// "Disabled" mode is available after unbinding the device
// NOTE: the IsModified flag to be able to apply the settings.
NotificationIsVisible = false;
IsModified = true;
}
public void LoadDevice() public void LoadDevice()
{ {
if (Config == null || Config.Backend == InputBackendType.Invalid) if (Config == null || Config.Backend == InputBackendType.Invalid)
@ -378,14 +457,34 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
} }
} }
private void HandleOnGamepadDisconnected(string id) private async void HandleOnGamepadDisconnected(string id)
{ {
Dispatcher.UIThread.Post(LoadDevices); _isChangeTrackingActive = false; // Disable configuration change tracking
await Dispatcher.UIThread.InvokeAsync(() =>
{
LoadDevices();
IsModified = true;
RevertChanges();
FindPairedDeviceInConfigFile();
_isChangeTrackingActive = true; // Enable configuration change tracking
return System.Threading.Tasks.Task.CompletedTask;
});
} }
private void HandleOnGamepadConnected(string id) private async void HandleOnGamepadConnected(string id)
{ {
Dispatcher.UIThread.Post(LoadDevices); _isChangeTrackingActive = false; // Disable configuration change tracking
await Dispatcher.UIThread.InvokeAsync(() =>
{
LoadDevices();
IsModified = true;
RevertChanges();
_isChangeTrackingActive = true;// Enable configuration change tracking
});
} }
private string GetCurrentGamepadId() private string GetCurrentGamepadId()
@ -558,12 +657,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
if (activeDevice.Type == DeviceType.Keyboard) if (activeDevice.Type == DeviceType.Keyboard)
{ {
string id = activeDevice.Id; string id = activeDevice.Id;
string name = activeDevice.Name;
config = new StandardKeyboardInputConfig config = new StandardKeyboardInputConfig
{ {
Version = InputConfig.CurrentVersion, Version = InputConfig.CurrentVersion,
Backend = InputBackendType.WindowKeyboard, Backend = InputBackendType.WindowKeyboard,
Id = id, Id = id,
Name = name,
ControllerType = ControllerType.ProController, ControllerType = ControllerType.ProController,
LeftJoycon = new LeftJoyconCommonConfig<Key> LeftJoycon = new LeftJoyconCommonConfig<Key>
{ {
@ -613,12 +714,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
bool isNintendoStyle = Devices.ToList().FirstOrDefault(x => x.Id == activeDevice.Id).Name.Contains("Nintendo"); bool isNintendoStyle = Devices.ToList().FirstOrDefault(x => x.Id == activeDevice.Id).Name.Contains("Nintendo");
string id = activeDevice.Id.Split(" ")[0]; string id = activeDevice.Id.Split(" ")[0];
string name = activeDevice.Name;
config = new StandardControllerInputConfig config = new StandardControllerInputConfig
{ {
Version = InputConfig.CurrentVersion, Version = InputConfig.CurrentVersion,
Backend = InputBackendType.GamepadSDL2, Backend = InputBackendType.GamepadSDL2,
Id = id, Id = id,
Name = name,
ControllerType = ControllerType.ProController, ControllerType = ControllerType.ProController,
DeadzoneLeft = 0.1f, DeadzoneLeft = 0.1f,
DeadzoneRight = 0.1f, DeadzoneRight = 0.1f,
@ -688,6 +791,12 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
return config; return config;
} }
public void LoadProfileButton()
{
LoadProfile();
IsModified = true;
}
public async void LoadProfile() public async void LoadProfile()
{ {
if (Device == 0) if (Device == 0)
@ -739,9 +848,11 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
_isLoaded = false; _isLoaded = false;
config.Id = Config.Id; // Set current device id instead of changing device(independent profiles)
LoadConfiguration(config); LoadConfiguration(config);
LoadDevice(); //LoadDevice(); This line of code hard-links profiles to controllers, the commented line allows profiles to be applied to all controllers
_isLoaded = true; _isLoaded = true;
@ -751,6 +862,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public async void SaveProfile() public async void SaveProfile()
{ {
if (Device == 0) if (Device == 0)
{ {
return; return;
@ -793,12 +905,15 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
await File.WriteAllTextAsync(path, jsonString); await File.WriteAllTextAsync(path, jsonString);
LoadProfiles(); LoadProfiles();
ProfileChoose = ProfileName; // Show new profile
} }
else else
{ {
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]); await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]);
} }
} }
} }
public async void RemoveProfile() public async void RemoveProfile()
@ -825,13 +940,32 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
} }
LoadProfiles(); LoadProfiles();
ProfileChoose = ProfilesList[0].ToString(); // Show default profile
} }
} }
public void RevertChanges()
{
Device = Devices.ToList().FindIndex(d => d.Id == RevertDeviceId);
LoadDevice();
LoadConfiguration();
OnPropertyChanged();
IsModified = false;
}
public void Save() public void Save()
{ {
if (!IsModified)
{
return; //If the input settings were not touched, then do nothing
}
IsModified = false; IsModified = false;
RevertDeviceId = Devices[Device].Id; // Remember selected device after saving
List <InputConfig> newConfig = []; List <InputConfig> newConfig = [];
newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value); newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value);
@ -862,6 +996,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
: (ConfigViewModel as ControllerInputViewModel).Config.GetConfig(); : (ConfigViewModel as ControllerInputViewModel).Config.GetConfig();
config.ControllerType = Controllers[_controller].Type; config.ControllerType = Controllers[_controller].Type;
config.PlayerIndex = _playerId; config.PlayerIndex = _playerId;
config.Name = device.Name;
int i = newConfig.FindIndex(x => x.PlayerIndex == PlayerId); int i = newConfig.FindIndex(x => x.PlayerIndex == PlayerId);
if (i == -1) if (i == -1)

View file

@ -65,7 +65,8 @@ namespace Ryujinx.Ava.UI.Views.Input
if (!float.IsNaN(_changeSlider) && _changeSlider != (float)check.Value) if (!float.IsNaN(_changeSlider) && _changeSlider != (float)check.Value)
{ {
(DataContext as ControllerInputViewModel)!.ParentModel.IsModified = true; FlagInputConfigChanged();
_changeSlider = (float)check.Value; _changeSlider = (float)check.Value;
} }
} }
@ -75,7 +76,8 @@ namespace Ryujinx.Ava.UI.Views.Input
{ {
if (sender is CheckBox { IsPointerOver: true }) if (sender is CheckBox { IsPointerOver: true })
{ {
(DataContext as ControllerInputViewModel)!.ParentModel.IsModified = true; FlagInputConfigChanged();
_currentAssigner?.Cancel(); _currentAssigner?.Cancel();
_currentAssigner = null; _currentAssigner = null;
} }
@ -115,7 +117,7 @@ namespace Ryujinx.Ava.UI.Views.Input
if (e.ButtonValue.HasValue) if (e.ButtonValue.HasValue)
{ {
Button buttonValue = e.ButtonValue.Value; Button buttonValue = e.ButtonValue.Value;
viewModel.ParentModel.IsModified = true; FlagInputConfigChanged();
switch (button.Name) switch (button.Name)
{ {
@ -209,6 +211,11 @@ namespace Ryujinx.Ava.UI.Views.Input
} }
} }
private void FlagInputConfigChanged()
{
(DataContext as ControllerInputViewModel)!.ParentModel.IsModified = true;
}
private void MouseClick(object sender, PointerPressedEventArgs e) private void MouseClick(object sender, PointerPressedEventArgs e)
{ {
bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed; bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed;
@ -232,7 +239,6 @@ namespace Ryujinx.Ava.UI.Views.Input
{ {
gamepad?.ClearLed(); gamepad?.ClearLed();
} }
_currentAssigner?.Cancel(); _currentAssigner?.Cancel();
_currentAssigner = null; _currentAssigner = null;
} }

View file

@ -41,13 +41,20 @@
Grid.Column="0" Grid.Column="0"
Margin="2" Margin="2"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Center" ColumnDefinitions="Auto,*"> VerticalAlignment="Center" ColumnDefinitions="Auto,*,Auto">
<TextBlock <StackPanel
Orientation="Vertical"
Margin="5,0,10,0" Margin="5,0,10,0"
Width="90"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
Width="90">
<TextBlock
Text="{ext:Locale ControllerSettingsPlayer}" /> Text="{ext:Locale ControllerSettingsPlayer}" />
<TextBlock
Classes="pending"
Text ="{ext:Locale ControllerSettingsModifiedNotification}"
IsVisible="{Binding IsModified}"/>
</StackPanel>
<ComboBox <ComboBox
Grid.Column="1" Grid.Column="1"
Name="PlayerIndexBox" Name="PlayerIndexBox"
@ -62,6 +69,18 @@
</DataTemplate> </DataTemplate>
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
<Button
Grid.Column="2"
MinWidth="0"
Margin="5,0,0,0"
VerticalAlignment="Center"
ToolTip.Tip="{ext:Locale ControllerSettingsCancelCurrentChangesToolTip}"
Command="{Binding RevertChanges}">
<ui:SymbolIcon
Symbol="Undo"
FontSize="15"
Height="20" />
</Button>
</Grid> </Grid>
<!-- Profile Selection --> <!-- Profile Selection -->
<Grid <Grid
@ -81,7 +100,8 @@
Name="ProfileBox" Name="ProfileBox"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Center" VerticalAlignment="Center"
SelectedIndex="0" SelectedItem="{Binding ProfileChoose, Mode=TwoWay}"
SelectionChanged="ComboBox_SelectionChanged"
ItemsSource="{Binding ProfilesList}" ItemsSource="{Binding ProfilesList}"
Text="{Binding ProfileName, Mode=TwoWay}" /> Text="{Binding ProfileName, Mode=TwoWay}" />
<Button <Button
@ -90,7 +110,7 @@
Margin="5,0,0,0" Margin="5,0,0,0"
VerticalAlignment="Center" VerticalAlignment="Center"
ToolTip.Tip="{ext:Locale ControllerSettingsLoadProfileToolTip}" ToolTip.Tip="{ext:Locale ControllerSettingsLoadProfileToolTip}"
Command="{Binding LoadProfile}"> Command="{Binding LoadProfileButton}">
<ui:SymbolIcon <ui:SymbolIcon
Symbol="View" Symbol="View"
FontSize="15" FontSize="15"
@ -148,7 +168,7 @@
MinWidth="0" MinWidth="0"
Margin="5,0,0,0" Margin="5,0,0,0"
VerticalAlignment="Center" VerticalAlignment="Center"
Command="{Binding LoadDevices}"> Command="{Binding LoadDevice}">
<ui:SymbolIcon <ui:SymbolIcon
Symbol="Refresh" Symbol="Refresh"
FontSize="15" FontSize="15"
@ -181,6 +201,28 @@
</Grid> </Grid>
</Grid> </Grid>
</StackPanel> </StackPanel>
<ContentControl IsVisible="{Binding NotificationIsVisible}">
<ContentControl.Content>
<StackPanel>
<TextBlock
Margin="5,20,0,0"
Text="{Binding NotificationText}" />
<Button
MinWidth="0"
Width="90"
Height="27"
Margin="0,10,0,0"
VerticalAlignment="Center"
Command="{Binding UnlinkDevice}">
<TextBlock
Text="{ext:Locale ControllerSettingsUnlink}"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
</Button>
</StackPanel>
</ContentControl.Content>
</ContentControl>
<ContentControl Content="{Binding ConfigViewModel}" IsVisible="{Binding ShowSettings}"> <ContentControl Content="{Binding ConfigViewModel}" IsVisible="{Binding ShowSettings}">
<ContentControl.DataTemplates> <ContentControl.DataTemplates>
<DataTemplate DataType="viewModels:ControllerInputViewModel"> <DataTemplate DataType="viewModels:ControllerInputViewModel">

View file

@ -1,4 +1,5 @@
using Avalonia.Controls; using Avalonia.Controls;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls; using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
@ -63,13 +64,22 @@ namespace Ryujinx.Ava.UI.Views.Input
return; return;
} }
ViewModel.IsModified = false;
ViewModel.PlayerId = ViewModel.PlayerIdChoose; ViewModel.PlayerId = ViewModel.PlayerIdChoose;
ViewModel.IsModified = false;
} }
} }
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is FAComboBox faComboBox)
{
faComboBox.IsDropDownOpen = false;
ViewModel.IsModified = true;
}
}
public void Dispose() public void Dispose()
{ {
ViewModel.Dispose(); ViewModel.Dispose();

View file

@ -9,6 +9,8 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels.Input; using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Input; using Ryujinx.Input;
using Ryujinx.Input.Assigner; using Ryujinx.Input.Assigner;
using System.Collections.Generic;
using System;
using Button = Ryujinx.Input.Button; using Button = Ryujinx.Input.Button;
using Key = Ryujinx.Common.Configuration.Hid.Key; using Key = Ryujinx.Common.Configuration.Hid.Key;
@ -186,11 +188,63 @@ namespace Ryujinx.Ava.UI.Views.Input
{ {
bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed; bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed;
bool shouldRemoveBinding = e.GetCurrentPoint(this).Properties.IsRightButtonPressed;
if (shouldRemoveBinding)
{
DeleteBind();
}
_currentAssigner?.Cancel(shouldUnbind); _currentAssigner?.Cancel(shouldUnbind);
PointerPressed -= MouseClick; PointerPressed -= MouseClick;
} }
private void DeleteBind()
{
if (_currentAssigner != null)
{
Dictionary<string, Action> buttonActions = new Dictionary<string, Action>
{
{ "ButtonZl", () => ViewModel.Config.ButtonZl = Key.Unbound },
{ "ButtonL", () => ViewModel.Config.ButtonL = Key.Unbound },
{ "ButtonMinus", () => ViewModel.Config.ButtonMinus = Key.Unbound },
{ "LeftStickButton", () => ViewModel.Config.LeftStickButton = Key.Unbound },
{ "LeftStickUp", () => ViewModel.Config.LeftStickUp = Key.Unbound },
{ "LeftStickDown", () => ViewModel.Config.LeftStickDown = Key.Unbound },
{ "LeftStickRight", () => ViewModel.Config.LeftStickRight = Key.Unbound },
{ "LeftStickLeft", () => ViewModel.Config.LeftStickLeft = Key.Unbound },
{ "DpadUp", () => ViewModel.Config.DpadUp = Key.Unbound },
{ "DpadDown", () => ViewModel.Config.DpadDown = Key.Unbound },
{ "DpadLeft", () => ViewModel.Config.DpadLeft = Key.Unbound },
{ "DpadRight", () => ViewModel.Config.DpadRight = Key.Unbound },
{ "LeftButtonSr", () => ViewModel.Config.LeftButtonSr = Key.Unbound },
{ "LeftButtonSl", () => ViewModel.Config.LeftButtonSl = Key.Unbound },
{ "RightButtonSr", () => ViewModel.Config.RightButtonSr = Key.Unbound },
{ "RightButtonSl", () => ViewModel.Config.RightButtonSl = Key.Unbound },
{ "ButtonZr", () => ViewModel.Config.ButtonZr = Key.Unbound },
{ "ButtonR", () => ViewModel.Config.ButtonR = Key.Unbound },
{ "ButtonPlus", () => ViewModel.Config.ButtonPlus = Key.Unbound },
{ "ButtonA", () => ViewModel.Config.ButtonA = Key.Unbound },
{ "ButtonB", () => ViewModel.Config.ButtonB = Key.Unbound },
{ "ButtonX", () => ViewModel.Config.ButtonX = Key.Unbound },
{ "ButtonY", () => ViewModel.Config.ButtonY = Key.Unbound },
{ "RightStickButton", () => ViewModel.Config.RightStickButton = Key.Unbound },
{ "RightStickUp", () => ViewModel.Config.RightStickUp = Key.Unbound },
{ "RightStickDown", () => ViewModel.Config.RightStickDown = Key.Unbound },
{ "RightStickRight", () => ViewModel.Config.RightStickRight = Key.Unbound },
{ "RightStickLeft", () => ViewModel.Config.RightStickLeft = Key.Unbound }
};
if (buttonActions.TryGetValue(_currentAssigner.ToggledButton.Name, out Action action))
{
action();
ViewModel.ParentModel.IsModified = true;
}
}
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnDetachedFromVisualTree(e); base.OnDetachedFromVisualTree(e);

View file

@ -10,6 +10,8 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Input; using Ryujinx.Input;
using Ryujinx.Input.Assigner; using Ryujinx.Input.Assigner;
using System.Collections.Generic;
using System;
using Button = Ryujinx.Input.Button; using Button = Ryujinx.Input.Button;
using Key = Ryujinx.Common.Configuration.Hid.Key; using Key = Ryujinx.Common.Configuration.Hid.Key;
@ -45,6 +47,51 @@ namespace Ryujinx.Ava.UI.Views.Settings
} }
} }
private void MouseClick(object sender, PointerPressedEventArgs e)
{
bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed;
bool shouldRemoveBinding = e.GetCurrentPoint(this).Properties.IsRightButtonPressed;
if (shouldRemoveBinding)
{
DeleteBind();
}
_currentAssigner?.Cancel(shouldUnbind);
PointerPressed -= MouseClick;
}
private void DeleteBind()
{
if (DataContext is not SettingsViewModel viewModel)
return;
if (_currentAssigner != null)
{
Dictionary<string, Action> buttonActions = new Dictionary<string, Action>
{
{ "ToggleVSyncMode", () => viewModel.KeyboardHotkey.ToggleVSyncMode = Key.Unbound },
{ "Screenshot", () => viewModel.KeyboardHotkey.Screenshot = Key.Unbound },
{ "ShowUI", () => viewModel.KeyboardHotkey.ShowUI = Key.Unbound },
{ "Pause", () => viewModel.KeyboardHotkey.Pause = Key.Unbound },
{ "ToggleMute", () => viewModel.KeyboardHotkey.ToggleMute = Key.Unbound },
{ "ResScaleUp", () => viewModel.KeyboardHotkey.ResScaleUp = Key.Unbound },
{ "ResScaleDown", () => viewModel.KeyboardHotkey.ResScaleDown = Key.Unbound },
{ "VolumeUp", () => viewModel.KeyboardHotkey.VolumeUp = Key.Unbound },
{ "VolumeDown", () => viewModel.KeyboardHotkey.VolumeDown = Key.Unbound },
{ "CustomVSyncIntervalIncrement", () => viewModel.KeyboardHotkey.CustomVSyncIntervalIncrement = Key.Unbound },
{ "CustomVSyncIntervalDecrement", () => viewModel.KeyboardHotkey.CustomVSyncIntervalDecrement = Key.Unbound },
{ "TurboMode", () => viewModel.KeyboardHotkey.TurboMode = Key.Unbound }
};
if (buttonActions.TryGetValue(_currentAssigner.ToggledButton.Name, out Action action))
{
action();
}
}
}
private void Button_IsCheckedChanged(object sender, RoutedEventArgs e) private void Button_IsCheckedChanged(object sender, RoutedEventArgs e)
{ {
if (sender is ToggleButton button) if (sender is ToggleButton button)
@ -62,6 +109,8 @@ namespace Ryujinx.Ava.UI.Views.Settings
this.Focus(NavigationMethod.Pointer); this.Focus(NavigationMethod.Pointer);
PointerPressed += MouseClick;
IKeyboard keyboard = (IKeyboard)_avaloniaKeyboardDriver.GetGamepad("0"); IKeyboard keyboard = (IKeyboard)_avaloniaKeyboardDriver.GetGamepad("0");
IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard); IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard);