mirror of
https://git.743378673.xyz/MeloNX/MeloNX.git
synced 2025-08-02 12:37:11 +02:00
UI - Avalonia Part 2 (#3351)
* add settings windows and children views * Expose hotkeys configuration on the UI * Remove double spacing from locale JSON * simplify button assigner * add cemuhook buttons and title to locale * move common button assigner to own class * cancel button assigner when window is closed * remove unused setting * address review. fix controller profile not loading default when switching devices * fix updater file name * Input cleanup (#37) * addressed review * add device type to controller device checks * change accessibility modifier of public classes to internal * Update Ryujinx.Ava/Ui/ViewModels/ControllerSettingsViewModel.cs Co-authored-by: gdkchan <gab.dark.100@gmail.com> * Update de_DE.json * Update de_DE.json * Update tr_TR.json Translated newly added lines * Update it_IT.json * fix rebase * update avalonia * fix wrong key used for button text * Align settings window elements * Tabs to spaces * Update brazilian portuguese translation * Minor improvement on brazilian portuguese translation * fix turkish translation * remove unused text * change view related classes to public * unsubscribe from deferred event if dialog is closed * Load the default language before loading any other when switching languages * Make controller settings more compact * increase default width of settings window, reduce profile buttons width Co-authored-by: gdk <gab.dark.100@gmail.com> Co-authored-by: MutantAura <44103205+MutantAura@users.noreply.github.com> Co-authored-by: Niwu34 <67392333+Niwu34@users.noreply.github.com> Co-authored-by: aegiff <99728970+aegiff@users.noreply.github.com> Co-authored-by: Antonio Brugnolo <36473846+AntoSkate@users.noreply.github.com>
This commit is contained in:
parent
f0817388d3
commit
a76e1f6145
66 changed files with 4631 additions and 145 deletions
887
Ryujinx.Ava/Ui/ViewModels/ControllerSettingsViewModel.cs
Normal file
887
Ryujinx.Ava/Ui/ViewModels/ControllerSettingsViewModel.cs
Normal file
|
@ -0,0 +1,887 @@
|
|||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Svg.Skia;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia.VisualTree;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Input;
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.Ava.Ui.Models;
|
||||
using Ryujinx.Ava.Ui.Windows;
|
||||
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 Ryujinx.Ui.Common.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
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
|
||||
{
|
||||
public class ControllerSettingsViewModel : BaseModel, IDisposable
|
||||
{
|
||||
private const string Disabled = "disabled";
|
||||
private const string ProControllerResource = "Ryujinx.Ui.Common/Resources/Controller_ProCon.svg";
|
||||
private const string JoyConPairResource = "Ryujinx.Ui.Common/Resources/Controller_JoyConPair.svg";
|
||||
private const string JoyConLeftResource = "Ryujinx.Ui.Common/Resources/Controller_JoyConLeft.svg";
|
||||
private const string JoyConRightResource = "Ryujinx.Ui.Common/Resources/Controller_JoyConRight.svg";
|
||||
private const string KeyboardString = "keyboard";
|
||||
private const string ControllerString = "controller";
|
||||
private readonly MainWindow _mainWindow;
|
||||
|
||||
private PlayerIndex _playerId;
|
||||
private int _controller;
|
||||
private string _controllerImage;
|
||||
private int _device;
|
||||
private object _configuration;
|
||||
private string _profileName;
|
||||
private bool _isLoaded;
|
||||
private readonly UserControl _owner;
|
||||
|
||||
public IGamepadDriver AvaloniaKeyboardDriver { get; }
|
||||
public IGamepad SelectedGamepad { get; private set; }
|
||||
|
||||
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 IsModified { get; set; }
|
||||
|
||||
public object Configuration
|
||||
{
|
||||
get => _configuration;
|
||||
set
|
||||
{
|
||||
_configuration = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public PlayerIndex PlayerId
|
||||
{
|
||||
get => _playerId;
|
||||
set
|
||||
{
|
||||
if (IsModified)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsModified = false;
|
||||
_playerId = value;
|
||||
|
||||
if (!Enum.IsDefined(typeof(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 SvgImage();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_controllerImage))
|
||||
{
|
||||
SvgSource source = new SvgSource();
|
||||
|
||||
source.Load(EmbeddedResources.GetStream(_controllerImage));
|
||||
|
||||
image.Source = source;
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
public string ProfileName
|
||||
{
|
||||
get => _profileName; set
|
||||
{
|
||||
_profileName = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public int Device
|
||||
{
|
||||
get => _device;
|
||||
set
|
||||
{
|
||||
_device = value < 0 ? 0 : value;
|
||||
|
||||
if (_device >= Devices.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var selected = Devices[_device].Type;
|
||||
|
||||
if (selected != DeviceType.None)
|
||||
{
|
||||
LoadControllers();
|
||||
|
||||
if (_isLoaded)
|
||||
{
|
||||
LoadConfiguration(LoadDefaultConfiguration());
|
||||
}
|
||||
}
|
||||
|
||||
OnPropertyChanged();
|
||||
NotifyChanges();
|
||||
}
|
||||
}
|
||||
|
||||
public InputConfig Config { get; set; }
|
||||
|
||||
public ControllerSettingsViewModel(UserControl owner) : this()
|
||||
{
|
||||
_owner = owner;
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
_mainWindow =
|
||||
(MainWindow)((IClassicDesktopStyleApplicationLifetime)Avalonia.Application.Current
|
||||
.ApplicationLifetime).MainWindow;
|
||||
|
||||
AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner);
|
||||
|
||||
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
|
||||
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
|
||||
if (_mainWindow.AppHost != null)
|
||||
{
|
||||
_mainWindow.AppHost.NpadManager.BlockInputUpdates();
|
||||
}
|
||||
|
||||
_isLoaded = false;
|
||||
|
||||
LoadDevices();
|
||||
|
||||
PlayerId = PlayerIndex.Player1;
|
||||
}
|
||||
}
|
||||
|
||||
public ControllerSettingsViewModel()
|
||||
{
|
||||
PlayerIndexes = new ObservableCollection<PlayerModel>();
|
||||
Controllers = new ObservableCollection<ControllerModel>();
|
||||
Devices = new ObservableCollection<(DeviceType Type, string Id, string Name)>();
|
||||
ProfilesList = new AvaloniaList<string>();
|
||||
DeviceList = new AvaloniaList<string>();
|
||||
|
||||
ControllerImage = ProControllerResource;
|
||||
|
||||
PlayerIndexes.Add(new(PlayerIndex.Player1, LocaleManager.Instance["ControllerSettingsPlayer1"]));
|
||||
PlayerIndexes.Add(new(PlayerIndex.Player2, LocaleManager.Instance["ControllerSettingsPlayer2"]));
|
||||
PlayerIndexes.Add(new(PlayerIndex.Player3, LocaleManager.Instance["ControllerSettingsPlayer3"]));
|
||||
PlayerIndexes.Add(new(PlayerIndex.Player4, LocaleManager.Instance["ControllerSettingsPlayer4"]));
|
||||
PlayerIndexes.Add(new(PlayerIndex.Player5, LocaleManager.Instance["ControllerSettingsPlayer5"]));
|
||||
PlayerIndexes.Add(new(PlayerIndex.Player6, LocaleManager.Instance["ControllerSettingsPlayer6"]));
|
||||
PlayerIndexes.Add(new(PlayerIndex.Player7, LocaleManager.Instance["ControllerSettingsPlayer7"]));
|
||||
PlayerIndexes.Add(new(PlayerIndex.Player8, LocaleManager.Instance["ControllerSettingsPlayer8"]));
|
||||
PlayerIndexes.Add(new(PlayerIndex.Handheld, LocaleManager.Instance["ControllerSettingsHandheld"]));
|
||||
}
|
||||
|
||||
private void LoadConfiguration(InputConfig inputConfig = null)
|
||||
{
|
||||
Config = inputConfig ?? ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerId);
|
||||
|
||||
if (Config is StandardKeyboardInputConfig keyboardInputConfig)
|
||||
{
|
||||
Configuration = new InputConfiguration<Key, ConfigStickInputId>(keyboardInputConfig);
|
||||
}
|
||||
|
||||
if (Config is StandardControllerInputConfig controllerInputConfig)
|
||||
{
|
||||
Configuration = new InputConfiguration<ConfigGamepadInputId, ConfigStickInputId>(controllerInputConfig);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadDevice()
|
||||
{
|
||||
if (Config == null || Config.Backend == InputBackendType.Invalid)
|
||||
{
|
||||
Device = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var type = DeviceType.None;
|
||||
|
||||
if (Config is StandardKeyboardInputConfig)
|
||||
{
|
||||
type = DeviceType.Keyboard;
|
||||
}
|
||||
|
||||
if (Config is StandardControllerInputConfig)
|
||||
{
|
||||
type = DeviceType.Controller;
|
||||
}
|
||||
|
||||
var 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async void ShowMotionConfig()
|
||||
{
|
||||
await MotionSettingsWindow.Show(this, _owner.GetVisualRoot() as StyleableWindow);
|
||||
}
|
||||
|
||||
public async void ShowRumbleConfig()
|
||||
{
|
||||
await RumbleSettingsWindow.Show(this, _owner.GetVisualRoot() as StyleableWindow);
|
||||
}
|
||||
|
||||
private void LoadInputDriver()
|
||||
{
|
||||
if (_device < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string id = GetCurrentGamepadId();
|
||||
var type = Devices[Device].Type;
|
||||
|
||||
if (type == DeviceType.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else 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)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
LoadDevices();
|
||||
});
|
||||
}
|
||||
|
||||
private void HandleOnGamepadConnected(string id)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
LoadDevices();
|
||||
});
|
||||
}
|
||||
|
||||
private string GetCurrentGamepadId()
|
||||
{
|
||||
if (_device < 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var 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["ControllerSettingsControllerTypeHandheld"]));
|
||||
|
||||
Controller = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Controllers.Add(new(ControllerType.ProController, LocaleManager.Instance["ControllerSettingsControllerTypeProController"]));
|
||||
Controllers.Add(new(ControllerType.JoyconPair, LocaleManager.Instance["ControllerSettingsControllerTypeJoyConPair"]));
|
||||
Controllers.Add(new(ControllerType.JoyconLeft, LocaleManager.Instance["ControllerSettingsControllerTypeJoyConLeft"]));
|
||||
Controllers.Add(new(ControllerType.JoyconRight, LocaleManager.Instance["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.Substring(0, MaxSize - Ellipsis.Length) + Ellipsis;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
public void LoadDevices()
|
||||
{
|
||||
lock (Devices)
|
||||
{
|
||||
Devices.Clear();
|
||||
DeviceList.Clear();
|
||||
Devices.Add((DeviceType.None, Disabled, LocaleManager.Instance["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)} ({id})"));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (string id in _mainWindow.InputManager.GamepadDriver.GamepadsIds)
|
||||
{
|
||||
using IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id);
|
||||
|
||||
if (gamepad != null)
|
||||
{
|
||||
Devices.Add((DeviceType.Controller, id, $"{GetShortGamepadName(gamepad.Name)} ({id})"));
|
||||
}
|
||||
}
|
||||
|
||||
DeviceList.AddRange(Devices.Select(x => x.Name));
|
||||
Device = Math.Min(Device, DeviceList.Count);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetProfileBasePath()
|
||||
{
|
||||
string path = AppDataManager.ProfilesDirPath;
|
||||
var 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["ControllerSettingsProfileDefault"]));
|
||||
|
||||
foreach (string profile in Directory.GetFiles(basePath, "*.json", SearchOption.AllDirectories))
|
||||
{
|
||||
ProfilesList.Add(Path.GetFileNameWithoutExtension(profile));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ProfileName))
|
||||
{
|
||||
ProfileName = LocaleManager.Instance["ControllerSettingsProfileDefault"];
|
||||
}
|
||||
}
|
||||
|
||||
public InputConfig LoadDefaultConfiguration()
|
||||
{
|
||||
var 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 = Ryujinx.Common.Configuration.Hid.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)
|
||||
{
|
||||
bool isNintendoStyle = Devices.ToList().Find(x => x.Id == activeDevice.Id).Name.Contains("Nintendo");
|
||||
|
||||
string id = activeDevice.Id.Split(" ")[0];
|
||||
|
||||
config = new StandardControllerInputConfig
|
||||
{
|
||||
Version = Ryujinx.Common.Configuration.Hid.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,
|
||||
GyroDeadzone = 1
|
||||
},
|
||||
Rumble = new RumbleConfigController
|
||||
{
|
||||
StrongRumble = 1f,
|
||||
WeakRumble = 1f,
|
||||
EnableRumble = false
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
config = new InputConfig();
|
||||
}
|
||||
|
||||
config.PlayerIndex = _playerId;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public void LoadProfile()
|
||||
{
|
||||
if (Device == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InputConfig config = null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ProfileName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ProfileName == LocaleManager.Instance["ControllerSettingsProfileDefault"])
|
||||
{
|
||||
config = LoadDefaultConfiguration();
|
||||
}
|
||||
else
|
||||
{
|
||||
string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json");
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
var index = ProfilesList.IndexOf(ProfileName);
|
||||
if (index != -1)
|
||||
{
|
||||
ProfilesList.RemoveAt(index);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (Stream stream = File.OpenRead(path))
|
||||
{
|
||||
config = JsonHelper.Deserialize<InputConfig>(stream);
|
||||
}
|
||||
}
|
||||
catch (JsonException) { }
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow,
|
||||
String.Format(LocaleManager.Instance["DialogProfileInvalidProfileErrorMessage"], ProfileName));
|
||||
Logger.Error?.Print(LogClass.Configuration, $"Profile {ProfileName} is incompatible with the current input configuration system.");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (config != null)
|
||||
{
|
||||
_isLoaded = false;
|
||||
|
||||
LoadConfiguration(config);
|
||||
|
||||
LoadDevice();
|
||||
|
||||
_isLoaded = true;
|
||||
|
||||
NotifyChanges();
|
||||
}
|
||||
}
|
||||
|
||||
public async void SaveProfile()
|
||||
{
|
||||
if (Device == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Configuration == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ProfileName == LocaleManager.Instance["ControllerSettingsProfileDefault"])
|
||||
{
|
||||
ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow, LocaleManager.Instance["DialogProfileDefaultProfileOverwriteErrorMessage"]);
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1;
|
||||
|
||||
if (validFileName)
|
||||
{
|
||||
string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json");
|
||||
|
||||
InputConfig config = null;
|
||||
|
||||
if (IsKeyboard)
|
||||
{
|
||||
config = (Configuration as InputConfiguration<Key, ConfigStickInputId>).GetConfig();
|
||||
}
|
||||
else if (IsController)
|
||||
{
|
||||
config = (Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>).GetConfig();
|
||||
}
|
||||
|
||||
config.ControllerType = Controllers[_controller].Type;
|
||||
|
||||
string jsonString = JsonHelper.Serialize(config, true);
|
||||
|
||||
await File.WriteAllTextAsync(path, jsonString);
|
||||
|
||||
LoadProfiles();
|
||||
}
|
||||
else
|
||||
{
|
||||
ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow, LocaleManager.Instance["DialogProfileInvalidProfileNameErrorMessage"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async void RemoveProfile()
|
||||
{
|
||||
if (Device == 0 || ProfileName == LocaleManager.Instance["ControllerSettingsProfileDefault"] || ProfilesList.IndexOf(ProfileName) == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
||||
_owner.GetVisualRoot() as StyleableWindow,
|
||||
LocaleManager.Instance["DialogProfileDeleteProfileTitle"],
|
||||
LocaleManager.Instance["DialogProfileDeleteProfileMessage"],
|
||||
LocaleManager.Instance["InputDialogYes"],
|
||||
LocaleManager.Instance["InputDialogNo"],
|
||||
LocaleManager.Instance["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 = new();
|
||||
|
||||
newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value);
|
||||
|
||||
newConfig.Remove(newConfig.Find(x => x == null));
|
||||
|
||||
if (Device == 0)
|
||||
{
|
||||
newConfig.Remove(newConfig.Find(x => x.PlayerIndex == this.PlayerId));
|
||||
}
|
||||
else
|
||||
{
|
||||
var device = Devices[Device];
|
||||
|
||||
if (device.Type == DeviceType.Keyboard)
|
||||
{
|
||||
var inputConfig = Configuration as InputConfiguration<Key, ConfigStickInputId>;
|
||||
inputConfig.Id = device.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
var inputConfig = Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>;
|
||||
inputConfig.Id = device.Id.Split(" ")[0];
|
||||
}
|
||||
|
||||
var config = !IsController
|
||||
? (Configuration as InputConfiguration<Key, ConfigStickInputId>).GetConfig()
|
||||
: (Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>).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.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(Configuration));
|
||||
OnPropertyChanged(nameof(IsController));
|
||||
OnPropertyChanged(nameof(ShowSettings));
|
||||
OnPropertyChanged(nameof(IsKeyboard));
|
||||
OnPropertyChanged(nameof(IsRight));
|
||||
OnPropertyChanged(nameof(IsLeft));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected;
|
||||
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected;
|
||||
|
||||
_mainWindow.AppHost?.NpadManager.UnblockInputUpdates();
|
||||
|
||||
SelectedGamepad?.Dispose();
|
||||
|
||||
AvaloniaKeyboardDriver.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ using LibHac.FsSystem;
|
|||
using LibHac.Ncm;
|
||||
using Ryujinx.Ava.Common;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Input;
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.Ava.Ui.Windows;
|
||||
using Ryujinx.Common;
|
||||
|
@ -35,7 +36,7 @@ using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
|
|||
|
||||
namespace Ryujinx.Ava.Ui.ViewModels
|
||||
{
|
||||
public class MainWindowViewModel : BaseModel
|
||||
internal class MainWindowViewModel : BaseModel
|
||||
{
|
||||
private readonly MainWindow _owner;
|
||||
private ObservableCollection<ApplicationData> _applications;
|
||||
|
@ -86,9 +87,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
ShowUiKey = KeyGesture.Parse(ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi.ToString());
|
||||
ScreenshotKey = KeyGesture.Parse(ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot.ToString());
|
||||
PauseKey = KeyGesture.Parse(ConfigurationState.Instance.Hid.Hotkeys.Value.Pause.ToString());
|
||||
LoadConfigurableHotKeys();
|
||||
|
||||
Volume = ConfigurationState.Instance.System.AudioVolume;
|
||||
}
|
||||
|
@ -836,6 +835,22 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
public void LoadConfigurableHotKeys()
|
||||
{
|
||||
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi, out var showUiKey))
|
||||
{
|
||||
ShowUiKey = new KeyGesture(showUiKey, KeyModifiers.None);
|
||||
}
|
||||
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey))
|
||||
{
|
||||
ScreenshotKey = new KeyGesture(screenshotKey, KeyModifiers.None);
|
||||
}
|
||||
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey))
|
||||
{
|
||||
PauseKey = new KeyGesture(pauseKey, KeyModifiers.None);
|
||||
}
|
||||
}
|
||||
|
||||
public void TakeScreenshot()
|
||||
{
|
||||
_owner.AppHost.ScreenshotRequested = true;
|
||||
|
@ -930,10 +945,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
public void OpenSettings()
|
||||
public async void OpenSettings()
|
||||
{
|
||||
// TODO : Implement Settings window
|
||||
ContentDialogHelper.ShowNotAvailableMessage(_owner);
|
||||
_owner.SettingsWindow = new(_owner.VirtualFileSystem, _owner.ContentManager);
|
||||
|
||||
await _owner.SettingsWindow.ShowDialog(_owner);
|
||||
LoadConfigurableHotKeys();
|
||||
}
|
||||
|
||||
public void ManageProfiles()
|
||||
|
@ -951,6 +968,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||
|
||||
public void ChangeLanguage(object obj)
|
||||
{
|
||||
LocaleManager.Instance.LoadDefaultLanguage();
|
||||
LocaleManager.Instance.LoadLanguage((string)obj);
|
||||
}
|
||||
|
||||
|
@ -1350,7 +1368,13 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||
|
||||
dialogMessage += LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallConfirmMessage"];
|
||||
|
||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(_owner, dialogTitle, dialogMessage, LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
|
||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
||||
_owner,
|
||||
dialogTitle,
|
||||
dialogMessage,
|
||||
LocaleManager.Instance["InputDialogYes"],
|
||||
LocaleManager.Instance["InputDialogNo"],
|
||||
LocaleManager.Instance["RyujinxConfirm"]);
|
||||
|
||||
UpdateWaitWindow waitingDialog = ContentDialogHelper.CreateWaitingDialog(dialogTitle, LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallWaitMessage"]);
|
||||
|
||||
|
|
403
Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs
Normal file
403
Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs
Normal file
|
@ -0,0 +1,403 @@
|
|||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using Ryujinx.Audio.Backends.OpenAL;
|
||||
using Ryujinx.Audio.Backends.SDL2;
|
||||
using Ryujinx.Audio.Backends.SoundIo;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Input;
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.Ava.Ui.Windows;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.GraphicsDriver;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
|
||||
using Ryujinx.Input;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
using Ryujinx.Ui.Common.Configuration.System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TimeZone = Ryujinx.Ava.Ui.Models.TimeZone;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.ViewModels
|
||||
{
|
||||
internal class SettingsViewModel : BaseModel
|
||||
{
|
||||
private readonly VirtualFileSystem _virtualFileSystem;
|
||||
private readonly ContentManager _contentManager;
|
||||
private readonly StyleableWindow _owner;
|
||||
private TimeZoneContentManager _timeZoneContentManager;
|
||||
|
||||
private readonly List<string> _validTzRegions;
|
||||
|
||||
private float _customResolutionScale;
|
||||
private int _resolutionScale;
|
||||
private int _graphicsBackendMultithreadingIndex;
|
||||
private float _previousVolumeLevel;
|
||||
private float _volume;
|
||||
|
||||
public int ResolutionScale
|
||||
{
|
||||
get => _resolutionScale;
|
||||
set
|
||||
{
|
||||
_resolutionScale = value;
|
||||
|
||||
OnPropertyChanged(nameof(CustomResolutionScale));
|
||||
OnPropertyChanged(nameof(IsCustomResolutionScaleActive));
|
||||
}
|
||||
}
|
||||
public int GraphicsBackendMultithreadingIndex
|
||||
{
|
||||
get => _graphicsBackendMultithreadingIndex;
|
||||
set
|
||||
{
|
||||
_graphicsBackendMultithreadingIndex = value;
|
||||
|
||||
if (_owner != null)
|
||||
{
|
||||
if (_graphicsBackendMultithreadingIndex != (int)ConfigurationState.Instance.Graphics.BackendThreading.Value)
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateInfoDialog(_owner,
|
||||
LocaleManager.Instance["DialogSettingsBackendThreadingWarningMessage"],
|
||||
"",
|
||||
"",
|
||||
LocaleManager.Instance["InputDialogOk"],
|
||||
LocaleManager.Instance["DialogSettingsBackendThreadingWarningTitle"]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public float CustomResolutionScale
|
||||
{
|
||||
get => _customResolutionScale;
|
||||
set
|
||||
{
|
||||
_customResolutionScale = MathF.Round(value, 1);
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool EnableDiscordIntegration { get; set; }
|
||||
public bool CheckUpdatesOnStart { get; set; }
|
||||
public bool ShowConfirmExit { get; set; }
|
||||
public bool HideCursorOnIdle { get; set; }
|
||||
public bool EnableDockedMode { get; set; }
|
||||
public bool EnableKeyboard { get; set; }
|
||||
public bool EnableMouse { get; set; }
|
||||
public bool EnableVsync { get; set; }
|
||||
public bool EnablePptc { get; set; }
|
||||
public bool EnableInternetAccess { get; set; }
|
||||
public bool EnableFsIntegrityChecks { get; set; }
|
||||
public bool IgnoreMissingServices { get; set; }
|
||||
public bool ExpandDramSize { get; set; }
|
||||
public bool EnableShaderCache { get; set; }
|
||||
public bool EnableFileLog { get; set; }
|
||||
public bool EnableStub { get; set; }
|
||||
public bool EnableInfo { get; set; }
|
||||
public bool EnableWarn { get; set; }
|
||||
public bool EnableError { get; set; }
|
||||
public bool EnableTrace { get; set; }
|
||||
public bool EnableGuest { get; set; }
|
||||
public bool EnableFsAccessLog { get; set; }
|
||||
public bool EnableDebug { get; set; }
|
||||
public bool IsOpenAlEnabled { get; set; }
|
||||
public bool IsSoundIoEnabled { get; set; }
|
||||
public bool IsSDL2Enabled { get; set; }
|
||||
public bool EnableCustomTheme { get; set; }
|
||||
public bool IsCustomResolutionScaleActive => _resolutionScale == 0;
|
||||
|
||||
public string TimeZone { get; set; }
|
||||
public string ShaderDumpPath { get; set; }
|
||||
public string CustomThemePath { get; set; }
|
||||
|
||||
public int Language { get; set; }
|
||||
public int Region { get; set; }
|
||||
public int FsGlobalAccessLogMode { get; set; }
|
||||
public int AudioBackend { get; set; }
|
||||
public int MaxAnisotropy { get; set; }
|
||||
public int AspectRatio { get; set; }
|
||||
public int OpenglDebugLevel { get; set; }
|
||||
public int MemoryMode { get; set; }
|
||||
public int BaseStyleIndex { get; set; }
|
||||
|
||||
public float Volume
|
||||
{
|
||||
get => _volume;
|
||||
set
|
||||
{
|
||||
_volume = value;
|
||||
|
||||
ConfigurationState.Instance.System.AudioVolume.Value = (float)(_volume / 100);
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public DateTimeOffset DateOffset { get; set; }
|
||||
public TimeSpan TimeOffset { get; set; }
|
||||
public AvaloniaList<TimeZone> TimeZones { get; set; }
|
||||
|
||||
public AvaloniaList<string> GameDirectories { get; set; }
|
||||
|
||||
private KeyboardHotkeys _keyboardHotkeys;
|
||||
|
||||
public KeyboardHotkeys KeyboardHotkeys
|
||||
{
|
||||
get => _keyboardHotkeys;
|
||||
set
|
||||
{
|
||||
_keyboardHotkeys = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public IGamepadDriver AvaloniaKeyboardDriver { get; }
|
||||
|
||||
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager, StyleableWindow owner) : this()
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
_contentManager = contentManager;
|
||||
_owner = owner;
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
LoadTimeZones();
|
||||
AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner);
|
||||
}
|
||||
}
|
||||
|
||||
public SettingsViewModel()
|
||||
{
|
||||
GameDirectories = new AvaloniaList<string>();
|
||||
TimeZones = new AvaloniaList<TimeZone>();
|
||||
_validTzRegions = new List<string>();
|
||||
|
||||
CheckSoundBackends();
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
LoadCurrentConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckSoundBackends()
|
||||
{
|
||||
IsOpenAlEnabled = OpenALHardwareDeviceDriver.IsSupported;
|
||||
IsSoundIoEnabled = SoundIoHardwareDeviceDriver.IsSupported;
|
||||
IsSDL2Enabled = SDL2HardwareDeviceDriver.IsSupported;
|
||||
}
|
||||
|
||||
public void LoadTimeZones()
|
||||
{
|
||||
_timeZoneContentManager = new TimeZoneContentManager();
|
||||
|
||||
_timeZoneContentManager.InitializeInstance(_virtualFileSystem, _contentManager, IntegrityCheckLevel.None);
|
||||
|
||||
foreach ((int offset, string location, string abbr) in _timeZoneContentManager.ParseTzOffsets())
|
||||
{
|
||||
int hours = Math.DivRem(offset, 3600, out int seconds);
|
||||
int minutes = Math.Abs(seconds) / 60;
|
||||
|
||||
string abbr2 = abbr.StartsWith('+') || abbr.StartsWith('-') ? string.Empty : abbr;
|
||||
|
||||
TimeZones.Add(new TimeZone($"UTC{hours:+0#;-0#;+00}:{minutes:D2}", location, abbr2));
|
||||
|
||||
_validTzRegions.Add(location);
|
||||
}
|
||||
}
|
||||
|
||||
public void ValidateAndSetTimeZone(string location)
|
||||
{
|
||||
if (_validTzRegions.Contains(location))
|
||||
{
|
||||
TimeZone = location;
|
||||
|
||||
OnPropertyChanged(nameof(TimeZone));
|
||||
}
|
||||
}
|
||||
|
||||
public async void BrowseTheme()
|
||||
{
|
||||
var dialog = new OpenFileDialog()
|
||||
{
|
||||
Title = LocaleManager.Instance["SettingsSelectThemeFileDialogTitle"],
|
||||
AllowMultiple = false
|
||||
};
|
||||
|
||||
dialog.Filters.Add(new FileDialogFilter() { Extensions = { "xaml" }, Name = LocaleManager.Instance["SettingsXamlThemeFile"] });
|
||||
|
||||
var file = await dialog.ShowAsync(_owner);
|
||||
|
||||
if (file != null && file.Length > 0)
|
||||
{
|
||||
CustomThemePath = file[0];
|
||||
OnPropertyChanged(nameof(CustomThemePath));
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadCurrentConfiguration()
|
||||
{
|
||||
ConfigurationState config = ConfigurationState.Instance;
|
||||
|
||||
GameDirectories.Clear();
|
||||
GameDirectories.AddRange(config.Ui.GameDirs.Value);
|
||||
|
||||
EnableDiscordIntegration = config.EnableDiscordIntegration;
|
||||
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
|
||||
ShowConfirmExit = config.ShowConfirmExit;
|
||||
HideCursorOnIdle = config.HideCursorOnIdle;
|
||||
EnableDockedMode = config.System.EnableDockedMode;
|
||||
EnableKeyboard = config.Hid.EnableKeyboard;
|
||||
EnableMouse = config.Hid.EnableMouse;
|
||||
EnableVsync = config.Graphics.EnableVsync;
|
||||
EnablePptc = config.System.EnablePtc;
|
||||
EnableInternetAccess = config.System.EnableInternetAccess;
|
||||
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
|
||||
IgnoreMissingServices = config.System.IgnoreMissingServices;
|
||||
ExpandDramSize = config.System.ExpandRam;
|
||||
EnableShaderCache = config.Graphics.EnableShaderCache;
|
||||
EnableFileLog = config.Logger.EnableFileLog;
|
||||
EnableStub = config.Logger.EnableStub;
|
||||
EnableInfo = config.Logger.EnableInfo;
|
||||
EnableWarn = config.Logger.EnableWarn;
|
||||
EnableError = config.Logger.EnableError;
|
||||
EnableTrace = config.Logger.EnableTrace;
|
||||
EnableGuest = config.Logger.EnableGuest;
|
||||
EnableDebug = config.Logger.EnableDebug;
|
||||
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
|
||||
EnableCustomTheme = config.Ui.EnableCustomTheme;
|
||||
Volume = config.System.AudioVolume * 100;
|
||||
|
||||
GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value;
|
||||
|
||||
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
||||
|
||||
TimeZone = config.System.TimeZone;
|
||||
ShaderDumpPath = config.Graphics.ShadersDumpPath;
|
||||
CustomThemePath = config.Ui.CustomThemePath;
|
||||
BaseStyleIndex = config.Ui.BaseStyle == "Light" ? 0 : 1;
|
||||
|
||||
Language = (int)config.System.Language.Value;
|
||||
Region = (int)config.System.Region.Value;
|
||||
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
||||
AudioBackend = (int)config.System.AudioBackend.Value;
|
||||
MemoryMode = (int)config.System.MemoryManagerMode.Value;
|
||||
|
||||
float anisotropy = config.Graphics.MaxAnisotropy;
|
||||
|
||||
MaxAnisotropy = anisotropy == -1 ? 0 : (int)(MathF.Log2(anisotropy));
|
||||
AspectRatio = (int)config.Graphics.AspectRatio.Value;
|
||||
|
||||
int resolution = config.Graphics.ResScale;
|
||||
|
||||
ResolutionScale = resolution == -1 ? 0 : resolution;
|
||||
CustomResolutionScale = config.Graphics.ResScaleCustom;
|
||||
|
||||
DateTime dateTimeOffset = DateTime.Now.AddSeconds(config.System.SystemTimeOffset);
|
||||
|
||||
DateOffset = dateTimeOffset.Date;
|
||||
TimeOffset = dateTimeOffset.TimeOfDay;
|
||||
|
||||
KeyboardHotkeys = config.Hid.Hotkeys.Value;
|
||||
|
||||
_previousVolumeLevel = Volume;
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
List<string> gameDirs = new List<string>(GameDirectories);
|
||||
|
||||
ConfigurationState config = ConfigurationState.Instance;
|
||||
|
||||
if (_validTzRegions.Contains(TimeZone))
|
||||
{
|
||||
config.System.TimeZone.Value = TimeZone;
|
||||
}
|
||||
|
||||
config.Logger.EnableError.Value = EnableError;
|
||||
config.Logger.EnableTrace.Value = EnableTrace;
|
||||
config.Logger.EnableWarn.Value = EnableWarn;
|
||||
config.Logger.EnableInfo.Value = EnableInfo;
|
||||
config.Logger.EnableStub.Value = EnableStub;
|
||||
config.Logger.EnableDebug.Value = EnableDebug;
|
||||
config.Logger.EnableGuest.Value = EnableGuest;
|
||||
config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog;
|
||||
config.Logger.EnableFileLog.Value = EnableFileLog;
|
||||
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
||||
config.System.EnableDockedMode.Value = EnableDockedMode;
|
||||
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
|
||||
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
|
||||
config.ShowConfirmExit.Value = ShowConfirmExit;
|
||||
config.HideCursorOnIdle.Value = HideCursorOnIdle;
|
||||
config.Graphics.EnableVsync.Value = EnableVsync;
|
||||
config.Graphics.EnableShaderCache.Value = EnableShaderCache;
|
||||
config.System.EnablePtc.Value = EnablePptc;
|
||||
config.System.EnableInternetAccess.Value = EnableInternetAccess;
|
||||
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
|
||||
config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
|
||||
config.System.ExpandRam.Value = ExpandDramSize;
|
||||
config.Hid.EnableKeyboard.Value = EnableKeyboard;
|
||||
config.Hid.EnableMouse.Value = EnableMouse;
|
||||
config.Ui.CustomThemePath.Value = CustomThemePath;
|
||||
config.Ui.EnableCustomTheme.Value = EnableCustomTheme;
|
||||
config.Ui.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark";
|
||||
config.System.Language.Value = (Language)Language;
|
||||
config.System.Region.Value = (Region)Region;
|
||||
|
||||
if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex)
|
||||
{
|
||||
DriverUtilities.ToggleOGLThreading(GraphicsBackendMultithreadingIndex == (int)BackendThreading.Off);
|
||||
}
|
||||
|
||||
config.Graphics.BackendThreading.Value = (BackendThreading)GraphicsBackendMultithreadingIndex;
|
||||
|
||||
TimeSpan systemTimeOffset = DateOffset - DateTime.Now;
|
||||
|
||||
config.System.SystemTimeOffset.Value = systemTimeOffset.Seconds;
|
||||
config.Graphics.ShadersDumpPath.Value = ShaderDumpPath;
|
||||
config.Ui.GameDirs.Value = gameDirs;
|
||||
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
||||
config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode;
|
||||
|
||||
float anisotropy = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy);
|
||||
|
||||
config.Graphics.MaxAnisotropy.Value = anisotropy;
|
||||
config.Graphics.AspectRatio.Value = (AspectRatio)AspectRatio;
|
||||
config.Graphics.ResScale.Value = ResolutionScale == 0 ? -1 : ResolutionScale;
|
||||
config.Graphics.ResScaleCustom.Value = CustomResolutionScale;
|
||||
config.System.AudioVolume.Value = Volume / 100;
|
||||
|
||||
AudioBackend audioBackend = (AudioBackend)AudioBackend;
|
||||
if (audioBackend != config.System.AudioBackend.Value)
|
||||
{
|
||||
config.System.AudioBackend.Value = audioBackend;
|
||||
|
||||
Logger.Info?.Print(LogClass.Application, $"AudioBackend toggled to: {audioBackend}");
|
||||
}
|
||||
|
||||
config.Hid.Hotkeys.Value = KeyboardHotkeys;
|
||||
|
||||
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
|
||||
MainWindow.UpdateGraphicsConfig();
|
||||
|
||||
_previousVolumeLevel = Volume;
|
||||
}
|
||||
|
||||
public void RevertIfNotSaved()
|
||||
{
|
||||
Program.ReloadConfig();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue