Skip to content
Snippets Groups Projects
InputViewModel.cs 31.7 KiB
Newer Older
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Svg.Skia;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Input;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text.Json;
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using Key = Ryujinx.Common.Configuration.Hid.Key;

namespace Ryujinx.Ava.UI.ViewModels.Input
    public partial class InputViewModel : BaseModel, IDisposable
    {
        private const string Disabled = "disabled";
        private const string ProControllerResource = "Ryujinx/Assets/Icons/Controller_ProCon.svg";
        private const string JoyConPairResource = "Ryujinx/Assets/Icons/Controller_JoyConPair.svg";
        private const string JoyConLeftResource = "Ryujinx/Assets/Icons/Controller_JoyConLeft.svg";
        private const string JoyConRightResource = "Ryujinx/Assets/Icons/Controller_JoyConRight.svg";
        private const string KeyboardString = "keyboard";
        private const string ControllerString = "controller";
        private readonly MainWindow _mainWindow;

        private PlayerIndex _playerId;
        private PlayerIndex _playerIdChoose;
        private int _controller;
        private string _controllerImage;
        private int _device;
        [ObservableProperty] private object _configViewModel;
        [ObservableProperty] private string _profileName;
        private bool _isLoaded;

        private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
        public IGamepadDriver AvaloniaKeyboardDriver { get; }

        private IGamepad _selectedGamepad;

        public IGamepad SelectedGamepad
        {
            get => _selectedGamepad;
            private set
            {
                _selectedGamepad = value;

                if (ConfigViewModel is ControllerInputViewModel { Config.UseRainbowLed: true })
                    Rainbow.Updated += (ref Color color) => _selectedGamepad.SetLed((uint)color.ToArgb());
                OnPropertiesChanged(nameof(HasLed), nameof(CanClearLed));
            }
        }

        public ObservableCollection<PlayerModel> PlayerIndexes { get; set; }
        public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; }
        internal ObservableCollection<ControllerModel> Controllers { get; set; }
        public AvaloniaList<string> ProfilesList { get; set; }
        public AvaloniaList<string> DeviceList { get; set; }

        // XAML Flags
        public bool ShowSettings => _device > 0;
        public bool IsController => _device > 1;
        public bool IsKeyboard => !IsController;
        public bool IsRight { get; set; }
        public bool IsLeft { get; set; }
        public bool HasLed => SelectedGamepad.Features.HasFlag(GamepadFeaturesFlag.Led);
        public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense");

        public bool IsModified { get; set; }
        public event Action NotifyChangesEvent;
        public PlayerIndex PlayerIdChoose
        {
            get => _playerIdChoose;
            set { }
        }

        public PlayerIndex PlayerId
        {
            get => _playerId;
            set
            {
                if (IsModified)
                {
                    return;
                }

                IsModified = false;
                _playerId = value;

                if (!Enum.IsDefined<PlayerIndex>(_playerId))
                {
                    _playerId = PlayerIndex.Player1;

                LoadConfiguration();
                LoadDevice();
                LoadProfiles();

                _isLoaded = true;

                OnPropertyChanged();
            }
        }

        public int Controller
        {
            get => _controller;
            set
            {
                _controller = value;

                if (_controller == -1)
                {
                    _controller = 0;
                }

                if (Controllers.Count > 0 && value < Controllers.Count && _controller > -1)
                {
                    ControllerType controller = Controllers[_controller].Type;

                    IsLeft = true;
                    IsRight = true;

                    switch (controller)
                    {
                        case ControllerType.Handheld:
                            ControllerImage = JoyConPairResource;
                            break;
                        case ControllerType.ProController:
                            ControllerImage = ProControllerResource;
                            break;
                        case ControllerType.JoyconPair:
                            ControllerImage = JoyConPairResource;
                            break;
                        case ControllerType.JoyconLeft:
                            ControllerImage = JoyConLeftResource;
                            IsRight = false;
                            break;
                        case ControllerType.JoyconRight:
                            ControllerImage = JoyConRightResource;
                            IsLeft = false;
                            break;
                    }

                    LoadInputDriver();
                    LoadProfiles();
                }

                OnPropertyChanged();
                NotifyChanges();
            }
        }

        public string ControllerImage
        {
            get => _controllerImage;
            set
            {
                _controllerImage = value;

                OnPropertyChanged();
                OnPropertyChanged(nameof(Image));
            }
        }

        public SvgImage Image
        {
            get
            {
                SvgImage image = new();

                if (!string.IsNullOrWhiteSpace(_controllerImage))
                {
Isaac Marovitz's avatar
Isaac Marovitz committed
                    SvgSource source = SvgSource.LoadFromStream(EmbeddedResources.GetStream(_controllerImage));

                    image.Source = source;
                }

                return image;
            }
        }

        public int Device
        {
            get => _device;
            set
            {
                _device = value < 0 ? 0 : value;

                if (_device >= Devices.Count)
                {
                    return;
                }

                DeviceType selected = Devices[_device].Type;

                if (selected != DeviceType.None)
                {
                    LoadControllers();

                    if (_isLoaded)
                    {
                        LoadConfiguration(LoadDefaultConfiguration());
                    }
                }

                OnPropertyChanged();
                NotifyChanges();
            }
        }

        public InputConfig Config { get; set; }

        public InputViewModel(UserControl owner) : this()
        {
            if (Program.PreviewerDetached)
            {
                _mainWindow = RyujinxApp.MainWindow;

                AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner);

                _mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
                _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
                _mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates();

                _isLoaded = false;

                LoadDevices();

                PlayerId = PlayerIndex.Player1;
            }
        }

        public InputViewModel()
            PlayerIndexes = [];
            Controllers = [];
            Devices = [];
            ProfilesList = [];
            DeviceList = [];

            ControllerImage = ProControllerResource;

            PlayerIndexes.Add(new(PlayerIndex.Player1, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer1]));
            PlayerIndexes.Add(new(PlayerIndex.Player2, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer2]));
            PlayerIndexes.Add(new(PlayerIndex.Player3, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer3]));
            PlayerIndexes.Add(new(PlayerIndex.Player4, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer4]));
            PlayerIndexes.Add(new(PlayerIndex.Player5, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer5]));
            PlayerIndexes.Add(new(PlayerIndex.Player6, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer6]));
            PlayerIndexes.Add(new(PlayerIndex.Player7, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer7]));
            PlayerIndexes.Add(new(PlayerIndex.Player8, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer8]));
            PlayerIndexes.Add(new(PlayerIndex.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsHandheld]));
        }

        private void LoadConfiguration(InputConfig inputConfig = null)
        {
Marco Carvalho's avatar
Marco Carvalho committed
            Config = inputConfig ?? ConfigurationState.Instance.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId);

            if (Config is StandardKeyboardInputConfig keyboardInputConfig)
            {
                ConfigViewModel = new KeyboardInputViewModel(this, new KeyboardInputConfig(keyboardInputConfig));
            }

            if (Config is StandardControllerInputConfig controllerInputConfig)
            {
                ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig));
            }
        }

        public void LoadDevice()
        {
            if (Config == null || Config.Backend == InputBackendType.Invalid)
            {
                Device = 0;
            }
            else
            {
                DeviceType type = DeviceType.None;

                if (Config is StandardKeyboardInputConfig)
                {
                    type = DeviceType.Keyboard;
                }

                if (Config is StandardControllerInputConfig)
                {
                    type = DeviceType.Controller;
                }

                (DeviceType Type, string Id, string Name) item = Devices.FirstOrDefault(x => x.Type == type && x.Id == Config.Id);
                if (item != default)
                {
                    Device = Devices.ToList().FindIndex(x => x.Id == item.Id);
                }
                else
                {
                    Device = 0;
                }
            }
        }

        private void LoadInputDriver()
        {
            if (_device < 0)
            {
                return;
            }

            string id = GetCurrentGamepadId();
            DeviceType type = Devices[Device].Type;

            if (type == DeviceType.None)
            {
                return;
            }

            if (type == DeviceType.Keyboard)
            {
                if (_mainWindow.InputManager.KeyboardDriver is AvaloniaKeyboardDriver)
                {
                    // NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused...
                    SelectedGamepad = AvaloniaKeyboardDriver.GetGamepad(id);
                }
                else
                {
                    SelectedGamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id);
                }
            }
            else
            {
                SelectedGamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id);
            }
        }

        private void HandleOnGamepadDisconnected(string id)
        {
        }

        private void HandleOnGamepadConnected(string id)
        {
        }

        private string GetCurrentGamepadId()
        {
            if (_device < 0)
            {
                return string.Empty;
            }

            (DeviceType Type, string Id, string Name) device = Devices[Device];

            if (device.Type == DeviceType.None)
            {
                return null;
            }

            return device.Id.Split(" ")[0];
        }

        public void LoadControllers()
        {
            Controllers.Clear();

            if (_playerId == PlayerIndex.Handheld)
            {
                Controllers.Add(new(ControllerType.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeHandheld]));
                Controllers.Add(new(ControllerType.ProController, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeProController]));
                Controllers.Add(new(ControllerType.JoyconPair, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConPair]));
                Controllers.Add(new(ControllerType.JoyconLeft, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConLeft]));
                Controllers.Add(new(ControllerType.JoyconRight, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConRight]));

                if (Config != null && Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType) != -1)
                {
                    Controller = Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType);
                }
                else
                {
                    Controller = 0;
                }
            }
        }

        private static string GetShortGamepadName(string str)
        {
            const string Ellipsis = "...";
            const int MaxSize = 50;

            if (str.Length > MaxSize)
            {
                return $"{str.AsSpan(0, MaxSize - Ellipsis.Length)}{Ellipsis}";
        private static string GetShortGamepadId(string str)
        {
            const string Hyphen = "-";
            const int Offset = 1;

            return str[(str.IndexOf(Hyphen) + Offset)..];
        public void LoadDevices()
        {
            string GetGamepadName(IGamepad gamepad, int controllerNumber)
            {
                return $"{GetShortGamepadName(gamepad.Name)} ({controllerNumber})";
            }
            string GetUniqueGamepadName(IGamepad gamepad, ref int controllerNumber)
            {
                string name = GetGamepadName(gamepad, controllerNumber);
                if (Devices.Any(controller => controller.Name == name))
                {
                    controllerNumber++;
                    name = GetGamepadName(gamepad, controllerNumber);
                }
                return name;
            }

            lock (Devices)
            {
                Devices.Clear();
                DeviceList.Clear();
                Devices.Add((DeviceType.None, Disabled, LocaleManager.Instance[LocaleKeys.ControllerSettingsDeviceDisabled]));
                foreach (string id in _mainWindow.InputManager.KeyboardDriver.GamepadsIds)
                {
                    using IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id);

                    if (gamepad != null)
                    {
                        Devices.Add((DeviceType.Keyboard, id, $"{GetShortGamepadName(gamepad.Name)}"));
                    }
                }

                foreach (string id in _mainWindow.InputManager.GamepadDriver.GamepadsIds)
                {
                    using IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id);

                    if (gamepad != null)
                    {
                        string name = GetUniqueGamepadName(gamepad, ref controllerNumber);
                        Devices.Add((DeviceType.Controller, id, name));
                    }
                }

                DeviceList.AddRange(Devices.Select(x => x.Name));
                Device = Math.Min(Device, DeviceList.Count);
            }
        }

        private string GetProfileBasePath()
        {
            string path = AppDataManager.ProfilesDirPath;
            DeviceType type = Devices[Device == -1 ? 0 : Device].Type;

            if (type == DeviceType.Keyboard)
            {
                path = Path.Combine(path, KeyboardString);
            }
            else if (type == DeviceType.Controller)
            {
                path = Path.Combine(path, ControllerString);
            }

            return path;
        }

        private void LoadProfiles()
        {
            ProfilesList.Clear();

            string basePath = GetProfileBasePath();

            if (!Directory.Exists(basePath))
            {
                Directory.CreateDirectory(basePath);
            }

            ProfilesList.Add((LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault]));

            foreach (string profile in Directory.GetFiles(basePath, "*.json", SearchOption.AllDirectories))
            {
                ProfilesList.Add(Path.GetFileNameWithoutExtension(profile));
            }

            if (string.IsNullOrWhiteSpace(ProfileName))
            {
                ProfileName = LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault];
            }
        }

        public InputConfig LoadDefaultConfiguration()
        {
            (DeviceType Type, string Id, string Name) activeDevice = Devices.FirstOrDefault();

            if (Devices.Count > 0 && Device < Devices.Count && Device >= 0)
            {
                activeDevice = Devices[Device];
            }

            InputConfig config;
            if (activeDevice.Type == DeviceType.Keyboard)
            {
                string id = activeDevice.Id;

                config = new StandardKeyboardInputConfig
                {
                    Version = InputConfig.CurrentVersion,
                    Backend = InputBackendType.WindowKeyboard,
                    Id = id,
                    ControllerType = ControllerType.ProController,
                    LeftJoycon = new LeftJoyconCommonConfig<Key>
                    {
                        DpadUp = Key.Up,
                        DpadDown = Key.Down,
                        DpadLeft = Key.Left,
                        DpadRight = Key.Right,
                        ButtonMinus = Key.Minus,
                        ButtonL = Key.E,
                        ButtonZl = Key.Q,
                        ButtonSl = Key.Unbound,
                        ButtonSr = Key.Unbound,
                    },
                    LeftJoyconStick =
                        new JoyconConfigKeyboardStick<Key>
                        {
                            StickUp = Key.W,
                            StickDown = Key.S,
                            StickLeft = Key.A,
                            StickRight = Key.D,
                            StickButton = Key.F,
                        },
                    RightJoycon = new RightJoyconCommonConfig<Key>
                    {
                        ButtonA = Key.Z,
                        ButtonB = Key.X,
                        ButtonX = Key.C,
                        ButtonY = Key.V,
                        ButtonPlus = Key.Plus,
                        ButtonR = Key.U,
                        ButtonZr = Key.O,
                        ButtonSl = Key.Unbound,
                        ButtonSr = Key.Unbound,
                    },
                    RightJoyconStick = new JoyconConfigKeyboardStick<Key>
                    {
                        StickUp = Key.I,
                        StickDown = Key.K,
                        StickLeft = Key.J,
                        StickRight = Key.L,
                        StickButton = Key.H,
                    },
                };
            }
            else if (activeDevice.Type == DeviceType.Controller)
            {
Marco Carvalho's avatar
Marco Carvalho committed
                bool isNintendoStyle = Devices.ToList().FirstOrDefault(x => x.Id == activeDevice.Id).Name.Contains("Nintendo");
                string id = activeDevice.Id.Split(" ")[0];

                config = new StandardControllerInputConfig
                {
                    Version = InputConfig.CurrentVersion,
                    Backend = InputBackendType.GamepadSDL2,
                    Id = id,
                    ControllerType = ControllerType.ProController,
                    DeadzoneLeft = 0.1f,
                    DeadzoneRight = 0.1f,
                    RangeLeft = 1.0f,
                    RangeRight = 1.0f,
                    TriggerThreshold = 0.5f,
                    LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId>
                    {
                        DpadUp = ConfigGamepadInputId.DpadUp,
                        DpadDown = ConfigGamepadInputId.DpadDown,
                        DpadLeft = ConfigGamepadInputId.DpadLeft,
                        DpadRight = ConfigGamepadInputId.DpadRight,
                        ButtonMinus = ConfigGamepadInputId.Minus,
                        ButtonL = ConfigGamepadInputId.LeftShoulder,
                        ButtonZl = ConfigGamepadInputId.LeftTrigger,
                        ButtonSl = ConfigGamepadInputId.Unbound,
                        ButtonSr = ConfigGamepadInputId.Unbound,
                    },
                    LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
                    {
                        Joystick = ConfigStickInputId.Left,
                        StickButton = ConfigGamepadInputId.LeftStick,
                        InvertStickX = false,
                        InvertStickY = false,
                    },
                    RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
                    {
                        ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B,
                        ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A,
                        ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y,
                        ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X,
                        ButtonPlus = ConfigGamepadInputId.Plus,
                        ButtonR = ConfigGamepadInputId.RightShoulder,
                        ButtonZr = ConfigGamepadInputId.RightTrigger,
                        ButtonSl = ConfigGamepadInputId.Unbound,
                        ButtonSr = ConfigGamepadInputId.Unbound,
                    },
                    RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
                    {
                        Joystick = ConfigStickInputId.Right,
                        StickButton = ConfigGamepadInputId.RightStick,
                        InvertStickX = false,
                        InvertStickY = false,
                    },
                    Motion = new StandardMotionConfigController
                    {
                        MotionBackend = MotionInputBackendType.GamepadDriver,
                        EnableMotion = true,
                        Sensitivity = 100,
                    },
                    Rumble = new RumbleConfigController
                    {
                        StrongRumble = 1f,
                        WeakRumble = 1f,
                        EnableRumble = false,
                    },
                };
            }
            else
            {
                config = new InputConfig();
            }

            config.PlayerIndex = _playerId;

            return config;
        }

        public async void LoadProfile()
        {
            if (Device == 0)
            {
                return;
            }

            InputConfig config = null;

            if (string.IsNullOrWhiteSpace(ProfileName))
            {
                return;
            }

            if (ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault])
            {
                config = LoadDefaultConfiguration();
            }
            else
            {
                string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json");

                if (!File.Exists(path))
                {
                    int index = ProfilesList.IndexOf(ProfileName);
                    if (index != -1)
                    {
                        ProfilesList.RemoveAt(index);
                    }
                    return;
                }

                try
                {
                    config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig);
                }
                catch (JsonException) { }
                catch (InvalidOperationException)
                {
                    Logger.Error?.Print(LogClass.Configuration, $"Profile {ProfileName} is incompatible with the current input configuration system.");
                    await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogProfileInvalidProfileErrorMessage, ProfileName));

                    return;
                }
            }

            if (config != null)
            {
                _isLoaded = false;

                LoadConfiguration(config);

                LoadDevice();

                _isLoaded = true;

                NotifyChanges();
            }
        }

        public async void SaveProfile()
        {
            if (Device == 0)
            {
                return;
            }

            if (ConfigViewModel == null)
            if (ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault])
                await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileDefaultProfileOverwriteErrorMessage]);
                bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1;
                if (validFileName)
                    string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json");
                    InputConfig config = null;
                    if (IsKeyboard)
                    {
                        config = (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig();
                    }
                    else if (IsController)
                    {
                        config = (ConfigViewModel as ControllerInputViewModel).Config.GetConfig();
                    }
                    config.ControllerType = Controllers[_controller].Type;
                    string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig);

                    await File.WriteAllTextAsync(path, jsonString);

                    LoadProfiles();
                }
                else
                {
                    await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]);
                }
            }
        }

        public async void RemoveProfile()
        {
            if (Device == 0 || ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault] || ProfilesList.IndexOf(ProfileName) == -1)
            {
                return;
            }

            UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
                LocaleManager.Instance[LocaleKeys.DialogProfileDeleteProfileTitle],
                LocaleManager.Instance[LocaleKeys.DialogProfileDeleteProfileMessage],
                LocaleManager.Instance[LocaleKeys.InputDialogYes],
                LocaleManager.Instance[LocaleKeys.InputDialogNo],
                LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);

            if (result == UserResult.Yes)
            {
                string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json");

                if (File.Exists(path))
                {
                    File.Delete(path);
                }

                LoadProfiles();
            }
        }

        public void Save()
        {
            IsModified = false;

            List<InputConfig> newConfig = [];

            newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value);

Marco Carvalho's avatar
Marco Carvalho committed
            newConfig.Remove(newConfig.FirstOrDefault(x => x == null));

            if (Device == 0)
            {
Marco Carvalho's avatar
Marco Carvalho committed
                newConfig.Remove(newConfig.FirstOrDefault(x => x.PlayerIndex == this.PlayerId));
                (DeviceType Type, string Id, string Name) device = Devices[Device];

                if (device.Type == DeviceType.Keyboard)
                {
                    KeyboardInputConfig inputConfig = (ConfigViewModel as KeyboardInputViewModel).Config;
                    inputConfig.Id = device.Id;
                }
                else
                {
                    GamepadInputConfig inputConfig = (ConfigViewModel as ControllerInputViewModel).Config;
                    inputConfig.Id = device.Id.Split(" ")[0];
                }

                InputConfig config = !IsController
                    ? (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig()
                    : (ConfigViewModel as ControllerInputViewModel).Config.GetConfig();
                config.ControllerType = Controllers[_controller].Type;
                config.PlayerIndex = _playerId;

                int i = newConfig.FindIndex(x => x.PlayerIndex == PlayerId);
                if (i == -1)
                {
                    newConfig.Add(config);
                }
                else
                {
                    newConfig[i] = config;
                }
            }

            _mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);

            // Atomically replace and signal input change.
            // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event.
            ConfigurationState.Instance.Hid.InputConfig.Value = newConfig;

            ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
        }

        public void NotifyChange(string property)
        {
            OnPropertyChanged(property);
        }

        public void NotifyChanges()
        {
            OnPropertyChanged(nameof(ConfigViewModel));
            OnPropertyChanged(nameof(IsController));
            OnPropertyChanged(nameof(ShowSettings));
            OnPropertyChanged(nameof(IsKeyboard));
            OnPropertyChanged(nameof(IsRight));
            OnPropertyChanged(nameof(IsLeft));
            NotifyChangesEvent?.Invoke();
        }

        public void Dispose()
        {
            GC.SuppressFinalize(this);

            _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected;
            _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected;

            _mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates();

            SelectedGamepad?.Dispose();

            AvaloniaKeyboardDriver.Dispose();
        }
    }