Compare commits
6 Commits
Canary-1.2
...
714c68b548
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
714c68b548 | ||
|
|
fec197d9ec | ||
|
|
a4b2feef79 | ||
|
|
ad7d9d1ce0 | ||
|
|
86f9544910 | ||
|
|
e9ecbd44fc |
@@ -1034,16 +1034,16 @@ namespace Ryujinx.HLE.FileSystem
|
||||
switch (fileName)
|
||||
{
|
||||
case "prod.keys":
|
||||
verified = VerifyKeys(lines, genericPattern);
|
||||
verified = verifyKeys(lines, genericPattern);
|
||||
break;
|
||||
case "title.keys":
|
||||
verified = VerifyKeys(lines, titlePattern);
|
||||
verified = verifyKeys(lines, titlePattern);
|
||||
break;
|
||||
case "console.keys":
|
||||
verified = VerifyKeys(lines, genericPattern);
|
||||
verified = verifyKeys(lines, genericPattern);
|
||||
break;
|
||||
case "dev.keys":
|
||||
verified = VerifyKeys(lines, genericPattern);
|
||||
verified = verifyKeys(lines, genericPattern);
|
||||
break;
|
||||
default:
|
||||
throw new FormatException($"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.");
|
||||
@@ -1056,22 +1056,20 @@ namespace Ryujinx.HLE.FileSystem
|
||||
{
|
||||
throw new FileNotFoundException($"Keys file not found at \"{filePath}\".");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
bool VerifyKeys(string[] lines, string regex)
|
||||
{
|
||||
foreach (string line in lines)
|
||||
{
|
||||
if (!Regex.IsMatch(line, regex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool verifyKeys(string[] lines, string regex)
|
||||
{
|
||||
foreach (string line in lines)
|
||||
{
|
||||
if (!Regex.IsMatch(line, regex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool AreKeysAlredyPresent(string pathToCheck)
|
||||
{
|
||||
string[] fileNames = { "prod.keys", "title.keys", "console.keys", "dev.keys" };
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.SDL2.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -11,6 +12,7 @@ namespace Ryujinx.Input.SDL2
|
||||
private readonly Dictionary<int, string> _gamepadsInstanceIdsMapping;
|
||||
private readonly List<string> _gamepadsIds;
|
||||
private readonly Lock _lock = new();
|
||||
private readonly SDL2JoyConPair _joyConPair;
|
||||
|
||||
public ReadOnlySpan<string> GamepadsIds
|
||||
{
|
||||
@@ -36,7 +38,7 @@ namespace Ryujinx.Input.SDL2
|
||||
SDL2Driver.Instance.Initialize();
|
||||
SDL2Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected;
|
||||
SDL2Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected;
|
||||
|
||||
_joyConPair = new SDL2JoyConPair();
|
||||
// Add already connected gamepads
|
||||
int numJoysticks = SDL_NumJoysticks();
|
||||
|
||||
@@ -89,6 +91,10 @@ namespace Ryujinx.Input.SDL2
|
||||
lock (_lock)
|
||||
{
|
||||
_gamepadsIds.Remove(id);
|
||||
if (_joyConPair.GetGamepad(_gamepadsIds) == null)
|
||||
{
|
||||
_gamepadsIds.Remove(_joyConPair.Id);
|
||||
}
|
||||
}
|
||||
|
||||
OnGamepadDisconnected?.Invoke(id);
|
||||
@@ -120,6 +126,13 @@ namespace Ryujinx.Input.SDL2
|
||||
_gamepadsIds.Insert(joystickDeviceId, id);
|
||||
else
|
||||
_gamepadsIds.Add(id);
|
||||
var powerLevel = SDL_JoystickCurrentPowerLevel(GetJoystickIndexByGamepadId(id));
|
||||
Logger.Info?.Print(LogClass.Hid, $"Gamepad connected: {id}, power level: {powerLevel}");
|
||||
if (_joyConPair.GetGamepad(_gamepadsIds) != null)
|
||||
{
|
||||
_gamepadsIds.Remove(_joyConPair.Id);
|
||||
_gamepadsIds.Add(_joyConPair.Id);
|
||||
}
|
||||
}
|
||||
|
||||
OnGamepadConnected?.Invoke(id);
|
||||
@@ -157,6 +170,14 @@ namespace Ryujinx.Input.SDL2
|
||||
|
||||
public IGamepad GetGamepad(string id)
|
||||
{
|
||||
if (id == _joyConPair.Id)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _joyConPair.GetGamepad(_gamepadsIds);
|
||||
}
|
||||
}
|
||||
|
||||
int joystickIndex = GetJoystickIndexByGamepadId(id);
|
||||
|
||||
if (joystickIndex == -1)
|
||||
|
||||
233
src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs
Normal file
233
src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs
Normal file
@@ -0,0 +1,233 @@
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using static SDL2.SDL;
|
||||
|
||||
namespace Ryujinx.Input.SDL2
|
||||
{
|
||||
internal class SDL2JoyConPair : IGamepad
|
||||
{
|
||||
private IGamepad _left;
|
||||
private IGamepad _right;
|
||||
|
||||
private StandardControllerInputConfig _configuration;
|
||||
private readonly StickInputId[] _stickUserMapping =
|
||||
[
|
||||
StickInputId.Unbound,
|
||||
StickInputId.Left,
|
||||
StickInputId.Right
|
||||
];
|
||||
private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From)
|
||||
{
|
||||
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not GamepadButtonInputId.Unbound;
|
||||
}
|
||||
|
||||
private readonly List<ButtonMappingEntry> _buttonsUserMapping = new(20);
|
||||
|
||||
private readonly Lock _userMappingLock = new();
|
||||
|
||||
public GamepadFeaturesFlag Features => (_left?.Features ?? GamepadFeaturesFlag.None) | (_right?.Features ?? GamepadFeaturesFlag.None);
|
||||
|
||||
public string Id => "JoyConPair";
|
||||
|
||||
public string Name => "Nintendo Switch Joy-Con (L/R)";
|
||||
private const string _leftName = "Nintendo Switch Joy-Con (L)";
|
||||
private const string _rightName = "Nintendo Switch Joy-Con (R)";
|
||||
public bool IsConnected => _left is { IsConnected: true } && _right is { IsConnected: true };
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_left?.Dispose();
|
||||
_right?.Dispose();
|
||||
}
|
||||
public GamepadStateSnapshot GetMappedStateSnapshot()
|
||||
{
|
||||
GamepadStateSnapshot rawState = GetStateSnapshot();
|
||||
GamepadStateSnapshot result = default;
|
||||
|
||||
lock (_userMappingLock)
|
||||
{
|
||||
if (_buttonsUserMapping.Count == 0)
|
||||
return rawState;
|
||||
|
||||
|
||||
// ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
|
||||
foreach (ButtonMappingEntry entry in _buttonsUserMapping)
|
||||
{
|
||||
if (!entry.IsValid)
|
||||
continue;
|
||||
|
||||
// Do not touch state of button already pressed
|
||||
if (!result.IsPressed(entry.To))
|
||||
{
|
||||
result.SetPressed(entry.To, rawState.IsPressed(entry.From));
|
||||
}
|
||||
}
|
||||
|
||||
(float leftStickX, float leftStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Left]);
|
||||
(float rightStickX, float rightStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Right]);
|
||||
|
||||
result.SetStick(StickInputId.Left, leftStickX, leftStickY);
|
||||
result.SetStick(StickInputId.Right, rightStickX, rightStickY);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Vector3 GetMotionData(MotionInputId inputId)
|
||||
{
|
||||
return inputId switch
|
||||
{
|
||||
MotionInputId.SecondAccelerometer => _right.GetMotionData(MotionInputId.Accelerometer),
|
||||
MotionInputId.SecondGyroscope => _right.GetMotionData(MotionInputId.Gyroscope),
|
||||
_ => _left.GetMotionData(inputId)
|
||||
};
|
||||
}
|
||||
|
||||
public GamepadStateSnapshot GetStateSnapshot()
|
||||
{
|
||||
return IGamepad.GetStateSnapshot(this);
|
||||
}
|
||||
|
||||
public (float, float) GetStick(StickInputId inputId)
|
||||
{
|
||||
switch (inputId)
|
||||
{
|
||||
case StickInputId.Left:
|
||||
{
|
||||
(float x, float y) = _left.GetStick(StickInputId.Left);
|
||||
return (y, -x);
|
||||
}
|
||||
case StickInputId.Right:
|
||||
{
|
||||
(float x, float y) = _right.GetStick(StickInputId.Left);
|
||||
return (-y, x);
|
||||
}
|
||||
case StickInputId.Unbound:
|
||||
case StickInputId.Count:
|
||||
default:
|
||||
return (0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPressed(GamepadButtonInputId inputId)
|
||||
{
|
||||
return inputId switch
|
||||
{
|
||||
GamepadButtonInputId.LeftStick => _left.IsPressed(GamepadButtonInputId.LeftStick),
|
||||
GamepadButtonInputId.DpadUp => _left.IsPressed(GamepadButtonInputId.Y),
|
||||
GamepadButtonInputId.DpadDown => _left.IsPressed(GamepadButtonInputId.A),
|
||||
GamepadButtonInputId.DpadLeft => _left.IsPressed(GamepadButtonInputId.B),
|
||||
GamepadButtonInputId.DpadRight => _left.IsPressed(GamepadButtonInputId.X),
|
||||
GamepadButtonInputId.Minus => _left.IsPressed(GamepadButtonInputId.Start),
|
||||
GamepadButtonInputId.LeftShoulder => _left.IsPressed(GamepadButtonInputId.Paddle2),
|
||||
GamepadButtonInputId.LeftTrigger => _left.IsPressed(GamepadButtonInputId.Paddle4),
|
||||
GamepadButtonInputId.SingleRightTrigger0 => _left.IsPressed(GamepadButtonInputId.LeftShoulder),
|
||||
GamepadButtonInputId.SingleLeftTrigger0 => _left.IsPressed(GamepadButtonInputId.RightShoulder),
|
||||
|
||||
GamepadButtonInputId.RightStick => _right.IsPressed(GamepadButtonInputId.LeftStick),
|
||||
GamepadButtonInputId.A => _right.IsPressed(GamepadButtonInputId.B),
|
||||
GamepadButtonInputId.B => _right.IsPressed(GamepadButtonInputId.Y),
|
||||
GamepadButtonInputId.X => _right.IsPressed(GamepadButtonInputId.A),
|
||||
GamepadButtonInputId.Y => _right.IsPressed(GamepadButtonInputId.X),
|
||||
GamepadButtonInputId.Plus => _right.IsPressed(GamepadButtonInputId.Start),
|
||||
GamepadButtonInputId.RightShoulder => _right.IsPressed(GamepadButtonInputId.Paddle1),
|
||||
GamepadButtonInputId.RightTrigger => _right.IsPressed(GamepadButtonInputId.Paddle3),
|
||||
GamepadButtonInputId.SingleRightTrigger1 => _right.IsPressed(GamepadButtonInputId.LeftShoulder),
|
||||
GamepadButtonInputId.SingleLeftTrigger1 => _right.IsPressed(GamepadButtonInputId.RightShoulder),
|
||||
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||
{
|
||||
if (lowFrequency != 0)
|
||||
{
|
||||
_right.Rumble(lowFrequency, lowFrequency, durationMs);
|
||||
}
|
||||
if (highFrequency != 0)
|
||||
{
|
||||
_left.Rumble(highFrequency, highFrequency, durationMs);
|
||||
}
|
||||
if (lowFrequency == 0 && highFrequency == 0)
|
||||
{
|
||||
_left.Rumble(0, 0, durationMs);
|
||||
_right.Rumble(0, 0, durationMs);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetConfiguration(InputConfig configuration)
|
||||
{
|
||||
lock (_userMappingLock)
|
||||
{
|
||||
|
||||
_configuration = (StandardControllerInputConfig)configuration;
|
||||
_left.SetConfiguration(configuration);
|
||||
_right.SetConfiguration(configuration);
|
||||
|
||||
_buttonsUserMapping.Clear();
|
||||
|
||||
// First update sticks
|
||||
_stickUserMapping[(int)StickInputId.Left] = (StickInputId)_configuration.LeftJoyconStick.Joystick;
|
||||
_stickUserMapping[(int)StickInputId.Right] = (StickInputId)_configuration.RightJoyconStick.Joystick;
|
||||
|
||||
// Then left joycon
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (GamepadButtonInputId)_configuration.LeftJoyconStick.StickButton));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (GamepadButtonInputId)_configuration.LeftJoycon.DpadUp));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (GamepadButtonInputId)_configuration.LeftJoycon.DpadDown));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (GamepadButtonInputId)_configuration.LeftJoycon.DpadLeft));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (GamepadButtonInputId)_configuration.LeftJoycon.DpadRight));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonMinus));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonL));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonZl));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSl));
|
||||
|
||||
// Finally right joycon
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (GamepadButtonInputId)_configuration.RightJoyconStick.StickButton));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (GamepadButtonInputId)_configuration.RightJoycon.ButtonA));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (GamepadButtonInputId)_configuration.RightJoycon.ButtonB));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (GamepadButtonInputId)_configuration.RightJoycon.ButtonX));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (GamepadButtonInputId)_configuration.RightJoycon.ButtonY));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (GamepadButtonInputId)_configuration.RightJoycon.ButtonPlus));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (GamepadButtonInputId)_configuration.RightJoycon.ButtonR));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (GamepadButtonInputId)_configuration.RightJoycon.ButtonZr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSl));
|
||||
|
||||
SetTriggerThreshold(_configuration.TriggerThreshold);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void SetTriggerThreshold(float triggerThreshold)
|
||||
{
|
||||
_left.SetTriggerThreshold(triggerThreshold);
|
||||
_right.SetTriggerThreshold(triggerThreshold);
|
||||
}
|
||||
|
||||
public IGamepad GetGamepad(List<string> gamepadsIds)
|
||||
{
|
||||
this.Dispose();
|
||||
var gamepadNames = gamepadsIds.Where(gamepadId => gamepadId != Id)
|
||||
.Select((_, index) => SDL_GameControllerNameForIndex(index)).ToList();
|
||||
int leftIndex = gamepadNames.IndexOf(_leftName);
|
||||
int rightIndex = gamepadNames.IndexOf(_rightName);
|
||||
|
||||
if (leftIndex == -1 || rightIndex == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
nint leftGamepadHandle = SDL_GameControllerOpen(leftIndex);
|
||||
nint rightGamepadHandle = SDL_GameControllerOpen(rightIndex);
|
||||
_left = new SDL2Gamepad(leftGamepadHandle, gamepadsIds[leftIndex]);
|
||||
_right = new SDL2Gamepad(rightGamepadHandle, gamepadsIds[leftIndex]);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,6 +266,7 @@ namespace Ryujinx.Input.HLE
|
||||
if (motionConfig.MotionBackend != MotionInputBackendType.CemuHook)
|
||||
{
|
||||
_leftMotionInput = new MotionInput();
|
||||
_rightMotionInput = new MotionInput();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -298,7 +299,20 @@ namespace Ryujinx.Input.HLE
|
||||
|
||||
if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair)
|
||||
{
|
||||
_rightMotionInput = _leftMotionInput;
|
||||
if (gamepad.Id== "JoyConPair")
|
||||
{
|
||||
Vector3 rightAccelerometer = gamepad.GetMotionData(MotionInputId.SecondAccelerometer);
|
||||
Vector3 rightGyroscope = gamepad.GetMotionData(MotionInputId.SecondGyroscope);
|
||||
|
||||
rightAccelerometer = new Vector3(rightAccelerometer.X, -rightAccelerometer.Z, rightAccelerometer.Y);
|
||||
rightGyroscope = new Vector3(rightGyroscope.X, -rightGyroscope.Z, rightGyroscope.Y);
|
||||
|
||||
_rightMotionInput.Update(rightAccelerometer, rightGyroscope, (ulong)PerformanceCounter.ElapsedNanoseconds / 1000, controllerConfig.Motion.Sensitivity, (float)controllerConfig.Motion.GyroDeadzone);
|
||||
}
|
||||
else
|
||||
{
|
||||
_rightMotionInput = _leftMotionInput;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -333,6 +347,7 @@ namespace Ryujinx.Input.HLE
|
||||
// Reset states
|
||||
State = default;
|
||||
_leftMotionInput = null;
|
||||
_rightMotionInput = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,5 +21,17 @@ namespace Ryujinx.Input
|
||||
/// </summary>
|
||||
/// <remarks>Values are in degrees</remarks>
|
||||
Gyroscope,
|
||||
|
||||
/// <summary>
|
||||
/// Second accelerometer.
|
||||
/// </summary>
|
||||
/// <remarks>Values are in m/s^2</remarks>
|
||||
SecondAccelerometer,
|
||||
|
||||
/// <summary>
|
||||
/// Second gyroscope.
|
||||
/// </summary>
|
||||
/// <remarks>Values are in degrees</remarks>
|
||||
SecondGyroscope
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
using LibHac.Common;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Ns;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.HLE;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.UI.App.Common;
|
||||
|
||||
namespace Ryujinx.UI.Common.Helper
|
||||
{
|
||||
public readonly struct AppletMetadata
|
||||
{
|
||||
private readonly ContentManager _contentManager;
|
||||
|
||||
public string Name { get; }
|
||||
public ulong ProgramId { get; }
|
||||
|
||||
public string Version { get; }
|
||||
|
||||
public AppletMetadata(ContentManager contentManager, string name, ulong programId, string version = "1.0.0")
|
||||
: this(name, programId, version)
|
||||
{
|
||||
_contentManager = contentManager;
|
||||
}
|
||||
|
||||
public AppletMetadata(string name, ulong programId, string version = "1.0.0")
|
||||
{
|
||||
Name = name;
|
||||
ProgramId = programId;
|
||||
Version = version;
|
||||
}
|
||||
|
||||
public string GetContentPath(ContentManager contentManager)
|
||||
=> (contentManager ?? _contentManager)
|
||||
.GetInstalledContentPath(ProgramId, StorageId.BuiltInSystem, NcaContentType.Program);
|
||||
|
||||
public bool CanStart(ContentManager contentManager, out ApplicationData appData, out BlitStruct<ApplicationControlProperty> appControl)
|
||||
{
|
||||
contentManager ??= _contentManager;
|
||||
if (contentManager == null)
|
||||
{
|
||||
appData = null;
|
||||
appControl = new BlitStruct<ApplicationControlProperty>(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
appData = new()
|
||||
{
|
||||
Name = Name,
|
||||
Id = ProgramId,
|
||||
Path = GetContentPath(contentManager)
|
||||
};
|
||||
|
||||
if (string.IsNullOrEmpty(appData.Path))
|
||||
{
|
||||
appControl = new BlitStruct<ApplicationControlProperty>(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
appControl = StructHelpers.CreateCustomNacpData(Name, Version);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
BIN
src/Ryujinx.UI.Common/Resources/Logo_Thiccjinx.png
Normal file
BIN
src/Ryujinx.UI.Common/Resources/Logo_Thiccjinx.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 609 KiB |
@@ -33,7 +33,7 @@
|
||||
<EmbeddedResource Include="Resources\Icon_XCI.png" />
|
||||
<EmbeddedResource Include="Resources\Logo_Amiibo.png" />
|
||||
<EmbeddedResource Include="Resources\Logo_Ryujinx.png" />
|
||||
<EmbeddedResource Include="Resources\Logo_Ryujinx_AntiAlias.png" />
|
||||
<EmbeddedResource Include="Resources\Logo_Thiccjinx.png" />
|
||||
<EmbeddedResource Include="Resources\Logo_Discord_Dark.png" />
|
||||
<EmbeddedResource Include="Resources\Logo_Discord_Light.png" />
|
||||
<EmbeddedResource Include="Resources\Logo_GitHub_Dark.png" />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Gommon;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using System;
|
||||
@@ -16,6 +17,7 @@ namespace Ryujinx.Ava.Common.Locale
|
||||
private const string DefaultLanguageCode = "en_US";
|
||||
|
||||
private readonly Dictionary<LocaleKeys, string> _localeStrings;
|
||||
private Dictionary<LocaleKeys, string> _localeDefaultStrings;
|
||||
private readonly ConcurrentDictionary<LocaleKeys, object[]> _dynamicValues;
|
||||
private string _localeLanguageCode;
|
||||
|
||||
@@ -25,6 +27,7 @@ namespace Ryujinx.Ava.Common.Locale
|
||||
public LocaleManager()
|
||||
{
|
||||
_localeStrings = new Dictionary<LocaleKeys, string>();
|
||||
_localeDefaultStrings = new Dictionary<LocaleKeys, string>();
|
||||
_dynamicValues = new ConcurrentDictionary<LocaleKeys, object[]>();
|
||||
|
||||
Load();
|
||||
@@ -34,7 +37,9 @@ namespace Ryujinx.Ava.Common.Locale
|
||||
{
|
||||
var localeLanguageCode = !string.IsNullOrEmpty(ConfigurationState.Instance.UI.LanguageCode.Value) ?
|
||||
ConfigurationState.Instance.UI.LanguageCode.Value : CultureInfo.CurrentCulture.Name.Replace('-', '_');
|
||||
|
||||
|
||||
// Load en_US as default, if the target language translation is missing or incomplete.
|
||||
LoadDefaultLanguage();
|
||||
LoadLanguage(localeLanguageCode);
|
||||
|
||||
// Save whatever we ended up with.
|
||||
@@ -61,14 +66,26 @@ namespace Ryujinx.Ava.Common.Locale
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If formatting the text failed,
|
||||
// continue to the below line & return the text without formatting.
|
||||
// If formatting failed use the default text instead.
|
||||
if (_localeDefaultStrings.TryGetValue(key, out value))
|
||||
try
|
||||
{
|
||||
return string.Format(value, dynamicValue);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If formatting the default text failed return the key.
|
||||
return key.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
return key.ToString(); // If the locale text doesn't exist return the key.
|
||||
|
||||
// If the locale doesn't contain the key return the default one.
|
||||
return _localeDefaultStrings.TryGetValue(key, out string defaultValue)
|
||||
? defaultValue
|
||||
: key.ToString(); // If the locale text doesn't exist return the key.
|
||||
}
|
||||
set
|
||||
{
|
||||
@@ -92,11 +109,16 @@ namespace Ryujinx.Ava.Common.Locale
|
||||
{
|
||||
_dynamicValues[key] = values;
|
||||
|
||||
OnPropertyChanged("Translation");
|
||||
OnPropertyChanged("Item");
|
||||
|
||||
return this[key];
|
||||
}
|
||||
|
||||
private void LoadDefaultLanguage()
|
||||
{
|
||||
_localeDefaultStrings = LoadJsonLanguage(DefaultLanguageCode);
|
||||
}
|
||||
|
||||
public void LoadLanguage(string languageCode)
|
||||
{
|
||||
var locale = LoadJsonLanguage(languageCode);
|
||||
@@ -104,7 +126,7 @@ namespace Ryujinx.Ava.Common.Locale
|
||||
if (locale == null)
|
||||
{
|
||||
_localeLanguageCode = DefaultLanguageCode;
|
||||
locale = LoadJsonLanguage(_localeLanguageCode);
|
||||
locale = _localeDefaultStrings;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -116,12 +138,16 @@ namespace Ryujinx.Ava.Common.Locale
|
||||
_localeStrings[key] = val;
|
||||
}
|
||||
|
||||
OnPropertyChanged("Translation");
|
||||
OnPropertyChanged("Item");
|
||||
|
||||
LocaleChanged?.Invoke();
|
||||
}
|
||||
|
||||
#nullable enable
|
||||
|
||||
private static LocalesJson? _localeData;
|
||||
|
||||
#nullable disable
|
||||
|
||||
private static Dictionary<LocaleKeys, string> LoadJsonLanguage(string languageCode)
|
||||
{
|
||||
@@ -132,29 +158,18 @@ namespace Ryujinx.Ava.Common.Locale
|
||||
|
||||
foreach (LocalesEntry locale in _localeData.Value.Locales)
|
||||
{
|
||||
if (locale.Translations.Count < _localeData.Value.Languages.Count)
|
||||
if (locale.Translations.Count != _localeData.Value.Languages.Count)
|
||||
{
|
||||
throw new Exception($"Locale key {{{locale.ID}}} is missing languages! Has {locale.Translations.Count} translations, expected {_localeData.Value.Languages.Count}!");
|
||||
}
|
||||
|
||||
if (locale.Translations.Count > _localeData.Value.Languages.Count)
|
||||
{
|
||||
throw new Exception($"Locale key {{{locale.ID}}} has too many languages! Has {locale.Translations.Count} translations, expected {_localeData.Value.Languages.Count}!");
|
||||
}
|
||||
|
||||
if (!Enum.TryParse<LocaleKeys>(locale.ID, out var localeKey))
|
||||
continue;
|
||||
|
||||
var str = locale.Translations.TryGetValue(languageCode, out string val) && !string.IsNullOrEmpty(val)
|
||||
? val
|
||||
: locale.Translations[DefaultLanguageCode];
|
||||
|
||||
if (string.IsNullOrEmpty(str))
|
||||
{
|
||||
throw new Exception($"Locale key '{locale.ID}' has no valid translations for desired language {languageCode}! {DefaultLanguageCode} is an empty string or null");
|
||||
}
|
||||
|
||||
localeStrings[localeKey] = str;
|
||||
localeStrings[localeKey] =
|
||||
locale.Translations.TryGetValue(languageCode, out string val) && val != string.Empty
|
||||
? val
|
||||
: locale.Translations[DefaultLanguageCode];
|
||||
}
|
||||
|
||||
return localeStrings;
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Ryujinx.Ava.Common.Markup
|
||||
{
|
||||
internal abstract class BasicMarkupExtension<T> : MarkupExtension
|
||||
{
|
||||
public abstract string Name { get; }
|
||||
public virtual string Name => "Item";
|
||||
public virtual Action<object, T?>? Setter => null;
|
||||
|
||||
protected abstract T? Value { get; }
|
||||
|
||||
@@ -6,19 +6,16 @@ namespace Ryujinx.Ava.Common.Markup
|
||||
{
|
||||
internal class IconExtension(string iconString) : BasicMarkupExtension<Icon>
|
||||
{
|
||||
public override string Name => "Icon";
|
||||
protected override Icon Value => new() { Value = iconString };
|
||||
}
|
||||
|
||||
internal class SpinningIconExtension(string iconString) : BasicMarkupExtension<Icon>
|
||||
{
|
||||
public override string Name => "SIcon";
|
||||
protected override Icon Value => new() { Value = iconString, Animation = IconAnimation.Spin };
|
||||
}
|
||||
|
||||
internal class LocaleExtension(LocaleKeys key) : BasicMarkupExtension<string>
|
||||
{
|
||||
public override string Name => "Translation";
|
||||
protected override string Value => LocaleManager.Instance[key];
|
||||
|
||||
protected override void ConfigureBindingExtension(CompiledBindingExtension bindingExtension)
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Controls
|
||||
{
|
||||
public class MiniVerticalSeparator : Border
|
||||
{
|
||||
public MiniVerticalSeparator()
|
||||
{
|
||||
Width = 2;
|
||||
Height = 12;
|
||||
Margin = new Thickness();
|
||||
BorderBrush = Brushes.Gray;
|
||||
Background = Brushes.Gray;
|
||||
BorderThickness = new Thickness(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Data.Converters;
|
||||
using Gommon;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.UI.Common.Models;
|
||||
using System;
|
||||
@@ -33,11 +32,11 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
|
||||
if (app.CurrentSavingsB < app.PotentialSavingsB)
|
||||
{
|
||||
return LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleXCICanSaveLabel, ((app.PotentialSavingsB - app.CurrentSavingsB) / _bytesPerMB).CoerceAtLeast(0));
|
||||
return LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleXCICanSaveLabel, (app.PotentialSavingsB - app.CurrentSavingsB) / _bytesPerMB);
|
||||
}
|
||||
else
|
||||
{
|
||||
return LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleXCISavingLabel, (app.CurrentSavingsB / _bytesPerMB).CoerceAtLeast(0));
|
||||
return LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleXCISavingLabel, app.CurrentSavingsB / _bytesPerMB);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
@@ -12,13 +11,5 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
protected void OnPropertiesChanged(params ReadOnlySpan<string> propertyNames)
|
||||
{
|
||||
foreach (var propertyName in propertyNames)
|
||||
{
|
||||
OnPropertyChanged(propertyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
// For an example of this, download canary 1.2.95, then open the settings menu, and look at the icon in the top-left.
|
||||
// The border gets reduced to colored pixels in the 4 corners.
|
||||
public static readonly Bitmap IconBitmap =
|
||||
new(Assembly.GetAssembly(typeof(ConfigurationState))!.GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Ryujinx_AntiAlias.png")!);
|
||||
new(Assembly.GetAssembly(typeof(ConfigurationState))!.GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Thiccjinx.png")!);
|
||||
|
||||
public MainWindow Window { get; init; }
|
||||
|
||||
|
||||
@@ -71,7 +71,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
_resolutionScale = value;
|
||||
|
||||
OnPropertiesChanged(nameof(CustomResolutionScale), nameof(IsCustomResolutionScaleActive));
|
||||
OnPropertyChanged(nameof(CustomResolutionScale));
|
||||
OnPropertyChanged(nameof(IsCustomResolutionScaleActive));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,9 +181,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
int newInterval = (int)((value / 100f) * 60);
|
||||
_customVSyncInterval = newInterval;
|
||||
_customVSyncIntervalPercentageProxy = value;
|
||||
OnPropertiesChanged(
|
||||
nameof(CustomVSyncInterval),
|
||||
nameof(CustomVSyncIntervalPercentageText));
|
||||
OnPropertyChanged((nameof(CustomVSyncInterval)));
|
||||
OnPropertyChanged((nameof(CustomVSyncIntervalPercentageText)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
get
|
||||
{
|
||||
string text = CustomVSyncIntervalPercentageProxy + "%";
|
||||
string text = CustomVSyncIntervalPercentageProxy.ToString() + "%";
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@@ -221,9 +221,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
_customVSyncInterval = value;
|
||||
int newPercent = (int)((value / 60f) * 100);
|
||||
_customVSyncIntervalPercentageProxy = newPercent;
|
||||
OnPropertiesChanged(
|
||||
nameof(CustomVSyncIntervalPercentageProxy),
|
||||
nameof(CustomVSyncIntervalPercentageText));
|
||||
OnPropertyChanged(nameof(CustomVSyncIntervalPercentageProxy));
|
||||
OnPropertyChanged(nameof(CustomVSyncIntervalPercentageText));
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,42 +91,39 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
private void SortingChanged()
|
||||
{
|
||||
OnPropertiesChanged(
|
||||
nameof(IsSortedByName),
|
||||
nameof(IsSortedBySaved),
|
||||
nameof(SortingAscending),
|
||||
nameof(SortingField),
|
||||
nameof(SortingFieldName));
|
||||
|
||||
OnPropertyChanged(nameof(IsSortedByName));
|
||||
OnPropertyChanged(nameof(IsSortedBySaved));
|
||||
OnPropertyChanged(nameof(SortingAscending));
|
||||
OnPropertyChanged(nameof(SortingField));
|
||||
OnPropertyChanged(nameof(SortingFieldName));
|
||||
SortAndFilter();
|
||||
}
|
||||
|
||||
private void DisplayedChanged()
|
||||
{
|
||||
OnPropertiesChanged(nameof(Status), nameof(DisplayedXCIFiles), nameof(SelectedDisplayedXCIFiles));
|
||||
OnPropertyChanged(nameof(Status));
|
||||
OnPropertyChanged(nameof(DisplayedXCIFiles));
|
||||
OnPropertyChanged(nameof(SelectedDisplayedXCIFiles));
|
||||
}
|
||||
|
||||
private void ApplicationsChanged()
|
||||
{
|
||||
OnPropertiesChanged(
|
||||
nameof(AllXCIFiles),
|
||||
nameof(Status),
|
||||
nameof(PotentialSavings),
|
||||
nameof(ActualSavings),
|
||||
nameof(CanTrim),
|
||||
nameof(CanUntrim));
|
||||
|
||||
OnPropertyChanged(nameof(AllXCIFiles));
|
||||
OnPropertyChanged(nameof(Status));
|
||||
OnPropertyChanged(nameof(PotentialSavings));
|
||||
OnPropertyChanged(nameof(ActualSavings));
|
||||
OnPropertyChanged(nameof(CanTrim));
|
||||
OnPropertyChanged(nameof(CanUntrim));
|
||||
DisplayedChanged();
|
||||
SortAndFilter();
|
||||
}
|
||||
|
||||
private void SelectionChanged(bool displayedChanged = true)
|
||||
{
|
||||
OnPropertiesChanged(
|
||||
nameof(Status),
|
||||
nameof(CanTrim),
|
||||
nameof(CanUntrim),
|
||||
nameof(SelectedXCIFiles));
|
||||
OnPropertyChanged(nameof(Status));
|
||||
OnPropertyChanged(nameof(CanTrim));
|
||||
OnPropertyChanged(nameof(CanUntrim));
|
||||
OnPropertyChanged(nameof(SelectedXCIFiles));
|
||||
|
||||
if (displayedChanged)
|
||||
OnPropertyChanged(nameof(SelectedDisplayedXCIFiles));
|
||||
@@ -134,12 +131,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
private void ProcessingChanged()
|
||||
{
|
||||
OnPropertiesChanged(
|
||||
nameof(Processing),
|
||||
nameof(Cancel),
|
||||
nameof(Status),
|
||||
nameof(CanTrim),
|
||||
nameof(CanUntrim));
|
||||
OnPropertyChanged(nameof(Processing));
|
||||
OnPropertyChanged(nameof(Cancel));
|
||||
OnPropertyChanged(nameof(Status));
|
||||
OnPropertyChanged(nameof(CanTrim));
|
||||
OnPropertyChanged(nameof(CanUntrim));
|
||||
}
|
||||
|
||||
private IEnumerable<XCITrimmerFileModel> GetSelectedDisplayedXCIFiles()
|
||||
@@ -364,7 +360,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
value = _processingApplication.Value with { PercentageProgress = null };
|
||||
|
||||
if (value.HasValue)
|
||||
_displayedXCIFiles.ReplaceWith(value);
|
||||
_displayedXCIFiles.ReplaceWith(value.Value);
|
||||
|
||||
_processingApplication = value;
|
||||
OnPropertyChanged();
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
Height="25"
|
||||
Width="25"
|
||||
ToolTip.Tip="{Binding Title}"
|
||||
Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx_AntiAlias.png?assembly=Ryujinx.UI.Common" />
|
||||
Source="resm:Ryujinx.UI.Common.Resources.Logo_Thiccjinx.png?assembly=Ryujinx.UI.Common" />
|
||||
<Menu
|
||||
Name="Menu"
|
||||
Height="32"
|
||||
|
||||
@@ -3,6 +3,8 @@ using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Threading;
|
||||
using Gommon;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
@@ -10,6 +12,8 @@ using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
|
||||
using Ryujinx.HLE;
|
||||
using Ryujinx.UI.App.Common;
|
||||
using Ryujinx.UI.Common;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using Ryujinx.UI.Common.Helper;
|
||||
@@ -120,13 +124,26 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
ViewModel.LoadConfigurableHotKeys();
|
||||
}
|
||||
|
||||
public static readonly AppletMetadata MiiApplet = new("miiEdit", 0x0100000000001009);
|
||||
|
||||
public async void OpenMiiApplet(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (MiiApplet.CanStart(ViewModel.ContentManager, out var appData, out var nacpData))
|
||||
const string AppletName = "miiEdit";
|
||||
const ulong AppletProgramId = 0x0100000000001009;
|
||||
const string AppletVersion = "1.0.0";
|
||||
|
||||
string contentPath = ViewModel.ContentManager.GetInstalledContentPath(AppletProgramId, StorageId.BuiltInSystem, NcaContentType.Program);
|
||||
|
||||
if (!string.IsNullOrEmpty(contentPath))
|
||||
{
|
||||
await ViewModel.LoadApplication(appData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData);
|
||||
ApplicationData applicationData = new()
|
||||
{
|
||||
Name = AppletName,
|
||||
Id = AppletProgramId,
|
||||
Path = contentPath
|
||||
};
|
||||
|
||||
var nacpData = StructHelpers.CreateCustomNacpData(AppletName, AppletVersion);
|
||||
|
||||
await ViewModel.LoadApplication(applicationData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:local="clr-namespace:Ryujinx.Ava.UI.Views.Main"
|
||||
xmlns:config="clr-namespace:Ryujinx.Common.Configuration;assembly=Ryujinx.Common"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Ryujinx.Ava.UI.Views.Main.MainStatusBarView"
|
||||
@@ -133,7 +132,14 @@
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<Border
|
||||
Width="2"
|
||||
Height="12"
|
||||
Margin="0"
|
||||
BorderBrush="Gray"
|
||||
Background="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Name="DockedStatus"
|
||||
Margin="5,0,5,0"
|
||||
@@ -143,7 +149,14 @@
|
||||
PointerReleased="DockedStatus_PointerReleased"
|
||||
Text="{Binding DockedStatusText}"
|
||||
TextAlignment="Start" />
|
||||
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<Border
|
||||
Width="2"
|
||||
Height="12"
|
||||
Margin="0"
|
||||
BorderBrush="Gray"
|
||||
Background="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<SplitButton
|
||||
Name="AspectRatioStatus"
|
||||
Padding="5,0,5,0"
|
||||
@@ -190,7 +203,14 @@
|
||||
</MenuFlyout>
|
||||
</SplitButton.Flyout>
|
||||
</SplitButton>
|
||||
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<Border
|
||||
Width="2"
|
||||
Height="12"
|
||||
Margin="0"
|
||||
BorderBrush="Gray"
|
||||
Background="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<ToggleSplitButton
|
||||
Name="VolumeStatus"
|
||||
Padding="5,0,5,0"
|
||||
@@ -234,7 +254,14 @@
|
||||
</Flyout>
|
||||
</ToggleSplitButton.Flyout>
|
||||
</ToggleSplitButton>
|
||||
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<Border
|
||||
Width="2"
|
||||
Height="12"
|
||||
Margin="0"
|
||||
BorderBrush="Gray"
|
||||
Background="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Margin="5,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
@@ -242,7 +269,14 @@
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Text="{Binding GameStatusText}"
|
||||
TextAlignment="Start" />
|
||||
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<Border
|
||||
Width="2"
|
||||
Height="12"
|
||||
Margin="0"
|
||||
BorderBrush="Gray"
|
||||
Background="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Margin="5,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
@@ -264,7 +298,13 @@
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding ShowShaderCompilationHint}"
|
||||
Text="{Binding ShaderCountText}" />
|
||||
<controls:MiniVerticalSeparator IsVisible="{Binding ShowShaderCompilationHint}" />
|
||||
<Border
|
||||
Width="2"
|
||||
Height="12"
|
||||
Margin="0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding ShowShaderCompilationHint}" />
|
||||
<TextBlock
|
||||
Margin="5,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
@@ -272,7 +312,14 @@
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Text="{Binding BackendText}"
|
||||
TextAlignment="Start" />
|
||||
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<Border
|
||||
Width="2"
|
||||
Height="12"
|
||||
Margin="0"
|
||||
BorderBrush="Gray"
|
||||
Background="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Margin="5,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
@@ -287,7 +334,13 @@
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding ShowFirmwareStatus}"
|
||||
Orientation="Horizontal">
|
||||
<controls:MiniVerticalSeparator IsVisible="{Binding IsGameRunning}" />
|
||||
<Border
|
||||
Width="2"
|
||||
Height="12"
|
||||
Margin="0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding IsGameRunning}" />
|
||||
<TextBlock
|
||||
Name="FirmwareStatus"
|
||||
Margin="5, 0, 0, 0"
|
||||
|
||||
@@ -62,7 +62,12 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
// Change the volume by 5% at a time
|
||||
float newValue = Window.ViewModel.Volume + (float)e.Delta.Y * 0.05f;
|
||||
|
||||
Window.ViewModel.Volume = Math.Clamp(newValue, 0, 1);
|
||||
Window.ViewModel.Volume = newValue switch
|
||||
{
|
||||
< 0 => 0,
|
||||
> 1 => 1,
|
||||
_ => newValue,
|
||||
};
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
@@ -91,14 +91,12 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
TitleBar.ExtendsContentIntoTitleBar = !ConfigurationState.Instance.ShowTitleBar;
|
||||
TitleBar.TitleBarHitTestType = (ConfigurationState.Instance.ShowTitleBar) ? TitleBarHitTestType.Simple : TitleBarHitTestType.Complex;
|
||||
|
||||
// Correctly size window when 'TitleBar' is enabled (Nov. 14, 2024)
|
||||
TitleBarHeight = (ConfigurationState.Instance.ShowTitleBar ? TitleBar.Height : 0);
|
||||
|
||||
// NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
|
||||
StatusBarHeight = StatusBarView.StatusBar.MinHeight;
|
||||
MenuBarHeight = MenuBar.MinHeight;
|
||||
|
||||
TitleBar.Height = MenuBarHeight;
|
||||
|
||||
// Correctly size window when 'TitleBar' is enabled (Nov. 14, 2024)
|
||||
TitleBarHeight = (ConfigurationState.Instance.ShowTitleBar ? TitleBar.Height : 0);
|
||||
|
||||
ApplicationList.DataContext = DataContext;
|
||||
ApplicationGrid.DataContext = DataContext;
|
||||
|
||||
Reference in New Issue
Block a user