Compare commits

..

9 Commits

Author SHA1 Message Date
Evan Husted
9c12f52805 UI: Pretty Atmosphère mod names (#601)
Changes the mods from the Atmosphère folder to show a pretty name
instead of just the name of the folder they're in, because those names
are always just a title ID.

NOTE: The DLC names are from the file names, not retrieved from the
content file itself like the main applications.
2025-01-30 17:41:25 -06:00
Otozinclus
059fc83d4d Add more games to Metal Auto list (#592)
ARMS: Tested every character and every Map, played a cup as well. It
works flawless in my testing. (If it freezes, that is caused by the
Hypervisor, not Metal. You need to disable the Hypervisor for this game)

Luigi's Mansion 2 HD: I tested every world a bit and had no issue. Isaac
said he specifically worked on it as well

Following games were flawless in my testing, but I only tested earlier
parts of the game so far, a late game part might have an issue,
therefore I will further test these in the future:

- Astral Chain
- Bayonetta Origins
- New Pokemon Snap
2025-01-30 17:22:00 -06:00
Evan Husted
04ce7fb764 misc: chore: [ci skip] VSync progression as an extension 2025-01-30 16:54:05 -06:00
Evan Husted
359852b5c0 UI: Change order of VSync mode changing
VSync -> VSync Off -> Custom
is now
VSync -> Custom -> VSync Off

Note that Custom only appears if it's enabled in settings. This has no change if you aren't using custom VSync.
2025-01-30 16:47:40 -06:00
Evan Husted
796674d9cf UI: Allow more freedom changing the Speed value & clamp the visible number to a sane amount of trailing digits 2025-01-30 03:30:50 -06:00
Evan Husted
4efe24a3bc misc: chore: [ci skip] forgot to make that a locale entry 2025-01-30 03:21:42 -06:00
Evan Husted
1a42d1396c UI: Rainbow cycling speed settings
Note: this setting is global, even though it appears in the settings for the individual gamepad. This is simply for consistency; you access all the rainbow stuff in one place.
2025-01-30 03:18:34 -06:00
Evan Husted
11f29361eb misc: chore: [ci skip] Log errors from TaskScheduler.UnobservedTaskException 2025-01-29 21:07:43 -06:00
Evan Husted
71d8cfd232 misc: chore: Pass rainbow color by reference in the event instead of passing around a packed int. 2025-01-29 20:51:55 -06:00
26 changed files with 235 additions and 568 deletions

View File

@@ -6,4 +6,16 @@ namespace Ryujinx.Common.Configuration
Unbounded,
Custom
}
public static class VSyncModeExtensions
{
public static VSyncMode Next(this VSyncMode vsync, bool customEnabled = false) =>
vsync switch
{
VSyncMode.Switch => customEnabled ? VSyncMode.Custom : VSyncMode.Unbounded,
VSyncMode.Unbounded => VSyncMode.Switch,
VSyncMode.Custom => VSyncMode.Unbounded,
_ => VSyncMode.Switch
};
}
}

View File

@@ -0,0 +1,58 @@
using Gommon;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Common.Helper
{
public class RefEvent<T>
{
public delegate void Handler(ref T arg);
private readonly Lock _subLock = new();
private readonly List<Handler> _subscriptions = [];
public bool HasSubscribers
{
get
{
lock (_subLock)
return _subscriptions.Count != 0;
}
}
public IReadOnlyList<Handler> Subscriptions
{
get
{
lock (_subLock)
return _subscriptions;
}
}
public void Add(Handler subscriber)
{
Guard.Require(subscriber, nameof(subscriber));
lock (_subLock)
_subscriptions.Add(subscriber);
}
public void Remove(Handler subscriber)
{
Guard.Require(subscriber, nameof(subscriber));
lock (_subLock)
_subscriptions.Remove(subscriber);
}
public void Clear()
{
lock (_subLock)
_subscriptions.Clear();
}
public void Call(ref T arg)
{
foreach (Handler subscription in Subscriptions)
subscription(ref arg);
}
}
}

View File

@@ -30,10 +30,11 @@ namespace Ryujinx.Common
public static readonly string[] GreatMetalTitles =
[
"010076f0049a2000", // Bayonetta
"01009b500007c000", // ARMS
"0100a5c00d162000", // Cuphead
"010023800d64a000", // Deltarune
"01003a30012c0000", // LEGO City Undercover
"010048701995e000", // Luigi's Manion 2 HD
"010028600EBDA000", // Mario 3D World
"0100152000022000", // Mario Kart 8 Deluxe
"010075a016a3a000", // Persona 4 Arena Ultimax
@@ -48,10 +49,14 @@ namespace Ryujinx.Common
"01009bf0072d4000", // Captain Toad: Treasure Tracker
"01009510001ca000", // Fast RMX
"01005CA01580E000", // Persona 5 Royale
"010015100b514000", // Super Mario Bros. Wonder
"0100000000010000", // Super Mario Odyssey
//Isaac claims it has a issue in level 2, but I am not able to replicate it on my M3. More testing would be appreciated:
"010015100b514000", // Super Mario Bros. Wonder
// Further testing is appreciated, I did not test the entire game:
"01007300020fa000", // Astral Chain
"010076f0049a2000", // Bayonetta
"0100cf5010fec000", // Bayonetta Origins: Cereza and the Lost Demon
"0100f4300bf2c000", // New Pokemon Snap
];
public static string GetDiscordGameAsset(string titleId)

View File

@@ -1,4 +1,5 @@
using Gommon;
using Ryujinx.Common.Helper;
using System;
using System.Drawing;
using System.Threading;
@@ -55,7 +56,7 @@ namespace Ryujinx.Common.Utilities
{
_color = HsbToRgb((_color.GetHue() + Speed) / 360);
_updatedHandler.Call(_color.ToArgb());
_updatedHandler.Call(ref _color);
}
}
@@ -67,13 +68,13 @@ namespace Ryujinx.Common.Utilities
_color = Color.Blue;
}
public static event Action<int> Updated
public static event RefEvent<Color>.Handler Updated
{
add => _updatedHandler.Add(value);
remove => _updatedHandler.Remove(value);
}
private static readonly Event<int> _updatedHandler = new();
private static readonly RefEvent<Color> _updatedHandler = new();
private static Color HsbToRgb(float hue, float saturation = 1, float brightness = 1)
{

View File

@@ -111,7 +111,7 @@ namespace Ryujinx.Input.SDL2
byte blue = packedRgb > 0 ? (byte)(packedRgb % 256) : (byte)0;
if (SDL_GameControllerSetLED(_gamepadHandle, red, green, blue) != 0)
Logger.Error?.Print(LogClass.Hid, "LED is not supported on this game controller.");
Logger.Error?.Print(LogClass.Hid, "LED setting failed; probably in the middle of disconnecting.");
}
private GamepadFeaturesFlag GetFeaturesFlag()

View File

@@ -319,31 +319,12 @@ namespace Ryujinx.Ava
public void VSyncModeToggle()
{
VSyncMode oldVSyncMode = Device.VSyncMode;
VSyncMode newVSyncMode = VSyncMode.Switch;
bool customVSyncIntervalEnabled = ConfigurationState.Instance.Graphics.EnableCustomVSyncInterval.Value;
switch (oldVSyncMode)
{
case VSyncMode.Switch:
newVSyncMode = VSyncMode.Unbounded;
break;
case VSyncMode.Unbounded:
if (customVSyncIntervalEnabled)
{
newVSyncMode = VSyncMode.Custom;
}
else
{
newVSyncMode = VSyncMode.Switch;
}
break;
case VSyncMode.Custom:
newVSyncMode = VSyncMode.Switch;
break;
}
UpdateVSyncMode(this, new ReactiveEventArgs<VSyncMode>(oldVSyncMode, newVSyncMode));
UpdateVSyncMode(this, new ReactiveEventArgs<VSyncMode>(
oldVSyncMode,
oldVSyncMode.Next(customVSyncIntervalEnabled))
);
}
private void UpdateCustomVSyncIntervalValue(object sender, ReactiveEventArgs<int> e)

View File

@@ -7747,6 +7747,31 @@
"zh_TW": ""
}
},
{
"ID": "ControllerSettingsLedColorRainbowSpeed",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Rainbow Speed",
"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": "ControllerSettingsLedColor",
"Translations": {

View File

@@ -112,7 +112,11 @@ namespace Ryujinx.Ava
// Hook unhandled exception and process exit events.
AppDomain.CurrentDomain.UnhandledException += (sender, e)
=> ProcessUnhandledException(sender, e.ExceptionObject as Exception, e.IsTerminating);
TaskScheduler.UnobservedTaskException += (sender, e)
=> ProcessUnhandledException(sender, e.Exception, false);
AppDomain.CurrentDomain.ProcessExit += (_, _) => Exit();
// Setup base data directory.
AppDataManager.Initialize(CommandLineState.BaseDirPathArg);
@@ -282,9 +286,7 @@ namespace Ryujinx.Ava
log.PrintMsg(LogClass.Application, message);
}
if (isTerminating)
Exit();
}

View File

@@ -1,260 +0,0 @@
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Input;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Models.Input
{
public class StickVisualizer : BaseModel, IDisposable
{
public const int DrawStickPollRate = 50; // Milliseconds per poll.
public const int DrawStickCircumference = 5;
public const float DrawStickScaleFactor = DrawStickCanvasCenter;
public const int DrawStickCanvasSize = 100;
public const int DrawStickBorderSize = DrawStickCanvasSize + 5;
public const float DrawStickCanvasCenter = (DrawStickCanvasSize - DrawStickCircumference) / 2;
public const float MaxVectorLength = DrawStickCanvasSize / 2;
public CancellationTokenSource PollTokenSource;
public CancellationToken PollToken;
private static float _vectorLength;
private static float _vectorMultiplier;
private bool disposedValue;
private DeviceType _type;
public DeviceType Type
{
get => _type;
set
{
_type = value;
OnPropertyChanged();
}
}
private GamepadInputConfig _gamepadConfig;
public GamepadInputConfig GamepadConfig
{
get => _gamepadConfig;
set
{
_gamepadConfig = value;
OnPropertyChanged();
}
}
private KeyboardInputConfig _keyboardConfig;
public KeyboardInputConfig KeyboardConfig
{
get => _keyboardConfig;
set
{
_keyboardConfig = value;
OnPropertyChanged();
}
}
private (float, float) _uiStickLeft;
public (float, float) UiStickLeft
{
get => (_uiStickLeft.Item1 * DrawStickScaleFactor, _uiStickLeft.Item2 * DrawStickScaleFactor);
set
{
_uiStickLeft = value;
OnPropertyChanged();
OnPropertyChanged(nameof(UiStickRightX));
OnPropertyChanged(nameof(UiStickRightY));
OnPropertyChanged(nameof(UiDeadzoneRight));
}
}
private (float, float) _uiStickRight;
public (float, float) UiStickRight
{
get => (_uiStickRight.Item1 * DrawStickScaleFactor, _uiStickRight.Item2 * DrawStickScaleFactor);
set
{
_uiStickRight = value;
OnPropertyChanged();
OnPropertyChanged(nameof(UiStickLeftX));
OnPropertyChanged(nameof(UiStickLeftY));
OnPropertyChanged(nameof(UiDeadzoneLeft));
}
}
public float UiStickLeftX => ClampVector(UiStickLeft).Item1;
public float UiStickLeftY => ClampVector(UiStickLeft).Item2;
public float UiStickRightX => ClampVector(UiStickRight).Item1;
public float UiStickRightY => ClampVector(UiStickRight).Item2;
public int UiStickCircumference => DrawStickCircumference;
public int UiCanvasSize => DrawStickCanvasSize;
public int UiStickBorderSize => DrawStickBorderSize;
public float? UiDeadzoneLeft => _gamepadConfig?.DeadzoneLeft * DrawStickCanvasSize - DrawStickCircumference;
public float? UiDeadzoneRight => _gamepadConfig?.DeadzoneRight * DrawStickCanvasSize - DrawStickCircumference;
private InputViewModel Parent;
public StickVisualizer(InputViewModel parent)
{
Parent = parent;
PollTokenSource = new CancellationTokenSource();
PollToken = PollTokenSource.Token;
Task.Run(Initialize, PollToken);
}
public void UpdateConfig(object config)
{
if (config is ControllerInputViewModel padConfig)
{
GamepadConfig = padConfig.Config;
Type = DeviceType.Controller;
return;
}
else if (config is KeyboardInputViewModel keyConfig)
{
KeyboardConfig = keyConfig.Config;
Type = DeviceType.Keyboard;
return;
}
Type = DeviceType.None;
}
public async Task Initialize()
{
(float, float) leftBuffer;
(float, float) rightBuffer;
while (!PollToken.IsCancellationRequested)
{
leftBuffer = (0f, 0f);
rightBuffer = (0f, 0f);
switch (Type)
{
case DeviceType.Keyboard:
IKeyboard keyboard = (IKeyboard)Parent.AvaloniaKeyboardDriver.GetGamepad("0");
if (keyboard != null)
{
KeyboardStateSnapshot snapshot = keyboard.GetKeyboardStateSnapshot();
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickRight))
{
leftBuffer.Item1 += 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickLeft))
{
leftBuffer.Item1 -= 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickUp))
{
leftBuffer.Item2 += 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickDown))
{
leftBuffer.Item2 -= 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickRight))
{
rightBuffer.Item1 += 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickLeft))
{
rightBuffer.Item1 -= 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickUp))
{
rightBuffer.Item2 += 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickDown))
{
rightBuffer.Item2 -= 1;
}
UiStickLeft = leftBuffer;
UiStickRight = rightBuffer;
}
break;
case DeviceType.Controller:
IGamepad controller = Parent.SelectedGamepad;
if (controller != null)
{
leftBuffer = controller.GetStick((StickInputId)GamepadConfig.LeftJoystick);
rightBuffer = controller.GetStick((StickInputId)GamepadConfig.RightJoystick);
}
break;
case DeviceType.None:
break;
default:
throw new ArgumentException($"Unable to poll device type \"{Type}\"");
}
UiStickLeft = leftBuffer;
UiStickRight = rightBuffer;
await Task.Delay(DrawStickPollRate, PollToken);
}
PollTokenSource.Dispose();
}
public static (float, float) ClampVector((float, float) vect)
{
_vectorMultiplier = 1;
_vectorLength = MathF.Sqrt((vect.Item1 * vect.Item1) + (vect.Item2 * vect.Item2));
if (_vectorLength > MaxVectorLength)
{
_vectorMultiplier = MaxVectorLength / _vectorLength;
}
vect.Item1 = vect.Item1 * _vectorMultiplier + DrawStickCanvasCenter;
vect.Item2 = vect.Item2 * _vectorMultiplier + DrawStickCanvasCenter;
return vect;
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
PollTokenSource.Cancel();
}
KeyboardConfig = null;
GamepadConfig = null;
Parent = null;
disposedValue = true;
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@@ -1,5 +1,5 @@
using Ryujinx.Ava.UI.ViewModels;
using System.IO;
using System.Globalization;
namespace Ryujinx.Ava.UI.Models
{
@@ -21,6 +21,11 @@ namespace Ryujinx.Ava.UI.Models
public string Path { get; }
public string Name { get; }
public string FormattedName =>
InSd && ulong.TryParse(Name, NumberStyles.HexNumber, null, out ulong applicationId)
? $"Atmosphère: {System.IO.Path.GetFileNameWithoutExtension(RyujinxApp.MainWindow.ApplicationLibrary.GetNameForApplicationId(applicationId))}"
: Name;
public ModModel(string path, string name, bool enabled, bool inSd)
{
Path = path;

View File

@@ -1,42 +1,17 @@
using Avalonia.Svg.Skia;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.Views.Input;
using Ryujinx.Common.Utilities;
using Ryujinx.UI.Views.Input;
using System.Drawing;
namespace Ryujinx.Ava.UI.ViewModels.Input
{
public partial class ControllerInputViewModel : BaseModel
{
private GamepadInputConfig _config;
public GamepadInputConfig Config
{
get => _config;
set
{
_config = value;
[ObservableProperty] private GamepadInputConfig _config;
OnPropertyChanged();
}
}
private StickVisualizer _visualizer;
public StickVisualizer Visualizer
{
get => _visualizer;
set
{
_visualizer = value;
OnPropertyChanged();
}
}
private bool _isLeft;
public bool IsLeft
{
@@ -62,15 +37,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
}
public bool HasSides => IsLeft ^ IsRight;
[ObservableProperty] private SvgImage _image;
public InputViewModel ParentModel { get; }
public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config, StickVisualizer visualizer)
public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config)
{
ParentModel = model;
Visualizer = visualizer;
model.NotifyChangesEvent += OnParentModelChanged;
OnParentModelChanged();
config.PropertyChanged += (_, args) =>
@@ -78,7 +52,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
if (args.PropertyName is nameof(Config.UseRainbowLed))
{
if (Config is { UseRainbowLed: true, TurnOffLed: false, EnableLedChanging: true })
Rainbow.Updated += color => ParentModel.SelectedGamepad.SetLed((uint)color);
Rainbow.Updated += (ref Color color) => ParentModel.SelectedGamepad.SetLed((uint)color.ToArgb());
else
{
Rainbow.Reset();

View File

@@ -20,10 +20,10 @@ using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Input;
using Ryujinx.Input.SDL2;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text.Json;
@@ -49,7 +49,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
private int _controller;
private string _controllerImage;
private int _device;
private object _configViewModel;
[ObservableProperty] private object _configViewModel;
[ObservableProperty] private string _profileName;
private bool _isLoaded;
@@ -69,12 +69,11 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
_selectedGamepad = value;
if (ConfigViewModel is ControllerInputViewModel { Config.UseRainbowLed: true })
Rainbow.Updated += color => _selectedGamepad.SetLed((uint)color);
Rainbow.Updated += (ref Color color) => _selectedGamepad.SetLed((uint)color.ToArgb());
OnPropertiesChanged(nameof(HasLed), nameof(CanClearLed));
}
}
public StickVisualizer VisualStick { get; private set; }
public ObservableCollection<PlayerModel> PlayerIndexes { get; set; }
public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; }
@@ -95,19 +94,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public bool IsModified { get; set; }
public event Action NotifyChangesEvent;
public object ConfigViewModel
{
get => _configViewModel;
set
{
_configViewModel = value;
VisualStick.UpdateConfig(value);
OnPropertyChanged();
}
}
public PlayerIndex PlayerIdChoose
{
get => _playerIdChoose;
@@ -283,7 +269,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
Devices = [];
ProfilesList = [];
DeviceList = [];
VisualStick = new StickVisualizer(this);
ControllerImage = ProControllerResource;
@@ -304,12 +289,12 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
if (Config is StandardKeyboardInputConfig keyboardInputConfig)
{
ConfigViewModel = new KeyboardInputViewModel(this, new KeyboardInputConfig(keyboardInputConfig), VisualStick);
ConfigViewModel = new KeyboardInputViewModel(this, new KeyboardInputConfig(keyboardInputConfig));
}
if (Config is StandardControllerInputConfig controllerInputConfig)
{
ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig), VisualStick);
ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig));
}
}
@@ -908,8 +893,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
_mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates();
VisualStick.Dispose();
SelectedGamepad?.Dispose();
AvaloniaKeyboardDriver.Dispose();

View File

@@ -6,29 +6,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
{
public partial class KeyboardInputViewModel : BaseModel
{
private KeyboardInputConfig _config;
public KeyboardInputConfig Config
{
get => _config;
set
{
_config = value;
OnPropertyChanged();
}
}
private StickVisualizer _visualizer;
public StickVisualizer Visualizer
{
get => _visualizer;
set
{
_visualizer = value;
OnPropertyChanged();
}
}
[ObservableProperty] private KeyboardInputConfig _config;
private bool _isLeft;
public bool IsLeft
@@ -60,10 +38,9 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public readonly InputViewModel ParentModel;
public KeyboardInputViewModel(InputViewModel model, KeyboardInputConfig config, StickVisualizer visualizer)
public KeyboardInputViewModel(InputViewModel model, KeyboardInputConfig config)
{
ParentModel = model;
Visualizer = visualizer;
model.NotifyChangesEvent += OnParentModelChanged;
OnParentModelChanged();
Config = config;

View File

@@ -1,7 +1,13 @@
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Gommon;
using Humanizer;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.Utilities.Configuration;
using System;
using System.Globalization;
using System.Linq;
namespace Ryujinx.Ava.UI.ViewModels.Input
{
@@ -21,6 +27,19 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
[ObservableProperty] private bool _enableLedChanging;
[ObservableProperty] private Color _ledColor;
public string RainbowSpeedText => RainbowSpeed.ToString(CultureInfo.CurrentCulture).Truncate(4, string.Empty);
public float RainbowSpeed
{
get => ConfigurationState.Instance.Hid.RainbowSpeed;
set
{
ConfigurationState.Instance.Hid.RainbowSpeed.Value = value;
OnPropertyChanged();
OnPropertyChanged(nameof(RainbowSpeedText));
}
}
public bool ShowLedColorPicker => !TurnOffLed && !UseRainbowLed;

View File

@@ -316,103 +316,17 @@
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<!-- Controller Picture -->
<Image
Margin="0,10"
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{Binding Image}" />
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
Margin="0,10"
MinHeight="90">
<StackPanel Orientation="Vertical">
<Image
Margin="5,10"
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{Binding Image}" />
<StackPanel
Margin="10"
Orientation="Horizontal"
Spacing="20"
HorizontalAlignment="Center">
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
Height="{Binding Visualizer.UiStickBorderSize}"
Width="{Binding Visualizer.UiStickBorderSize}"
IsVisible="{Binding IsLeft}">
<Canvas
Background="{DynamicResource ThemeBackgroundColor}"
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}">
<Grid
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}"
Background="{DynamicResource ThemeBackgroundColor}">
<Ellipse
HorizontalAlignment="Center"
Stroke="Black"
StrokeThickness="1"
Width="{Binding Visualizer.UiCanvasSize}"
Height="{Binding Visualizer.UiCanvasSize}" />
<Ellipse
HorizontalAlignment="Center"
Fill="Gray"
Opacity="100"
Height="{Binding Visualizer.UiDeadzoneLeft}"
Width="{Binding Visualizer.UiDeadzoneLeft}" />
</Grid>
<Ellipse
Fill="Red"
Width="{Binding Visualizer.UiStickCircumference}"
Height="{Binding Visualizer.UiStickCircumference}"
Canvas.Bottom="{Binding Visualizer.UiStickLeftY}"
Canvas.Left="{Binding Visualizer.UiStickLeftX}" />
</Canvas>
</Border>
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
Height="{Binding Visualizer.UiStickBorderSize}"
Width="{Binding Visualizer.UiStickBorderSize}"
IsVisible="{Binding IsRight}">
<Canvas
Background="{DynamicResource ThemeBackgroundColor}"
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}">
<Grid
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}"
Background="{DynamicResource ThemeBackgroundColor}">
<Ellipse
HorizontalAlignment="Center"
Stroke="Black"
StrokeThickness="1"
Width="{Binding Visualizer.UiCanvasSize}"
Height="{Binding Visualizer.UiCanvasSize}" />
<Ellipse
HorizontalAlignment="Center"
Fill="Gray"
Opacity="100"
Height="{Binding Visualizer.UiDeadzoneRight}"
Width="{Binding Visualizer.UiDeadzoneRight}" />
</Grid>
<Ellipse
Fill="Red"
Width="{Binding Visualizer.UiStickCircumference}"
Height="{Binding Visualizer.UiStickCircumference}"
Canvas.Bottom="{Binding Visualizer.UiStickRightY}"
Canvas.Left="{Binding Visualizer.UiStickRightX}" />
</Canvas>
</Border>
</StackPanel>
</StackPanel>
</Border>
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5">
<StackPanel
Margin="8"
Orientation="Vertical">
@@ -431,8 +345,8 @@
Minimum="0"
Value="{Binding Config.TriggerThreshold, Mode=TwoWay}" />
<TextBlock
Width="25"
Text="{Binding Config.TriggerThreshold, StringFormat=\{0:0.00\}}" />
Width="25"
Text="{Binding Config.TriggerThreshold, StringFormat=\{0:0.00\}}" />
</StackPanel>
<StackPanel
Orientation="Vertical"

View File

@@ -309,79 +309,12 @@
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<!-- Controller Picture -->
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
<Image
Margin="0,10"
MinHeight="90">
<StackPanel
Margin="10"
Orientation="Horizontal"
Spacing="20"
HorizontalAlignment="Center">
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
Height="{Binding Visualizer.UiStickBorderSize}"
Width="{Binding Visualizer.UiStickBorderSize}"
IsVisible="{Binding IsLeft}">
<Canvas
Background="{DynamicResource ThemeBackgroundColor}"
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}">
<Grid
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}"
Background="{DynamicResource ThemeBackgroundColor}">
<Ellipse
HorizontalAlignment="Center"
Stroke="Black"
StrokeThickness="1"
Width="{Binding Visualizer.UiCanvasSize}"
Height="{Binding Visualizer.UiCanvasSize}"/>
</Grid>
<Ellipse
Fill="Red"
Width="{Binding Visualizer.UiStickCircumference}"
Height="{Binding Visualizer.UiStickCircumference}"
Canvas.Bottom="{Binding Visualizer.UiStickLeftY}"
Canvas.Left="{Binding Visualizer.UiStickLeftX}" />
</Canvas>
</Border>
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
Height="{Binding Visualizer.UiStickBorderSize}"
Width="{Binding Visualizer.UiStickBorderSize}"
IsVisible="{Binding IsRight}">
<Canvas
Background="{DynamicResource ThemeBackgroundColor}"
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}">
<Grid
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}"
Background="{DynamicResource ThemeBackgroundColor}">
<Ellipse
HorizontalAlignment="Center"
Stroke="Black"
StrokeThickness="1"
Width="{Binding Visualizer.UiCanvasSize}"
Height="{Binding Visualizer.UiCanvasSize}"/>
</Grid>
<Ellipse
Fill="Red"
Width="{Binding Visualizer.UiStickCircumference}"
Height="{Binding Visualizer.UiStickCircumference}"
Canvas.Bottom="{Binding Visualizer.UiStickRightY}"
Canvas.Left="{Binding Visualizer.UiStickRightX}" />
</Canvas>
</Border>
</StackPanel>
</Border>
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{Binding Image}" />
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"

View File

@@ -11,7 +11,7 @@
x:Class="Ryujinx.UI.Views.Input.LedInputView">
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal" IsVisible="{Binding ParentModel.CanClearLed}">
<TextBlock MinWidth="75" MaxWidth="150" Text="{ext:Locale ControllerSettingsLedColorDisable}" />
<TextBlock MinWidth="75" MaxWidth="200" Text="{ext:Locale ControllerSettingsLedColorDisable}" />
<CheckBox
Margin="5"
MinWidth="0"
@@ -20,15 +20,33 @@
</CheckBox>
</StackPanel>
<StackPanel Orientation="Horizontal" IsEnabled="{Binding !TurnOffLed}">
<TextBlock MinWidth="75" MaxWidth="150" Text="{ext:Locale ControllerSettingsLedColorRainbow}" />
<TextBlock MinWidth="75" MaxWidth="200" Text="{ext:Locale ControllerSettingsLedColorRainbow}" />
<CheckBox
Margin="5"
MinWidth="0"
IsChecked="{Binding UseRainbowLed, Mode=TwoWay}">
</CheckBox>
</StackPanel>
<StackPanel Orientation="Horizontal" IsEnabled="{Binding !TurnOffLed}">
<TextBlock MinWidth="75" MaxWidth="200" Text="{ext:Locale ControllerSettingsLedColorRainbowSpeed}" />
<Slider HorizontalAlignment="Center"
Value="{Binding RainbowSpeed}"
Width="175"
Margin="0,-3,0,0"
Height="32"
Padding="0,-5"
TickFrequency="0.25"
LargeChange="1"
SmallChange="0.25"
VerticalAlignment="Center"
Minimum="1"
Maximum="10" />
<TextBlock Margin="5,0"
MinWidth="75"
Text="{Binding RainbowSpeedText}" />
</StackPanel>
<StackPanel Orientation="Horizontal" IsEnabled="{Binding ShowLedColorPicker}">
<TextBlock MinWidth="75" MaxWidth="150" Text="{ext:Locale ControllerSettingsLedColor}" />
<TextBlock MinWidth="75" MaxWidth="200" Text="{ext:Locale ControllerSettingsLedColor}" />
<ui:ColorPickerButton
Margin="5"
IsMoreButtonVisible="False"

View File

@@ -1,11 +1,9 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Ava.UI.Views.Input;
using System.Threading.Tasks;
namespace Ryujinx.UI.Views.Input

View File

@@ -81,7 +81,7 @@
MaxLines="2"
TextWrapping="Wrap"
TextTrimming="CharacterEllipsis"
Text="{Binding Name}" />
Text="{Binding FormattedName}" />
<StackPanel
Grid.Column="1"
Spacing="10"

View File

@@ -11,7 +11,7 @@
xmlns:settings="clr-namespace:Ryujinx.Ava.UI.Views.Settings"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
Width="1100"
Height="850"
Height="768"
MinWidth="800"
MinHeight="480"
WindowStartupLocation="CenterOwner"

View File

@@ -34,7 +34,6 @@ namespace Ryujinx.Ava.UI.Windows
#if DEBUG
this.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Alt));
#endif
}
public SettingsWindow()

View File

@@ -111,6 +111,30 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
return data;
}
/// <summary>
/// Gets a name for an available content file based on the Application ID '<paramref name="id"/>'.
/// <br/><br/>
/// For Applications, this returns the localized name of the app found in the file.
/// For DLCs, this returns the name of the file that contains the DLC, minus the file extension.
/// </summary>
/// <param name="id">The Application ID to search for.</param>
/// <remarks>
/// If the provided Application ID does not have a corresponding Application OR DLC file,
/// <paramref name="id"/> formatted as hexadecimal is returned.
/// </remarks>
/// <returns>A formatted Application name, or <paramref name="id"/> as hexadecimal if none is found.</returns>
public string GetNameForApplicationId(ulong id)
{
DynamicData.Kernel.Optional<ApplicationData> appData = Applications.Lookup(id);
if (appData.HasValue)
return appData.Value.Name;
if (DownloadableContents.Keys.FindFirst(x => x.TitleId == id).TryGet(out DownloadableContentModel dlcData))
return dlcData.FileName;
return id.ToString("X16");
}
/// <exception cref="LibHac.Common.Keys.MissingKeyException">The configured key set is missing a key.</exception>
/// <exception cref="InvalidDataException">The NCA header could not be decrypted.</exception>
/// <exception cref="NotSupportedException">The NCA version is not supported.</exception>

View File

@@ -1,6 +1,5 @@
using Ryujinx.Ava.Utilities.Configuration.System;
using Ryujinx.Ava.Utilities.Configuration.UI;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Multiplayer;
@@ -8,7 +7,6 @@ using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE;
using System.Collections.Generic;
using System.Text.Json.Nodes;
namespace Ryujinx.Ava.Utilities.Configuration
{
@@ -17,7 +15,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// <summary>
/// The current version of the file format
/// </summary>
public const int CurrentVersion = 61;
public const int CurrentVersion = 62;
/// <summary>
/// Version of the configuration file format
@@ -376,24 +374,15 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// </summary>
public KeyboardHotkeys Hotkeys { get; set; }
/// <summary>
/// Legacy keyboard control bindings
/// </summary>
/// <remarks>Kept for file format compatibility (to avoid possible failure when parsing configuration on old versions)</remarks>
/// TODO: Remove this when those older versions aren't in use anymore.
public List<JsonObject> KeyboardConfig { get; set; }
/// <summary>
/// Legacy controller control bindings
/// </summary>
/// <remarks>Kept for file format compatibility (to avoid possible failure when parsing configuration on old versions)</remarks>
/// TODO: Remove this when those older versions aren't in use anymore.
public List<JsonObject> ControllerConfig { get; set; }
/// <summary>
/// Input configurations
/// </summary>
public List<InputConfig> InputConfig { get; set; }
/// <summary>
/// The speed of spectrum cycling for the Rainbow LED feature.
/// </summary>
public float RainbowSpeed { get; set; }
/// <summary>
/// Graphics backend

View File

@@ -140,6 +140,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
Hid.EnableMouse.Value = cff.EnableMouse;
Hid.Hotkeys.Value = cff.Hotkeys;
Hid.InputConfig.Value = cff.InputConfig ?? [];
Hid.RainbowSpeed.Value = cff.RainbowSpeed;
Multiplayer.LanInterfaceId.Value = cff.MultiplayerLanInterfaceId;
Multiplayer.Mode.Value = cff.MultiplayerMode;
@@ -427,7 +428,8 @@ namespace Ryujinx.Ava.Utilities.Configuration
LedColor = new Color(255, 5, 1, 253).ToUInt32()
};
}
})
}),
(62, static cff => cff.RainbowSpeed = 1f)
);
}
}

View File

@@ -7,6 +7,7 @@ using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Multiplayer;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE;
using System.Collections.Generic;
using System.Linq;
@@ -444,6 +445,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// TODO: Implement a ReactiveList class.
/// </summary>
public ReactiveObject<List<InputConfig>> InputConfig { get; private set; }
/// <summary>
/// The speed of spectrum cycling for the Rainbow LED feature.
/// </summary>
public ReactiveObject<float> RainbowSpeed { get; }
public HidSection()
{
@@ -451,6 +457,8 @@ namespace Ryujinx.Ava.Utilities.Configuration
EnableMouse = new ReactiveObject<bool>();
Hotkeys = new ReactiveObject<KeyboardHotkeys>();
InputConfig = new ReactiveObject<List<InputConfig>>();
RainbowSpeed = new ReactiveObject<float>();
RainbowSpeed.Event += (_, args) => Rainbow.Speed = args.NewValue;
}
}

View File

@@ -130,9 +130,8 @@ namespace Ryujinx.Ava.Utilities.Configuration
EnableKeyboard = Hid.EnableKeyboard,
EnableMouse = Hid.EnableMouse,
Hotkeys = Hid.Hotkeys,
KeyboardConfig = [],
ControllerConfig = [],
InputConfig = Hid.InputConfig,
RainbowSpeed = Hid.RainbowSpeed,
GraphicsBackend = Graphics.GraphicsBackend,
PreferredGpu = Graphics.PreferredGpu,
MultiplayerLanInterfaceId = Multiplayer.LanInterfaceId,
@@ -255,6 +254,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
VolumeUp = Key.Unbound,
VolumeDown = Key.Unbound,
};
Hid.RainbowSpeed.Value = 1f;
Hid.InputConfig.Value =
[
new StandardKeyboardInputConfig