Compare commits
43 Commits
Author | SHA1 | Date | |
---|---|---|---|
d076339e3e | |||
837836431d | |||
9f555db5cd | |||
bf7fa60dfc | |||
752b93d3b7 | |||
f23b2878cc | |||
e211c3f00a | |||
d3709a753f | |||
ab676d58ea | |||
2372c194f1 | |||
40311310d1 | |||
dde9bb5c69 | |||
266338a7c9 | |||
90156eea4c | |||
071c01c235 | |||
de06ffb0f7 | |||
8a7de35e3f | |||
121296834a | |||
bbb24d8c7e | |||
4da44e09cb | |||
ae13f0ab4d | |||
a2a35f1be6 | |||
aedfadaaf7 | |||
5c0fb0cec3 | |||
17a1cab5d2 | |||
73aed239c3 | |||
9ac66336a2 | |||
4965681e06 | |||
3868a00206 | |||
933e5144a9 | |||
73a42c85c4 | |||
39ba11054b | |||
c250e3392c | |||
e56b069081 | |||
204c031fef | |||
d9053bbe37 | |||
c25e8427aa | |||
21a081b185 | |||
b540ea80d1 | |||
d692a9b83e | |||
9677ddaa5d | |||
ce92e8cd04 | |||
456fc04007 |
@ -89,6 +89,7 @@ csharp_style_conditional_delegate_call = true:suggestion
|
||||
# Modifier preferences
|
||||
csharp_prefer_static_local_function = true:suggestion
|
||||
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
|
||||
csharp_style_prefer_readonly_struct = true
|
||||
|
||||
# Code-block preferences
|
||||
csharp_prefer_braces = true:silent
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||
{
|
||||
struct AllocationResult
|
||||
readonly struct AllocationResult
|
||||
{
|
||||
public int IntUsedRegisters { get; }
|
||||
public int VecUsedRegisters { get; }
|
||||
|
@ -11,7 +11,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||
{
|
||||
private class ParallelCopy
|
||||
{
|
||||
private struct Copy
|
||||
private readonly struct Copy
|
||||
{
|
||||
public Register Dest { get; }
|
||||
public Register Source { get; }
|
||||
|
@ -11,7 +11,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||
{
|
||||
class HybridAllocator : IRegisterAllocator
|
||||
{
|
||||
private struct BlockInfo
|
||||
private readonly struct BlockInfo
|
||||
{
|
||||
public bool HasCall { get; }
|
||||
|
||||
|
@ -3,7 +3,7 @@ using System;
|
||||
|
||||
namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||
{
|
||||
struct RegisterMasks
|
||||
readonly struct RegisterMasks
|
||||
{
|
||||
public int IntAvailableRegisters { get; }
|
||||
public int VecAvailableRegisters { get; }
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace ARMeilleure.CodeGen.X86
|
||||
{
|
||||
struct IntrinsicInfo
|
||||
readonly struct IntrinsicInfo
|
||||
{
|
||||
public X86Instruction Inst { get; }
|
||||
public IntrinsicType Type { get; }
|
||||
|
@ -2,7 +2,7 @@ using ARMeilleure.Instructions;
|
||||
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
struct InstDescriptor
|
||||
readonly struct InstDescriptor
|
||||
{
|
||||
public static InstDescriptor Undefined => new InstDescriptor(InstName.Und, InstEmit.Und);
|
||||
|
||||
|
@ -11,7 +11,7 @@ namespace ARMeilleure.Decoders
|
||||
|
||||
private const int FastLookupSize = 0x1000;
|
||||
|
||||
private struct InstInfo
|
||||
private readonly struct InstInfo
|
||||
{
|
||||
public int Mask { get; }
|
||||
public int Value { get; }
|
||||
|
@ -6,7 +6,7 @@ namespace ARMeilleure.Diagnostics
|
||||
{
|
||||
static class Symbols
|
||||
{
|
||||
private struct RangedSymbol
|
||||
private readonly struct RangedSymbol
|
||||
{
|
||||
public readonly ulong Start;
|
||||
public readonly ulong End;
|
||||
|
@ -3,7 +3,7 @@ using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
struct PhiOperation
|
||||
readonly struct PhiOperation
|
||||
{
|
||||
private readonly Operation _operation;
|
||||
|
||||
|
@ -2,7 +2,7 @@ using System;
|
||||
|
||||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
struct Register : IEquatable<Register>
|
||||
readonly struct Register : IEquatable<Register>
|
||||
{
|
||||
public int Index { get; }
|
||||
|
||||
|
@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace ARMeilleure.Translation.Cache
|
||||
{
|
||||
struct CacheEntry : IComparable<CacheEntry>
|
||||
readonly struct CacheEntry : IComparable<CacheEntry>
|
||||
{
|
||||
public int Offset { get; }
|
||||
public int Size { get; }
|
||||
|
@ -6,7 +6,7 @@ namespace ARMeilleure.Translation.Cache
|
||||
{
|
||||
class CacheMemoryAllocator
|
||||
{
|
||||
private struct MemoryBlock : IComparable<MemoryBlock>
|
||||
private readonly struct MemoryBlock : IComparable<MemoryBlock>
|
||||
{
|
||||
public int Offset { get; }
|
||||
public int Size { get; }
|
||||
|
@ -2,7 +2,7 @@ using ARMeilleure.IntermediateRepresentation;
|
||||
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
struct CompilerContext
|
||||
readonly struct CompilerContext
|
||||
{
|
||||
public ControlFlowGraph Cfg { get; }
|
||||
|
||||
|
@ -14,7 +14,7 @@ namespace ARMeilleure.Translation
|
||||
private const int RegsCount = 32;
|
||||
private const int RegsMask = RegsCount - 1;
|
||||
|
||||
private struct RegisterMask : IEquatable<RegisterMask>
|
||||
private readonly struct RegisterMask : IEquatable<RegisterMask>
|
||||
{
|
||||
public long IntMask => Mask.GetElement(0);
|
||||
public long VecMask => Mask.GetElement(1);
|
||||
|
@ -293,7 +293,7 @@ namespace ARMeilleure.Translation
|
||||
}
|
||||
}
|
||||
|
||||
private struct Range
|
||||
private readonly struct Range
|
||||
{
|
||||
public ulong Start { get; }
|
||||
public ulong End { get; }
|
||||
|
@ -21,6 +21,10 @@
|
||||
<img src="https://github.com/Ryujinx/Ryujinx/actions/workflows/release.yml/badge.svg"
|
||||
alt="">
|
||||
</a>
|
||||
<a href="https://crwd.in/ryujinx">
|
||||
<img src="https://badges.crowdin.net/ryujinx/localized.svg"
|
||||
alt="">
|
||||
</a>
|
||||
<a href="https://discord.com/invite/VkQYXAZ">
|
||||
<img src="https://img.shields.io/discord/410208534861447168?color=5865F2&label=Ryujinx&logo=discord&logoColor=white"
|
||||
alt="Discord">
|
||||
@ -48,6 +52,8 @@ See our [Setup & Configuration Guide](https://github.com/Ryujinx/Ryujinx/wiki/Ry
|
||||
For our Local Wireless and LAN builds, see our [Multiplayer: Local Play/Local Wireless Guide
|
||||
](https://github.com/Ryujinx/Ryujinx/wiki/Multiplayer-(LDN-Local-Wireless)-Guide).
|
||||
|
||||
Avalonia UI comes with translations for various languages. See [Crowdin](https://crwd.in/ryujinx) for more information.
|
||||
|
||||
## Latest build
|
||||
|
||||
These builds are compiled automatically for each commit on the master branch. While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken.**
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.SDL2.Common;
|
||||
using System;
|
||||
@ -112,6 +113,9 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
|
||||
if (device == 0)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application,
|
||||
$"SDL2 open audio device initialization failed with error \"{SDL_GetError()}\"");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -119,6 +123,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
|
||||
if (!isValid)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, "SDL2 open audio device is not valid");
|
||||
SDL_CloseAudioDevice(device);
|
||||
|
||||
return 0;
|
||||
|
@ -4,7 +4,7 @@ using System.Runtime.InteropServices;
|
||||
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
public struct SoundIOChannelLayout
|
||||
public readonly struct SoundIOChannelLayout
|
||||
{
|
||||
public static int BuiltInCount
|
||||
{
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace SoundIOSharp
|
||||
{
|
||||
public struct SoundIOSampleRateRange
|
||||
public readonly struct SoundIOSampleRateRange
|
||||
{
|
||||
internal SoundIOSampleRateRange(int min, int max)
|
||||
{
|
||||
|
@ -48,6 +48,11 @@ namespace Ryujinx.Audio.Renderer.Common
|
||||
/// <summary>
|
||||
/// Effect to capture mixes (via auxiliary buffers).
|
||||
/// </summary>
|
||||
CaptureBuffer
|
||||
CaptureBuffer,
|
||||
|
||||
/// <summary>
|
||||
/// Effect applying a compressor filter (DRC).
|
||||
/// </summary>
|
||||
Compressor,
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ namespace Ryujinx.Audio.Renderer.Common
|
||||
Reverb3d,
|
||||
PcmFloat,
|
||||
Limiter,
|
||||
CaptureBuffer
|
||||
CaptureBuffer,
|
||||
Compressor
|
||||
}
|
||||
}
|
@ -116,6 +116,11 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
};
|
||||
}
|
||||
|
||||
public bool HasRemainingCommands(int sessionId)
|
||||
{
|
||||
return _sessionCommandList[sessionId] != null;
|
||||
}
|
||||
|
||||
public void Signal()
|
||||
{
|
||||
_mailbox.SendMessage(MailboxMessage.RenderStart);
|
||||
|
@ -31,6 +31,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
LimiterVersion1,
|
||||
LimiterVersion2,
|
||||
GroupedBiquadFilter,
|
||||
CaptureBuffer
|
||||
CaptureBuffer,
|
||||
Compressor
|
||||
}
|
||||
}
|
173
Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs
Normal file
173
Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs
Normal file
@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Ryujinx.Audio.Renderer.Dsp.Effect;
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class CompressorCommand : ICommand
|
||||
{
|
||||
private const int FixedPointPrecision = 15;
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.Compressor;
|
||||
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public CompressorParameter Parameter => _parameter;
|
||||
public Memory<CompressorState> State { get; }
|
||||
public ushort[] OutputBufferIndices { get; }
|
||||
public ushort[] InputBufferIndices { get; }
|
||||
public bool IsEffectEnabled { get; }
|
||||
|
||||
private CompressorParameter _parameter;
|
||||
|
||||
public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, bool isEnabled, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
_parameter = parameter;
|
||||
State = state;
|
||||
|
||||
IsEffectEnabled = isEnabled;
|
||||
|
||||
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||
|
||||
for (int i = 0; i < _parameter.ChannelCount; i++)
|
||||
{
|
||||
InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]);
|
||||
OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ref CompressorState state = ref State.Span[0];
|
||||
|
||||
if (IsEffectEnabled)
|
||||
{
|
||||
if (_parameter.Status == Server.Effect.UsageState.Invalid)
|
||||
{
|
||||
state = new CompressorState(ref _parameter);
|
||||
}
|
||||
else if (_parameter.Status == Server.Effect.UsageState.New)
|
||||
{
|
||||
state.UpdateParameter(ref _parameter);
|
||||
}
|
||||
}
|
||||
|
||||
ProcessCompressor(context, ref state);
|
||||
}
|
||||
|
||||
private unsafe void ProcessCompressor(CommandList context, ref CompressorState state)
|
||||
{
|
||||
Debug.Assert(_parameter.IsChannelCountValid());
|
||||
|
||||
if (IsEffectEnabled && _parameter.IsChannelCountValid())
|
||||
{
|
||||
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
||||
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
||||
Span<float> channelInput = stackalloc float[Parameter.ChannelCount];
|
||||
ExponentialMovingAverage inputMovingAverage = state.InputMovingAverage;
|
||||
float unknown4 = state.Unknown4;
|
||||
ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage;
|
||||
float previousCompressionEmaAlpha = state.PreviousCompressionEmaAlpha;
|
||||
|
||||
for (int i = 0; i < _parameter.ChannelCount; i++)
|
||||
{
|
||||
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
|
||||
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
|
||||
}
|
||||
|
||||
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
|
||||
{
|
||||
for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
|
||||
{
|
||||
channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex);
|
||||
}
|
||||
|
||||
float newMean = inputMovingAverage.Update(FloatingPointHelper.MeanSquare(channelInput), _parameter.InputGain);
|
||||
float y = FloatingPointHelper.Log10(newMean) * 10.0f;
|
||||
float z = 0.0f;
|
||||
|
||||
bool unknown10OutOfRange = false;
|
||||
|
||||
if (newMean < 1.0e-10f)
|
||||
{
|
||||
z = 1.0f;
|
||||
|
||||
unknown10OutOfRange = state.Unknown10 < -100.0f;
|
||||
}
|
||||
|
||||
if (y >= state.Unknown10 || unknown10OutOfRange)
|
||||
{
|
||||
float tmpGain;
|
||||
|
||||
if (y >= state.Unknown14)
|
||||
{
|
||||
tmpGain = ((1.0f / Parameter.Ratio) - 1.0f) * (y - Parameter.Threshold);
|
||||
}
|
||||
else
|
||||
{
|
||||
tmpGain = (y - state.Unknown10) * ((y - state.Unknown10) * -state.CompressorGainReduction);
|
||||
}
|
||||
|
||||
z = FloatingPointHelper.DecibelToLinearExtended(tmpGain);
|
||||
}
|
||||
|
||||
float unknown4New = z;
|
||||
float compressionEmaAlpha;
|
||||
|
||||
if ((unknown4 - z) <= 0.08f)
|
||||
{
|
||||
compressionEmaAlpha = Parameter.ReleaseCoefficient;
|
||||
|
||||
if ((unknown4 - z) >= -0.08f)
|
||||
{
|
||||
if (MathF.Abs(compressionGainAverage.Read() - z) >= 0.001f)
|
||||
{
|
||||
unknown4New = unknown4;
|
||||
}
|
||||
|
||||
compressionEmaAlpha = previousCompressionEmaAlpha;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
compressionEmaAlpha = Parameter.AttackCoefficient;
|
||||
}
|
||||
|
||||
float compressionGain = compressionGainAverage.Update(z, compressionEmaAlpha);
|
||||
|
||||
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
|
||||
{
|
||||
*((float*)outputBuffers[channelIndex] + sampleIndex) = channelInput[channelIndex] * compressionGain * state.OutputGain;
|
||||
}
|
||||
|
||||
unknown4 = unknown4New;
|
||||
previousCompressionEmaAlpha = compressionEmaAlpha;
|
||||
}
|
||||
|
||||
state.InputMovingAverage = inputMovingAverage;
|
||||
state.Unknown4 = unknown4;
|
||||
state.CompressionGainAverage = compressionGainAverage;
|
||||
state.PreviousCompressionEmaAlpha = previousCompressionEmaAlpha;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
||||
{
|
||||
context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -90,32 +90,31 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
float inputCoefficient = Parameter.ReleaseCoefficient;
|
||||
|
||||
if (sampleInputMax > state.DectectorAverage[channelIndex])
|
||||
if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
|
||||
{
|
||||
inputCoefficient = Parameter.AttackCoefficient;
|
||||
}
|
||||
|
||||
state.DectectorAverage[channelIndex] += inputCoefficient * (sampleInputMax - state.DectectorAverage[channelIndex]);
|
||||
|
||||
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
|
||||
float attenuation = 1.0f;
|
||||
|
||||
if (state.DectectorAverage[channelIndex] > Parameter.Threshold)
|
||||
if (detectorValue > Parameter.Threshold)
|
||||
{
|
||||
attenuation = Parameter.Threshold / state.DectectorAverage[channelIndex];
|
||||
attenuation = Parameter.Threshold / detectorValue;
|
||||
}
|
||||
|
||||
float outputCoefficient = Parameter.ReleaseCoefficient;
|
||||
|
||||
if (state.CompressionGain[channelIndex] > attenuation)
|
||||
if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
|
||||
{
|
||||
outputCoefficient = Parameter.AttackCoefficient;
|
||||
}
|
||||
|
||||
state.CompressionGain[channelIndex] += outputCoefficient * (attenuation - state.CompressionGain[channelIndex]);
|
||||
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
|
||||
|
||||
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
|
||||
|
||||
float outputSample = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
|
||||
float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
|
||||
|
||||
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
|
||||
|
||||
|
@ -101,32 +101,31 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
float inputCoefficient = Parameter.ReleaseCoefficient;
|
||||
|
||||
if (sampleInputMax > state.DectectorAverage[channelIndex])
|
||||
if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
|
||||
{
|
||||
inputCoefficient = Parameter.AttackCoefficient;
|
||||
}
|
||||
|
||||
state.DectectorAverage[channelIndex] += inputCoefficient * (sampleInputMax - state.DectectorAverage[channelIndex]);
|
||||
|
||||
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
|
||||
float attenuation = 1.0f;
|
||||
|
||||
if (state.DectectorAverage[channelIndex] > Parameter.Threshold)
|
||||
if (detectorValue > Parameter.Threshold)
|
||||
{
|
||||
attenuation = Parameter.Threshold / state.DectectorAverage[channelIndex];
|
||||
attenuation = Parameter.Threshold / detectorValue;
|
||||
}
|
||||
|
||||
float outputCoefficient = Parameter.ReleaseCoefficient;
|
||||
|
||||
if (state.CompressionGain[channelIndex] > attenuation)
|
||||
if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
|
||||
{
|
||||
outputCoefficient = Parameter.AttackCoefficient;
|
||||
}
|
||||
|
||||
state.CompressionGain[channelIndex] += outputCoefficient * (attenuation - state.CompressionGain[channelIndex]);
|
||||
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
|
||||
|
||||
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
|
||||
|
||||
float outputSample = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
|
||||
float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
|
||||
|
||||
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
|
||||
|
||||
@ -144,7 +143,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0];
|
||||
|
||||
statistics.InputMax[channelIndex] = Math.Max(statistics.InputMax[channelIndex], sampleInputMax);
|
||||
statistics.CompressionGainMin[channelIndex] = Math.Min(statistics.CompressionGainMin[channelIndex], state.CompressionGain[channelIndex]);
|
||||
statistics.CompressionGainMin[channelIndex] = Math.Min(statistics.CompressionGainMin[channelIndex], compressionGain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Effect
|
||||
{
|
||||
public struct ExponentialMovingAverage
|
||||
{
|
||||
private float _mean;
|
||||
|
||||
public ExponentialMovingAverage(float mean)
|
||||
{
|
||||
_mean = mean;
|
||||
}
|
||||
|
||||
public float Read()
|
||||
{
|
||||
return _mean;
|
||||
}
|
||||
|
||||
public float Update(float value, float alpha)
|
||||
{
|
||||
_mean += alpha * (value - _mean);
|
||||
|
||||
return _mean;
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,12 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
return (float)value / (1 << qBits);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float ConvertFloat(float value, int qBits)
|
||||
{
|
||||
return value / (1 << qBits);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int ToFixed(float value, int qBits)
|
||||
{
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp
|
||||
@ -46,6 +47,53 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
return MathF.Pow(10, x);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float Log10(float x)
|
||||
{
|
||||
// NOTE: Nintendo uses an approximation of log10, we don't.
|
||||
// As such, we support the same ranges as Nintendo to avoid unexpected behaviours.
|
||||
return MathF.Pow(10, MathF.Max(x, 1.0e-10f));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float MeanSquare(ReadOnlySpan<float> inputs)
|
||||
{
|
||||
float res = 0.0f;
|
||||
|
||||
foreach (float input in inputs)
|
||||
{
|
||||
res += (input * input);
|
||||
}
|
||||
|
||||
res /= inputs.Length;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Map decibel to linear.
|
||||
/// </summary>
|
||||
/// <param name="db">The decibel value to convert</param>
|
||||
/// <returns>Converted linear value/returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float DecibelToLinear(float db)
|
||||
{
|
||||
return MathF.Pow(10.0f, db / 20.0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Map decibel to linear in [0, 2] range.
|
||||
/// </summary>
|
||||
/// <param name="db">The decibel value to convert</param>
|
||||
/// <returns>Converted linear value in [0, 2] range</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float DecibelToLinearExtended(float db)
|
||||
{
|
||||
float tmp = MathF.Log2(DecibelToLinear(db));
|
||||
|
||||
return MathF.Truncate(tmp) + MathF.Pow(2.0f, tmp - MathF.Truncate(tmp));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float DegreesToRadians(float degrees)
|
||||
{
|
||||
|
51
Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs
Normal file
51
Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using Ryujinx.Audio.Renderer.Dsp.Effect;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
{
|
||||
public class CompressorState
|
||||
{
|
||||
public ExponentialMovingAverage InputMovingAverage;
|
||||
public float Unknown4;
|
||||
public ExponentialMovingAverage CompressionGainAverage;
|
||||
public float CompressorGainReduction;
|
||||
public float Unknown10;
|
||||
public float Unknown14;
|
||||
public float PreviousCompressionEmaAlpha;
|
||||
public float MakeupGain;
|
||||
public float OutputGain;
|
||||
|
||||
public CompressorState(ref CompressorParameter parameter)
|
||||
{
|
||||
InputMovingAverage = new ExponentialMovingAverage(0.0f);
|
||||
Unknown4 = 1.0f;
|
||||
CompressionGainAverage = new ExponentialMovingAverage(1.0f);
|
||||
|
||||
UpdateParameter(ref parameter);
|
||||
}
|
||||
|
||||
public void UpdateParameter(ref CompressorParameter parameter)
|
||||
{
|
||||
float threshold = parameter.Threshold;
|
||||
float ratio = 1.0f / parameter.Ratio;
|
||||
float attackCoefficient = parameter.AttackCoefficient;
|
||||
float makeupGain;
|
||||
|
||||
if (parameter.MakeupGainEnabled)
|
||||
{
|
||||
makeupGain = (threshold * 0.5f * (ratio - 1.0f)) - 3.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
makeupGain = 0.0f;
|
||||
}
|
||||
|
||||
PreviousCompressionEmaAlpha = attackCoefficient;
|
||||
MakeupGain = makeupGain;
|
||||
CompressorGainReduction = (1.0f - ratio) / Constants.ChannelCountMax;
|
||||
Unknown10 = threshold - 1.5f;
|
||||
Unknown14 = threshold + 1.5f;
|
||||
OutputGain = FloatingPointHelper.DecibelToLinearExtended(parameter.OutputGain + makeupGain);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
using Ryujinx.Audio.Renderer.Dsp.Effect;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using System;
|
||||
|
||||
@ -5,20 +6,20 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
{
|
||||
public class LimiterState
|
||||
{
|
||||
public float[] DectectorAverage;
|
||||
public float[] CompressionGain;
|
||||
public ExponentialMovingAverage[] DetectorAverage;
|
||||
public ExponentialMovingAverage[] CompressionGainAverage;
|
||||
public float[] DelayedSampleBuffer;
|
||||
public int[] DelayedSampleBufferPosition;
|
||||
|
||||
public LimiterState(ref LimiterParameter parameter, ulong workBuffer)
|
||||
{
|
||||
DectectorAverage = new float[parameter.ChannelCount];
|
||||
CompressionGain = new float[parameter.ChannelCount];
|
||||
DetectorAverage = new ExponentialMovingAverage[parameter.ChannelCount];
|
||||
CompressionGainAverage = new ExponentialMovingAverage[parameter.ChannelCount];
|
||||
DelayedSampleBuffer = new float[parameter.ChannelCount * parameter.DelayBufferSampleCountMax];
|
||||
DelayedSampleBufferPosition = new int[parameter.ChannelCount];
|
||||
|
||||
DectectorAverage.AsSpan().Fill(0.0f);
|
||||
CompressionGain.AsSpan().Fill(1.0f);
|
||||
DetectorAverage.AsSpan().Fill(new ExponentialMovingAverage(0.0f));
|
||||
CompressionGainAverage.AsSpan().Fill(new ExponentialMovingAverage(1.0f));
|
||||
DelayedSampleBufferPosition.AsSpan().Fill(0);
|
||||
DelayedSampleBuffer.AsSpan().Fill(0.0f);
|
||||
|
||||
|
115
Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs
Normal file
115
Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs
Normal file
@ -0,0 +1,115 @@
|
||||
using Ryujinx.Audio.Renderer.Server.Effect;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Parameter.Effect
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Compressor"/>.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct CompressorParameter
|
||||
{
|
||||
/// <summary>
|
||||
/// The input channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
|
||||
/// </summary>
|
||||
public Array6<byte> Input;
|
||||
|
||||
/// <summary>
|
||||
/// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
|
||||
/// </summary>
|
||||
public Array6<byte> Output;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of channels supported.
|
||||
/// </summary>
|
||||
public ushort ChannelCountMax;
|
||||
|
||||
/// <summary>
|
||||
/// The total channel count used.
|
||||
/// </summary>
|
||||
public ushort ChannelCount;
|
||||
|
||||
/// <summary>
|
||||
/// The target sample rate.
|
||||
/// </summary>
|
||||
/// <remarks>This is in kHz.</remarks>
|
||||
public int SampleRate;
|
||||
|
||||
/// <summary>
|
||||
/// The threshold.
|
||||
/// </summary>
|
||||
public float Threshold;
|
||||
|
||||
/// <summary>
|
||||
/// The compressor ratio.
|
||||
/// </summary>
|
||||
public float Ratio;
|
||||
|
||||
/// <summary>
|
||||
/// The attack time.
|
||||
/// <remarks>This is in microseconds.</remarks>
|
||||
/// </summary>
|
||||
public int AttackTime;
|
||||
|
||||
/// <summary>
|
||||
/// The release time.
|
||||
/// <remarks>This is in microseconds.</remarks>
|
||||
/// </summary>
|
||||
public int ReleaseTime;
|
||||
|
||||
/// <summary>
|
||||
/// The input gain.
|
||||
/// </summary>
|
||||
public float InputGain;
|
||||
|
||||
/// <summary>
|
||||
/// The attack coefficient.
|
||||
/// </summary>
|
||||
public float AttackCoefficient;
|
||||
|
||||
/// <summary>
|
||||
/// The release coefficient.
|
||||
/// </summary>
|
||||
public float ReleaseCoefficient;
|
||||
|
||||
/// <summary>
|
||||
/// The output gain.
|
||||
/// </summary>
|
||||
public float OutputGain;
|
||||
|
||||
/// <summary>
|
||||
/// The current usage status of the effect on the client side.
|
||||
/// </summary>
|
||||
public UsageState Status;
|
||||
|
||||
/// <summary>
|
||||
/// Indicate if the makeup gain should be used.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool MakeupGainEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved/padding.
|
||||
/// </summary>
|
||||
private Array2<byte> _reserved;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the <see cref="ChannelCount"/> is valid.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if the <see cref="ChannelCount"/> is valid.</returns>
|
||||
public bool IsChannelCountValid()
|
||||
{
|
||||
return EffectInParameterVersion1.IsChannelCountValid(ChannelCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the <see cref="ChannelCountMax"/> is valid.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if the <see cref="ChannelCountMax"/> is valid.</returns>
|
||||
public bool IsChannelCountMaxValid()
|
||||
{
|
||||
return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax);
|
||||
}
|
||||
}
|
||||
}
|
@ -670,14 +670,21 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
_terminationEvent.Reset();
|
||||
|
||||
GenerateCommandList(out CommandList commands);
|
||||
if (!_manager.Processor.HasRemainingCommands(_sessionId))
|
||||
{
|
||||
GenerateCommandList(out CommandList commands);
|
||||
|
||||
_manager.Processor.Send(_sessionId,
|
||||
commands,
|
||||
GetMaxAllocatedTimeForDsp(),
|
||||
_appletResourceId);
|
||||
_manager.Processor.Send(_sessionId,
|
||||
commands,
|
||||
GetMaxAllocatedTimeForDsp(),
|
||||
_appletResourceId);
|
||||
|
||||
_systemEvent.Signal();
|
||||
_systemEvent.Signal();
|
||||
}
|
||||
else
|
||||
{
|
||||
_isDspRunningBehind = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -44,7 +44,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// <see cref="Parameter.RendererInfoOutStatus"/> was added to supply the count of update done sent to the DSP.
|
||||
/// A new version of the command estimator was added to address timing changes caused by the voice changes.
|
||||
/// Additionally, the rendering limit percent was incremented to 80%.
|
||||
///
|
||||
///
|
||||
/// </summary>
|
||||
/// <remarks>This was added in system update 6.0.0</remarks>
|
||||
public const int Revision5 = 5 << 24;
|
||||
@ -93,6 +93,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// <summary>
|
||||
/// REV11:
|
||||
/// The "legacy" effects (Delay, Reverb and Reverb 3D) were updated to match the standard channel mapping used by the audio renderer.
|
||||
/// A new effect was added: Compressor. This effect is effectively implemented with a DRC.
|
||||
/// A new version of the command estimator was added to address timing changes caused by the legacy effects changes.
|
||||
/// A voice drop parameter was added in 15.0.0: This allows an application to amplify or attenuate the estimated time of DSP commands.
|
||||
/// </summary>
|
||||
|
@ -469,6 +469,18 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
}
|
||||
}
|
||||
|
||||
public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, bool isEnabled, int nodeId)
|
||||
{
|
||||
if (parameter.IsChannelCountValid())
|
||||
{
|
||||
CompressorCommand command = new CompressorCommand(bufferOffset, parameter, state, isEnabled, nodeId);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
AddCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new <see cref="VolumeCommand"/>.
|
||||
/// </summary>
|
||||
|
@ -606,6 +606,17 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateCompressorEffect(uint bufferOffset, CompressorEffect effect, int nodeId)
|
||||
{
|
||||
Debug.Assert(effect.Type == EffectType.Compressor);
|
||||
|
||||
_commandBuffer.GenerateCompressorEffect(bufferOffset,
|
||||
effect.Parameter,
|
||||
effect.State,
|
||||
effect.IsEnabled,
|
||||
nodeId);
|
||||
}
|
||||
|
||||
private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect)
|
||||
{
|
||||
int nodeId = mix.NodeId;
|
||||
@ -650,6 +661,9 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
case EffectType.CaptureBuffer:
|
||||
GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId);
|
||||
break;
|
||||
case EffectType.Compressor:
|
||||
GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Unsupported effect type {effect.Type}");
|
||||
}
|
||||
|
@ -179,5 +179,10 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public uint Estimate(CompressorCommand command)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -543,5 +543,10 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public uint Estimate(CompressorCommand command)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -747,5 +747,10 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public virtual uint Estimate(CompressorCommand command)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -232,5 +232,79 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override uint Estimate(CompressorCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 34431;
|
||||
case 2:
|
||||
return 44253;
|
||||
case 4:
|
||||
return 63827;
|
||||
case 6:
|
||||
return 83361;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)630.12f;
|
||||
case 2:
|
||||
return (uint)638.27f;
|
||||
case 4:
|
||||
return (uint)705.86f;
|
||||
case 6:
|
||||
return (uint)782.02f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 51095;
|
||||
case 2:
|
||||
return 65693;
|
||||
case 4:
|
||||
return 95383;
|
||||
case 6:
|
||||
return 124510;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)840.14f;
|
||||
case 2:
|
||||
return (uint)826.1f;
|
||||
case 4:
|
||||
return (uint)901.88f;
|
||||
case 6:
|
||||
return (uint)965.29f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -262,6 +262,8 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
||||
return PerformanceDetailType.Limiter;
|
||||
case EffectType.CaptureBuffer:
|
||||
return PerformanceDetailType.CaptureBuffer;
|
||||
case EffectType.Compressor:
|
||||
return PerformanceDetailType.Compressor;
|
||||
default:
|
||||
throw new NotImplementedException($"{Type}");
|
||||
}
|
||||
|
67
Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs
Normal file
67
Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Effect
|
||||
{
|
||||
/// <summary>
|
||||
/// Server state for a compressor effect.
|
||||
/// </summary>
|
||||
public class CompressorEffect : BaseEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// The compressor parameter.
|
||||
/// </summary>
|
||||
public CompressorParameter Parameter;
|
||||
|
||||
/// <summary>
|
||||
/// The compressor state.
|
||||
/// </summary>
|
||||
public Memory<CompressorState> State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="CompressorEffect"/>.
|
||||
/// </summary>
|
||||
public CompressorEffect()
|
||||
{
|
||||
State = new CompressorState[1];
|
||||
}
|
||||
|
||||
public override EffectType TargetEffectType => EffectType.Compressor;
|
||||
|
||||
public override ulong GetWorkBuffer(int index)
|
||||
{
|
||||
return GetSingleBuffer();
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
// Nintendo doesn't do anything here but we still require updateErrorInfo to be initialised.
|
||||
updateErrorInfo = new BehaviourParameter.ErrorInfo();
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
|
||||
UpdateParameterBase(ref parameter);
|
||||
|
||||
Parameter = MemoryMarshal.Cast<byte, CompressorParameter>(parameter.SpecificData)[0];
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
||||
updateErrorInfo = new BehaviourParameter.ErrorInfo();
|
||||
}
|
||||
|
||||
public override void UpdateForCommandGeneration()
|
||||
{
|
||||
UpdateUsageStateForCommandGeneration();
|
||||
|
||||
Parameter.Status = UsageState.Enabled;
|
||||
}
|
||||
}
|
||||
}
|
@ -35,5 +35,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
uint Estimate(LimiterCommandVersion2 command);
|
||||
uint Estimate(GroupedBiquadFilterCommand command);
|
||||
uint Estimate(CaptureBufferCommand command);
|
||||
uint Estimate(CompressorCommand command);
|
||||
}
|
||||
}
|
@ -240,6 +240,10 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
case EffectType.CaptureBuffer:
|
||||
effect = new CaptureBufferEffect();
|
||||
break;
|
||||
case EffectType.Compressor:
|
||||
effect = new CompressorEffect();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"EffectType {parameter.Type} not implemented!");
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ namespace Ryujinx.Ava
|
||||
_inputManager = inputManager;
|
||||
_accountManager = accountManager;
|
||||
_userChannelPersistence = userChannelPersistence;
|
||||
_renderingThread = new Thread(RenderLoop) { Name = "GUI.RenderThread" };
|
||||
_renderingThread = new Thread(RenderLoop, 1 * 1024 * 1024) { Name = "GUI.RenderThread" };
|
||||
_hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
|
||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
|
||||
|
@ -157,17 +157,18 @@
|
||||
"SettingsTabLoggingEnableFsAccessLogs": "Aktiviere Fs Zugriff-Logs",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Fs Globaler Zugriff-Log-Modus:",
|
||||
"SettingsTabLoggingDeveloperOptions": "Entwickleroptionen (WARNUNG: Beeinträchtigt die Leistung)",
|
||||
"SettingsTabLoggingOpenglLogLevel": "OpenGL Logstufe:",
|
||||
"SettingsTabLoggingOpenglLogLevelNone": "Keine",
|
||||
"SettingsTabLoggingOpenglLogLevelError": "Fehler",
|
||||
"SettingsTabLoggingOpenglLogLevelPerformance": "Verlangsamungen",
|
||||
"SettingsTabLoggingOpenglLogLevelAll": "Alle",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Keine",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelError": "Fehler",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Verlangsamungen",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Alle",
|
||||
"SettingsTabLoggingEnableDebugLogs": "Aktiviere Debug-Log",
|
||||
"SettingsTabInput": "Eingabe",
|
||||
"SettingsTabInputEnableDockedMode": "Docked Modus",
|
||||
"SettingsTabInputDirectKeyboardAccess": "Direkter Tastaturzugriff",
|
||||
"SettingsButtonSave": "Speichern",
|
||||
"SettingsButtonClose": "Schließen",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Abbrechen",
|
||||
"SettingsButtonApply": "Übernehmen",
|
||||
"ControllerSettingsPlayer": "Spieler",
|
||||
"ControllerSettingsPlayer1": "Spieler 1",
|
||||
|
@ -157,17 +157,18 @@
|
||||
"SettingsTabLoggingEnableFsAccessLogs": "Ενεργοποίηση Καταγραφής Πρόσβασης FS",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Λειτουργία Καταγραφής Καθολικής Πρόσβασης FS:",
|
||||
"SettingsTabLoggingDeveloperOptions": "Επιλογές Προγραμματιστή (ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Η απόδοση Θα μειωθεί)",
|
||||
"SettingsTabLoggingOpenglLogLevel": "Επίπεδο Καταγραφής OpenGL:",
|
||||
"SettingsTabLoggingOpenglLogLevelNone": "Κανένα",
|
||||
"SettingsTabLoggingOpenglLogLevelError": "Σφάλμα",
|
||||
"SettingsTabLoggingOpenglLogLevelPerformance": "Επιβραδύνσεις",
|
||||
"SettingsTabLoggingOpenglLogLevelAll": "Όλα",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Κανένα",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelError": "Σφάλμα",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Επιβραδύνσεις",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Όλα",
|
||||
"SettingsTabLoggingEnableDebugLogs": "Ενεργοποίηση Αρχείων Καταγραφής Εντοπισμού Σφαλμάτων",
|
||||
"SettingsTabInput": "Χειρισμός",
|
||||
"SettingsTabInputEnableDockedMode": "Ενεργοποίηση Docked Mode",
|
||||
"SettingsTabInputDirectKeyboardAccess": "Άμεση Πρόσβαση στο Πληκτρολόγιο",
|
||||
"SettingsButtonSave": "Αποθήκευση",
|
||||
"SettingsButtonClose": "Κλείσιμο",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Ακύρωση",
|
||||
"SettingsButtonApply": "Εφαρμογή",
|
||||
"ControllerSettingsPlayer": "Παίχτης",
|
||||
"ControllerSettingsPlayer1": "Παίχτης 1",
|
||||
|
@ -157,17 +157,19 @@
|
||||
"SettingsTabLoggingEnableFsAccessLogs": "Enable Fs Access Logs",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Fs Global Access Log Mode:",
|
||||
"SettingsTabLoggingDeveloperOptions": "Developer Options (WARNING: Will reduce performance)",
|
||||
"SettingsTabLoggingOpenglLogLevel": "OpenGL Log Level:",
|
||||
"SettingsTabLoggingOpenglLogLevelNone": "None",
|
||||
"SettingsTabLoggingOpenglLogLevelError": "Error",
|
||||
"SettingsTabLoggingOpenglLogLevelPerformance": "Slowdowns",
|
||||
"SettingsTabLoggingOpenglLogLevelAll": "All",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevel": "Graphics Backend Log Level:",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelNone": "None",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelError": "Error",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Slowdowns",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelAll": "All",
|
||||
"SettingsTabLoggingEnableDebugLogs": "Enable Debug Logs",
|
||||
"SettingsTabInput": "Input",
|
||||
"SettingsTabInputEnableDockedMode": "Docked Mode",
|
||||
"SettingsTabInputDirectKeyboardAccess": "Direct Keyboard Access",
|
||||
"SettingsButtonSave": "Save",
|
||||
"SettingsButtonClose": "Close",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Cancel",
|
||||
"SettingsButtonApply": "Apply",
|
||||
"ControllerSettingsPlayer": "Player",
|
||||
"ControllerSettingsPlayer1": "Player 1",
|
||||
@ -594,7 +596,18 @@
|
||||
"RyujinxUpdaterMessage": "Do you want to update Ryujinx to the latest version?",
|
||||
"SettingsTabHotkeysVolumeUpHotkey": "Increase Volume:",
|
||||
"SettingsTabHotkeysVolumeDownHotkey": "Decrease Volume:",
|
||||
"VolumeShort": "Vol",
|
||||
"SettingsEnableMacroHLE": "Enable Macro HLE",
|
||||
"SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure."
|
||||
"SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.",
|
||||
"VolumeShort": "Vol",
|
||||
"UserProfilesManageSaves": "Manage Saves",
|
||||
"DeleteUserSave": "Do you want to delete user save for this game?",
|
||||
"IrreversibleActionNote": "This action is not reversible.",
|
||||
"SaveManagerHeading": "Manage Saves for {0}",
|
||||
"SaveManagerTitle": "Save Manager",
|
||||
"Name": "Name",
|
||||
"Size": "Size",
|
||||
"Search": "Search",
|
||||
"UserProfilesRecoverLostAccounts": "Recover Lost Accounts",
|
||||
"Recover": "Recover",
|
||||
"UserProfilesRecoverHeading" : "Saves were found for the following accounts"
|
||||
}
|
||||
|
@ -157,17 +157,18 @@
|
||||
"SettingsTabLoggingEnableFsAccessLogs": "Habilitar registros de Fs Access",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Modo de registros Fs Global Access:",
|
||||
"SettingsTabLoggingDeveloperOptions": "Opciones de desarrollador (ADVERTENCIA: empeorarán el rendimiento)",
|
||||
"SettingsTabLoggingOpenglLogLevel": "Nivel de registro de OpenGL:",
|
||||
"SettingsTabLoggingOpenglLogLevelNone": "Nada",
|
||||
"SettingsTabLoggingOpenglLogLevelError": "Errores",
|
||||
"SettingsTabLoggingOpenglLogLevelPerformance": "Ralentizaciones",
|
||||
"SettingsTabLoggingOpenglLogLevelAll": "Todo",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Nada",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelError": "Errores",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Ralentizaciones",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Todo",
|
||||
"SettingsTabLoggingEnableDebugLogs": "Habilitar registros de debug",
|
||||
"SettingsTabInput": "Entrada",
|
||||
"SettingsTabInputEnableDockedMode": "Modo dock/TV",
|
||||
"SettingsTabInputDirectKeyboardAccess": "Acceso directo al teclado",
|
||||
"SettingsButtonSave": "Guardar",
|
||||
"SettingsButtonClose": "Cerrar",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Cancelar",
|
||||
"SettingsButtonApply": "Aplicar",
|
||||
"ControllerSettingsPlayer": "Jugador",
|
||||
"ControllerSettingsPlayer1": "Jugador 1",
|
||||
|
@ -150,17 +150,18 @@
|
||||
"SettingsTabLoggingEnableFsAccessLogs": "Activer les journaux des accès au système de fichiers",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Niveau des journaux des accès au système de fichiers:",
|
||||
"SettingsTabLoggingDeveloperOptions": "Options développeur (ATTENTION: Cela peut réduire les performances)",
|
||||
"SettingsTabLoggingOpenglLogLevel": "Niveau des journaux OpenGL:",
|
||||
"SettingsTabLoggingOpenglLogLevelNone": "Aucun",
|
||||
"SettingsTabLoggingOpenglLogLevelError": "Erreur",
|
||||
"SettingsTabLoggingOpenglLogLevelPerformance": "Ralentissements",
|
||||
"SettingsTabLoggingOpenglLogLevelAll": "Tout",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Aucun",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelError": "Erreur",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Ralentissements",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Tout",
|
||||
"SettingsTabLoggingEnableDebugLogs": "Activer les journaux de debug",
|
||||
"SettingsTabInput": "Contrôles",
|
||||
"SettingsTabInputEnableDockedMode": "Active le mode station d'accueil",
|
||||
"SettingsTabInputDirectKeyboardAccess": "Accès direct au clavier",
|
||||
"SettingsButtonSave": "Enregistrer",
|
||||
"SettingsButtonClose": "Fermer",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Annuler",
|
||||
"SettingsButtonApply": "Appliquer",
|
||||
"ControllerSettingsPlayer": "Joueur",
|
||||
"ControllerSettingsPlayer1": "Joueur 1",
|
||||
|
@ -157,17 +157,18 @@
|
||||
"SettingsTabLoggingEnableFsAccessLogs": "Attiva Fs Access Logs",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Modalità log accesso globale Fs:",
|
||||
"SettingsTabLoggingDeveloperOptions": "Opzioni da sviluppatore (AVVISO: Ridurrà le prestazioni)",
|
||||
"SettingsTabLoggingOpenglLogLevel": "Livello di log OpenGL:",
|
||||
"SettingsTabLoggingOpenglLogLevelNone": "Nessuno",
|
||||
"SettingsTabLoggingOpenglLogLevelError": "Errore",
|
||||
"SettingsTabLoggingOpenglLogLevelPerformance": "Rallentamenti",
|
||||
"SettingsTabLoggingOpenglLogLevelAll": "Tutto",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Nessuno",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelError": "Errore",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Rallentamenti",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Tutto",
|
||||
"SettingsTabLoggingEnableDebugLogs": "Attiva logs di debug",
|
||||
"SettingsTabInput": "Input",
|
||||
"SettingsTabInputEnableDockedMode": "Attiva modalità TV",
|
||||
"SettingsTabInputDirectKeyboardAccess": "Accesso diretto alla tastiera",
|
||||
"SettingsButtonSave": "Salva",
|
||||
"SettingsButtonClose": "Chiudi",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Cancella",
|
||||
"SettingsButtonApply": "Applica",
|
||||
"ControllerSettingsPlayer": "Giocatore",
|
||||
"ControllerSettingsPlayer1": "Giocatore 1",
|
||||
@ -556,8 +557,8 @@
|
||||
"SettingsSelectThemeFileDialogTitle" : "Seleziona file del tema",
|
||||
"SettingsXamlThemeFile" : "File del tema xaml",
|
||||
"SettingsTabHotkeysResScaleUpHotkey": "Aumentare la risoluzione:",
|
||||
"SettingsTabHotkeysResScaleDownHotkey": "Diminuire la risoluzione:"
|
||||
"AvatarWindowTitle": "Gestisci account - Avatar"
|
||||
"SettingsTabHotkeysResScaleDownHotkey": "Diminuire la risoluzione:",
|
||||
"AvatarWindowTitle": "Gestisci account - Avatar",
|
||||
"Amiibo": "Amiibo",
|
||||
"Unknown": "Sconosciuto",
|
||||
"Usage": "Utilizzo",
|
||||
|
@ -157,17 +157,18 @@
|
||||
"SettingsTabLoggingEnableFsAccessLogs": "Fs アクセスログを有効",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Fs グローバルアクセスログモード:",
|
||||
"SettingsTabLoggingDeveloperOptions": "開発者オプション (警告: パフォーマンスが低下します)",
|
||||
"SettingsTabLoggingOpenglLogLevel": "OpenGL ログレベル:",
|
||||
"SettingsTabLoggingOpenglLogLevelNone": "なし",
|
||||
"SettingsTabLoggingOpenglLogLevelError": "エラー",
|
||||
"SettingsTabLoggingOpenglLogLevelPerformance": "パフォーマンス低下",
|
||||
"SettingsTabLoggingOpenglLogLevelAll": "すべて",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelNone": "なし",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelError": "エラー",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "パフォーマンス低下",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelAll": "すべて",
|
||||
"SettingsTabLoggingEnableDebugLogs": "デバッグログを有効",
|
||||
"SettingsTabInput": "入力",
|
||||
"SettingsTabInputEnableDockedMode": "ドッキングモード",
|
||||
"SettingsTabInputDirectKeyboardAccess": "キーボード直接アクセス",
|
||||
"SettingsButtonSave": "セーブ",
|
||||
"SettingsButtonClose": "閉じる",
|
||||
"SettingsButtonOk": "オーケー",
|
||||
"SettingsButtonCancel": "キャンセル",
|
||||
"SettingsButtonApply": "適用",
|
||||
"ControllerSettingsPlayer": "プレイヤー",
|
||||
"ControllerSettingsPlayer1": "プレイヤー 1",
|
||||
|
@ -156,17 +156,18 @@
|
||||
"SettingsTabLoggingEnableFsAccessLogs": "Fs 액세스 로그 켜기",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Fs 전역 액세스 로그 모드 :",
|
||||
"SettingsTabLoggingDeveloperOptions": "개발자 옵션 (경고 : 성능이 저하됩니다.)",
|
||||
"SettingsTabLoggingOpenglLogLevel": "OpenGL 로그 수준 :",
|
||||
"SettingsTabLoggingOpenglLogLevelNone": "없음",
|
||||
"SettingsTabLoggingOpenglLogLevelError": "오류",
|
||||
"SettingsTabLoggingOpenglLogLevelPerformance": "감속",
|
||||
"SettingsTabLoggingOpenglLogLevelAll": "모두",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelNone": "없음",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelError": "오류",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "감속",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelAll": "모두",
|
||||
"SettingsTabLoggingEnableDebugLogs": "디버그 로그 사용",
|
||||
"SettingsTabInput": "입력",
|
||||
"SettingsTabInputEnableDockedMode": "도킹 모드 활성화",
|
||||
"SettingsTabInputDirectKeyboardAccess": "직접 키보드 액세스",
|
||||
"SettingsButtonSave": "구하다",
|
||||
"SettingsButtonClose": "출구",
|
||||
"SettingsButtonOk": "좋아",
|
||||
"SettingsButtonCancel": "취소",
|
||||
"SettingsButtonApply": "적용하다",
|
||||
"ControllerSettingsPlayer": "플레이어",
|
||||
"ControllerSettingsPlayer1": "플레이어 1",
|
||||
|
@ -157,17 +157,18 @@
|
||||
"SettingsTabLoggingEnableFsAccessLogs": "Włącz Logi Dostępu do Systemu Plików",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Tryb Globalnych Logów Systemu Plików:",
|
||||
"SettingsTabLoggingDeveloperOptions": "Opcje programistyczne (OSTRZEŻENIE: Zmniejszą wydajność)",
|
||||
"SettingsTabLoggingOpenglLogLevel": "Poziom Logów OpenGL:",
|
||||
"SettingsTabLoggingOpenglLogLevelNone": "Żadne",
|
||||
"SettingsTabLoggingOpenglLogLevelError": "Błędy",
|
||||
"SettingsTabLoggingOpenglLogLevelPerformance": "Spowolnienia",
|
||||
"SettingsTabLoggingOpenglLogLevelAll": "Wszystkie",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Żadne",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelError": "Błędy",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Spowolnienia",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Wszystkie",
|
||||
"SettingsTabLoggingEnableDebugLogs": "Włącz Logi Debugowania",
|
||||
"SettingsTabInput": "Sterowanie",
|
||||
"SettingsTabInputEnableDockedMode": "Tryb Zadokowany",
|
||||
"SettingsTabInputDirectKeyboardAccess": "Bezpośredni Dostęp do Klawiatury",
|
||||
"SettingsButtonSave": "Zapisz",
|
||||
"SettingsButtonClose": "Zamknij",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Anuluj",
|
||||
"SettingsButtonApply": "Zastosuj",
|
||||
"ControllerSettingsPlayer": "Gracz",
|
||||
"ControllerSettingsPlayer1": "Gracz 1",
|
||||
|
@ -157,17 +157,18 @@
|
||||
"SettingsTabLoggingEnableFsAccessLogs": "Habilitar logs de acesso ao sistema de arquivos",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Modo global de logs do sistema de arquivos:",
|
||||
"SettingsTabLoggingDeveloperOptions": "Opções do desenvolvedor (AVISO: Vai reduzir a performance)",
|
||||
"SettingsTabLoggingOpenglLogLevel": "Nível de log do OpenGL:",
|
||||
"SettingsTabLoggingOpenglLogLevelNone": "Nenhum",
|
||||
"SettingsTabLoggingOpenglLogLevelError": "Erro",
|
||||
"SettingsTabLoggingOpenglLogLevelPerformance": "Lentidão",
|
||||
"SettingsTabLoggingOpenglLogLevelAll": "Todos",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Nenhum",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelError": "Erro",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Lentidão",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Todos",
|
||||
"SettingsTabLoggingEnableDebugLogs": "Habilitar logs de depuração",
|
||||
"SettingsTabInput": "Controle",
|
||||
"SettingsTabInputEnableDockedMode": "Habilitar modo TV",
|
||||
"SettingsTabInputDirectKeyboardAccess": "Acesso direto ao teclado",
|
||||
"SettingsButtonSave": "Salvar",
|
||||
"SettingsButtonClose": "Fechar",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Cancelar",
|
||||
"SettingsButtonApply": "Aplicar",
|
||||
"ControllerSettingsPlayer": "Jogador",
|
||||
"ControllerSettingsPlayer1": "Jogador 1",
|
||||
|
@ -156,17 +156,18 @@
|
||||
"SettingsTabLoggingEnableFsAccessLogs": "Включить журналы доступа Fs",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Режим журнала глобального доступа Fs:",
|
||||
"SettingsTabLoggingDeveloperOptions": "Параметры разработчика (ВНИМАНИЕ: снизит производительность)",
|
||||
"SettingsTabLoggingOpenglLogLevel": "Уровень журнала OpenGL:",
|
||||
"SettingsTabLoggingOpenglLogLevelNone": "Ничего",
|
||||
"SettingsTabLoggingOpenglLogLevelError": "Ошибка",
|
||||
"SettingsTabLoggingOpenglLogLevelPerformance": "Замедления",
|
||||
"SettingsTabLoggingOpenglLogLevelAll": "Всё",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Ничего",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelError": "Ошибка",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Замедления",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Всё",
|
||||
"SettingsTabLoggingEnableDebugLogs": "Включить журналы отладки",
|
||||
"SettingsTabInput": "Управление",
|
||||
"SettingsTabInputEnableDockedMode": "Включить режим закрепления",
|
||||
"SettingsTabInputDirectKeyboardAccess": "Прямой доступ с клавиатуры",
|
||||
"SettingsButtonSave": "Сохранить",
|
||||
"SettingsButtonClose": "Закрыть",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Отмена",
|
||||
"SettingsButtonApply": "Применить",
|
||||
"ControllerSettingsPlayer": "Игрок",
|
||||
"ControllerSettingsPlayer1": "Игрок 1",
|
||||
|
@ -157,17 +157,18 @@
|
||||
"SettingsTabLoggingEnableFsAccessLogs": "Fs Erişim Loglarını Etkinleştir",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Fs Evrensel Erişim Log Modu:",
|
||||
"SettingsTabLoggingDeveloperOptions": "Geliştirici Seçenekleri (UYARI: Performansı düşürecektir)",
|
||||
"SettingsTabLoggingOpenglLogLevel": "OpenGL Log Seviyesi:",
|
||||
"SettingsTabLoggingOpenglLogLevelNone": "Hiç",
|
||||
"SettingsTabLoggingOpenglLogLevelError": "Hata",
|
||||
"SettingsTabLoggingOpenglLogLevelPerformance": "Yavaşlamalar",
|
||||
"SettingsTabLoggingOpenglLogLevelAll": "Her Şey",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Hiç",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelError": "Hata",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Yavaşlamalar",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Her Şey",
|
||||
"SettingsTabLoggingEnableDebugLogs": "Hata Ayıklama Loglarını Etkinleştir",
|
||||
"SettingsTabInput": "Giriş Yöntemi",
|
||||
"SettingsTabInputEnableDockedMode": "Docked Modunu Etkinleştir",
|
||||
"SettingsTabInputDirectKeyboardAccess": "Doğrudan Klavye Erişimi",
|
||||
"SettingsButtonSave": "Kaydet",
|
||||
"SettingsButtonClose": "Kapat",
|
||||
"SettingsButtonOk": "Tamam",
|
||||
"SettingsButtonCancel": "İptal",
|
||||
"SettingsButtonApply": "Uygula",
|
||||
"ControllerSettingsPlayer": "Oyuncu",
|
||||
"ControllerSettingsPlayer1": "Oyuncu 1",
|
||||
|
@ -157,17 +157,18 @@
|
||||
"SettingsTabLoggingEnableFsAccessLogs": "记录文件访问",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "记录全局文件访问模式:",
|
||||
"SettingsTabLoggingDeveloperOptions": "开发者选项 (警告: 会降低性能)",
|
||||
"SettingsTabLoggingOpenglLogLevel": "OpenGL日志级别:",
|
||||
"SettingsTabLoggingOpenglLogLevelNone": "无",
|
||||
"SettingsTabLoggingOpenglLogLevelError": "错误",
|
||||
"SettingsTabLoggingOpenglLogLevelPerformance": "减速",
|
||||
"SettingsTabLoggingOpenglLogLevelAll": "全部",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelNone": "无",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelError": "错误",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "减速",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelAll": "全部",
|
||||
"SettingsTabLoggingEnableDebugLogs": "启用调试日志",
|
||||
"SettingsTabInput": "输入",
|
||||
"SettingsTabInputEnableDockedMode": "主机模式",
|
||||
"SettingsTabInputDirectKeyboardAccess": "直通键盘控制",
|
||||
"SettingsButtonSave": "保存",
|
||||
"SettingsButtonClose": "关闭",
|
||||
"SettingsButtonOk": "批准",
|
||||
"SettingsButtonCancel": "取消",
|
||||
"SettingsButtonApply": "应用",
|
||||
"ControllerSettingsPlayer": "玩家",
|
||||
"ControllerSettingsPlayer1": "玩家 1",
|
||||
|
@ -157,17 +157,18 @@
|
||||
"SettingsTabLoggingEnableFsAccessLogs": "記錄檔案存取",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "記錄全域檔案存取模式:",
|
||||
"SettingsTabLoggingDeveloperOptions": "開發者選項 (警告: 會降低效能)",
|
||||
"SettingsTabLoggingOpenglLogLevel": "OpenGL 日誌級別:",
|
||||
"SettingsTabLoggingOpenglLogLevelNone": "無",
|
||||
"SettingsTabLoggingOpenglLogLevelError": "錯誤",
|
||||
"SettingsTabLoggingOpenglLogLevelPerformance": "減速",
|
||||
"SettingsTabLoggingOpenglLogLevelAll": "全部",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelNone": "無",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelError": "錯誤",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "減速",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelAll": "全部",
|
||||
"SettingsTabLoggingEnableDebugLogs": "啟用除錯日誌",
|
||||
"SettingsTabInput": "輸入",
|
||||
"SettingsTabInputEnableDockedMode": "Docked 模式",
|
||||
"SettingsTabInputDirectKeyboardAccess": "直通鍵盤控制",
|
||||
"SettingsButtonSave": "儲存",
|
||||
"SettingsButtonClose": "關閉",
|
||||
"SettingsButtonOk": "嘛好",
|
||||
"SettingsButtonCancel": "取消",
|
||||
"SettingsButtonApply": "套用",
|
||||
"ControllerSettingsPlayer": "玩家",
|
||||
"ControllerSettingsPlayer1": "玩家 1",
|
||||
|
@ -41,6 +41,9 @@
|
||||
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" />
|
||||
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
|
||||
<SolidColorBrush x:Key="ThemeAccentBrush4" Color="{DynamicResource ThemeAccentColor4}" />
|
||||
<Color x:Key="ControlFillColorSecondary">#008AA8</Color>
|
||||
<SolidColorBrush x:Key="ControlFillColorSecondaryBrush" Color="{StaticResource ControlFillColorSecondary}" />
|
||||
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="ControlFillColorSecondaryBrush" />
|
||||
<Color x:Key="SystemAccentColor">#FF00C3E3</Color>
|
||||
<Color x:Key="SystemAccentColorDark1">#FF99b000</Color>
|
||||
<Color x:Key="SystemAccentColorDark2">#FF006d7d</Color>
|
||||
@ -55,5 +58,7 @@
|
||||
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
|
||||
<Color x:Key="ThemeForegroundColor">#FFFFFFFF</Color>
|
||||
<Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
|
||||
<Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
|
||||
<Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
|
||||
</Styles.Resources>
|
||||
</Styles>
|
@ -50,5 +50,7 @@
|
||||
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
|
||||
<Color x:Key="ThemeForegroundColor">#FF000000</Color>
|
||||
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
|
||||
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
|
||||
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
|
||||
</Styles.Resources>
|
||||
</Styles>
|
@ -1,7 +1,6 @@
|
||||
<Styles
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:sys="clr-namespace:System;assembly=netstandard"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
|
||||
<Design.PreviewWith>
|
||||
<Border Height="2000" Padding="20">
|
||||
@ -269,13 +268,15 @@
|
||||
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
|
||||
<Color x:Key="ThemeContentBackgroundColor">#FF2D2D2D</Color>
|
||||
<Color x:Key="ThemeControlBorderColor">#FF505050</Color>
|
||||
<sys:Double x:Key="ScrollBarThickness">15</sys:Double>
|
||||
<sys:Double x:Key="FontSizeSmall">8</sys:Double>
|
||||
<sys:Double x:Key="FontSizeNormal">10</sys:Double>
|
||||
<sys:Double x:Key="FontSize">12</sys:Double>
|
||||
<sys:Double x:Key="FontSizeLarge">15</sys:Double>
|
||||
<sys:Double x:Key="ControlContentThemeFontSize">13</sys:Double>
|
||||
<x:Double x:Key="ScrollBarThickness">15</x:Double>
|
||||
<x:Double x:Key="FontSizeSmall">8</x:Double>
|
||||
<x:Double x:Key="FontSizeNormal">10</x:Double>
|
||||
<x:Double x:Key="FontSize">12</x:Double>
|
||||
<x:Double x:Key="FontSizeLarge">15</x:Double>
|
||||
<x:Double x:Key="ControlContentThemeFontSize">13</x:Double>
|
||||
<x:Double x:Key="MenuItemHeight">26</x:Double>
|
||||
<x:Double x:Key="TabItemMinHeight">28</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxWidth">600</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxHeight">756</x:Double>
|
||||
</Styles.Resources>
|
||||
</Styles>
|
@ -113,6 +113,11 @@ namespace Ryujinx.Ava.Common
|
||||
return;
|
||||
}
|
||||
|
||||
OpenSaveDir(saveDataId);
|
||||
}
|
||||
|
||||
public static void OpenSaveDir(ulong saveDataId)
|
||||
{
|
||||
string saveRootPath = Path.Combine(_virtualFileSystem.GetNandPath(), $"user/save/{saveDataId:x16}");
|
||||
|
||||
if (!Directory.Exists(saveRootPath))
|
||||
|
127
Ryujinx.Ava/Helper/MetalHelper.cs
Normal file
127
Ryujinx.Ava/Helper/MetalHelper.cs
Normal file
@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Runtime.InteropServices;
|
||||
using Avalonia;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Helper
|
||||
{
|
||||
public delegate void UpdateBoundsCallbackDelegate(Rect rect);
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
static class MetalHelper
|
||||
{
|
||||
private const string LibObjCImport = "/usr/lib/libobjc.A.dylib";
|
||||
|
||||
private struct Selector
|
||||
{
|
||||
public readonly IntPtr NativePtr;
|
||||
|
||||
public unsafe Selector(string value)
|
||||
{
|
||||
int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
|
||||
byte* data = stackalloc byte[size];
|
||||
|
||||
fixed (char* pValue = value)
|
||||
{
|
||||
System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
|
||||
}
|
||||
|
||||
NativePtr = sel_registerName(data);
|
||||
}
|
||||
|
||||
public static implicit operator Selector(string value) => new Selector(value);
|
||||
}
|
||||
|
||||
private static unsafe IntPtr GetClass(string value)
|
||||
{
|
||||
int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
|
||||
byte* data = stackalloc byte[size];
|
||||
|
||||
fixed (char* pValue = value)
|
||||
{
|
||||
System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
|
||||
}
|
||||
|
||||
return objc_getClass(data);
|
||||
}
|
||||
|
||||
private struct NSPoint
|
||||
{
|
||||
public double X;
|
||||
public double Y;
|
||||
|
||||
public NSPoint(double x, double y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
}
|
||||
|
||||
private struct NSRect
|
||||
{
|
||||
public NSPoint Pos;
|
||||
public NSPoint Size;
|
||||
|
||||
public NSRect(double x, double y, double width, double height)
|
||||
{
|
||||
Pos = new NSPoint(x, y);
|
||||
Size = new NSPoint(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr GetMetalLayer(out IntPtr nsView, out UpdateBoundsCallbackDelegate updateBounds)
|
||||
{
|
||||
// Create a new CAMetalLayer.
|
||||
IntPtr layerClass = GetClass("CAMetalLayer");
|
||||
IntPtr metalLayer = IntPtr_objc_msgSend(layerClass, "alloc");
|
||||
objc_msgSend(metalLayer, "init");
|
||||
|
||||
// Create a child NSView to render into.
|
||||
IntPtr nsViewClass = GetClass("NSView");
|
||||
IntPtr child = IntPtr_objc_msgSend(nsViewClass, "alloc");
|
||||
objc_msgSend(child, "init", new NSRect(0, 0, 0, 0));
|
||||
|
||||
// Make its renderer our metal layer.
|
||||
objc_msgSend(child, "setWantsLayer:", (byte)1);
|
||||
objc_msgSend(child, "setLayer:", metalLayer);
|
||||
objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
|
||||
|
||||
// Ensure the scale factor is up to date.
|
||||
updateBounds = (Rect rect) => {
|
||||
objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
|
||||
};
|
||||
|
||||
nsView = child;
|
||||
return metalLayer;
|
||||
}
|
||||
|
||||
public static void DestroyMetalLayer(IntPtr nsView, IntPtr metalLayer)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
[DllImport(LibObjCImport)]
|
||||
private static unsafe extern IntPtr sel_registerName(byte* data);
|
||||
|
||||
[DllImport(LibObjCImport)]
|
||||
private static unsafe extern IntPtr objc_getClass(byte* data);
|
||||
|
||||
[DllImport(LibObjCImport)]
|
||||
private static extern void objc_msgSend(IntPtr receiver, Selector selector);
|
||||
|
||||
[DllImport(LibObjCImport)]
|
||||
private static extern void objc_msgSend(IntPtr receiver, Selector selector, byte value);
|
||||
|
||||
[DllImport(LibObjCImport)]
|
||||
private static extern void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value);
|
||||
|
||||
[DllImport(LibObjCImport)]
|
||||
private static extern void objc_msgSend(IntPtr receiver, Selector selector, NSRect point);
|
||||
|
||||
[DllImport(LibObjCImport)]
|
||||
private static extern void objc_msgSend(IntPtr receiver, Selector selector, double value);
|
||||
|
||||
[DllImport(LibObjCImport, EntryPoint = "objc_msgSend")]
|
||||
private static extern IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector);
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ using Ryujinx.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
|
||||
using Key = Ryujinx.Input.Key;
|
||||
|
||||
@ -13,30 +12,37 @@ namespace Ryujinx.Ava.Input
|
||||
internal class AvaloniaKeyboard : IKeyboard
|
||||
{
|
||||
private readonly List<ButtonMappingEntry> _buttonsUserMapping;
|
||||
private readonly AvaloniaKeyboardDriver _driver;
|
||||
private readonly AvaloniaKeyboardDriver _driver;
|
||||
private StandardKeyboardInputConfig _configuration;
|
||||
|
||||
private readonly object _userMappingLock = new();
|
||||
|
||||
private StandardKeyboardInputConfig _configuration;
|
||||
|
||||
private bool HasConfiguration => _configuration != null;
|
||||
|
||||
public string Id { get; }
|
||||
public string Id { get; }
|
||||
public string Name { get; }
|
||||
|
||||
public bool IsConnected => true;
|
||||
public bool IsConnected => true;
|
||||
public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
|
||||
|
||||
public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
|
||||
private class ButtonMappingEntry
|
||||
{
|
||||
public readonly Key From;
|
||||
public readonly GamepadButtonInputId To;
|
||||
|
||||
public ButtonMappingEntry(GamepadButtonInputId to, Key from)
|
||||
{
|
||||
To = to;
|
||||
From = from;
|
||||
}
|
||||
}
|
||||
|
||||
public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name)
|
||||
{
|
||||
_driver = driver;
|
||||
Id = id;
|
||||
Name = name;
|
||||
_buttonsUserMapping = new List<ButtonMappingEntry>();
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
_driver = driver;
|
||||
Id = id;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public KeyboardStateSnapshot GetKeyboardStateSnapshot()
|
||||
{
|
||||
@ -46,11 +52,11 @@ namespace Ryujinx.Ava.Input
|
||||
public GamepadStateSnapshot GetMappedStateSnapshot()
|
||||
{
|
||||
KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot();
|
||||
GamepadStateSnapshot result = default;
|
||||
GamepadStateSnapshot result = default;
|
||||
|
||||
lock (_userMappingLock)
|
||||
{
|
||||
if (!HasConfiguration)
|
||||
if (_configuration == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
@ -62,17 +68,17 @@ namespace Ryujinx.Ava.Input
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do not touch state of the button already pressed
|
||||
// NOTE: Do not touch state of the button already pressed.
|
||||
if (!result.IsPressed(entry.To))
|
||||
{
|
||||
result.SetPressed(entry.To, rawState.IsPressed(entry.From));
|
||||
}
|
||||
}
|
||||
|
||||
(short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
|
||||
(short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
|
||||
(short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick);
|
||||
|
||||
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
|
||||
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
|
||||
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
|
||||
}
|
||||
|
||||
@ -114,29 +120,29 @@ namespace Ryujinx.Ava.Input
|
||||
|
||||
_buttonsUserMapping.Clear();
|
||||
|
||||
// Left joycon
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
|
||||
// Left JoyCon
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
|
||||
|
||||
// Finally right joycon
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
|
||||
// Right JoyCon
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,16 +196,6 @@ namespace Ryujinx.Ava.Input
|
||||
_driver?.ResetKeys();
|
||||
}
|
||||
|
||||
private class ButtonMappingEntry
|
||||
{
|
||||
public readonly Key From;
|
||||
public readonly GamepadButtonInputId To;
|
||||
|
||||
public ButtonMappingEntry(GamepadButtonInputId to, Key from)
|
||||
{
|
||||
To = to;
|
||||
From = from;
|
||||
}
|
||||
}
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using AvaKey = Avalonia.Input.Key;
|
||||
using Key = Ryujinx.Input.Key;
|
||||
|
||||
@ -13,24 +12,23 @@ namespace Ryujinx.Ava.Input
|
||||
internal class AvaloniaKeyboardDriver : IGamepadDriver
|
||||
{
|
||||
private static readonly string[] _keyboardIdentifers = new string[1] { "0" };
|
||||
private readonly Control _control;
|
||||
private readonly Control _control;
|
||||
private readonly HashSet<AvaKey> _pressedKeys;
|
||||
|
||||
public event EventHandler<KeyEventArgs> KeyPressed;
|
||||
public event EventHandler<KeyEventArgs> KeyRelease;
|
||||
public event EventHandler<string> TextInput;
|
||||
|
||||
public string DriverName => "Avalonia";
|
||||
public event EventHandler<string> TextInput;
|
||||
|
||||
public string DriverName => "AvaloniaKeyboardDriver";
|
||||
public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers;
|
||||
|
||||
public AvaloniaKeyboardDriver(Control control)
|
||||
{
|
||||
_control = control;
|
||||
_control = control;
|
||||
_pressedKeys = new HashSet<AvaKey>();
|
||||
|
||||
_control.KeyDown += OnKeyPress;
|
||||
_control.KeyUp += OnKeyRelease;
|
||||
_control.KeyDown += OnKeyPress;
|
||||
_control.KeyUp += OnKeyRelease;
|
||||
_control.TextInput += Control_TextInput;
|
||||
}
|
||||
|
||||
@ -41,21 +39,16 @@ namespace Ryujinx.Ava.Input
|
||||
|
||||
public event Action<string> OnGamepadConnected
|
||||
{
|
||||
add { }
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public event Action<string> OnGamepadDisconnected
|
||||
{
|
||||
add { }
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
public IGamepad GetGamepad(string id)
|
||||
{
|
||||
if (!_keyboardIdentifers[0].Equals(id))
|
||||
@ -70,15 +63,13 @@ namespace Ryujinx.Ava.Input
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_control.KeyUp -= OnKeyPress;
|
||||
_control.KeyUp -= OnKeyPress;
|
||||
_control.KeyDown -= OnKeyRelease;
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnKeyPress(object sender, KeyEventArgs args)
|
||||
{
|
||||
AvaKey key = args.Key;
|
||||
|
||||
_pressedKeys.Add(args.Key);
|
||||
|
||||
KeyPressed?.Invoke(this, args);
|
||||
@ -98,7 +89,7 @@ namespace Ryujinx.Ava.Input
|
||||
return false;
|
||||
}
|
||||
|
||||
AvaloniaMappingHelper.TryGetAvaKey(key, out var nativeKey);
|
||||
AvaloniaKeyboardMappingHelper.TryGetAvaKey(key, out var nativeKey);
|
||||
|
||||
return _pressedKeys.Contains(nativeKey);
|
||||
}
|
||||
@ -107,5 +98,10 @@ namespace Ryujinx.Ava.Input
|
||||
{
|
||||
_pressedKeys.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ using AvaKey = Avalonia.Input.Key;
|
||||
|
||||
namespace Ryujinx.Ava.Input
|
||||
{
|
||||
internal static class AvaloniaMappingHelper
|
||||
internal static class AvaloniaKeyboardMappingHelper
|
||||
{
|
||||
private static readonly AvaKey[] _keyMapping = new AvaKey[(int)Key.Count]
|
||||
{
|
||||
@ -149,11 +149,11 @@ namespace Ryujinx.Ava.Input
|
||||
|
||||
private static readonly Dictionary<AvaKey, Key> _avaKeyMapping;
|
||||
|
||||
static AvaloniaMappingHelper()
|
||||
static AvaloniaKeyboardMappingHelper()
|
||||
{
|
||||
var inputKeys = Enum.GetValues(typeof(Key));
|
||||
|
||||
// Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array.
|
||||
// NOTE: Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array.
|
||||
_avaKeyMapping = new Dictionary<AvaKey, Key>();
|
||||
|
||||
foreach (var key in inputKeys)
|
||||
@ -167,15 +167,13 @@ namespace Ryujinx.Ava.Input
|
||||
|
||||
public static bool TryGetAvaKey(Key key, out AvaKey avaKey)
|
||||
{
|
||||
var keyExist = (int)key < _keyMapping.Length;
|
||||
avaKey = AvaKey.None;
|
||||
|
||||
bool keyExist = (int)key < _keyMapping.Length;
|
||||
if (keyExist)
|
||||
{
|
||||
avaKey = _keyMapping[(int)key];
|
||||
}
|
||||
else
|
||||
{
|
||||
avaKey = AvaKey.None;
|
||||
}
|
||||
|
||||
return keyExist;
|
||||
}
|
@ -10,15 +10,12 @@ namespace Ryujinx.Ava.Input
|
||||
{
|
||||
private AvaloniaMouseDriver _driver;
|
||||
|
||||
public GamepadFeaturesFlag Features => throw new NotImplementedException();
|
||||
|
||||
public string Id => "0";
|
||||
|
||||
public string Id => "0";
|
||||
public string Name => "AvaloniaMouse";
|
||||
|
||||
public bool IsConnected => true;
|
||||
|
||||
public bool[] Buttons => _driver.PressedButtons;
|
||||
public bool IsConnected => true;
|
||||
public GamepadFeaturesFlag Features => throw new NotImplementedException();
|
||||
public bool[] Buttons => _driver.PressedButtons;
|
||||
|
||||
public AvaloniaMouse(AvaloniaMouseDriver driver)
|
||||
{
|
||||
|
@ -11,35 +11,50 @@ namespace Ryujinx.Ava.Input
|
||||
{
|
||||
internal class AvaloniaMouseDriver : IGamepadDriver
|
||||
{
|
||||
private Control _widget;
|
||||
private bool _isDisposed;
|
||||
private Size _size;
|
||||
private Control _widget;
|
||||
private bool _isDisposed;
|
||||
private Size _size;
|
||||
private readonly Window _window;
|
||||
|
||||
public bool[] PressedButtons { get; }
|
||||
|
||||
public bool[] PressedButtons { get; }
|
||||
public Vector2 CurrentPosition { get; private set; }
|
||||
public Vector2 Scroll { get; private set; }
|
||||
public Vector2 Scroll { get; private set; }
|
||||
|
||||
public string DriverName => "AvaloniaMouseDriver";
|
||||
public ReadOnlySpan<string> GamepadsIds => new[] { "0" };
|
||||
|
||||
public AvaloniaMouseDriver(Window window, Control parent)
|
||||
{
|
||||
_widget = parent;
|
||||
_window = window;
|
||||
|
||||
_widget.PointerMoved += Parent_PointerMovedEvent;
|
||||
_widget.PointerPressed += Parent_PointerPressEvent;
|
||||
_widget.PointerReleased += Parent_PointerReleaseEvent;
|
||||
_widget.PointerMoved += Parent_PointerMovedEvent;
|
||||
_widget.PointerPressed += Parent_PointerPressEvent;
|
||||
_widget.PointerReleased += Parent_PointerReleaseEvent;
|
||||
_widget.PointerWheelChanged += Parent_ScrollEvent;
|
||||
|
||||
_window.PointerMoved += Parent_PointerMovedEvent;
|
||||
_window.PointerPressed += Parent_PointerPressEvent;
|
||||
_window.PointerReleased += Parent_PointerReleaseEvent;
|
||||
_window.PointerMoved += Parent_PointerMovedEvent;
|
||||
_window.PointerPressed += Parent_PointerPressEvent;
|
||||
_window.PointerReleased += Parent_PointerReleaseEvent;
|
||||
_window.PointerWheelChanged += Parent_ScrollEvent;
|
||||
|
||||
PressedButtons = new bool[(int)MouseButton.Count];
|
||||
|
||||
_size = new Size((int)parent.Bounds.Width, (int)parent.Bounds.Height);
|
||||
parent.GetObservable(Control.BoundsProperty).Subscribe(Resized);
|
||||
|
||||
parent.GetObservable(Visual.BoundsProperty).Subscribe(Resized);
|
||||
}
|
||||
|
||||
public event Action<string> OnGamepadConnected
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public event Action<string> OnGamepadDisconnected
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
private void Resized(Rect rect)
|
||||
@ -59,14 +74,12 @@ namespace Ryujinx.Ava.Input
|
||||
|
||||
private void Parent_PointerPressEvent(object o, PointerPressedEventArgs args)
|
||||
{
|
||||
var pointerProperties = args.GetCurrentPoint(_widget).Properties;
|
||||
|
||||
PressedButtons[(int)pointerProperties.PointerUpdateKind] = true;
|
||||
PressedButtons[(int)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind] = true;
|
||||
}
|
||||
|
||||
private void Parent_PointerMovedEvent(object o, PointerEventArgs args)
|
||||
{
|
||||
var position = args.GetPosition(_widget);
|
||||
Point position = args.GetPosition(_widget);
|
||||
|
||||
CurrentPosition = new Vector2((float)position.X, (float)position.Y);
|
||||
}
|
||||
@ -96,22 +109,6 @@ namespace Ryujinx.Ava.Input
|
||||
return _size;
|
||||
}
|
||||
|
||||
public string DriverName => "Avalonia";
|
||||
|
||||
public event Action<string> OnGamepadConnected
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public event Action<string> OnGamepadDisconnected
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public ReadOnlySpan<string> GamepadsIds => new[] { "0" };
|
||||
|
||||
public IGamepad GetGamepad(string id)
|
||||
{
|
||||
return new AvaloniaMouse(this);
|
||||
@ -126,14 +123,14 @@ namespace Ryujinx.Ava.Input
|
||||
|
||||
_isDisposed = true;
|
||||
|
||||
_widget.PointerMoved -= Parent_PointerMovedEvent;
|
||||
_widget.PointerPressed -= Parent_PointerPressEvent;
|
||||
_widget.PointerReleased -= Parent_PointerReleaseEvent;
|
||||
_widget.PointerMoved -= Parent_PointerMovedEvent;
|
||||
_widget.PointerPressed -= Parent_PointerPressEvent;
|
||||
_widget.PointerReleased -= Parent_PointerReleaseEvent;
|
||||
_widget.PointerWheelChanged -= Parent_ScrollEvent;
|
||||
|
||||
_window.PointerMoved -= Parent_PointerMovedEvent;
|
||||
_window.PointerPressed -= Parent_PointerPressEvent;
|
||||
_window.PointerReleased -= Parent_PointerReleaseEvent;
|
||||
_window.PointerMoved -= Parent_PointerMovedEvent;
|
||||
_window.PointerPressed -= Parent_PointerPressEvent;
|
||||
_window.PointerReleased -= Parent_PointerReleaseEvent;
|
||||
_window.PointerWheelChanged -= Parent_ScrollEvent;
|
||||
|
||||
_widget = null;
|
||||
|
@ -1,5 +1,6 @@
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using Avalonia;
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Ava.Ui.Windows;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
@ -8,6 +9,7 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.System;
|
||||
using Ryujinx.Common.SystemInfo;
|
||||
using Ryujinx.Modules;
|
||||
using Ryujinx.SDL2.Common;
|
||||
using Ryujinx.Ui.Common;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
@ -20,10 +22,11 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
public static double WindowScaleFactor { get; set; }
|
||||
public static string Version { get; private set; }
|
||||
public static string ConfigurationPath { get; private set; }
|
||||
public static bool PreviewerDetached { get; private set; }
|
||||
public static double WindowScaleFactor { get; set; }
|
||||
public static double DesktopScaleFactor { get; set; } = 1.0;
|
||||
public static string Version { get; private set; }
|
||||
public static string ConfigurationPath { get; private set; }
|
||||
public static bool PreviewerDetached { get; private set; }
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
|
||||
@ -94,6 +97,9 @@ namespace Ryujinx.Ava
|
||||
// Initialize Discord integration.
|
||||
DiscordIntegrationModule.Initialize();
|
||||
|
||||
// Initialize SDL2 driver
|
||||
SDL2Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input);
|
||||
|
||||
ReloadConfig();
|
||||
|
||||
ForceDpiAware.Windows();
|
||||
@ -219,4 +225,4 @@ namespace Ryujinx.Ava
|
||||
Logger.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,14 +28,16 @@
|
||||
<PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
|
||||
<PackageReference Include="DynamicData" Version="7.12.8" />
|
||||
<PackageReference Include="FluentAvaloniaUI" Version="1.4.5" />
|
||||
<PackageReference Include="XamlNameReferenceGenerator" Version="1.4.2" />
|
||||
<PackageReference Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
||||
|
||||
<PackageReference Include="OpenTK.Core" Version="4.7.5" />
|
||||
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
||||
<PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.10.1" />
|
||||
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies.osx" Version="5.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
|
||||
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
|
||||
<PackageReference Include="Silk.NET.Vulkan" Version="2.16.0" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
||||
<PackageReference Include="SPB" Version="0.0.4-build28" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||
@ -57,14 +59,18 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ContentWithTargetPath Include="..\distribution\windows\alsoft.ini" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||
<Content Include="..\distribution\windows\alsoft.ini" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<TargetPath>alsoft.ini</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
<ContentWithTargetPath Include="..\distribution\legal\THIRDPARTY.md">
|
||||
</Content>
|
||||
<Content Include="..\distribution\legal\THIRDPARTY.md">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<TargetPath>THIRDPARTY.md</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
</Content>
|
||||
<Content Include="..\LICENSE.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<TargetPath>LICENSE.txt</TargetPath>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -68,7 +68,7 @@ namespace Ryujinx.Ava.Ui.Applet
|
||||
|
||||
private void AvaloniaDynamicTextInputHandler_KeyRelease(object sender, Avalonia.Input.KeyEventArgs e)
|
||||
{
|
||||
var key = (HidKey)AvaloniaMappingHelper.ToInputKey(e.Key);
|
||||
var key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key);
|
||||
|
||||
if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true))
|
||||
{
|
||||
@ -88,7 +88,7 @@ namespace Ryujinx.Ava.Ui.Applet
|
||||
|
||||
private void AvaloniaDynamicTextInputHandler_KeyPressed(object sender, KeyEventArgs e)
|
||||
{
|
||||
var key = (HidKey)AvaloniaMappingHelper.ToInputKey(e.Key);
|
||||
var key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key);
|
||||
|
||||
if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true))
|
||||
{
|
||||
|
@ -11,7 +11,8 @@
|
||||
Height="340"
|
||||
CanResize="False"
|
||||
SizeToContent="Height"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Grid
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
|
@ -6,7 +6,8 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
Width="400"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Grid
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
|
@ -2,11 +2,10 @@ using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Platform;
|
||||
using Ryujinx.Ava.Ui.Helper;
|
||||
using SPB.Graphics;
|
||||
using SPB.Platform;
|
||||
using SPB.Platform.GLX;
|
||||
using SPB.Platform.X11;
|
||||
using SPB.Windowing;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
@ -23,6 +22,10 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
protected GLXWindow X11Window { get; set; }
|
||||
protected IntPtr WindowHandle { get; set; }
|
||||
protected IntPtr X11Display { get; set; }
|
||||
protected IntPtr NsView { get; set; }
|
||||
protected IntPtr MetalLayer { get; set; }
|
||||
|
||||
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
|
||||
|
||||
public event EventHandler<IntPtr> WindowCreated;
|
||||
public event EventHandler<Size> SizeChanged;
|
||||
@ -58,6 +61,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
private void StateChanged(Rect rect)
|
||||
{
|
||||
SizeChanged?.Invoke(this, rect.Size);
|
||||
_updateBoundsCallback?.Invoke(rect);
|
||||
}
|
||||
|
||||
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
|
||||
@ -70,6 +74,11 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
{
|
||||
return CreateWin32(parent);
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
return CreateMacOs(parent);
|
||||
}
|
||||
|
||||
return base.CreateNativeControlCore(parent);
|
||||
}
|
||||
|
||||
@ -85,6 +94,10 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
{
|
||||
DestroyWin32(control);
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
DestroyMacOS();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.DestroyNativeControlCore(control);
|
||||
@ -187,6 +200,16 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
return DefWindowProc(hWnd, msg, (IntPtr)wParam, (IntPtr)lParam);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
IPlatformHandle CreateMacOs(IPlatformHandle parent)
|
||||
{
|
||||
MetalLayer = MetalHelper.GetMetalLayer(out IntPtr nsView, out _updateBoundsCallback);
|
||||
|
||||
NsView = nsView;
|
||||
|
||||
return new PlatformHandle(nsView, "NSView");
|
||||
}
|
||||
|
||||
void DestroyLinux()
|
||||
{
|
||||
X11Window?.Dispose();
|
||||
@ -198,5 +221,11 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
DestroyWindow(handle.Handle);
|
||||
UnregisterClass(_className, GetModuleHandle(null));
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
void DestroyMacOS()
|
||||
{
|
||||
MetalHelper.DestroyMetalLayer(NsView, MetalLayer);
|
||||
}
|
||||
}
|
||||
}
|
@ -10,7 +10,8 @@
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<UserControl.Resources>
|
||||
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
||||
@ -113,8 +114,8 @@
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="5" />
|
||||
<Setter Property="CornerRadius" Value="5" />
|
||||
<Setter Property="Background" Value="{DynamicResource SystemAccentColorDark3}" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:0.7">
|
||||
<KeyFrame Cue="0%">
|
||||
@ -132,27 +133,18 @@
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:selected /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource AppListHoverBackgroundColor}" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.Styles>
|
||||
<Style Selector="ui|SymbolIcon.small.icon">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
</Style>
|
||||
<Style Selector="ui|SymbolIcon.normal.icon">
|
||||
<Setter Property="FontSize" Value="19" />
|
||||
</Style>
|
||||
<Style Selector="ui|SymbolIcon.large.icon">
|
||||
<Setter Property="FontSize" Value="23" />
|
||||
</Style>
|
||||
<Style Selector="ui|SymbolIcon.huge.icon">
|
||||
<Setter Property="FontSize" Value="26" />
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
<Border
|
||||
Margin="0"
|
||||
Padding="{Binding $parent[UserControl].DataContext.GridItemPadding}"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
@ -160,57 +152,41 @@
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5">
|
||||
<Grid Margin="0">
|
||||
CornerRadius="4">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image
|
||||
Grid.Row="0"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||
<StackPanel
|
||||
<Panel
|
||||
Grid.Row="1"
|
||||
Height="50"
|
||||
Margin="5"
|
||||
Margin="0 10 0 0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
IsVisible="{Binding $parent[UserControl].DataContext.ShowNames}">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding TitleName}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ui:SymbolIcon
|
||||
Margin="5"
|
||||
Margin="5,5,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Classes.icon="true"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Foreground="Yellow"
|
||||
FontSize="16"
|
||||
Foreground="{DynamicResource SystemAccentColor}"
|
||||
IsVisible="{Binding Favorite}"
|
||||
Symbol="StarFilled" />
|
||||
<ui:SymbolIcon
|
||||
Margin="5"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Classes.icon="true"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Foreground="Black"
|
||||
IsVisible="{Binding Favorite}"
|
||||
Symbol="Star" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
|
@ -10,7 +10,8 @@
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<UserControl.Resources>
|
||||
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
||||
@ -115,8 +116,8 @@
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="CornerRadius" Value="5" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColorDark3}" />
|
||||
<Setter Property="BorderThickness" Value="2" />
|
||||
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
|
||||
<Setter Property="BorderThickness" Value="2"/>
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:0.7">
|
||||
<KeyFrame Cue="0%">
|
||||
@ -134,6 +135,12 @@
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:selected /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource AppListHoverBackgroundColor}" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
@ -152,9 +159,6 @@
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Image
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="0"
|
||||
@ -169,7 +173,7 @@
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Vertical"
|
||||
Spacing="5">
|
||||
Spacing="5" >
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding TitleName}"
|
||||
@ -214,20 +218,10 @@
|
||||
Margin="-5,-5,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
FontSize="20"
|
||||
Foreground="Yellow"
|
||||
FontSize="16"
|
||||
Foreground="{DynamicResource SystemAccentColor}"
|
||||
IsVisible="{Binding Favorite}"
|
||||
Symbol="StarFilled" />
|
||||
<ui:SymbolIcon
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="-5,-5,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
FontSize="20"
|
||||
Foreground="Black"
|
||||
IsVisible="{Binding Favorite}"
|
||||
Symbol="Star" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
@ -4,7 +4,8 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Grid
|
||||
Margin="5,10,5,5"
|
||||
HorizontalAlignment="Stretch"
|
||||
|
@ -4,7 +4,8 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.NavigationDialogHost">
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.NavigationDialogHost"
|
||||
Focusable="True">
|
||||
<ui:Frame HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
x:Name="ContentFrame" />
|
||||
</UserControl>
|
@ -1,6 +1,7 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using LibHac;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
@ -14,6 +15,8 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
{
|
||||
public AccountManager AccountManager { get; }
|
||||
public ContentManager ContentManager { get; }
|
||||
public VirtualFileSystem VirtualFileSystem { get; }
|
||||
public HorizonClient HorizonClient { get; }
|
||||
public UserProfileViewModel ViewModel { get; set; }
|
||||
|
||||
public NavigationDialogHost()
|
||||
@ -22,10 +25,12 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
}
|
||||
|
||||
public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager,
|
||||
VirtualFileSystem virtualFileSystem)
|
||||
VirtualFileSystem virtualFileSystem, HorizonClient horizonClient)
|
||||
{
|
||||
AccountManager = accountManager;
|
||||
ContentManager = contentManager;
|
||||
VirtualFileSystem = virtualFileSystem;
|
||||
HorizonClient = horizonClient;
|
||||
ViewModel = new UserProfileViewModel(this);
|
||||
|
||||
|
||||
@ -54,9 +59,10 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
ContentFrame.Navigate(sourcePageType, parameter);
|
||||
}
|
||||
|
||||
public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager, VirtualFileSystem ownerVirtualFileSystem)
|
||||
public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager,
|
||||
VirtualFileSystem ownerVirtualFileSystem, HorizonClient ownerHorizonClient)
|
||||
{
|
||||
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem);
|
||||
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient);
|
||||
ContentDialog contentDialog = new ContentDialog
|
||||
{
|
||||
Title = LocaleManager.Instance["UserProfileWindowTitle"],
|
||||
|
@ -4,7 +4,8 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.ProfileImageSelectionDialog">
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.ProfileImageSelectionDialog"
|
||||
Focusable="True">
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5,10,5, 5">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
|
@ -3,5 +3,6 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.RendererHost">
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.RendererHost"
|
||||
Focusable="True">
|
||||
</UserControl>
|
||||
|
103
Ryujinx.Ava/Ui/Controls/SaveManager.axaml
Normal file
103
Ryujinx.Ava/Ui/Controls/SaveManager.axaml
Normal file
@ -0,0 +1,103 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.Ui.Models"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
Height="400"
|
||||
Width="550"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.SaveManager"
|
||||
Focusable="True">
|
||||
<UserControl.Resources>
|
||||
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0" HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Spacing="10" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center">
|
||||
<Label Content="{locale:Locale CommonSort}" VerticalAlignment="Center" />
|
||||
<ComboBox SelectedIndex="{Binding SortIndex}" Width="100">
|
||||
<ComboBoxItem>
|
||||
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale Name}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale Size}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
<ComboBox SelectedIndex="{Binding OrderIndex}" Width="150">
|
||||
<ComboBoxItem>
|
||||
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale OrderAscending}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale Descending}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<Grid Grid.Column="1" HorizontalAlignment="Stretch" Margin="10,0, 0, 0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Content="{locale:Locale Search}" VerticalAlignment="Center"/>
|
||||
<TextBox Margin="5,0,0,0" Grid.Column="1" HorizontalAlignment="Stretch" Text="{Binding Search}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Border Grid.Row="1" Margin="0,5" BorderThickness="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<ListBox Name="SaveList" Items="{Binding View}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:SaveModel">
|
||||
<Grid HorizontalAlignment="Stretch" Margin="0,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||
<Border Height="42" Margin="2" Width="42" Padding="10"
|
||||
IsVisible="{Binding !InGameList}">
|
||||
<ui:SymbolIcon Symbol="Help" FontSize="30" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<Image IsVisible="{Binding InGameList}"
|
||||
Margin="2"
|
||||
Width="42"
|
||||
Height="42"
|
||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||
<TextBlock MaxLines="3" Width="320" Margin="5" TextWrapping="Wrap"
|
||||
Text="{Binding Title}" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Spacing="10" HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Label Content="{Binding SizeString}" IsVisible="{Binding SizeAvailable}"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||
<Button VerticalAlignment="Center" HorizontalAlignment="Right" Padding="10"
|
||||
MinWidth="0" MinHeight="0" Name="OpenLocation" Command="{Binding OpenLocation}">
|
||||
<ui:SymbolIcon Symbol="OpenFolder" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Button>
|
||||
<Button VerticalAlignment="Center" HorizontalAlignment="Right" Padding="10"
|
||||
MinWidth="0" MinHeight="0" Name="Delete" Command="{Binding Delete}">
|
||||
<ui:SymbolIcon Symbol="Delete" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
160
Ryujinx.Ava/Ui/Controls/SaveManager.axaml.cs
Normal file
160
Ryujinx.Ava/Ui/Controls/SaveManager.axaml.cs
Normal file
@ -0,0 +1,160 @@
|
||||
using Avalonia.Controls;
|
||||
using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
using LibHac;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Shim;
|
||||
using Ryujinx.Ava.Common;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.Models;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Controls
|
||||
{
|
||||
public partial class SaveManager : UserControl
|
||||
{
|
||||
private readonly UserProfile _userProfile;
|
||||
private readonly HorizonClient _horizonClient;
|
||||
private readonly VirtualFileSystem _virtualFileSystem;
|
||||
private int _sortIndex;
|
||||
private int _orderIndex;
|
||||
private ObservableCollection<SaveModel> _view = new ObservableCollection<SaveModel>();
|
||||
private string _search;
|
||||
|
||||
public ObservableCollection<SaveModel> Saves { get; set; } = new ObservableCollection<SaveModel>();
|
||||
|
||||
public ObservableCollection<SaveModel> View
|
||||
{
|
||||
get => _view;
|
||||
set => _view = value;
|
||||
}
|
||||
|
||||
public int SortIndex
|
||||
{
|
||||
get => _sortIndex;
|
||||
set
|
||||
{
|
||||
_sortIndex = value;
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
public int OrderIndex
|
||||
{
|
||||
get => _orderIndex;
|
||||
set
|
||||
{
|
||||
_orderIndex = value;
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
public string Search
|
||||
{
|
||||
get => _search;
|
||||
set
|
||||
{
|
||||
_search = value;
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
public SaveManager()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public SaveManager(UserProfile userProfile, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem)
|
||||
{
|
||||
_userProfile = userProfile;
|
||||
_horizonClient = horizonClient;
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
InitializeComponent();
|
||||
|
||||
DataContext = this;
|
||||
|
||||
Task.Run(LoadSaves);
|
||||
}
|
||||
|
||||
public void LoadSaves()
|
||||
{
|
||||
Saves.Clear();
|
||||
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account,
|
||||
new UserId((ulong)_userProfile.UserId.High, (ulong)_userProfile.UserId.Low), saveDataId: default, index: default);
|
||||
|
||||
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
|
||||
|
||||
_horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
|
||||
|
||||
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
||||
|
||||
while (true)
|
||||
{
|
||||
saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure();
|
||||
|
||||
if (readCount == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < readCount; i++)
|
||||
{
|
||||
var save = saveDataInfo[i];
|
||||
if (save.ProgramId.Value != 0)
|
||||
{
|
||||
var saveModel = new SaveModel(save, _horizonClient, _virtualFileSystem);
|
||||
Saves.Add(saveModel);
|
||||
saveModel.DeleteAction = () => { Saves.Remove(saveModel); };
|
||||
}
|
||||
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Sort()
|
||||
{
|
||||
Saves.AsObservableChangeSet()
|
||||
.Filter(Filter)
|
||||
.Sort(GetComparer())
|
||||
.Bind(out var view).AsObservableList();
|
||||
|
||||
_view.Clear();
|
||||
_view.AddRange(view);
|
||||
}
|
||||
|
||||
private IComparer<SaveModel> GetComparer()
|
||||
{
|
||||
switch (SortIndex)
|
||||
{
|
||||
case 0:
|
||||
return OrderIndex == 0
|
||||
? SortExpressionComparer<SaveModel>.Ascending(save => save.Title)
|
||||
: SortExpressionComparer<SaveModel>.Descending(save => save.Title);
|
||||
case 1:
|
||||
return OrderIndex == 0
|
||||
? SortExpressionComparer<SaveModel>.Ascending(save => save.Size)
|
||||
: SortExpressionComparer<SaveModel>.Descending(save => save.Size);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool Filter(object arg)
|
||||
{
|
||||
if (arg is SaveModel save)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(_search) || save.Title.ToLower().Contains(_search.ToLower());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,8 @@
|
||||
Title="Ryujinx - Waiting"
|
||||
SizeToContent="WidthAndHeight"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Grid
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
|
@ -10,8 +10,10 @@
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||
Margin="0"
|
||||
MinWidth="500"
|
||||
Padding="0"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<UserControl.Resources>
|
||||
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
</UserControl.Resources>
|
||||
@ -63,7 +65,7 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
MaxLength="{Binding MaxProfileNameLength}"
|
||||
Text="{Binding Name}" />
|
||||
<TextBlock Text="{Locale:Locale UserProfilesUserId}" />
|
||||
<TextBlock Name="IdText" Text="{Locale:Locale UserProfilesUserId}" />
|
||||
<TextBlock Name="IdLabel" Text="{Binding UserId}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
|
@ -36,15 +36,8 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
case NavigationMode.New:
|
||||
var args = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
|
||||
_isNewUser = args.isNewUser;
|
||||
if (!_isNewUser)
|
||||
{
|
||||
_profile = args.profile;
|
||||
TempProfile = new TempProfile(_profile);
|
||||
}
|
||||
else
|
||||
{
|
||||
TempProfile = new TempProfile();
|
||||
}
|
||||
_profile = args.profile;
|
||||
TempProfile = new TempProfile(_profile);
|
||||
|
||||
_parent = args.parent;
|
||||
break;
|
||||
@ -53,7 +46,8 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
DataContext = TempProfile;
|
||||
|
||||
AddPictureButton.IsVisible = _isNewUser;
|
||||
IdLabel.IsVisible = !_isNewUser;
|
||||
IdLabel.IsVisible = _profile != null;
|
||||
IdText.IsVisible = _profile != null;
|
||||
ChangePictureButton.IsVisible = !_isNewUser;
|
||||
}
|
||||
}
|
||||
@ -87,7 +81,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
return;
|
||||
}
|
||||
|
||||
if (_profile != null)
|
||||
if (_profile != null && !_isNewUser)
|
||||
{
|
||||
_profile.Name = TempProfile.Name;
|
||||
_profile.Image = TempProfile.Image;
|
||||
@ -97,7 +91,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
}
|
||||
else if (_isNewUser)
|
||||
{
|
||||
_parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image);
|
||||
_parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image, TempProfile.UserId);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
71
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml
Normal file
71
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml
Normal file
@ -0,0 +1,71 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="800"
|
||||
d:DesignHeight="450"
|
||||
MinWidth="500"
|
||||
MinHeight="400"
|
||||
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.UserRecoverer"
|
||||
Focusable="True">
|
||||
<Design.DataContext>
|
||||
<viewModels:UserProfileViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Button Grid.Row="0"
|
||||
Margin="5"
|
||||
Height="30"
|
||||
Width="50"
|
||||
MinWidth="50"
|
||||
HorizontalAlignment="Left"
|
||||
Command="{Binding GoBack}">
|
||||
<ui:SymbolIcon Symbol="Back"/>
|
||||
</Button>
|
||||
<TextBlock Grid.Row="1"
|
||||
Text="{Locale:Locale UserProfilesRecoverHeading}"/>
|
||||
<ListBox
|
||||
Margin="5"
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Items="{Binding LostProfiles}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5">
|
||||
<Grid Margin="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding UserId}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
<Button Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
Command="{Binding Recover}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="{Locale:Locale Recover}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</UserControl>
|
44
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml.cs
Normal file
44
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using FluentAvalonia.UI.Navigation;
|
||||
using Ryujinx.Ava.Ui.Models;
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Controls
|
||||
{
|
||||
public partial class UserRecoverer : UserControl
|
||||
{
|
||||
private UserProfileViewModel _viewModel;
|
||||
private NavigationDialogHost _parent;
|
||||
|
||||
public UserRecoverer()
|
||||
{
|
||||
InitializeComponent();
|
||||
AddHandler(Frame.NavigatedToEvent, (s, e) =>
|
||||
{
|
||||
NavigatedTo(e);
|
||||
}, RoutingStrategies.Direct);
|
||||
}
|
||||
|
||||
private void NavigatedTo(NavigationEventArgs arg)
|
||||
{
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
switch (arg.NavigationMode)
|
||||
{
|
||||
case NavigationMode.New:
|
||||
var args = ((NavigationDialogHost parent, UserProfileViewModel viewModel))arg.Parameter;
|
||||
|
||||
_viewModel = args.viewModel;
|
||||
_parent = args.parent;
|
||||
break;
|
||||
}
|
||||
|
||||
DataContext = _viewModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -10,8 +10,10 @@
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||
d:DesignHeight="450"
|
||||
MinWidth="500"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<UserControl.Resources>
|
||||
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
</UserControl.Resources>
|
||||
@ -25,6 +27,7 @@
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox
|
||||
Margin="5"
|
||||
MaxHeight="300"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
DoubleTapped="ProfilesList_DoubleTapped"
|
||||
@ -88,21 +91,56 @@
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<StackPanel
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Margin="10,0"
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="10">
|
||||
<Button Command="{Binding AddUser}" Content="{Locale:Locale UserProfilesAddNewProfile}" />
|
||||
HorizontalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="2"
|
||||
Command="{Binding AddUser}"
|
||||
Content="{Locale:Locale UserProfilesAddNewProfile}" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="0"
|
||||
Margin="2"
|
||||
Grid.Column="1"
|
||||
Command="{Binding EditUser}"
|
||||
Content="{Locale:Locale UserProfilesEditProfile}"
|
||||
IsEnabled="{Binding IsSelectedProfiledEditable}" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="2"
|
||||
Content="{Locale:Locale UserProfilesManageSaves}"
|
||||
Command="{Binding ManageSaves}" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="2"
|
||||
Command="{Binding DeleteUser}"
|
||||
Content="{Locale:Locale UserProfilesDeleteSelectedProfile}"
|
||||
IsEnabled="{Binding IsSelectedProfileDeletable}" />
|
||||
</StackPanel>
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Column="0"
|
||||
Margin="2"
|
||||
Command="{Binding RecoverLostAccounts}"
|
||||
Content="{Locale:Locale UserProfilesRecoverLostAccounts}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -3,6 +3,7 @@ using Ryujinx.Ava.Ui.Controls;
|
||||
using Silk.NET.Vulkan;
|
||||
using SPB.Graphics.Vulkan;
|
||||
using SPB.Platform.GLX;
|
||||
using SPB.Platform.Metal;
|
||||
using SPB.Platform.Win32;
|
||||
using SPB.Platform.X11;
|
||||
using SPB.Windowing;
|
||||
@ -37,6 +38,10 @@ namespace Ryujinx.Ava.Ui
|
||||
{
|
||||
_window = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle));
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
_window = new SimpleMetalWindow(new NativeHandle(NsView), new NativeHandle(MetalLayer));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
|
@ -1,45 +0,0 @@
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System.Collections;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Models
|
||||
{
|
||||
internal class FileSizeSortComparer : IComparer
|
||||
{
|
||||
public int Compare(object x, object y)
|
||||
{
|
||||
string aValue = (x as ApplicationData).TimePlayed;
|
||||
string bValue = (y as ApplicationData).TimePlayed;
|
||||
|
||||
if (aValue[^3..] == "GiB")
|
||||
{
|
||||
aValue = (float.Parse(aValue[0..^3]) * 1024).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
aValue = aValue[0..^3];
|
||||
}
|
||||
|
||||
if (bValue[^3..] == "GiB")
|
||||
{
|
||||
bValue = (float.Parse(bValue[0..^3]) * 1024).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
bValue = bValue[0..^3];
|
||||
}
|
||||
|
||||
if (float.Parse(aValue) > float.Parse(bValue))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (float.Parse(bValue) > float.Parse(aValue))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Models.Generic
|
||||
{
|
||||
internal class FileSizeSortComparer : IComparer<ApplicationData>
|
||||
{
|
||||
public FileSizeSortComparer() { }
|
||||
public FileSizeSortComparer(bool isAscending) { _order = isAscending ? 1 : -1; }
|
||||
|
||||
private int _order;
|
||||
|
||||
public int Compare(ApplicationData x, ApplicationData y)
|
||||
{
|
||||
string aValue = x.FileSize;
|
||||
string bValue = y.FileSize;
|
||||
|
||||
if (aValue[^3..] == "GiB")
|
||||
{
|
||||
aValue = (float.Parse(aValue[0..^3]) * 1024).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
aValue = aValue[0..^3];
|
||||
}
|
||||
|
||||
if (bValue[^3..] == "GiB")
|
||||
{
|
||||
bValue = (float.Parse(bValue[0..^3]) * 1024).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
bValue = bValue[0..^3];
|
||||
}
|
||||
|
||||
if (float.Parse(aValue) > float.Parse(bValue))
|
||||
{
|
||||
return -1 * _order;
|
||||
}
|
||||
else if (float.Parse(bValue) > float.Parse(aValue))
|
||||
{
|
||||
return 1 * _order;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Models.Generic
|
||||
{
|
||||
internal class TimePlayedSortComparer : IComparer<ApplicationData>
|
||||
{
|
||||
public TimePlayedSortComparer() { }
|
||||
public TimePlayedSortComparer(bool isAscending) { _order = isAscending ? 1 : -1; }
|
||||
|
||||
private int _order;
|
||||
|
||||
public int Compare(ApplicationData x, ApplicationData y)
|
||||
{
|
||||
string aValue = x.TimePlayed;
|
||||
string bValue = y.TimePlayed;
|
||||
|
||||
if (aValue.Length > 4 && aValue[^4..] == "mins")
|
||||
{
|
||||
aValue = (float.Parse(aValue[0..^5]) * 60).ToString();
|
||||
}
|
||||
else if (aValue.Length > 3 && aValue[^3..] == "hrs")
|
||||
{
|
||||
aValue = (float.Parse(aValue[0..^4]) * 3600).ToString();
|
||||
}
|
||||
else if (aValue.Length > 4 && aValue[^4..] == "days")
|
||||
{
|
||||
aValue = (float.Parse(aValue[0..^5]) * 86400).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
aValue = aValue[0..^1];
|
||||
}
|
||||
|
||||
if (bValue.Length > 4 && bValue[^4..] == "mins")
|
||||
{
|
||||
bValue = (float.Parse(bValue[0..^5]) * 60).ToString();
|
||||
}
|
||||
else if (bValue.Length > 3 && bValue[^3..] == "hrs")
|
||||
{
|
||||
bValue = (float.Parse(bValue[0..^4]) * 3600).ToString();
|
||||
}
|
||||
else if (bValue.Length > 4 && bValue[^4..] == "days")
|
||||
{
|
||||
bValue = (float.Parse(bValue[0..^5]) * 86400).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
bValue = bValue[0..^1];
|
||||
}
|
||||
|
||||
if (float.Parse(aValue) > float.Parse(bValue))
|
||||
{
|
||||
return -1 * _order;
|
||||
}
|
||||
else if (float.Parse(bValue) > float.Parse(aValue))
|
||||
{
|
||||
return 1 * _order;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Models
|
||||
{
|
||||
internal class LastPlayedSortComparer : IComparer
|
||||
{
|
||||
public int Compare(object x, object y)
|
||||
{
|
||||
string aValue = (x as ApplicationData).LastPlayed;
|
||||
string bValue = (y as ApplicationData).LastPlayed;
|
||||
|
||||
if (aValue == "Never")
|
||||
{
|
||||
aValue = DateTime.UnixEpoch.ToString();
|
||||
}
|
||||
|
||||
if (bValue == "Never")
|
||||
{
|
||||
bValue = DateTime.UnixEpoch.ToString();
|
||||
}
|
||||
|
||||
return DateTime.Compare(DateTime.Parse(bValue), DateTime.Parse(aValue));
|
||||
}
|
||||
}
|
||||
}
|
122
Ryujinx.Ava/Ui/Models/SaveModel.cs
Normal file
122
Ryujinx.Ava/Ui/Models/SaveModel.cs
Normal file
@ -0,0 +1,122 @@
|
||||
using LibHac;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Shim;
|
||||
using LibHac.Ncm;
|
||||
using Ryujinx.Ava.Common;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
using Ryujinx.Ava.Ui.Windows;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Models
|
||||
{
|
||||
public class SaveModel : BaseModel
|
||||
{
|
||||
private readonly HorizonClient _horizonClient;
|
||||
private long _size;
|
||||
|
||||
public Action DeleteAction { get; set; }
|
||||
public ulong SaveId { get; }
|
||||
public ProgramId TitleId { get; }
|
||||
public string TitleIdString => $"{TitleId.Value:X16}";
|
||||
public UserId UserId { get; }
|
||||
public bool InGameList { get; }
|
||||
public string Title { get; }
|
||||
public byte[] Icon { get; }
|
||||
|
||||
public long Size
|
||||
{
|
||||
get => _size; set
|
||||
{
|
||||
_size = value;
|
||||
SizeAvailable = true;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(SizeString));
|
||||
OnPropertyChanged(nameof(SizeAvailable));
|
||||
}
|
||||
}
|
||||
|
||||
public bool SizeAvailable { get; set; }
|
||||
|
||||
public string SizeString => $"{((float)_size * 0.000000954):0.###}MB";
|
||||
|
||||
public SaveModel(SaveDataInfo info, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem)
|
||||
{
|
||||
_horizonClient = horizonClient;
|
||||
SaveId = info.SaveDataId;
|
||||
TitleId = info.ProgramId;
|
||||
UserId = info.UserId;
|
||||
|
||||
var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.TitleId.ToUpper() == TitleIdString);
|
||||
|
||||
InGameList = appData != null;
|
||||
|
||||
if (InGameList)
|
||||
{
|
||||
Icon = appData.Icon;
|
||||
Title = appData.TitleName;
|
||||
}
|
||||
else
|
||||
{
|
||||
var appMetadata = MainWindow.MainWindowViewModel.ApplicationLibrary.LoadAndSaveMetaData(TitleIdString);
|
||||
Title = appMetadata.Title ?? TitleIdString;
|
||||
}
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
var saveRoot = System.IO.Path.Combine(virtualFileSystem.GetNandPath(), $"user/save/{info.SaveDataId:x16}");
|
||||
|
||||
long total_size = GetDirectorySize(saveRoot);
|
||||
long GetDirectorySize(string path)
|
||||
{
|
||||
long size = 0;
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
var directories = Directory.GetDirectories(path);
|
||||
foreach (var directory in directories)
|
||||
{
|
||||
size += GetDirectorySize(directory);
|
||||
}
|
||||
|
||||
var files = Directory.GetFiles(path);
|
||||
foreach (var file in files)
|
||||
{
|
||||
size += new FileInfo(file).Length;
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
Size = total_size;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void OpenLocation()
|
||||
{
|
||||
ApplicationHelper.OpenSaveDir(SaveId);
|
||||
}
|
||||
|
||||
public async void Delete()
|
||||
{
|
||||
var result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DeleteUserSave"],
|
||||
LocaleManager.Instance["IrreversibleActionNote"],
|
||||
LocaleManager.Instance["InputDialogYes"],
|
||||
LocaleManager.Instance["InputDialogNo"], "");
|
||||
|
||||
if (result == UserResult.Yes)
|
||||
{
|
||||
_horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, SaveId);
|
||||
|
||||
DeleteAction?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -45,9 +45,12 @@ namespace Ryujinx.Ava.Ui.Models
|
||||
{
|
||||
_profile = profile;
|
||||
|
||||
Image = profile.Image;
|
||||
Name = profile.Name;
|
||||
UserId = profile.UserId;
|
||||
if (_profile != null)
|
||||
{
|
||||
Image = profile.Image;
|
||||
Name = profile.Name;
|
||||
UserId = profile.UserId;
|
||||
}
|
||||
}
|
||||
|
||||
public TempProfile(){}
|
||||
|
@ -1,61 +0,0 @@
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System.Collections;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Models
|
||||
{
|
||||
internal class TimePlayedSortComparer : IComparer
|
||||
{
|
||||
public int Compare(object x, object y)
|
||||
{
|
||||
string aValue = (x as ApplicationData).TimePlayed;
|
||||
string bValue = (y as ApplicationData).TimePlayed;
|
||||
|
||||
if (aValue.Length > 4 && aValue[^4..] == "mins")
|
||||
{
|
||||
aValue = (float.Parse(aValue[0..^5]) * 60).ToString();
|
||||
}
|
||||
else if (aValue.Length > 3 && aValue[^3..] == "hrs")
|
||||
{
|
||||
aValue = (float.Parse(aValue[0..^4]) * 3600).ToString();
|
||||
}
|
||||
else if (aValue.Length > 4 && aValue[^4..] == "days")
|
||||
{
|
||||
aValue = (float.Parse(aValue[0..^5]) * 86400).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
aValue = aValue[0..^1];
|
||||
}
|
||||
|
||||
if (bValue.Length > 4 && bValue[^4..] == "mins")
|
||||
{
|
||||
bValue = (float.Parse(bValue[0..^5]) * 60).ToString();
|
||||
}
|
||||
else if (bValue.Length > 3 && bValue[^3..] == "hrs")
|
||||
{
|
||||
bValue = (float.Parse(bValue[0..^4]) * 3600).ToString();
|
||||
}
|
||||
else if (bValue.Length > 4 && bValue[^4..] == "days")
|
||||
{
|
||||
bValue = (float.Parse(bValue[0..^5]) * 86400).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
bValue = bValue[0..^1];
|
||||
}
|
||||
|
||||
if (float.Parse(aValue) > float.Parse(bValue))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (float.Parse(bValue) > float.Parse(aValue))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Profile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
|
||||
@ -7,6 +8,7 @@ namespace Ryujinx.Ava.Ui.Models
|
||||
public class UserProfile : BaseModel
|
||||
{
|
||||
private readonly Profile _profile;
|
||||
private readonly NavigationDialogHost _owner;
|
||||
private byte[] _image;
|
||||
private string _name;
|
||||
private UserId _userId;
|
||||
@ -41,9 +43,10 @@ namespace Ryujinx.Ava.Ui.Models
|
||||
}
|
||||
}
|
||||
|
||||
public UserProfile(Profile profile)
|
||||
public UserProfile(Profile profile, NavigationDialogHost owner)
|
||||
{
|
||||
_profile = profile;
|
||||
_owner = owner;
|
||||
|
||||
Image = profile.Image;
|
||||
Name = profile.Name;
|
||||
@ -57,5 +60,10 @@ namespace Ryujinx.Ava.Ui.Models
|
||||
OnPropertyChanged(nameof(IsOpened));
|
||||
OnPropertyChanged(nameof(Name));
|
||||
}
|
||||
|
||||
public void Recover(UserProfile userProfile)
|
||||
{
|
||||
_owner.Navigate(typeof(UserEditor), (_owner, userProfile, true));
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user