Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
41bba5310a | |||
8071c8c8c0 | |||
b402b4e7f6 | |||
93df366b2c | |||
cd3a15aea5 | |||
070136b3f7 | |||
08ab47c6c0 | |||
85faa9d8fa | |||
dca5b14493 | |||
4d2c8e2a44 | |||
8fa248ceb4 | |||
30862b5ffd | |||
9f57747c57 | |||
fe29a2ff6e | |||
e9a173e00c | |||
a11784fcbf | |||
fd36c8deca | |||
70638340b3 | |||
4b495f3333 | |||
934b5a64e5 |
185
ARMeilleure/CodeGen/Arm64/HardwareCapabilities.cs
Normal file
185
ARMeilleure/CodeGen/Arm64/HardwareCapabilities.cs
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Intrinsics.Arm;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
|
namespace ARMeilleure.CodeGen.Arm64
|
||||||
|
{
|
||||||
|
static partial class HardwareCapabilities
|
||||||
|
{
|
||||||
|
static HardwareCapabilities()
|
||||||
|
{
|
||||||
|
if (!ArmBase.Arm64.IsSupported)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
LinuxFeatureInfoHwCap = (LinuxFeatureFlagsHwCap)getauxval(AT_HWCAP);
|
||||||
|
LinuxFeatureInfoHwCap2 = (LinuxFeatureFlagsHwCap2)getauxval(AT_HWCAP2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _sysctlNames.Length; i++)
|
||||||
|
{
|
||||||
|
if (CheckSysctlName(_sysctlNames[i]))
|
||||||
|
{
|
||||||
|
MacOsFeatureInfo |= (MacOsFeatureFlags)(1 << i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Linux
|
||||||
|
|
||||||
|
private const ulong AT_HWCAP = 16;
|
||||||
|
private const ulong AT_HWCAP2 = 26;
|
||||||
|
|
||||||
|
[LibraryImport("libc", SetLastError = true)]
|
||||||
|
private static partial ulong getauxval(ulong type);
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum LinuxFeatureFlagsHwCap : ulong
|
||||||
|
{
|
||||||
|
Fp = 1 << 0,
|
||||||
|
Asimd = 1 << 1,
|
||||||
|
Evtstrm = 1 << 2,
|
||||||
|
Aes = 1 << 3,
|
||||||
|
Pmull = 1 << 4,
|
||||||
|
Sha1 = 1 << 5,
|
||||||
|
Sha2 = 1 << 6,
|
||||||
|
Crc32 = 1 << 7,
|
||||||
|
Atomics = 1 << 8,
|
||||||
|
FpHp = 1 << 9,
|
||||||
|
AsimdHp = 1 << 10,
|
||||||
|
CpuId = 1 << 11,
|
||||||
|
AsimdRdm = 1 << 12,
|
||||||
|
Jscvt = 1 << 13,
|
||||||
|
Fcma = 1 << 14,
|
||||||
|
Lrcpc = 1 << 15,
|
||||||
|
DcpOp = 1 << 16,
|
||||||
|
Sha3 = 1 << 17,
|
||||||
|
Sm3 = 1 << 18,
|
||||||
|
Sm4 = 1 << 19,
|
||||||
|
AsimdDp = 1 << 20,
|
||||||
|
Sha512 = 1 << 21,
|
||||||
|
Sve = 1 << 22,
|
||||||
|
AsimdFhm = 1 << 23,
|
||||||
|
Dit = 1 << 24,
|
||||||
|
Uscat = 1 << 25,
|
||||||
|
Ilrcpc = 1 << 26,
|
||||||
|
FlagM = 1 << 27,
|
||||||
|
Ssbs = 1 << 28,
|
||||||
|
Sb = 1 << 29,
|
||||||
|
Paca = 1 << 30,
|
||||||
|
Pacg = 1UL << 31
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum LinuxFeatureFlagsHwCap2 : ulong
|
||||||
|
{
|
||||||
|
Dcpodp = 1 << 0,
|
||||||
|
Sve2 = 1 << 1,
|
||||||
|
SveAes = 1 << 2,
|
||||||
|
SvePmull = 1 << 3,
|
||||||
|
SveBitperm = 1 << 4,
|
||||||
|
SveSha3 = 1 << 5,
|
||||||
|
SveSm4 = 1 << 6,
|
||||||
|
FlagM2 = 1 << 7,
|
||||||
|
Frint = 1 << 8,
|
||||||
|
SveI8mm = 1 << 9,
|
||||||
|
SveF32mm = 1 << 10,
|
||||||
|
SveF64mm = 1 << 11,
|
||||||
|
SveBf16 = 1 << 12,
|
||||||
|
I8mm = 1 << 13,
|
||||||
|
Bf16 = 1 << 14,
|
||||||
|
Dgh = 1 << 15,
|
||||||
|
Rng = 1 << 16,
|
||||||
|
Bti = 1 << 17,
|
||||||
|
Mte = 1 << 18,
|
||||||
|
Ecv = 1 << 19,
|
||||||
|
Afp = 1 << 20,
|
||||||
|
Rpres = 1 << 21,
|
||||||
|
Mte3 = 1 << 22,
|
||||||
|
Sme = 1 << 23,
|
||||||
|
Sme_i16i64 = 1 << 24,
|
||||||
|
Sme_f64f64 = 1 << 25,
|
||||||
|
Sme_i8i32 = 1 << 26,
|
||||||
|
Sme_f16f32 = 1 << 27,
|
||||||
|
Sme_b16f32 = 1 << 28,
|
||||||
|
Sme_f32f32 = 1 << 29,
|
||||||
|
Sme_fa64 = 1 << 30,
|
||||||
|
Wfxt = 1UL << 31,
|
||||||
|
Ebf16 = 1UL << 32,
|
||||||
|
Sve_Ebf16 = 1UL << 33,
|
||||||
|
Cssc = 1UL << 34,
|
||||||
|
Rprfm = 1UL << 35,
|
||||||
|
Sve2p1 = 1UL << 36
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LinuxFeatureFlagsHwCap LinuxFeatureInfoHwCap { get; } = 0;
|
||||||
|
public static LinuxFeatureFlagsHwCap2 LinuxFeatureInfoHwCap2 { get; } = 0;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region macOS
|
||||||
|
|
||||||
|
[LibraryImport("libSystem.dylib", SetLastError = true)]
|
||||||
|
private static unsafe partial int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string name, out int oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize);
|
||||||
|
|
||||||
|
[SupportedOSPlatform("macos")]
|
||||||
|
private static bool CheckSysctlName(string name)
|
||||||
|
{
|
||||||
|
ulong size = sizeof(int);
|
||||||
|
if (sysctlbyname(name, out int val, ref size, IntPtr.Zero, 0) == 0 && size == sizeof(int))
|
||||||
|
{
|
||||||
|
return val != 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] _sysctlNames = new string[]
|
||||||
|
{
|
||||||
|
"hw.optional.floatingpoint",
|
||||||
|
"hw.optional.AdvSIMD",
|
||||||
|
"hw.optional.arm.FEAT_FP16",
|
||||||
|
"hw.optional.arm.FEAT_AES",
|
||||||
|
"hw.optional.arm.FEAT_PMULL",
|
||||||
|
"hw.optional.arm.FEAT_LSE",
|
||||||
|
"hw.optional.armv8_crc32",
|
||||||
|
"hw.optional.arm.FEAT_SHA1",
|
||||||
|
"hw.optional.arm.FEAT_SHA256"
|
||||||
|
};
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum MacOsFeatureFlags
|
||||||
|
{
|
||||||
|
Fp = 1 << 0,
|
||||||
|
AdvSimd = 1 << 1,
|
||||||
|
Fp16 = 1 << 2,
|
||||||
|
Aes = 1 << 3,
|
||||||
|
Pmull = 1 << 4,
|
||||||
|
Lse = 1 << 5,
|
||||||
|
Crc32 = 1 << 6,
|
||||||
|
Sha1 = 1 << 7,
|
||||||
|
Sha256 = 1 << 8
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MacOsFeatureFlags MacOsFeatureInfo { get; } = 0;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public static bool SupportsAdvSimd => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Asimd) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.AdvSimd);
|
||||||
|
public static bool SupportsAes => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Aes) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Aes);
|
||||||
|
public static bool SupportsPmull => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Pmull) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Pmull);
|
||||||
|
public static bool SupportsLse => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Atomics) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Lse);
|
||||||
|
public static bool SupportsCrc32 => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Crc32) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Crc32);
|
||||||
|
public static bool SupportsSha1 => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Sha1) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Sha1);
|
||||||
|
public static bool SupportsSha256 => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Sha2) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Sha256);
|
||||||
|
}
|
||||||
|
}
|
@ -2556,7 +2556,7 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||||
|
|
||||||
if (Optimizations.UseAdvSimd && false) // Not supported by all Arm CPUs.
|
if (Optimizations.UseArm64Pmull)
|
||||||
{
|
{
|
||||||
InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64PmullV);
|
InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64PmullV);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
using ARMeilleure.CodeGen.X86;
|
|
||||||
using System.Runtime.Intrinsics.Arm;
|
using System.Runtime.Intrinsics.Arm;
|
||||||
|
|
||||||
namespace ARMeilleure
|
namespace ARMeilleure
|
||||||
{
|
{
|
||||||
|
using Arm64HardwareCapabilities = ARMeilleure.CodeGen.Arm64.HardwareCapabilities;
|
||||||
|
using X86HardwareCapabilities = ARMeilleure.CodeGen.X86.HardwareCapabilities;
|
||||||
|
|
||||||
public static class Optimizations
|
public static class Optimizations
|
||||||
{
|
{
|
||||||
public static bool FastFP { get; set; } = true;
|
public static bool FastFP { get; set; } = true;
|
||||||
@ -11,6 +13,7 @@ namespace ARMeilleure
|
|||||||
public static bool UseUnmanagedDispatchLoop { get; set; } = true;
|
public static bool UseUnmanagedDispatchLoop { get; set; } = true;
|
||||||
|
|
||||||
public static bool UseAdvSimdIfAvailable { get; set; } = true;
|
public static bool UseAdvSimdIfAvailable { get; set; } = true;
|
||||||
|
public static bool UseArm64PmullIfAvailable { get; set; } = true;
|
||||||
|
|
||||||
public static bool UseSseIfAvailable { get; set; } = true;
|
public static bool UseSseIfAvailable { get; set; } = true;
|
||||||
public static bool UseSse2IfAvailable { get; set; } = true;
|
public static bool UseSse2IfAvailable { get; set; } = true;
|
||||||
@ -29,25 +32,26 @@ namespace ARMeilleure
|
|||||||
|
|
||||||
public static bool ForceLegacySse
|
public static bool ForceLegacySse
|
||||||
{
|
{
|
||||||
get => HardwareCapabilities.ForceLegacySse;
|
get => X86HardwareCapabilities.ForceLegacySse;
|
||||||
set => HardwareCapabilities.ForceLegacySse = value;
|
set => X86HardwareCapabilities.ForceLegacySse = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool UseAdvSimd => UseAdvSimdIfAvailable && AdvSimd.IsSupported;
|
internal static bool UseAdvSimd => UseAdvSimdIfAvailable && Arm64HardwareCapabilities.SupportsAdvSimd;
|
||||||
|
internal static bool UseArm64Pmull => UseArm64PmullIfAvailable && Arm64HardwareCapabilities.SupportsPmull;
|
||||||
|
|
||||||
internal static bool UseSse => UseSseIfAvailable && HardwareCapabilities.SupportsSse;
|
internal static bool UseSse => UseSseIfAvailable && X86HardwareCapabilities.SupportsSse;
|
||||||
internal static bool UseSse2 => UseSse2IfAvailable && HardwareCapabilities.SupportsSse2;
|
internal static bool UseSse2 => UseSse2IfAvailable && X86HardwareCapabilities.SupportsSse2;
|
||||||
internal static bool UseSse3 => UseSse3IfAvailable && HardwareCapabilities.SupportsSse3;
|
internal static bool UseSse3 => UseSse3IfAvailable && X86HardwareCapabilities.SupportsSse3;
|
||||||
internal static bool UseSsse3 => UseSsse3IfAvailable && HardwareCapabilities.SupportsSsse3;
|
internal static bool UseSsse3 => UseSsse3IfAvailable && X86HardwareCapabilities.SupportsSsse3;
|
||||||
internal static bool UseSse41 => UseSse41IfAvailable && HardwareCapabilities.SupportsSse41;
|
internal static bool UseSse41 => UseSse41IfAvailable && X86HardwareCapabilities.SupportsSse41;
|
||||||
internal static bool UseSse42 => UseSse42IfAvailable && HardwareCapabilities.SupportsSse42;
|
internal static bool UseSse42 => UseSse42IfAvailable && X86HardwareCapabilities.SupportsSse42;
|
||||||
internal static bool UsePopCnt => UsePopCntIfAvailable && HardwareCapabilities.SupportsPopcnt;
|
internal static bool UsePopCnt => UsePopCntIfAvailable && X86HardwareCapabilities.SupportsPopcnt;
|
||||||
internal static bool UseAvx => UseAvxIfAvailable && HardwareCapabilities.SupportsAvx && !ForceLegacySse;
|
internal static bool UseAvx => UseAvxIfAvailable && X86HardwareCapabilities.SupportsAvx && !ForceLegacySse;
|
||||||
internal static bool UseF16c => UseF16cIfAvailable && HardwareCapabilities.SupportsF16c;
|
internal static bool UseF16c => UseF16cIfAvailable && X86HardwareCapabilities.SupportsF16c;
|
||||||
internal static bool UseFma => UseFmaIfAvailable && HardwareCapabilities.SupportsFma;
|
internal static bool UseFma => UseFmaIfAvailable && X86HardwareCapabilities.SupportsFma;
|
||||||
internal static bool UseAesni => UseAesniIfAvailable && HardwareCapabilities.SupportsAesni;
|
internal static bool UseAesni => UseAesniIfAvailable && X86HardwareCapabilities.SupportsAesni;
|
||||||
internal static bool UsePclmulqdq => UsePclmulqdqIfAvailable && HardwareCapabilities.SupportsPclmulqdq;
|
internal static bool UsePclmulqdq => UsePclmulqdqIfAvailable && X86HardwareCapabilities.SupportsPclmulqdq;
|
||||||
internal static bool UseSha => UseShaIfAvailable && HardwareCapabilities.SupportsSha;
|
internal static bool UseSha => UseShaIfAvailable && X86HardwareCapabilities.SupportsSha;
|
||||||
internal static bool UseGfni => UseGfniIfAvailable && HardwareCapabilities.SupportsGfni;
|
internal static bool UseGfni => UseGfniIfAvailable && X86HardwareCapabilities.SupportsGfni;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,6 @@
|
|||||||
using ARMeilleure.CodeGen;
|
using ARMeilleure.CodeGen;
|
||||||
using ARMeilleure.CodeGen.Linking;
|
using ARMeilleure.CodeGen.Linking;
|
||||||
using ARMeilleure.CodeGen.Unwinding;
|
using ARMeilleure.CodeGen.Unwinding;
|
||||||
using ARMeilleure.CodeGen.X86;
|
|
||||||
using ARMeilleure.Common;
|
using ARMeilleure.Common;
|
||||||
using ARMeilleure.Memory;
|
using ARMeilleure.Memory;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
@ -22,12 +21,15 @@ using static ARMeilleure.Translation.PTC.PtcFormatter;
|
|||||||
|
|
||||||
namespace ARMeilleure.Translation.PTC
|
namespace ARMeilleure.Translation.PTC
|
||||||
{
|
{
|
||||||
|
using Arm64HardwareCapabilities = ARMeilleure.CodeGen.Arm64.HardwareCapabilities;
|
||||||
|
using X86HardwareCapabilities = ARMeilleure.CodeGen.X86.HardwareCapabilities;
|
||||||
|
|
||||||
class Ptc : IPtcLoadState
|
class Ptc : IPtcLoadState
|
||||||
{
|
{
|
||||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||||
|
|
||||||
private const uint InternalVersion = 4114; //! To be incremented manually for each change to the ARMeilleure project.
|
private const uint InternalVersion = 4272; //! To be incremented manually for each change to the ARMeilleure project.
|
||||||
|
|
||||||
private const string ActualDir = "0";
|
private const string ActualDir = "0";
|
||||||
private const string BackupDir = "1";
|
private const string BackupDir = "1";
|
||||||
@ -259,6 +261,13 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (outerHeader.Architecture != (uint)RuntimeInformation.ProcessArchitecture)
|
||||||
|
{
|
||||||
|
InvalidateCompressedStream(compressedStream);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
IntPtr intPtr = IntPtr.Zero;
|
IntPtr intPtr = IntPtr.Zero;
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -435,6 +444,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
outerHeader.FeatureInfo = GetFeatureInfo();
|
outerHeader.FeatureInfo = GetFeatureInfo();
|
||||||
outerHeader.MemoryManagerMode = GetMemoryManagerMode();
|
outerHeader.MemoryManagerMode = GetMemoryManagerMode();
|
||||||
outerHeader.OSPlatform = GetOSPlatform();
|
outerHeader.OSPlatform = GetOSPlatform();
|
||||||
|
outerHeader.Architecture = (uint)RuntimeInformation.ProcessArchitecture;
|
||||||
|
|
||||||
outerHeader.UncompressedStreamSize =
|
outerHeader.UncompressedStreamSize =
|
||||||
(long)Unsafe.SizeOf<InnerHeader>() +
|
(long)Unsafe.SizeOf<InnerHeader>() +
|
||||||
@ -951,12 +961,27 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static FeatureInfo GetFeatureInfo()
|
private static FeatureInfo GetFeatureInfo()
|
||||||
|
{
|
||||||
|
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||||
{
|
{
|
||||||
return new FeatureInfo(
|
return new FeatureInfo(
|
||||||
(uint)HardwareCapabilities.FeatureInfo1Ecx,
|
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap,
|
||||||
(uint)HardwareCapabilities.FeatureInfo1Edx,
|
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap2,
|
||||||
(uint)HardwareCapabilities.FeatureInfo7Ebx,
|
(ulong)Arm64HardwareCapabilities.MacOsFeatureInfo,
|
||||||
(uint)HardwareCapabilities.FeatureInfo7Ecx);
|
0);
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
||||||
|
{
|
||||||
|
return new FeatureInfo(
|
||||||
|
(ulong)X86HardwareCapabilities.FeatureInfo1Ecx,
|
||||||
|
(ulong)X86HardwareCapabilities.FeatureInfo1Edx,
|
||||||
|
(ulong)X86HardwareCapabilities.FeatureInfo7Ebx,
|
||||||
|
(ulong)X86HardwareCapabilities.FeatureInfo7Ecx);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new FeatureInfo(0, 0, 0, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte GetMemoryManagerMode()
|
private byte GetMemoryManagerMode()
|
||||||
@ -976,7 +1001,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
return osPlatform;
|
return osPlatform;
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 58*/)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 78*/)]
|
||||||
private struct OuterHeader
|
private struct OuterHeader
|
||||||
{
|
{
|
||||||
public ulong Magic;
|
public ulong Magic;
|
||||||
@ -987,6 +1012,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
public FeatureInfo FeatureInfo;
|
public FeatureInfo FeatureInfo;
|
||||||
public byte MemoryManagerMode;
|
public byte MemoryManagerMode;
|
||||||
public uint OSPlatform;
|
public uint OSPlatform;
|
||||||
|
public uint Architecture;
|
||||||
|
|
||||||
public long UncompressedStreamSize;
|
public long UncompressedStreamSize;
|
||||||
|
|
||||||
@ -1007,8 +1033,8 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 16*/)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 32*/)]
|
||||||
private record struct FeatureInfo(uint FeatureInfo0, uint FeatureInfo1, uint FeatureInfo2, uint FeatureInfo3);
|
private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3);
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 128*/)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 128*/)]
|
||||||
private struct InnerHeader
|
private struct InnerHeader
|
||||||
|
@ -40,6 +40,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
info.InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]);
|
info.InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (info.BufferStates?.Length != (int)inputCount)
|
||||||
|
{
|
||||||
|
// Keep state if possible.
|
||||||
|
info.BufferStates = new UpsamplerBufferState[(int)inputCount];
|
||||||
|
}
|
||||||
|
|
||||||
UpsamplerInfo = info;
|
UpsamplerInfo = info;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,8 +56,6 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public void Process(CommandList context)
|
public void Process(CommandList context)
|
||||||
{
|
{
|
||||||
float ratio = (float)InputSampleRate / Constants.TargetSampleRate;
|
|
||||||
|
|
||||||
uint bufferCount = Math.Min(BufferCount, UpsamplerInfo.SourceSampleCount);
|
uint bufferCount = Math.Min(BufferCount, UpsamplerInfo.SourceSampleCount);
|
||||||
|
|
||||||
for (int i = 0; i < bufferCount; i++)
|
for (int i = 0; i < bufferCount; i++)
|
||||||
@ -59,9 +63,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
Span<float> inputBuffer = context.GetBuffer(UpsamplerInfo.InputBufferIndices[i]);
|
Span<float> inputBuffer = context.GetBuffer(UpsamplerInfo.InputBufferIndices[i]);
|
||||||
Span<float> outputBuffer = GetBuffer(UpsamplerInfo.InputBufferIndices[i], (int)UpsamplerInfo.SampleCount);
|
Span<float> outputBuffer = GetBuffer(UpsamplerInfo.InputBufferIndices[i], (int)UpsamplerInfo.SampleCount);
|
||||||
|
|
||||||
float fraction = 0.0f;
|
UpsamplerHelper.Upsample(outputBuffer, inputBuffer, (int)UpsamplerInfo.SampleCount, (int)InputSampleCount, ref UpsamplerInfo.BufferStates[i]);
|
||||||
|
|
||||||
ResamplerHelper.ResampleForUpsampler(outputBuffer, inputBuffer, ratio, ref fraction, (int)(InputSampleCount / ratio));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -579,52 +579,5 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||||||
fraction -= (int)fraction;
|
fraction -= (int)fraction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static void ResampleForUpsampler(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, float ratio, ref float fraction, int sampleCount)
|
|
||||||
{
|
|
||||||
// Currently a simple cubic interpolation, assuming duplicated values at edges.
|
|
||||||
// TODO: Discover and use algorithm that the switch uses.
|
|
||||||
|
|
||||||
int inputBufferIndex = 0;
|
|
||||||
int maxIndex = inputBuffer.Length - 1;
|
|
||||||
int cubicEnd = inputBuffer.Length - 3;
|
|
||||||
|
|
||||||
for (int i = 0; i < sampleCount; i++)
|
|
||||||
{
|
|
||||||
float s0, s1, s2, s3;
|
|
||||||
|
|
||||||
s1 = inputBuffer[inputBufferIndex];
|
|
||||||
|
|
||||||
if (inputBufferIndex == 0 || inputBufferIndex > cubicEnd)
|
|
||||||
{
|
|
||||||
// Clamp interplation values at the ends of the input buffer.
|
|
||||||
s0 = inputBuffer[Math.Max(0, inputBufferIndex - 1)];
|
|
||||||
s2 = inputBuffer[Math.Min(maxIndex, inputBufferIndex + 1)];
|
|
||||||
s3 = inputBuffer[Math.Min(maxIndex, inputBufferIndex + 2)];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
s0 = inputBuffer[inputBufferIndex - 1];
|
|
||||||
s2 = inputBuffer[inputBufferIndex + 1];
|
|
||||||
s3 = inputBuffer[inputBufferIndex + 2];
|
|
||||||
}
|
|
||||||
|
|
||||||
float a = s3 - s2 - s0 + s1;
|
|
||||||
float b = s0 - s1 - a;
|
|
||||||
float c = s2 - s0;
|
|
||||||
float d = s1;
|
|
||||||
|
|
||||||
float f2 = fraction * fraction;
|
|
||||||
float f3 = f2 * fraction;
|
|
||||||
|
|
||||||
outputBuffer[i] = a * f3 + b * f2 + c * fraction + d;
|
|
||||||
|
|
||||||
fraction += ratio;
|
|
||||||
inputBufferIndex += (int)MathF.Truncate(fraction);
|
|
||||||
|
|
||||||
fraction -= (int)fraction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
175
Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs
Normal file
175
Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
using Ryujinx.Audio.Renderer.Server.Upsampler;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Audio.Renderer.Dsp
|
||||||
|
{
|
||||||
|
public class UpsamplerHelper
|
||||||
|
{
|
||||||
|
private const int HistoryLength = UpsamplerBufferState.HistoryLength;
|
||||||
|
private const int FilterBankLength = 20;
|
||||||
|
// Bank0 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||||
|
private const int Bank0CenterIndex = 9;
|
||||||
|
private static readonly Array20<float> Bank1 = PrecomputeFilterBank(1.0f / 6.0f);
|
||||||
|
private static readonly Array20<float> Bank2 = PrecomputeFilterBank(2.0f / 6.0f);
|
||||||
|
private static readonly Array20<float> Bank3 = PrecomputeFilterBank(3.0f / 6.0f);
|
||||||
|
private static readonly Array20<float> Bank4 = PrecomputeFilterBank(4.0f / 6.0f);
|
||||||
|
private static readonly Array20<float> Bank5 = PrecomputeFilterBank(5.0f / 6.0f);
|
||||||
|
|
||||||
|
private static Array20<float> PrecomputeFilterBank(float offset)
|
||||||
|
{
|
||||||
|
float Sinc(float x)
|
||||||
|
{
|
||||||
|
if (x == 0)
|
||||||
|
{
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
return (MathF.Sin(MathF.PI * x) / (MathF.PI * x));
|
||||||
|
}
|
||||||
|
|
||||||
|
float BlackmanWindow(float x)
|
||||||
|
{
|
||||||
|
const float a = 0.18f;
|
||||||
|
const float a0 = 0.5f - 0.5f * a;
|
||||||
|
const float a1 = -0.5f;
|
||||||
|
const float a2 = 0.5f * a;
|
||||||
|
return a0 + a1 * MathF.Cos(2 * MathF.PI * x) + a2 * MathF.Cos(4 * MathF.PI * x);
|
||||||
|
}
|
||||||
|
|
||||||
|
Array20<float> result = new Array20<float>();
|
||||||
|
|
||||||
|
for (int i = 0; i < FilterBankLength; i++)
|
||||||
|
{
|
||||||
|
float x = (Bank0CenterIndex - i) + offset;
|
||||||
|
result[i] = Sinc(x) * BlackmanWindow(x / FilterBankLength + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyphase upsampling algorithm
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void Upsample(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, int outputSampleCount, int inputSampleCount, ref UpsamplerBufferState state)
|
||||||
|
{
|
||||||
|
if (!state.Initialized)
|
||||||
|
{
|
||||||
|
state.Scale = inputSampleCount switch
|
||||||
|
{
|
||||||
|
40 => 6.0f,
|
||||||
|
80 => 3.0f,
|
||||||
|
160 => 1.5f,
|
||||||
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
|
};
|
||||||
|
state.Initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputSampleCount == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
float DoFilterBank(ref UpsamplerBufferState state, in Array20<float> bank)
|
||||||
|
{
|
||||||
|
float result = 0.0f;
|
||||||
|
|
||||||
|
Debug.Assert(state.History.Length == HistoryLength);
|
||||||
|
Debug.Assert(bank.Length == FilterBankLength);
|
||||||
|
for (int j = 0; j < FilterBankLength; j++)
|
||||||
|
{
|
||||||
|
result += bank[j] * state.History[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
void NextInput(ref UpsamplerBufferState state, float input)
|
||||||
|
{
|
||||||
|
state.History.AsSpan().Slice(1).CopyTo(state.History.AsSpan());
|
||||||
|
state.History[HistoryLength - 1] = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
int inputBufferIndex = 0;
|
||||||
|
|
||||||
|
switch (state.Scale)
|
||||||
|
{
|
||||||
|
case 6.0f:
|
||||||
|
for (int i = 0; i < outputSampleCount; i++)
|
||||||
|
{
|
||||||
|
switch (state.Phase)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
NextInput(ref state, inputBuffer[inputBufferIndex++]);
|
||||||
|
outputBuffer[i] = state.History[Bank0CenterIndex];
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
outputBuffer[i] = DoFilterBank(ref state, Bank1);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
outputBuffer[i] = DoFilterBank(ref state, Bank2);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
outputBuffer[i] = DoFilterBank(ref state, Bank3);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
outputBuffer[i] = DoFilterBank(ref state, Bank4);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
outputBuffer[i] = DoFilterBank(ref state, Bank5);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Phase = (state.Phase + 1) % 6;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3.0f:
|
||||||
|
for (int i = 0; i < outputSampleCount; i++)
|
||||||
|
{
|
||||||
|
switch (state.Phase)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
NextInput(ref state, inputBuffer[inputBufferIndex++]);
|
||||||
|
outputBuffer[i] = state.History[Bank0CenterIndex];
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
outputBuffer[i] = DoFilterBank(ref state, Bank2);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
outputBuffer[i] = DoFilterBank(ref state, Bank4);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Phase = (state.Phase + 1) % 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1.5f:
|
||||||
|
// Upsample by 3 then decimate by 2.
|
||||||
|
for (int i = 0; i < outputSampleCount; i++)
|
||||||
|
{
|
||||||
|
switch (state.Phase)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
NextInput(ref state, inputBuffer[inputBufferIndex++]);
|
||||||
|
outputBuffer[i] = state.History[Bank0CenterIndex];
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
outputBuffer[i] = DoFilterBank(ref state, Bank4);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
NextInput(ref state, inputBuffer[inputBufferIndex++]);
|
||||||
|
outputBuffer[i] = DoFilterBank(ref state, Bank2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Phase = (state.Phase + 1) % 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
|
||||||
|
namespace Ryujinx.Audio.Renderer.Server.Upsampler
|
||||||
|
{
|
||||||
|
public struct UpsamplerBufferState
|
||||||
|
{
|
||||||
|
public const int HistoryLength = 20;
|
||||||
|
|
||||||
|
public float Scale;
|
||||||
|
public Array20<float> History;
|
||||||
|
public bool Initialized;
|
||||||
|
public int Phase;
|
||||||
|
}
|
||||||
|
}
|
@ -37,6 +37,11 @@ namespace Ryujinx.Audio.Renderer.Server.Upsampler
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort[] InputBufferIndices;
|
public ushort[] InputBufferIndices;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// State of each input buffer index kept across invocations of the upsampler.
|
||||||
|
/// </summary>
|
||||||
|
public UpsamplerBufferState[] BufferStates;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="UpsamplerState"/>.
|
/// Create a new <see cref="UpsamplerState"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -44,8 +44,10 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
||||||
using Image = SixLabors.ImageSharp.Image;
|
using Image = SixLabors.ImageSharp.Image;
|
||||||
using InputManager = Ryujinx.Input.HLE.InputManager;
|
using InputManager = Ryujinx.Input.HLE.InputManager;
|
||||||
using Key = Ryujinx.Input.Key;
|
using Key = Ryujinx.Input.Key;
|
||||||
@ -58,12 +60,14 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
internal class AppHost
|
internal class AppHost
|
||||||
{
|
{
|
||||||
private const int CursorHideIdleTime = 8; // Hide Cursor seconds.
|
private const int CursorHideIdleTime = 5; // Hide Cursor seconds.
|
||||||
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
|
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
|
||||||
private const int TargetFps = 60;
|
private const int TargetFps = 60;
|
||||||
private const float VolumeDelta = 0.05f;
|
private const float VolumeDelta = 0.05f;
|
||||||
|
|
||||||
private static readonly Cursor InvisibleCursor = new(StandardCursorType.None);
|
private static readonly Cursor InvisibleCursor = new(StandardCursorType.None);
|
||||||
|
private readonly IntPtr InvisibleCursorWin;
|
||||||
|
private readonly IntPtr DefaultCursorWin;
|
||||||
|
|
||||||
private readonly long _ticksPerFrame;
|
private readonly long _ticksPerFrame;
|
||||||
private readonly Stopwatch _chrono;
|
private readonly Stopwatch _chrono;
|
||||||
@ -81,7 +85,6 @@ namespace Ryujinx.Ava
|
|||||||
private float _newVolume;
|
private float _newVolume;
|
||||||
private KeyboardHotkeyState _prevHotkeyState;
|
private KeyboardHotkeyState _prevHotkeyState;
|
||||||
|
|
||||||
private bool _hideCursorOnIdle;
|
|
||||||
private long _lastCursorMoveTime;
|
private long _lastCursorMoveTime;
|
||||||
private bool _isCursorInRenderer;
|
private bool _isCursorInRenderer;
|
||||||
|
|
||||||
@ -131,7 +134,6 @@ namespace Ryujinx.Ava
|
|||||||
_accountManager = accountManager;
|
_accountManager = accountManager;
|
||||||
_userChannelPersistence = userChannelPersistence;
|
_userChannelPersistence = userChannelPersistence;
|
||||||
_renderingThread = new Thread(RenderLoop, 1 * 1024 * 1024) { Name = "GUI.RenderThread" };
|
_renderingThread = new Thread(RenderLoop, 1 * 1024 * 1024) { Name = "GUI.RenderThread" };
|
||||||
_hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
|
|
||||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||||
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
|
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
|
||||||
_topLevel = topLevel;
|
_topLevel = topLevel;
|
||||||
@ -159,9 +161,14 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorState_Changed;
|
ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorState_Changed;
|
||||||
|
|
||||||
_topLevel.PointerLeave += TopLevel_PointerLeave;
|
|
||||||
_topLevel.PointerMoved += TopLevel_PointerMoved;
|
_topLevel.PointerMoved += TopLevel_PointerMoved;
|
||||||
|
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
InvisibleCursorWin = CreateEmptyCursor();
|
||||||
|
DefaultCursorWin = CreateArrowCursor();
|
||||||
|
}
|
||||||
|
|
||||||
ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState;
|
ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState;
|
||||||
ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState;
|
ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState;
|
||||||
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
|
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
|
||||||
@ -172,20 +179,47 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
private void TopLevel_PointerMoved(object sender, PointerEventArgs e)
|
private void TopLevel_PointerMoved(object sender, PointerEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is Control visual)
|
if (sender is MainWindow window)
|
||||||
{
|
{
|
||||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||||
|
|
||||||
var point = e.GetCurrentPoint(visual).Position;
|
if ((Renderer.Content as EmbeddedWindow).TransformedBounds != null)
|
||||||
|
{
|
||||||
_isCursorInRenderer = Equals(visual.InputHitTest(point), Renderer);
|
var point = e.GetCurrentPoint(window).Position;
|
||||||
}
|
var bounds = (Renderer.Content as EmbeddedWindow).TransformedBounds.Value.Clip;
|
||||||
}
|
|
||||||
|
_isCursorInRenderer = point.X >= bounds.X &&
|
||||||
private void TopLevel_PointerLeave(object sender, PointerEventArgs e)
|
point.X <= bounds.Width + bounds.X &&
|
||||||
|
point.Y >= bounds.Y &&
|
||||||
|
point.Y <= bounds.Height + bounds.Y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowCursor()
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
_isCursorInRenderer = false;
|
|
||||||
_viewModel.Cursor = Cursor.Default;
|
_viewModel.Cursor = Cursor.Default;
|
||||||
|
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
SetCursor(DefaultCursorWin);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HideCursor()
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
_viewModel.Cursor = InvisibleCursor;
|
||||||
|
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
SetCursor(InvisibleCursorWin);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetRendererWindowSize(Size size)
|
private void SetRendererWindowSize(Size size)
|
||||||
@ -380,7 +414,6 @@ namespace Ryujinx.Ava
|
|||||||
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
|
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
|
||||||
ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState;
|
ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState;
|
||||||
|
|
||||||
_topLevel.PointerLeave -= TopLevel_PointerLeave;
|
|
||||||
_topLevel.PointerMoved -= TopLevel_PointerMoved;
|
_topLevel.PointerMoved -= TopLevel_PointerMoved;
|
||||||
|
|
||||||
_gpuCancellationTokenSource.Cancel();
|
_gpuCancellationTokenSource.Cancel();
|
||||||
@ -406,19 +439,10 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
private void HideCursorState_Changed(object sender, ReactiveEventArgs<bool> state)
|
private void HideCursorState_Changed(object sender, ReactiveEventArgs<bool> state)
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.InvokeAsync(delegate
|
if (state.NewValue)
|
||||||
{
|
|
||||||
_hideCursorOnIdle = state.NewValue;
|
|
||||||
|
|
||||||
if (_hideCursorOnIdle)
|
|
||||||
{
|
{
|
||||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
_viewModel.Cursor = Cursor.Default;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> LoadGuestApplication()
|
public async Task<bool> LoadGuestApplication()
|
||||||
@ -860,29 +884,6 @@ namespace Ryujinx.Ava
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleScreenState()
|
|
||||||
{
|
|
||||||
if (ConfigurationState.Instance.Hid.EnableMouse)
|
|
||||||
{
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
{
|
|
||||||
_viewModel.Cursor = _isCursorInRenderer ? InvisibleCursor : Cursor.Default;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_hideCursorOnIdle)
|
|
||||||
{
|
|
||||||
long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime;
|
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
{
|
|
||||||
_viewModel.Cursor = cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency ? InvisibleCursor : Cursor.Default;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool UpdateFrame()
|
private bool UpdateFrame()
|
||||||
{
|
{
|
||||||
if (!_isActive)
|
if (!_isActive)
|
||||||
@ -890,23 +891,44 @@ namespace Ryujinx.Ava
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
|
||||||
|
|
||||||
if (_viewModel.IsActive)
|
if (_viewModel.IsActive)
|
||||||
{
|
{
|
||||||
|
if (ConfigurationState.Instance.Hid.EnableMouse)
|
||||||
|
{
|
||||||
|
if (_isCursorInRenderer)
|
||||||
|
{
|
||||||
|
HideCursor();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ShowCursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ConfigurationState.Instance.HideCursorOnIdle)
|
||||||
|
{
|
||||||
|
if (Stopwatch.GetTimestamp() - _lastCursorMoveTime >= CursorHideIdleTime * Stopwatch.Frequency)
|
||||||
|
{
|
||||||
|
HideCursor();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ShowCursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
HandleScreenState();
|
|
||||||
|
|
||||||
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
|
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
|
||||||
{
|
{
|
||||||
Device.Application.DiskCacheLoadState?.Cancel();
|
Device.Application.DiskCacheLoadState?.Cancel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
|
|
||||||
|
|
||||||
if (_viewModel.IsActive)
|
|
||||||
{
|
|
||||||
KeyboardHotkeyState currentHotkeyState = GetHotkeyState();
|
KeyboardHotkeyState currentHotkeyState = GetHotkeyState();
|
||||||
|
|
||||||
if (currentHotkeyState != _prevHotkeyState)
|
if (currentHotkeyState != _prevHotkeyState)
|
||||||
|
@ -260,7 +260,7 @@
|
|||||||
"UserProfilesChangeProfileImage": "Profilbild ändern",
|
"UserProfilesChangeProfileImage": "Profilbild ändern",
|
||||||
"UserProfilesAvailableUserProfiles": "Verfügbare Profile:",
|
"UserProfilesAvailableUserProfiles": "Verfügbare Profile:",
|
||||||
"UserProfilesAddNewProfile": "Neues Profil",
|
"UserProfilesAddNewProfile": "Neues Profil",
|
||||||
"UserProfilesDeleteSelectedProfile": "Profil löschen",
|
"UserProfilesDelete": "Löschen",
|
||||||
"UserProfilesClose": "Schließen",
|
"UserProfilesClose": "Schließen",
|
||||||
"ProfileImageSelectionTitle": "Auswahl des Profilbildes",
|
"ProfileImageSelectionTitle": "Auswahl des Profilbildes",
|
||||||
"ProfileImageSelectionHeader": "Wähle ein Profilbild aus",
|
"ProfileImageSelectionHeader": "Wähle ein Profilbild aus",
|
||||||
|
@ -260,7 +260,7 @@
|
|||||||
"UserProfilesChangeProfileImage": "Αλλαγή Εικόνας Προφίλ",
|
"UserProfilesChangeProfileImage": "Αλλαγή Εικόνας Προφίλ",
|
||||||
"UserProfilesAvailableUserProfiles": "Διαθέσιμα Προφίλ Χρηστών:",
|
"UserProfilesAvailableUserProfiles": "Διαθέσιμα Προφίλ Χρηστών:",
|
||||||
"UserProfilesAddNewProfile": "Προσθήκη Νέου Προφίλ",
|
"UserProfilesAddNewProfile": "Προσθήκη Νέου Προφίλ",
|
||||||
"UserProfilesDeleteSelectedProfile": "Διαγραφή Επιλεγμένου Προφίλ",
|
"UserProfilesDelete": "Διαγράφω",
|
||||||
"UserProfilesClose": "Κλείσιμο",
|
"UserProfilesClose": "Κλείσιμο",
|
||||||
"ProfileImageSelectionTitle": "Επιλογή Εικόνας Προφίλ",
|
"ProfileImageSelectionTitle": "Επιλογή Εικόνας Προφίλ",
|
||||||
"ProfileImageSelectionHeader": "Επιλέξτε μία Εικόνα Προφίλ",
|
"ProfileImageSelectionHeader": "Επιλέξτε μία Εικόνα Προφίλ",
|
||||||
|
@ -119,7 +119,7 @@
|
|||||||
"SettingsTabSystemAudioBackendSoundIO": "SoundIO",
|
"SettingsTabSystemAudioBackendSoundIO": "SoundIO",
|
||||||
"SettingsTabSystemAudioBackendSDL2": "SDL2",
|
"SettingsTabSystemAudioBackendSDL2": "SDL2",
|
||||||
"SettingsTabSystemHacks": "Hacks",
|
"SettingsTabSystemHacks": "Hacks",
|
||||||
"SettingsTabSystemHacksNote": " (may cause instability)",
|
"SettingsTabSystemHacksNote": "May cause instability",
|
||||||
"SettingsTabSystemExpandDramSize": "Use alternative memory layout (Developers)",
|
"SettingsTabSystemExpandDramSize": "Use alternative memory layout (Developers)",
|
||||||
"SettingsTabSystemIgnoreMissingServices": "Ignore Missing Services",
|
"SettingsTabSystemIgnoreMissingServices": "Ignore Missing Services",
|
||||||
"SettingsTabGraphics": "Graphics",
|
"SettingsTabGraphics": "Graphics",
|
||||||
@ -157,7 +157,8 @@
|
|||||||
"SettingsTabLoggingEnableGuestLogs": "Enable Guest Logs",
|
"SettingsTabLoggingEnableGuestLogs": "Enable Guest Logs",
|
||||||
"SettingsTabLoggingEnableFsAccessLogs": "Enable Fs Access Logs",
|
"SettingsTabLoggingEnableFsAccessLogs": "Enable Fs Access Logs",
|
||||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Fs Global Access Log Mode:",
|
"SettingsTabLoggingFsGlobalAccessLogMode": "Fs Global Access Log Mode:",
|
||||||
"SettingsTabLoggingDeveloperOptions": "Developer Options (WARNING: Will reduce performance)",
|
"SettingsTabLoggingDeveloperOptions": "Developer Options",
|
||||||
|
"SettingsTabLoggingDeveloperOptionsNote": "WARNING: Will reduce performance",
|
||||||
"SettingsTabLoggingGraphicsBackendLogLevel": "Graphics Backend Log Level:",
|
"SettingsTabLoggingGraphicsBackendLogLevel": "Graphics Backend Log Level:",
|
||||||
"SettingsTabLoggingGraphicsBackendLogLevelNone": "None",
|
"SettingsTabLoggingGraphicsBackendLogLevelNone": "None",
|
||||||
"SettingsTabLoggingGraphicsBackendLogLevelError": "Error",
|
"SettingsTabLoggingGraphicsBackendLogLevelError": "Error",
|
||||||
@ -260,8 +261,9 @@
|
|||||||
"UserProfilesChangeProfileImage": "Change Profile Image",
|
"UserProfilesChangeProfileImage": "Change Profile Image",
|
||||||
"UserProfilesAvailableUserProfiles": "Available User Profiles:",
|
"UserProfilesAvailableUserProfiles": "Available User Profiles:",
|
||||||
"UserProfilesAddNewProfile": "Create Profile",
|
"UserProfilesAddNewProfile": "Create Profile",
|
||||||
"UserProfilesDeleteSelectedProfile": "Delete Selected",
|
"UserProfilesDelete": "Delete",
|
||||||
"UserProfilesClose": "Close",
|
"UserProfilesClose": "Close",
|
||||||
|
"ProfileNameSelectionWatermark": "Choose a nickname",
|
||||||
"ProfileImageSelectionTitle": "Profile Image Selection",
|
"ProfileImageSelectionTitle": "Profile Image Selection",
|
||||||
"ProfileImageSelectionHeader": "Choose a profile Image",
|
"ProfileImageSelectionHeader": "Choose a profile Image",
|
||||||
"ProfileImageSelectionNote": "You may import a custom profile image, or select an avatar from system firmware",
|
"ProfileImageSelectionNote": "You may import a custom profile image, or select an avatar from system firmware",
|
||||||
@ -273,7 +275,7 @@
|
|||||||
"InputDialogAddNewProfileTitle": "Choose the Profile Name",
|
"InputDialogAddNewProfileTitle": "Choose the Profile Name",
|
||||||
"InputDialogAddNewProfileHeader": "Please Enter a Profile Name",
|
"InputDialogAddNewProfileHeader": "Please Enter a Profile Name",
|
||||||
"InputDialogAddNewProfileSubtext": "(Max Length: {0})",
|
"InputDialogAddNewProfileSubtext": "(Max Length: {0})",
|
||||||
"AvatarChoose": "Choose",
|
"AvatarChoose": "Choose Avatar",
|
||||||
"AvatarSetBackgroundColor": "Set Background Color",
|
"AvatarSetBackgroundColor": "Set Background Color",
|
||||||
"AvatarClose": "Close",
|
"AvatarClose": "Close",
|
||||||
"ControllerSettingsLoadProfileToolTip": "Load Profile",
|
"ControllerSettingsLoadProfileToolTip": "Load Profile",
|
||||||
@ -368,6 +370,9 @@
|
|||||||
"DialogFirmwareInstallerFirmwareInstallSuccessMessage": "System version {0} successfully installed.",
|
"DialogFirmwareInstallerFirmwareInstallSuccessMessage": "System version {0} successfully installed.",
|
||||||
"DialogUserProfileDeletionWarningMessage": "There would be no other profiles to be opened if selected profile is deleted",
|
"DialogUserProfileDeletionWarningMessage": "There would be no other profiles to be opened if selected profile is deleted",
|
||||||
"DialogUserProfileDeletionConfirmMessage": "Do you want to delete the selected profile",
|
"DialogUserProfileDeletionConfirmMessage": "Do you want to delete the selected profile",
|
||||||
|
"DialogUserProfileUnsavedChangesTitle": "Warning - Unsaved Changes",
|
||||||
|
"DialogUserProfileUnsavedChangesMessage": "You have made changes to this user profile that have not been saved.",
|
||||||
|
"DialogUserProfileUnsavedChangesSubMessage": "Do you want to discard your changes?",
|
||||||
"DialogControllerSettingsModifiedConfirmMessage": "The current controller settings has been updated.",
|
"DialogControllerSettingsModifiedConfirmMessage": "The current controller settings has been updated.",
|
||||||
"DialogControllerSettingsModifiedConfirmSubMessage": "Do you want to save?",
|
"DialogControllerSettingsModifiedConfirmSubMessage": "Do you want to save?",
|
||||||
"DialogDlcLoadNcaErrorMessage": "{0}. Errored File: {1}",
|
"DialogDlcLoadNcaErrorMessage": "{0}. Errored File: {1}",
|
||||||
@ -584,7 +589,7 @@
|
|||||||
"SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:",
|
"SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:",
|
||||||
"SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:",
|
"SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:",
|
||||||
"UserProfilesName": "Name:",
|
"UserProfilesName": "Name:",
|
||||||
"UserProfilesUserId": "User Id:",
|
"UserProfilesUserId": "User ID:",
|
||||||
"SettingsTabGraphicsBackend": "Graphics Backend",
|
"SettingsTabGraphicsBackend": "Graphics Backend",
|
||||||
"SettingsTabGraphicsBackendTooltip": "Graphics Backend to use",
|
"SettingsTabGraphicsBackendTooltip": "Graphics Backend to use",
|
||||||
"SettingsEnableTextureRecompression": "Enable Texture Recompression",
|
"SettingsEnableTextureRecompression": "Enable Texture Recompression",
|
||||||
@ -603,13 +608,15 @@
|
|||||||
"UserProfilesManageSaves": "Manage Saves",
|
"UserProfilesManageSaves": "Manage Saves",
|
||||||
"DeleteUserSave": "Do you want to delete user save for this game?",
|
"DeleteUserSave": "Do you want to delete user save for this game?",
|
||||||
"IrreversibleActionNote": "This action is not reversible.",
|
"IrreversibleActionNote": "This action is not reversible.",
|
||||||
"SaveManagerHeading": "Manage Saves for {0}",
|
"SaveManagerHeading": "Manage Saves for {0} ({1})",
|
||||||
"SaveManagerTitle": "Save Manager",
|
"SaveManagerTitle": "Save Manager",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"Size": "Size",
|
"Size": "Size",
|
||||||
"Search": "Search",
|
"Search": "Search",
|
||||||
"UserProfilesRecoverLostAccounts": "Recover Lost Accounts",
|
"UserProfilesRecoverLostAccounts": "Recover Lost Accounts",
|
||||||
"Recover": "Recover",
|
"Recover": "Recover",
|
||||||
"UserProfilesRecoverHeading" : "Saves were found for the following accounts"
|
"UserProfilesRecoverHeading" : "Saves were found for the following accounts",
|
||||||
|
"UserProfilesRecoverEmptyList": "No profiles to recover",
|
||||||
|
"UserEditorTitle" : "Edit User",
|
||||||
|
"UserEditorTitleCreate" : "Create User"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,7 +260,7 @@
|
|||||||
"UserProfilesChangeProfileImage": "Cambiar imagen de perfil",
|
"UserProfilesChangeProfileImage": "Cambiar imagen de perfil",
|
||||||
"UserProfilesAvailableUserProfiles": "Perfiles de usuario disponibles:",
|
"UserProfilesAvailableUserProfiles": "Perfiles de usuario disponibles:",
|
||||||
"UserProfilesAddNewProfile": "Añadir nuevo perfil",
|
"UserProfilesAddNewProfile": "Añadir nuevo perfil",
|
||||||
"UserProfilesDeleteSelectedProfile": "Eliminar perfil seleccionado",
|
"UserProfilesDelete": "Eliminar",
|
||||||
"UserProfilesClose": "Cerrar",
|
"UserProfilesClose": "Cerrar",
|
||||||
"ProfileImageSelectionTitle": "Selección de imagen de perfil",
|
"ProfileImageSelectionTitle": "Selección de imagen de perfil",
|
||||||
"ProfileImageSelectionHeader": "Elige una imagen de perfil",
|
"ProfileImageSelectionHeader": "Elige una imagen de perfil",
|
||||||
|
@ -260,7 +260,7 @@
|
|||||||
"UserProfilesChangeProfileImage": "Changer l'image du profil",
|
"UserProfilesChangeProfileImage": "Changer l'image du profil",
|
||||||
"UserProfilesAvailableUserProfiles": "Profils utilisateurs disponible:",
|
"UserProfilesAvailableUserProfiles": "Profils utilisateurs disponible:",
|
||||||
"UserProfilesAddNewProfile": "Ajouter un nouveau profil",
|
"UserProfilesAddNewProfile": "Ajouter un nouveau profil",
|
||||||
"UserProfilesDeleteSelectedProfile": "Supprimer le profil sélectionné",
|
"UserProfilesDelete": "Supprimer",
|
||||||
"UserProfilesClose": "Fermer",
|
"UserProfilesClose": "Fermer",
|
||||||
"ProfileImageSelectionTitle": "Sélection de l'image du profil",
|
"ProfileImageSelectionTitle": "Sélection de l'image du profil",
|
||||||
"ProfileImageSelectionHeader": "Choisir l'image du profil",
|
"ProfileImageSelectionHeader": "Choisir l'image du profil",
|
||||||
|
@ -260,7 +260,7 @@
|
|||||||
"UserProfilesChangeProfileImage": "プロファイル画像を変更",
|
"UserProfilesChangeProfileImage": "プロファイル画像を変更",
|
||||||
"UserProfilesAvailableUserProfiles": "利用可能なユーザプロファイル:",
|
"UserProfilesAvailableUserProfiles": "利用可能なユーザプロファイル:",
|
||||||
"UserProfilesAddNewProfile": "プロファイルを作成",
|
"UserProfilesAddNewProfile": "プロファイルを作成",
|
||||||
"UserProfilesDeleteSelectedProfile": "削除",
|
"UserProfilesDelete": "削除",
|
||||||
"UserProfilesClose": "閉じる",
|
"UserProfilesClose": "閉じる",
|
||||||
"ProfileImageSelectionTitle": "プロファイル画像選択",
|
"ProfileImageSelectionTitle": "プロファイル画像選択",
|
||||||
"ProfileImageSelectionHeader": "プロファイル画像を選択",
|
"ProfileImageSelectionHeader": "プロファイル画像を選択",
|
||||||
|
@ -260,7 +260,7 @@
|
|||||||
"UserProfilesChangeProfileImage": "Zmień Obraz Profilu",
|
"UserProfilesChangeProfileImage": "Zmień Obraz Profilu",
|
||||||
"UserProfilesAvailableUserProfiles": "Dostępne Profile Użytkowników:",
|
"UserProfilesAvailableUserProfiles": "Dostępne Profile Użytkowników:",
|
||||||
"UserProfilesAddNewProfile": "Utwórz Profil",
|
"UserProfilesAddNewProfile": "Utwórz Profil",
|
||||||
"UserProfilesDeleteSelectedProfile": "Usuń Zaznaczone",
|
"UserProfilesDelete": "Usuwać",
|
||||||
"UserProfilesClose": "Zamknij",
|
"UserProfilesClose": "Zamknij",
|
||||||
"ProfileImageSelectionTitle": "Wybór Obrazu Profilu",
|
"ProfileImageSelectionTitle": "Wybór Obrazu Profilu",
|
||||||
"ProfileImageSelectionHeader": "Wybierz zdjęcie profilowe",
|
"ProfileImageSelectionHeader": "Wybierz zdjęcie profilowe",
|
||||||
|
@ -260,7 +260,7 @@
|
|||||||
"UserProfilesChangeProfileImage": "Mudar imagem de perfil",
|
"UserProfilesChangeProfileImage": "Mudar imagem de perfil",
|
||||||
"UserProfilesAvailableUserProfiles": "Perfis de usuário disponíveis:",
|
"UserProfilesAvailableUserProfiles": "Perfis de usuário disponíveis:",
|
||||||
"UserProfilesAddNewProfile": "Adicionar novo perfil",
|
"UserProfilesAddNewProfile": "Adicionar novo perfil",
|
||||||
"UserProfilesDeleteSelectedProfile": "Apagar perfil selecionado",
|
"UserProfilesDelete": "Apagar",
|
||||||
"UserProfilesClose": "Fechar",
|
"UserProfilesClose": "Fechar",
|
||||||
"ProfileImageSelectionTitle": "Seleção da imagem de perfil",
|
"ProfileImageSelectionTitle": "Seleção da imagem de perfil",
|
||||||
"ProfileImageSelectionHeader": "Escolha uma imagem de perfil",
|
"ProfileImageSelectionHeader": "Escolha uma imagem de perfil",
|
||||||
|
@ -260,7 +260,7 @@
|
|||||||
"UserProfilesChangeProfileImage": "Изменить изображение профиля",
|
"UserProfilesChangeProfileImage": "Изменить изображение профиля",
|
||||||
"UserProfilesAvailableUserProfiles": "Доступные профили пользователей:",
|
"UserProfilesAvailableUserProfiles": "Доступные профили пользователей:",
|
||||||
"UserProfilesAddNewProfile": "Добавить новый профиль",
|
"UserProfilesAddNewProfile": "Добавить новый профиль",
|
||||||
"UserProfilesDeleteSelectedProfile": "Удалить выбранный профиль",
|
"UserProfilesDelete": "Удалить",
|
||||||
"UserProfilesClose": "Закрыть",
|
"UserProfilesClose": "Закрыть",
|
||||||
"ProfileImageSelectionTitle": "Выбор изображения профиля",
|
"ProfileImageSelectionTitle": "Выбор изображения профиля",
|
||||||
"ProfileImageSelectionHeader": "Выберите изображение профиля",
|
"ProfileImageSelectionHeader": "Выберите изображение профиля",
|
||||||
|
@ -260,7 +260,7 @@
|
|||||||
"UserProfilesChangeProfileImage": "Profil Resmini Değiştir",
|
"UserProfilesChangeProfileImage": "Profil Resmini Değiştir",
|
||||||
"UserProfilesAvailableUserProfiles": "Mevcut Kullanıcı Profilleri:",
|
"UserProfilesAvailableUserProfiles": "Mevcut Kullanıcı Profilleri:",
|
||||||
"UserProfilesAddNewProfile": "Yeni Profil Ekle",
|
"UserProfilesAddNewProfile": "Yeni Profil Ekle",
|
||||||
"UserProfilesDeleteSelectedProfile": "Seçili Profili Sil",
|
"UserProfilesDelete": "Sil",
|
||||||
"UserProfilesClose": "Kapat",
|
"UserProfilesClose": "Kapat",
|
||||||
"ProfileImageSelectionTitle": "Profil Resmi Seçimi",
|
"ProfileImageSelectionTitle": "Profil Resmi Seçimi",
|
||||||
"ProfileImageSelectionHeader": "Profil Resmi Seç",
|
"ProfileImageSelectionHeader": "Profil Resmi Seç",
|
||||||
|
@ -260,7 +260,7 @@
|
|||||||
"UserProfilesChangeProfileImage": "更換頭貼",
|
"UserProfilesChangeProfileImage": "更換頭貼",
|
||||||
"UserProfilesAvailableUserProfiles": "現有的帳號:",
|
"UserProfilesAvailableUserProfiles": "現有的帳號:",
|
||||||
"UserProfilesAddNewProfile": "建立帳號",
|
"UserProfilesAddNewProfile": "建立帳號",
|
||||||
"UserProfilesDeleteSelectedProfile": "刪除選擇的帳號",
|
"UserProfilesDelete": "刪除",
|
||||||
"UserProfilesClose": "關閉",
|
"UserProfilesClose": "關閉",
|
||||||
"ProfileImageSelectionTitle": "頭貼選擇",
|
"ProfileImageSelectionTitle": "頭貼選擇",
|
||||||
"ProfileImageSelectionHeader": "選擇合適的頭貼圖片",
|
"ProfileImageSelectionHeader": "選擇合適的頭貼圖片",
|
||||||
|
@ -60,5 +60,6 @@
|
|||||||
<Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
|
<Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
|
||||||
<Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
|
<Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
|
||||||
<Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
|
<Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
|
||||||
|
<Color x:Key="SecondaryTextColor">#A0FFFFFF</Color>
|
||||||
</Styles.Resources>
|
</Styles.Resources>
|
||||||
</Styles>
|
</Styles>
|
@ -52,5 +52,6 @@
|
|||||||
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
|
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
|
||||||
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
|
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
|
||||||
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
|
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
|
||||||
|
<Color x:Key="SecondaryTextColor">#A0000000</Color>
|
||||||
</Styles.Resources>
|
</Styles.Resources>
|
||||||
</Styles>
|
</Styles>
|
@ -56,8 +56,8 @@
|
|||||||
<Style Selector="Border.settings">
|
<Style Selector="Border.settings">
|
||||||
<Setter Property="Background" Value="{DynamicResource ThemeDarkColor}" />
|
<Setter Property="Background" Value="{DynamicResource ThemeDarkColor}" />
|
||||||
<Setter Property="BorderBrush" Value="{DynamicResource MenuFlyoutPresenterBorderColor}" />
|
<Setter Property="BorderBrush" Value="{DynamicResource MenuFlyoutPresenterBorderColor}" />
|
||||||
<Setter Property="BorderThickness" Value="2" />
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
<Setter Property="CornerRadius" Value="3" />
|
<Setter Property="CornerRadius" Value="5" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Image.small">
|
<Style Selector="Image.small">
|
||||||
<Setter Property="Width" Value="50" />
|
<Setter Property="Width" Value="50" />
|
||||||
@ -179,6 +179,9 @@
|
|||||||
<Style Selector="Button">
|
<Style Selector="Button">
|
||||||
<Setter Property="MinWidth" Value="80" />
|
<Setter Property="MinWidth" Value="80" />
|
||||||
</Style>
|
</Style>
|
||||||
|
<Style Selector="ProgressBar /template/ Border#ProgressBarTrack">
|
||||||
|
<Setter Property="IsVisible" Value="False" />
|
||||||
|
</Style>
|
||||||
<Style Selector="ToggleButton">
|
<Style Selector="ToggleButton">
|
||||||
<Setter Property="Padding" Value="0,-5,0,0" />
|
<Setter Property="Padding" Value="0,-5,0,0" />
|
||||||
</Style>
|
</Style>
|
||||||
@ -231,9 +234,41 @@
|
|||||||
<Setter Property="BorderBrush" Value="{DynamicResource MenuFlyoutPresenterBorderBrush}" />
|
<Setter Property="BorderBrush" Value="{DynamicResource MenuFlyoutPresenterBorderBrush}" />
|
||||||
<Setter Property="BorderThickness" Value="{DynamicResource MenuFlyoutPresenterBorderThemeThickness}" />
|
<Setter Property="BorderThickness" Value="{DynamicResource MenuFlyoutPresenterBorderThemeThickness}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
<Style Selector="TextBox">
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
<Style Selector="TextBox.NumberBoxTextBoxStyle">
|
<Style Selector="TextBox.NumberBoxTextBoxStyle">
|
||||||
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundColor}" />
|
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundColor}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
<Style Selector="ListBox ListBoxItem">
|
||||||
|
<Setter Property="Padding" Value="0" />
|
||||||
|
<Setter Property="Margin" Value="0" />
|
||||||
|
<Setter Property="CornerRadius" Value="5" />
|
||||||
|
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
|
||||||
|
<Setter Property="BorderThickness" Value="2"/>
|
||||||
|
<Style.Animations>
|
||||||
|
<Animation Duration="0:0:0.7">
|
||||||
|
<KeyFrame Cue="0%">
|
||||||
|
<Setter Property="MaxHeight" Value="0" />
|
||||||
|
<Setter Property="Opacity" Value="0.0" />
|
||||||
|
</KeyFrame>
|
||||||
|
<KeyFrame Cue="50%">
|
||||||
|
<Setter Property="MaxHeight" Value="1000" />
|
||||||
|
<Setter Property="Opacity" Value="0.3" />
|
||||||
|
</KeyFrame>
|
||||||
|
<KeyFrame Cue="100%">
|
||||||
|
<Setter Property="MaxHeight" Value="1000" />
|
||||||
|
<Setter Property="Opacity" Value="1.0" />
|
||||||
|
</KeyFrame>
|
||||||
|
</Animation>
|
||||||
|
</Style.Animations>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ListBox ListBoxItem:selected /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ListBox ListBoxItem:pointerover /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource AppListHoverBackgroundColor}" />
|
||||||
|
</Style>
|
||||||
<Styles.Resources>
|
<Styles.Resources>
|
||||||
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
|
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
|
||||||
<StaticResource x:Key="ListViewItemBackgroundSelected" ResourceKey="ThemeAccentColorBrush" />
|
<StaticResource x:Key="ListViewItemBackgroundSelected" ResourceKey="ThemeAccentColorBrush" />
|
||||||
@ -271,6 +306,9 @@
|
|||||||
<Color x:Key="ThemeControlBorderColor">#FF505050</Color>
|
<Color x:Key="ThemeControlBorderColor">#FF505050</Color>
|
||||||
<Color x:Key="VsyncEnabled">#FF2EEAC9</Color>
|
<Color x:Key="VsyncEnabled">#FF2EEAC9</Color>
|
||||||
<Color x:Key="VsyncDisabled">#FFFF4554</Color>
|
<Color x:Key="VsyncDisabled">#FFFF4554</Color>
|
||||||
|
<Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
|
||||||
|
<Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
|
||||||
|
<Color x:Key="SecondaryTextColor">#A0FFFFFF</Color>
|
||||||
<x:Double x:Key="ScrollBarThickness">15</x:Double>
|
<x:Double x:Key="ScrollBarThickness">15</x:Double>
|
||||||
<x:Double x:Key="FontSizeSmall">8</x:Double>
|
<x:Double x:Key="FontSizeSmall">8</x:Double>
|
||||||
<x:Double x:Key="FontSizeNormal">10</x:Double>
|
<x:Double x:Key="FontSizeNormal">10</x:Double>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
|
using FluentAvalonia.Core;
|
||||||
using Ryujinx.Input;
|
using Ryujinx.Input;
|
||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
@ -69,12 +70,22 @@ namespace Ryujinx.Ava.Input
|
|||||||
|
|
||||||
private void Parent_PointerReleaseEvent(object o, PointerReleasedEventArgs args)
|
private void Parent_PointerReleaseEvent(object o, PointerReleasedEventArgs args)
|
||||||
{
|
{
|
||||||
PressedButtons[(int)args.InitialPressMouseButton - 1] = false;
|
int button = (int)args.InitialPressMouseButton - 1;
|
||||||
|
|
||||||
|
if (PressedButtons.Count() >= button)
|
||||||
|
{
|
||||||
|
PressedButtons[button] = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Parent_PointerPressEvent(object o, PointerPressedEventArgs args)
|
private void Parent_PointerPressEvent(object o, PointerPressedEventArgs args)
|
||||||
{
|
{
|
||||||
PressedButtons[(int)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind] = true;
|
int button = (int)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind;
|
||||||
|
|
||||||
|
if (PressedButtons.Count() >= button)
|
||||||
|
{
|
||||||
|
PressedButtons[button] = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Parent_PointerMovedEvent(object o, PointerEventArgs args)
|
private void Parent_PointerMovedEvent(object o, PointerEventArgs args)
|
||||||
@ -85,14 +96,20 @@ namespace Ryujinx.Ava.Input
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void SetMousePressed(MouseButton button)
|
public void SetMousePressed(MouseButton button)
|
||||||
|
{
|
||||||
|
if (PressedButtons.Count() >= (int)button)
|
||||||
{
|
{
|
||||||
PressedButtons[(int)button] = true;
|
PressedButtons[(int)button] = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void SetMouseReleased(MouseButton button)
|
public void SetMouseReleased(MouseButton button)
|
||||||
|
{
|
||||||
|
if (PressedButtons.Count() >= (int)button)
|
||||||
{
|
{
|
||||||
PressedButtons[(int)button] = false;
|
PressedButtons[(int)button] = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void SetPosition(double x, double y)
|
public void SetPosition(double x, double y)
|
||||||
{
|
{
|
||||||
@ -100,10 +117,15 @@ namespace Ryujinx.Ava.Input
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool IsButtonPressed(MouseButton button)
|
public bool IsButtonPressed(MouseButton button)
|
||||||
|
{
|
||||||
|
if (PressedButtons.Count() >= (int)button)
|
||||||
{
|
{
|
||||||
return PressedButtons[(int)button];
|
return PressedButtons[(int)button];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public Size GetClientSize()
|
public Size GetClientSize()
|
||||||
{
|
{
|
||||||
return _size;
|
return _size;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Ryujinx.Ava.UI.Helper;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
|
@ -130,6 +130,18 @@
|
|||||||
<DependentUpon>GameListView.axaml</DependentUpon>
|
<DependentUpon>GameListView.axaml</DependentUpon>
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Update="UI\Views\User\UserEditorView.axaml.cs">
|
||||||
|
<DependentUpon>UserEditor.axaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="UI\Views\User\UserRecovererView.axaml.cs">
|
||||||
|
<DependentUpon>UserRecoverer.axaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="UI\Views\User\UserSelectorView.axaml.cs">
|
||||||
|
<DependentUpon>UserSelector.axaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -112,32 +112,8 @@
|
|||||||
</ListBox.ItemsPanel>
|
</ListBox.ItemsPanel>
|
||||||
<ListBox.Styles>
|
<ListBox.Styles>
|
||||||
<Style Selector="ListBoxItem">
|
<Style Selector="ListBoxItem">
|
||||||
<Setter Property="Padding" Value="0" />
|
|
||||||
<Setter Property="Margin" Value="5" />
|
<Setter Property="Margin" Value="5" />
|
||||||
<Setter Property="CornerRadius" Value="4" />
|
<Setter Property="CornerRadius" Value="4" />
|
||||||
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
|
|
||||||
<Style.Animations>
|
|
||||||
<Animation Duration="0:0:0.7">
|
|
||||||
<KeyFrame Cue="0%">
|
|
||||||
<Setter Property="MaxWidth" Value="0" />
|
|
||||||
<Setter Property="Opacity" Value="0.0" />
|
|
||||||
</KeyFrame>
|
|
||||||
<KeyFrame Cue="50%">
|
|
||||||
<Setter Property="MaxWidth" Value="1000" />
|
|
||||||
<Setter Property="Opacity" Value="0.3" />
|
|
||||||
</KeyFrame>
|
|
||||||
<KeyFrame Cue="100%">
|
|
||||||
<Setter Property="MaxWidth" Value="1000" />
|
|
||||||
<Setter Property="Opacity" Value="1.0" />
|
|
||||||
</KeyFrame>
|
|
||||||
</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>
|
</Style>
|
||||||
<Style Selector="ListBoxItem:selected /template/ Border#SelectionIndicator">
|
<Style Selector="ListBoxItem:selected /template/ Border#SelectionIndicator">
|
||||||
<Setter Property="MinHeight" Value="{Binding $parent[UserControl].DataContext.GridItemSelectorSize}" />
|
<Setter Property="MinHeight" Value="{Binding $parent[UserControl].DataContext.GridItemSelectorSize}" />
|
||||||
|
@ -111,35 +111,6 @@
|
|||||||
</ItemsPanelTemplate>
|
</ItemsPanelTemplate>
|
||||||
</ListBox.ItemsPanel>
|
</ListBox.ItemsPanel>
|
||||||
<ListBox.Styles>
|
<ListBox.Styles>
|
||||||
<Style Selector="ListBoxItem">
|
|
||||||
<Setter Property="Padding" Value="0" />
|
|
||||||
<Setter Property="Margin" Value="0" />
|
|
||||||
<Setter Property="CornerRadius" Value="5" />
|
|
||||||
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
|
|
||||||
<Setter Property="BorderThickness" Value="2"/>
|
|
||||||
<Style.Animations>
|
|
||||||
<Animation Duration="0:0:0.7">
|
|
||||||
<KeyFrame Cue="0%">
|
|
||||||
<Setter Property="MaxHeight" Value="0" />
|
|
||||||
<Setter Property="Opacity" Value="0.0" />
|
|
||||||
</KeyFrame>
|
|
||||||
<KeyFrame Cue="50%">
|
|
||||||
<Setter Property="MaxHeight" Value="1000" />
|
|
||||||
<Setter Property="Opacity" Value="0.3" />
|
|
||||||
</KeyFrame>
|
|
||||||
<KeyFrame Cue="100%">
|
|
||||||
<Setter Property="MaxHeight" Value="1000" />
|
|
||||||
<Setter Property="Opacity" Value="1.0" />
|
|
||||||
</KeyFrame>
|
|
||||||
</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>
|
|
||||||
<Style Selector="ListBoxItem:selected /template/ Border#SelectionIndicator">
|
<Style Selector="ListBoxItem:selected /template/ Border#SelectionIndicator">
|
||||||
<Setter Property="MinHeight" Value="{Binding $parent[UserControl].DataContext.ListItemSelectorSize}" />
|
<Setter Property="MinHeight" Value="{Binding $parent[UserControl].DataContext.ListItemSelectorSize}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
@ -12,5 +12,6 @@
|
|||||||
<ui:Frame
|
<ui:Frame
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
x:Name="ContentFrame" />
|
x:Name="ContentFrame">
|
||||||
|
</ui:Frame>
|
||||||
</UserControl>
|
</UserControl>
|
@ -1,13 +1,25 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Styling;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using FluentAvalonia.Core;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using LibHac;
|
using LibHac;
|
||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.Fs;
|
||||||
|
using LibHac.Fs.Shim;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
|
using Ryujinx.Ava.UI.Views.User;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Controls
|
namespace Ryujinx.Ava.UI.Controls
|
||||||
{
|
{
|
||||||
@ -31,14 +43,14 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
ContentManager = contentManager;
|
ContentManager = contentManager;
|
||||||
VirtualFileSystem = virtualFileSystem;
|
VirtualFileSystem = virtualFileSystem;
|
||||||
HorizonClient = horizonClient;
|
HorizonClient = horizonClient;
|
||||||
ViewModel = new UserProfileViewModel(this);
|
ViewModel = new UserProfileViewModel();
|
||||||
|
LoadProfiles();
|
||||||
|
|
||||||
if (contentManager.GetCurrentFirmwareVersion() != null)
|
if (contentManager.GetCurrentFirmwareVersion() != null)
|
||||||
{
|
{
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
AvatarProfileViewModel.PreloadAvatars(contentManager, virtualFileSystem);
|
UserFirmwareAvatarSelectorViewModel.PreloadAvatars(contentManager, virtualFileSystem);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@ -51,7 +63,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
ContentFrame.GoBack();
|
ContentFrame.GoBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewModel.LoadProfiles();
|
LoadProfiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Navigate(Type sourcePageType, object parameter)
|
public void Navigate(Type sourcePageType, object parameter)
|
||||||
@ -68,7 +80,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle],
|
Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle],
|
||||||
PrimaryButtonText = "",
|
PrimaryButtonText = "",
|
||||||
SecondaryButtonText = "",
|
SecondaryButtonText = "",
|
||||||
CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose],
|
CloseButtonText = "",
|
||||||
Content = content,
|
Content = content,
|
||||||
Padding = new Thickness(0)
|
Padding = new Thickness(0)
|
||||||
};
|
};
|
||||||
@ -78,6 +90,11 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
content.ViewModel.Dispose();
|
content.ViewModel.Dispose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Style footer = new(x => x.Name("DialogSpace").Child().OfType<Border>());
|
||||||
|
footer.Setters.Add(new Setter(IsVisibleProperty, false));
|
||||||
|
|
||||||
|
contentDialog.Styles.Add(footer);
|
||||||
|
|
||||||
await contentDialog.ShowAsync();
|
await contentDialog.ShowAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +102,117 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
{
|
{
|
||||||
base.OnAttachedToVisualTree(e);
|
base.OnAttachedToVisualTree(e);
|
||||||
|
|
||||||
Navigate(typeof(UserSelector), this);
|
Navigate(typeof(UserSelectorViews), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadProfiles()
|
||||||
|
{
|
||||||
|
ViewModel.Profiles.Clear();
|
||||||
|
ViewModel.LostProfiles.Clear();
|
||||||
|
|
||||||
|
var profiles = AccountManager.GetAllUsers().OrderBy(x => x.Name);
|
||||||
|
|
||||||
|
foreach (var profile in profiles)
|
||||||
|
{
|
||||||
|
ViewModel.Profiles.Add(new UserProfile(profile, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account, default, 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];
|
||||||
|
|
||||||
|
HashSet<HLE.HOS.Services.Account.Acc.UserId> lostAccounts = new();
|
||||||
|
|
||||||
|
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];
|
||||||
|
var id = new HLE.HOS.Services.Account.Acc.UserId((long)save.UserId.Id.Low, (long)save.UserId.Id.High);
|
||||||
|
if (ViewModel.Profiles.Cast<UserProfile>().FirstOrDefault( x=> x.UserId == id) == null)
|
||||||
|
{
|
||||||
|
lostAccounts.Add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(var account in lostAccounts)
|
||||||
|
{
|
||||||
|
ViewModel.LostProfiles.Add(new UserProfile(new HLE.HOS.Services.Account.Acc.UserProfile(account, "", null), this));
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewModel.Profiles.Add(new BaseModel());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void DeleteUser(UserProfile userProfile)
|
||||||
|
{
|
||||||
|
var lastUserId = AccountManager.LastOpenedUser.UserId;
|
||||||
|
|
||||||
|
if (userProfile.UserId == lastUserId)
|
||||||
|
{
|
||||||
|
// If we are deleting the currently open profile, then we must open something else before deleting.
|
||||||
|
var profile = ViewModel.Profiles.Cast<UserProfile>().FirstOrDefault(x => x.UserId != lastUserId);
|
||||||
|
|
||||||
|
if (profile == null)
|
||||||
|
{
|
||||||
|
async void Action()
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionWarningMessage]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dispatcher.UIThread.Post(Action);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountManager.OpenUser(profile.UserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await ContentDialogHelper.CreateConfirmationDialog(
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionConfirmMessage],
|
||||||
|
"",
|
||||||
|
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
||||||
|
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||||
|
"");
|
||||||
|
|
||||||
|
if (result == UserResult.Yes)
|
||||||
|
{
|
||||||
|
GoBack();
|
||||||
|
AccountManager.DeleteUser(userProfile.UserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadProfiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddUser()
|
||||||
|
{
|
||||||
|
Navigate(typeof(UserEditorView), (this, (UserProfile)null, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EditUser(UserProfile userProfile)
|
||||||
|
{
|
||||||
|
Navigate(typeof(UserEditorView), (this, userProfile, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RecoverLostAccounts()
|
||||||
|
{
|
||||||
|
Navigate(typeof(UserRecovererView), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ManageSaves()
|
||||||
|
{
|
||||||
|
Navigate(typeof(UserSaveManagerView), (this, AccountManager, HorizonClient, VirtualFileSystem));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,57 +0,0 @@
|
|||||||
<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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
x:Class="Ryujinx.Ava.UI.Controls.ProfileImageSelectionDialog"
|
|
||||||
Focusable="True">
|
|
||||||
<Grid
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Margin="5,10,5, 5">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="70" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<TextBlock
|
|
||||||
FontWeight="Bold"
|
|
||||||
FontSize="18"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
Grid.Row="1"
|
|
||||||
Text="{locale:Locale ProfileImageSelectionHeader}" />
|
|
||||||
<TextBlock
|
|
||||||
FontWeight="Bold"
|
|
||||||
Grid.Row="2"
|
|
||||||
Margin="10"
|
|
||||||
MaxWidth="400"
|
|
||||||
TextWrapping="Wrap"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
TextAlignment="Center"
|
|
||||||
Text="{locale:Locale ProfileImageSelectionNote}" />
|
|
||||||
<StackPanel
|
|
||||||
Margin="5,0"
|
|
||||||
Spacing="10"
|
|
||||||
Grid.Row="4"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
Orientation="Horizontal">
|
|
||||||
<Button
|
|
||||||
Name="Import"
|
|
||||||
Click="Import_OnClick"
|
|
||||||
Width="200">
|
|
||||||
<TextBlock Text="{locale:Locale ProfileImageSelectionImportImage}" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
Name="SelectFirmwareImage"
|
|
||||||
IsEnabled="{Binding FirmwareFound}"
|
|
||||||
Click="SelectFirmwareImage_OnClick"
|
|
||||||
Width="200">
|
|
||||||
<TextBlock Text="{locale:Locale ProfileImageSelectionSelectAvatar}" />
|
|
||||||
</Button>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</UserControl>
|
|
@ -1,175 +0,0 @@
|
|||||||
<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:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
|
||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
|
||||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignWidth="800"
|
|
||||||
d:DesignHeight="450"
|
|
||||||
Height="400"
|
|
||||||
Width="550"
|
|
||||||
x:Class="Ryujinx.Ava.UI.Controls.SaveManager"
|
|
||||||
Focusable="True">
|
|
||||||
<UserControl.Resources>
|
|
||||||
<helpers: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 OrderDescending}" />
|
|
||||||
</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>
|
|
@ -1,160 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
<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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
|
||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignWidth="800"
|
|
||||||
d:DesignHeight="450"
|
|
||||||
MinWidth="500"
|
|
||||||
MinHeight="400"
|
|
||||||
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>
|
|
@ -1,44 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,145 +0,0 @@
|
|||||||
<UserControl
|
|
||||||
x:Class="Ryujinx.Ava.UI.Controls.UserSelector"
|
|
||||||
xmlns="https://github.com/avaloniaui"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
|
||||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
|
||||||
d:DesignHeight="450"
|
|
||||||
MinWidth="500"
|
|
||||||
d:DesignWidth="800"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
Focusable="True">
|
|
||||||
<UserControl.Resources>
|
|
||||||
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
|
||||||
</UserControl.Resources>
|
|
||||||
<Design.DataContext>
|
|
||||||
<viewModels:UserProfileViewModel />
|
|
||||||
</Design.DataContext>
|
|
||||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<ListBox
|
|
||||||
Margin="5"
|
|
||||||
MaxHeight="300"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
DoubleTapped="ProfilesList_DoubleTapped"
|
|
||||||
Items="{Binding Profiles}"
|
|
||||||
SelectionChanged="SelectingItemsControl_SelectionChanged">
|
|
||||||
<ListBox.ItemsPanel>
|
|
||||||
<ItemsPanelTemplate>
|
|
||||||
<flex:FlexPanel
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
VerticalAlignment="Stretch"
|
|
||||||
AlignContent="FlexStart"
|
|
||||||
JustifyContent="Center" />
|
|
||||||
</ItemsPanelTemplate>
|
|
||||||
</ListBox.ItemsPanel>
|
|
||||||
<ListBox.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<Grid>
|
|
||||||
<Border
|
|
||||||
Margin="2"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
VerticalAlignment="Stretch"
|
|
||||||
ClipToBounds="True"
|
|
||||||
CornerRadius="5">
|
|
||||||
<Grid Margin="0">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<Image
|
|
||||||
Grid.Row="0"
|
|
||||||
Width="96"
|
|
||||||
Height="96"
|
|
||||||
Margin="0"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
VerticalAlignment="Top"
|
|
||||||
Source="{Binding Image, Converter={StaticResource ByteImage}}" />
|
|
||||||
<StackPanel
|
|
||||||
Grid.Row="1"
|
|
||||||
Height="30"
|
|
||||||
Margin="5"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
VerticalAlignment="Stretch">
|
|
||||||
<TextBlock
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Text="{Binding Name}"
|
|
||||||
TextAlignment="Center"
|
|
||||||
TextWrapping="Wrap" />
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
<Border
|
|
||||||
Width="10"
|
|
||||||
Height="10"
|
|
||||||
Margin="5"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
VerticalAlignment="Top"
|
|
||||||
Background="LimeGreen"
|
|
||||||
CornerRadius="5"
|
|
||||||
IsVisible="{Binding IsOpened}" />
|
|
||||||
</Grid>
|
|
||||||
</DataTemplate>
|
|
||||||
</ListBox.ItemTemplate>
|
|
||||||
</ListBox>
|
|
||||||
<Grid
|
|
||||||
Grid.Row="1"
|
|
||||||
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}" />
|
|
||||||
<Button
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Grid.Row="2"
|
|
||||||
Grid.ColumnSpan="2"
|
|
||||||
Grid.Column="0"
|
|
||||||
Margin="2"
|
|
||||||
Command="{Binding RecoverLostAccounts}"
|
|
||||||
Content="{locale:Locale UserProfilesRecoverLostAccounts}" />
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</UserControl>
|
|
@ -1,77 +0,0 @@
|
|||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Interactivity;
|
|
||||||
using FluentAvalonia.UI.Controls;
|
|
||||||
using FluentAvalonia.UI.Navigation;
|
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
|
||||||
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Controls
|
|
||||||
{
|
|
||||||
public partial class UserSelector : UserControl
|
|
||||||
{
|
|
||||||
private NavigationDialogHost _parent;
|
|
||||||
public UserProfileViewModel ViewModel { get; set; }
|
|
||||||
|
|
||||||
public UserSelector()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
|
|
||||||
if (Program.PreviewerDetached)
|
|
||||||
{
|
|
||||||
AddHandler(Frame.NavigatedToEvent, (s, e) =>
|
|
||||||
{
|
|
||||||
NavigatedTo(e);
|
|
||||||
}, RoutingStrategies.Direct);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void NavigatedTo(NavigationEventArgs arg)
|
|
||||||
{
|
|
||||||
if (Program.PreviewerDetached)
|
|
||||||
{
|
|
||||||
if (arg.NavigationMode == NavigationMode.New)
|
|
||||||
{
|
|
||||||
_parent = (NavigationDialogHost)arg.Parameter;
|
|
||||||
ViewModel = _parent.ViewModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
DataContext = ViewModel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ProfilesList_DoubleTapped(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is ListBox listBox)
|
|
||||||
{
|
|
||||||
int selectedIndex = listBox.SelectedIndex;
|
|
||||||
|
|
||||||
if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
|
|
||||||
{
|
|
||||||
ViewModel.SelectedProfile = ViewModel.Profiles[selectedIndex];
|
|
||||||
|
|
||||||
_parent?.AccountManager?.OpenUser(ViewModel.SelectedProfile.UserId);
|
|
||||||
|
|
||||||
ViewModel.LoadProfiles();
|
|
||||||
|
|
||||||
foreach (UserProfile profile in ViewModel.Profiles)
|
|
||||||
{
|
|
||||||
profile.UpdateState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SelectingItemsControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is ListBox listBox)
|
|
||||||
{
|
|
||||||
int selectedIndex = listBox.SelectedIndex;
|
|
||||||
|
|
||||||
if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
|
|
||||||
{
|
|
||||||
ViewModel.HighlightedProfile = ViewModel.Profiles[selectedIndex];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,7 +2,6 @@ using Avalonia;
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Platform;
|
using Avalonia.Platform;
|
||||||
using Ryujinx.Ava.UI.Helper;
|
|
||||||
using SPB.Graphics;
|
using SPB.Graphics;
|
||||||
using SPB.Platform;
|
using SPB.Platform;
|
||||||
using SPB.Platform.GLX;
|
using SPB.Platform.GLX;
|
||||||
@ -35,6 +34,8 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
{
|
{
|
||||||
WindowHandle = IntPtr.Zero;
|
WindowHandle = IntPtr.Zero;
|
||||||
X11Display = IntPtr.Zero;
|
X11Display = IntPtr.Zero;
|
||||||
|
NsView = IntPtr.Zero;
|
||||||
|
MetalLayer = IntPtr.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EmbeddedWindow()
|
public EmbeddedWindow()
|
||||||
@ -43,7 +44,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
stateObserverable.Subscribe(StateChanged);
|
stateObserverable.Subscribe(StateChanged);
|
||||||
|
|
||||||
this.Initialized += NativeEmbeddedWindow_Initialized;
|
Initialized += NativeEmbeddedWindow_Initialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnWindowCreated() { }
|
public virtual void OnWindowCreated() { }
|
||||||
@ -128,7 +129,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
|
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
|
||||||
style = ClassStyles.CS_OWNDC,
|
style = ClassStyles.CS_OWNDC,
|
||||||
lpszClassName = Marshal.StringToHGlobalUni(_className),
|
lpszClassName = Marshal.StringToHGlobalUni(_className),
|
||||||
hCursor = LoadCursor(IntPtr.Zero, (IntPtr)Cursors.IDC_ARROW)
|
hCursor = CreateArrowCursor()
|
||||||
};
|
};
|
||||||
|
|
||||||
var atom = RegisterClassEx(ref wndClassEx);
|
var atom = RegisterClassEx(ref wndClassEx);
|
||||||
@ -199,6 +200,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
KeyModifiers.None));
|
KeyModifiers.None));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return DefWindowProc(hWnd, msg, wParam, lParam);
|
return DefWindowProc(hWnd, msg, wParam, lParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ using Avalonia.Utilities;
|
|||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helper
|
namespace Ryujinx.Ava.UI.Helpers
|
||||||
{
|
{
|
||||||
using AvaLogger = Avalonia.Logging.Logger;
|
using AvaLogger = Avalonia.Logging.Logger;
|
||||||
using AvaLogLevel = Avalonia.Logging.LogEventLevel;
|
using AvaLogLevel = Avalonia.Logging.LogEventLevel;
|
||||||
@ -20,12 +20,12 @@ namespace Ryujinx.Ava.UI.Helper
|
|||||||
{
|
{
|
||||||
return level switch
|
return level switch
|
||||||
{
|
{
|
||||||
AvaLogLevel.Verbose => RyuLogger.Trace,
|
AvaLogLevel.Verbose => RyuLogger.Debug,
|
||||||
AvaLogLevel.Debug => RyuLogger.Debug,
|
AvaLogLevel.Debug => RyuLogger.Debug,
|
||||||
AvaLogLevel.Information => RyuLogger.Info,
|
AvaLogLevel.Information => RyuLogger.Debug,
|
||||||
AvaLogLevel.Warning => RyuLogger.Warning,
|
AvaLogLevel.Warning => RyuLogger.Debug,
|
||||||
AvaLogLevel.Error => RyuLogger.Error,
|
AvaLogLevel.Error => RyuLogger.Error,
|
||||||
AvaLogLevel.Fatal => RyuLogger.Notice,
|
AvaLogLevel.Fatal => RyuLogger.Error,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(level), level, null)
|
_ => throw new ArgumentOutOfRangeException(nameof(level), level, null)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -37,34 +37,38 @@ namespace Ryujinx.Ava.UI.Helper
|
|||||||
|
|
||||||
public void Log(AvaLogLevel level, string area, object source, string messageTemplate)
|
public void Log(AvaLogLevel level, string area, object source, string messageTemplate)
|
||||||
{
|
{
|
||||||
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, null));
|
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(level, area, messageTemplate, source, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Log<T0>(AvaLogLevel level, string area, object source, string messageTemplate, T0 propertyValue0)
|
public void Log<T0>(AvaLogLevel level, string area, object source, string messageTemplate, T0 propertyValue0)
|
||||||
{
|
{
|
||||||
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, new object[] { propertyValue0 }));
|
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(level, area, messageTemplate, source, new object[] { propertyValue0 }));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Log<T0, T1>(AvaLogLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1)
|
public void Log<T0, T1>(AvaLogLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1)
|
||||||
{
|
{
|
||||||
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, new object[] { propertyValue0, propertyValue1 }));
|
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(level, area, messageTemplate, source, new object[] { propertyValue0, propertyValue1 }));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Log<T0, T1, T2>(AvaLogLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2)
|
public void Log<T0, T1, T2>(AvaLogLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2)
|
||||||
{
|
{
|
||||||
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, new object[] { propertyValue0, propertyValue1, propertyValue2 }));
|
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(level, area, messageTemplate, source, new object[] { propertyValue0, propertyValue1, propertyValue2 }));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Log(AvaLogLevel level, string area, object source, string messageTemplate, params object[] propertyValues)
|
public void Log(AvaLogLevel level, string area, object source, string messageTemplate, params object[] propertyValues)
|
||||||
{
|
{
|
||||||
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, propertyValues));
|
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(level, area, messageTemplate, source, propertyValues));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Format(string area, string template, object source, object[] v)
|
private static string Format(AvaLogLevel level, string area, string template, object source, object[] v)
|
||||||
{
|
{
|
||||||
var result = new StringBuilder();
|
var result = new StringBuilder();
|
||||||
var r = new CharacterReader(template.AsSpan());
|
var r = new CharacterReader(template.AsSpan());
|
||||||
var i = 0;
|
int i = 0;
|
||||||
|
|
||||||
|
result.Append('[');
|
||||||
|
result.Append(level);
|
||||||
|
result.Append("] ");
|
||||||
|
|
||||||
result.Append('[');
|
result.Append('[');
|
||||||
result.Append(area);
|
result.Append(area);
|
@ -3,7 +3,7 @@ using System.Runtime.Versioning;
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helper
|
namespace Ryujinx.Ava.UI.Helpers
|
||||||
{
|
{
|
||||||
public delegate void UpdateBoundsCallbackDelegate(Rect rect);
|
public delegate void UpdateBoundsCallbackDelegate(Rect rect);
|
||||||
|
|
@ -70,6 +70,22 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IntPtr CreateEmptyCursor()
|
||||||
|
{
|
||||||
|
return CreateCursor(IntPtr.Zero, 0, 0, 1, 1, new byte[] { 0xFF }, new byte[] { 0x00 });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IntPtr CreateArrowCursor()
|
||||||
|
{
|
||||||
|
return LoadCursor(IntPtr.Zero, (IntPtr)Cursors.IDC_ARROW);
|
||||||
|
}
|
||||||
|
|
||||||
|
[LibraryImport("user32.dll")]
|
||||||
|
public static partial IntPtr SetCursor(IntPtr handle);
|
||||||
|
|
||||||
|
[LibraryImport("user32.dll")]
|
||||||
|
public static partial IntPtr CreateCursor(IntPtr hInst, int xHotSpot, int yHotSpot, int nWidth, int nHeight, byte[] pvANDPlane, byte[] pvXORPlane);
|
||||||
|
|
||||||
[LibraryImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassExW")]
|
[LibraryImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassExW")]
|
||||||
public static partial ushort RegisterClassEx(ref WNDCLASSEX param);
|
public static partial ushort RegisterClassEx(ref WNDCLASSEX param);
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
|
using Avalonia.Media;
|
||||||
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Models
|
namespace Ryujinx.Ava.UI.Models
|
||||||
{
|
{
|
||||||
public class ProfileImageModel
|
public class ProfileImageModel : BaseModel
|
||||||
{
|
{
|
||||||
public ProfileImageModel(string name, byte[] data)
|
public ProfileImageModel(string name, byte[] data)
|
||||||
{
|
{
|
||||||
@ -10,5 +13,20 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public byte[] Data { get; set; }
|
public byte[] Data { get; set; }
|
||||||
|
|
||||||
|
private SolidColorBrush _backgroundColor = new(Colors.White);
|
||||||
|
|
||||||
|
public SolidColorBrush BackgroundColor
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _backgroundColor;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_backgroundColor = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,15 +1,8 @@
|
|||||||
using LibHac;
|
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Shim;
|
|
||||||
using LibHac.Ncm;
|
using LibHac.Ncm;
|
||||||
using Ryujinx.Ava.Common;
|
|
||||||
using Ryujinx.Ava.Common.Locale;
|
|
||||||
using Ryujinx.Ava.UI.Controls;
|
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.Ui.App.Common;
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -19,10 +12,8 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
{
|
{
|
||||||
public class SaveModel : BaseModel
|
public class SaveModel : BaseModel
|
||||||
{
|
{
|
||||||
private readonly HorizonClient _horizonClient;
|
|
||||||
private long _size;
|
private long _size;
|
||||||
|
|
||||||
public Action DeleteAction { get; set; }
|
|
||||||
public ulong SaveId { get; }
|
public ulong SaveId { get; }
|
||||||
public ProgramId TitleId { get; }
|
public ProgramId TitleId { get; }
|
||||||
public string TitleIdString => $"{TitleId.Value:X16}";
|
public string TitleIdString => $"{TitleId.Value:X16}";
|
||||||
@ -45,11 +36,29 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
|
|
||||||
public bool SizeAvailable { get; set; }
|
public bool SizeAvailable { get; set; }
|
||||||
|
|
||||||
public string SizeString => $"{((float)_size * 0.000000954):0.###}MB";
|
public string SizeString => GetSizeString();
|
||||||
|
|
||||||
public SaveModel(SaveDataInfo info, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem)
|
private string GetSizeString()
|
||||||
|
{
|
||||||
|
const int scale = 1024;
|
||||||
|
string[] orders = { "GiB", "MiB", "KiB" };
|
||||||
|
long max = (long)Math.Pow(scale, orders.Length);
|
||||||
|
|
||||||
|
foreach (string order in orders)
|
||||||
|
{
|
||||||
|
if (Size > max)
|
||||||
|
{
|
||||||
|
return $"{decimal.Divide(Size, max):##.##} {order}";
|
||||||
|
}
|
||||||
|
|
||||||
|
max /= scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "0 KiB";
|
||||||
|
}
|
||||||
|
|
||||||
|
public SaveModel(SaveDataInfo info, VirtualFileSystem virtualFileSystem)
|
||||||
{
|
{
|
||||||
_horizonClient = horizonClient;
|
|
||||||
SaveId = info.SaveDataId;
|
SaveId = info.SaveDataId;
|
||||||
TitleId = info.ProgramId;
|
TitleId = info.ProgramId;
|
||||||
UserId = info.UserId;
|
UserId = info.UserId;
|
||||||
@ -99,25 +108,5 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenLocation()
|
|
||||||
{
|
|
||||||
ApplicationHelper.OpenSaveDir(SaveId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void Delete()
|
|
||||||
{
|
|
||||||
var result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DeleteUserSave],
|
|
||||||
LocaleManager.Instance[LocaleKeys.IrreversibleActionNote],
|
|
||||||
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
|
||||||
LocaleManager.Instance[LocaleKeys.InputDialogNo], "");
|
|
||||||
|
|
||||||
if (result == UserResult.Yes)
|
|
||||||
{
|
|
||||||
_horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, SaveId);
|
|
||||||
|
|
||||||
DeleteAction?.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,10 +7,12 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
public class TempProfile : BaseModel
|
public class TempProfile : BaseModel
|
||||||
{
|
{
|
||||||
private readonly UserProfile _profile;
|
private readonly UserProfile _profile;
|
||||||
private byte[] _image = null;
|
private byte[] _image;
|
||||||
private string _name = String.Empty;
|
private string _name = String.Empty;
|
||||||
private UserId _userId;
|
private UserId _userId;
|
||||||
|
|
||||||
|
public uint MaxProfileNameLength => 0x20;
|
||||||
|
|
||||||
public byte[] Image
|
public byte[] Image
|
||||||
{
|
{
|
||||||
get => _image;
|
get => _image;
|
||||||
@ -28,9 +30,12 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
{
|
{
|
||||||
_userId = value;
|
_userId = value;
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(UserIdString));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string UserIdString => _userId.ToString();
|
||||||
|
|
||||||
public string Name
|
public string Name
|
||||||
{
|
{
|
||||||
get => _name;
|
get => _name;
|
||||||
@ -52,7 +57,5 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
UserId = profile.UserId;
|
UserId = profile.UserId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TempProfile(){}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,7 @@
|
|||||||
|
using Avalonia.Media;
|
||||||
using Ryujinx.Ava.UI.Controls;
|
using Ryujinx.Ava.UI.Controls;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
|
using Ryujinx.Ava.UI.Views.User;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
using Profile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
|
using Profile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
|
||||||
|
|
||||||
@ -12,6 +14,8 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
private byte[] _image;
|
private byte[] _image;
|
||||||
private string _name;
|
private string _name;
|
||||||
private UserId _userId;
|
private UserId _userId;
|
||||||
|
private bool _isPointerOver;
|
||||||
|
private IBrush _backgroundColor;
|
||||||
|
|
||||||
public byte[] Image
|
public byte[] Image
|
||||||
{
|
{
|
||||||
@ -43,27 +47,57 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsPointerOver
|
||||||
|
{
|
||||||
|
get => _isPointerOver;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isPointerOver = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IBrush BackgroundColor
|
||||||
|
{
|
||||||
|
get => _backgroundColor;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_backgroundColor = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public UserProfile(Profile profile, NavigationDialogHost owner)
|
public UserProfile(Profile profile, NavigationDialogHost owner)
|
||||||
{
|
{
|
||||||
_profile = profile;
|
_profile = profile;
|
||||||
_owner = owner;
|
_owner = owner;
|
||||||
|
|
||||||
|
UpdateBackground();
|
||||||
|
|
||||||
Image = profile.Image;
|
Image = profile.Image;
|
||||||
Name = profile.Name;
|
Name = profile.Name;
|
||||||
UserId = profile.UserId;
|
UserId = profile.UserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsOpened => _profile.AccountState == AccountState.Open;
|
|
||||||
|
|
||||||
public void UpdateState()
|
public void UpdateState()
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(IsOpened));
|
UpdateBackground();
|
||||||
OnPropertyChanged(nameof(Name));
|
OnPropertyChanged(nameof(Name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateBackground()
|
||||||
|
{
|
||||||
|
Avalonia.Application.Current.Styles.TryGetResource("ControlFillColorSecondary", out object color);
|
||||||
|
|
||||||
|
if (color is not null)
|
||||||
|
{
|
||||||
|
BackgroundColor = _profile.AccountState == AccountState.Open ? new SolidColorBrush((Color)color) : Brushes.Transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Recover(UserProfile userProfile)
|
public void Recover(UserProfile userProfile)
|
||||||
{
|
{
|
||||||
_owner.Navigate(typeof(UserEditor), (_owner, userProfile, true));
|
_owner.Navigate(typeof(UserEditorView), (_owner, userProfile, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1918,7 +1918,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||||
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
||||||
|
|
||||||
if (result != UserResult.Yes)
|
if (result == UserResult.Yes)
|
||||||
{
|
{
|
||||||
ConfigurationState.Instance.Logger.EnableTrace.Value = false;
|
ConfigurationState.Instance.Logger.EnableTrace.Value = false;
|
||||||
|
|
||||||
@ -1938,7 +1938,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||||
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
||||||
|
|
||||||
if (result != UserResult.Yes)
|
if (result == UserResult.Yes)
|
||||||
{
|
{
|
||||||
ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = "";
|
ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = "";
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public bool IsSoundIoEnabled { get; set; }
|
public bool IsSoundIoEnabled { get; set; }
|
||||||
public bool IsSDL2Enabled { get; set; }
|
public bool IsSDL2Enabled { get; set; }
|
||||||
public bool EnableCustomTheme { get; set; }
|
public bool EnableCustomTheme { get; set; }
|
||||||
public bool IsCustomResolutionScaleActive => _resolutionScale == 0;
|
public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
|
||||||
public bool IsVulkanSelected => GraphicsBackendIndex == 0;
|
public bool IsVulkanSelected => GraphicsBackendIndex == 0;
|
||||||
|
|
||||||
public string TimeZone { get; set; }
|
public string TimeZone { get; set; }
|
||||||
@ -311,25 +311,66 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
ConfigurationState config = ConfigurationState.Instance;
|
ConfigurationState config = ConfigurationState.Instance;
|
||||||
|
|
||||||
GameDirectories.Clear();
|
// User Interface
|
||||||
GameDirectories.AddRange(config.Ui.GameDirs.Value);
|
|
||||||
|
|
||||||
EnableDiscordIntegration = config.EnableDiscordIntegration;
|
EnableDiscordIntegration = config.EnableDiscordIntegration;
|
||||||
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
|
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
|
||||||
ShowConfirmExit = config.ShowConfirmExit;
|
ShowConfirmExit = config.ShowConfirmExit;
|
||||||
HideCursorOnIdle = config.HideCursorOnIdle;
|
HideCursorOnIdle = config.HideCursorOnIdle;
|
||||||
|
|
||||||
|
GameDirectories.Clear();
|
||||||
|
GameDirectories.AddRange(config.Ui.GameDirs.Value);
|
||||||
|
|
||||||
|
EnableCustomTheme = config.Ui.EnableCustomTheme;
|
||||||
|
CustomThemePath = config.Ui.CustomThemePath;
|
||||||
|
BaseStyleIndex = config.Ui.BaseStyle == "Light" ? 0 : 1;
|
||||||
|
|
||||||
|
// Input
|
||||||
EnableDockedMode = config.System.EnableDockedMode;
|
EnableDockedMode = config.System.EnableDockedMode;
|
||||||
EnableKeyboard = config.Hid.EnableKeyboard;
|
EnableKeyboard = config.Hid.EnableKeyboard;
|
||||||
EnableMouse = config.Hid.EnableMouse;
|
EnableMouse = config.Hid.EnableMouse;
|
||||||
|
|
||||||
|
// Keyboard Hotkeys
|
||||||
|
KeyboardHotkeys = config.Hid.Hotkeys.Value;
|
||||||
|
|
||||||
|
// System
|
||||||
|
Region = (int)config.System.Region.Value;
|
||||||
|
Language = (int)config.System.Language.Value;
|
||||||
|
TimeZone = config.System.TimeZone;
|
||||||
|
|
||||||
|
DateTime dateTimeOffset = DateTime.Now.AddSeconds(config.System.SystemTimeOffset);
|
||||||
|
|
||||||
|
DateOffset = dateTimeOffset.Date;
|
||||||
|
TimeOffset = dateTimeOffset.TimeOfDay;
|
||||||
EnableVsync = config.Graphics.EnableVsync;
|
EnableVsync = config.Graphics.EnableVsync;
|
||||||
EnablePptc = config.System.EnablePtc;
|
|
||||||
EnableInternetAccess = config.System.EnableInternetAccess;
|
|
||||||
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
|
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
|
||||||
IgnoreMissingServices = config.System.IgnoreMissingServices;
|
|
||||||
ExpandDramSize = config.System.ExpandRam;
|
ExpandDramSize = config.System.ExpandRam;
|
||||||
|
IgnoreMissingServices = config.System.IgnoreMissingServices;
|
||||||
|
|
||||||
|
// CPU
|
||||||
|
EnablePptc = config.System.EnablePtc;
|
||||||
|
MemoryMode = (int)config.System.MemoryManagerMode.Value;
|
||||||
|
|
||||||
|
// Graphics
|
||||||
|
GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value;
|
||||||
|
PreferredGpuIndex = _gpuIds.Contains(config.Graphics.PreferredGpu) ? _gpuIds.IndexOf(config.Graphics.PreferredGpu) : 0;
|
||||||
EnableShaderCache = config.Graphics.EnableShaderCache;
|
EnableShaderCache = config.Graphics.EnableShaderCache;
|
||||||
EnableTextureRecompression = config.Graphics.EnableTextureRecompression;
|
EnableTextureRecompression = config.Graphics.EnableTextureRecompression;
|
||||||
EnableMacroHLE = config.Graphics.EnableMacroHLE;
|
EnableMacroHLE = config.Graphics.EnableMacroHLE;
|
||||||
|
ResolutionScale = config.Graphics.ResScale == -1 ? 4 : config.Graphics.ResScale - 1;
|
||||||
|
CustomResolutionScale = config.Graphics.ResScaleCustom;
|
||||||
|
MaxAnisotropy = config.Graphics.MaxAnisotropy == -1 ? 0 : (int)(MathF.Log2(config.Graphics.MaxAnisotropy));
|
||||||
|
AspectRatio = (int)config.Graphics.AspectRatio.Value;
|
||||||
|
GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value;
|
||||||
|
ShaderDumpPath = config.Graphics.ShadersDumpPath;
|
||||||
|
|
||||||
|
// Audio
|
||||||
|
AudioBackend = (int)config.System.AudioBackend.Value;
|
||||||
|
Volume = config.System.AudioVolume * 100;
|
||||||
|
|
||||||
|
// Network
|
||||||
|
EnableInternetAccess = config.System.EnableInternetAccess;
|
||||||
|
|
||||||
|
// Logging
|
||||||
EnableFileLog = config.Logger.EnableFileLog;
|
EnableFileLog = config.Logger.EnableFileLog;
|
||||||
EnableStub = config.Logger.EnableStub;
|
EnableStub = config.Logger.EnableStub;
|
||||||
EnableInfo = config.Logger.EnableInfo;
|
EnableInfo = config.Logger.EnableInfo;
|
||||||
@ -339,94 +380,69 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
EnableGuest = config.Logger.EnableGuest;
|
EnableGuest = config.Logger.EnableGuest;
|
||||||
EnableDebug = config.Logger.EnableDebug;
|
EnableDebug = config.Logger.EnableDebug;
|
||||||
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
|
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
|
||||||
EnableCustomTheme = config.Ui.EnableCustomTheme;
|
|
||||||
Volume = config.System.AudioVolume * 100;
|
|
||||||
|
|
||||||
GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value;
|
|
||||||
|
|
||||||
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
|
||||||
|
|
||||||
TimeZone = config.System.TimeZone;
|
|
||||||
ShaderDumpPath = config.Graphics.ShadersDumpPath;
|
|
||||||
CustomThemePath = config.Ui.CustomThemePath;
|
|
||||||
BaseStyleIndex = config.Ui.BaseStyle == "Light" ? 0 : 1;
|
|
||||||
GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value;
|
|
||||||
|
|
||||||
PreferredGpuIndex = _gpuIds.Contains(config.Graphics.PreferredGpu) ? _gpuIds.IndexOf(config.Graphics.PreferredGpu) : 0;
|
|
||||||
|
|
||||||
Language = (int)config.System.Language.Value;
|
|
||||||
Region = (int)config.System.Region.Value;
|
|
||||||
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
||||||
AudioBackend = (int)config.System.AudioBackend.Value;
|
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
||||||
MemoryMode = (int)config.System.MemoryManagerMode.Value;
|
|
||||||
|
|
||||||
float anisotropy = config.Graphics.MaxAnisotropy;
|
|
||||||
|
|
||||||
MaxAnisotropy = anisotropy == -1 ? 0 : (int)(MathF.Log2(anisotropy));
|
|
||||||
AspectRatio = (int)config.Graphics.AspectRatio.Value;
|
|
||||||
|
|
||||||
int resolution = config.Graphics.ResScale;
|
|
||||||
|
|
||||||
ResolutionScale = resolution == -1 ? 0 : resolution;
|
|
||||||
CustomResolutionScale = config.Graphics.ResScaleCustom;
|
|
||||||
|
|
||||||
DateTime dateTimeOffset = DateTime.Now.AddSeconds(config.System.SystemTimeOffset);
|
|
||||||
|
|
||||||
DateOffset = dateTimeOffset.Date;
|
|
||||||
TimeOffset = dateTimeOffset.TimeOfDay;
|
|
||||||
|
|
||||||
KeyboardHotkeys = config.Hid.Hotkeys.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveSettings()
|
public void SaveSettings()
|
||||||
{
|
{
|
||||||
ConfigurationState config = ConfigurationState.Instance;
|
ConfigurationState config = ConfigurationState.Instance;
|
||||||
|
|
||||||
|
// User Interface
|
||||||
|
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
|
||||||
|
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
|
||||||
|
config.ShowConfirmExit.Value = ShowConfirmExit;
|
||||||
|
config.HideCursorOnIdle.Value = HideCursorOnIdle;
|
||||||
|
|
||||||
if (_directoryChanged)
|
if (_directoryChanged)
|
||||||
{
|
{
|
||||||
List<string> gameDirs = new List<string>(GameDirectories);
|
List<string> gameDirs = new(GameDirectories);
|
||||||
config.Ui.GameDirs.Value = gameDirs;
|
config.Ui.GameDirs.Value = gameDirs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.Ui.EnableCustomTheme.Value = EnableCustomTheme;
|
||||||
|
config.Ui.CustomThemePath.Value = CustomThemePath;
|
||||||
|
config.Ui.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark";
|
||||||
|
|
||||||
|
// Input
|
||||||
|
config.System.EnableDockedMode.Value = EnableDockedMode;
|
||||||
|
config.Hid.EnableKeyboard.Value = EnableKeyboard;
|
||||||
|
config.Hid.EnableMouse.Value = EnableMouse;
|
||||||
|
|
||||||
|
// Keyboard Hotkeys
|
||||||
|
config.Hid.Hotkeys.Value = KeyboardHotkeys;
|
||||||
|
|
||||||
|
// System
|
||||||
|
config.System.Region.Value = (Region)Region;
|
||||||
|
config.System.Language.Value = (Language)Language;
|
||||||
|
|
||||||
if (_validTzRegions.Contains(TimeZone))
|
if (_validTzRegions.Contains(TimeZone))
|
||||||
{
|
{
|
||||||
config.System.TimeZone.Value = TimeZone;
|
config.System.TimeZone.Value = TimeZone;
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Logger.EnableError.Value = EnableError;
|
TimeSpan systemTimeOffset = DateOffset - DateTime.Now;
|
||||||
config.Logger.EnableTrace.Value = EnableTrace;
|
|
||||||
config.Logger.EnableWarn.Value = EnableWarn;
|
config.System.SystemTimeOffset.Value = systemTimeOffset.Seconds;
|
||||||
config.Logger.EnableInfo.Value = EnableInfo;
|
|
||||||
config.Logger.EnableStub.Value = EnableStub;
|
|
||||||
config.Logger.EnableDebug.Value = EnableDebug;
|
|
||||||
config.Logger.EnableGuest.Value = EnableGuest;
|
|
||||||
config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog;
|
|
||||||
config.Logger.EnableFileLog.Value = EnableFileLog;
|
|
||||||
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
|
||||||
config.System.EnableDockedMode.Value = EnableDockedMode;
|
|
||||||
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
|
|
||||||
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
|
|
||||||
config.ShowConfirmExit.Value = ShowConfirmExit;
|
|
||||||
config.HideCursorOnIdle.Value = HideCursorOnIdle;
|
|
||||||
config.Graphics.EnableVsync.Value = EnableVsync;
|
config.Graphics.EnableVsync.Value = EnableVsync;
|
||||||
|
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
|
||||||
|
config.System.ExpandRam.Value = ExpandDramSize;
|
||||||
|
config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
|
||||||
|
|
||||||
|
// CPU
|
||||||
|
config.System.EnablePtc.Value = EnablePptc;
|
||||||
|
config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode;
|
||||||
|
|
||||||
|
// Graphics
|
||||||
|
config.Graphics.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex;
|
||||||
|
config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex);
|
||||||
config.Graphics.EnableShaderCache.Value = EnableShaderCache;
|
config.Graphics.EnableShaderCache.Value = EnableShaderCache;
|
||||||
config.Graphics.EnableTextureRecompression.Value = EnableTextureRecompression;
|
config.Graphics.EnableTextureRecompression.Value = EnableTextureRecompression;
|
||||||
config.Graphics.EnableMacroHLE.Value = EnableMacroHLE;
|
config.Graphics.EnableMacroHLE.Value = EnableMacroHLE;
|
||||||
config.Graphics.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex;
|
config.Graphics.ResScale.Value = ResolutionScale == 4 ? -1 : ResolutionScale + 1;
|
||||||
config.System.EnablePtc.Value = EnablePptc;
|
config.Graphics.ResScaleCustom.Value = CustomResolutionScale;
|
||||||
config.System.EnableInternetAccess.Value = EnableInternetAccess;
|
config.Graphics.MaxAnisotropy.Value = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy);
|
||||||
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
|
config.Graphics.AspectRatio.Value = (AspectRatio)AspectRatio;
|
||||||
config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
|
|
||||||
config.System.ExpandRam.Value = ExpandDramSize;
|
|
||||||
config.Hid.EnableKeyboard.Value = EnableKeyboard;
|
|
||||||
config.Hid.EnableMouse.Value = EnableMouse;
|
|
||||||
config.Ui.CustomThemePath.Value = CustomThemePath;
|
|
||||||
config.Ui.EnableCustomTheme.Value = EnableCustomTheme;
|
|
||||||
config.Ui.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark";
|
|
||||||
config.System.Language.Value = (Language)Language;
|
|
||||||
config.System.Region.Value = (Region)Region;
|
|
||||||
|
|
||||||
config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex);
|
|
||||||
|
|
||||||
if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex)
|
if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex)
|
||||||
{
|
{
|
||||||
@ -434,22 +450,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
|
|
||||||
config.Graphics.BackendThreading.Value = (BackendThreading)GraphicsBackendMultithreadingIndex;
|
config.Graphics.BackendThreading.Value = (BackendThreading)GraphicsBackendMultithreadingIndex;
|
||||||
|
|
||||||
TimeSpan systemTimeOffset = DateOffset - DateTime.Now;
|
|
||||||
|
|
||||||
config.System.SystemTimeOffset.Value = systemTimeOffset.Seconds;
|
|
||||||
config.Graphics.ShadersDumpPath.Value = ShaderDumpPath;
|
config.Graphics.ShadersDumpPath.Value = ShaderDumpPath;
|
||||||
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
|
||||||
config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode;
|
|
||||||
|
|
||||||
float anisotropy = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy);
|
|
||||||
|
|
||||||
config.Graphics.MaxAnisotropy.Value = anisotropy;
|
|
||||||
config.Graphics.AspectRatio.Value = (AspectRatio)AspectRatio;
|
|
||||||
config.Graphics.ResScale.Value = ResolutionScale == 0 ? -1 : ResolutionScale;
|
|
||||||
config.Graphics.ResScaleCustom.Value = CustomResolutionScale;
|
|
||||||
config.System.AudioVolume.Value = Volume / 100;
|
|
||||||
|
|
||||||
|
// Audio
|
||||||
AudioBackend audioBackend = (AudioBackend)AudioBackend;
|
AudioBackend audioBackend = (AudioBackend)AudioBackend;
|
||||||
if (audioBackend != config.System.AudioBackend.Value)
|
if (audioBackend != config.System.AudioBackend.Value)
|
||||||
{
|
{
|
||||||
@ -458,7 +461,23 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
Logger.Info?.Print(LogClass.Application, $"AudioBackend toggled to: {audioBackend}");
|
Logger.Info?.Print(LogClass.Application, $"AudioBackend toggled to: {audioBackend}");
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Hid.Hotkeys.Value = KeyboardHotkeys;
|
config.System.AudioVolume.Value = Volume / 100;
|
||||||
|
|
||||||
|
// Network
|
||||||
|
config.System.EnableInternetAccess.Value = EnableInternetAccess;
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
config.Logger.EnableFileLog.Value = EnableFileLog;
|
||||||
|
config.Logger.EnableStub.Value = EnableStub;
|
||||||
|
config.Logger.EnableInfo.Value = EnableInfo;
|
||||||
|
config.Logger.EnableWarn.Value = EnableWarn;
|
||||||
|
config.Logger.EnableError.Value = EnableError;
|
||||||
|
config.Logger.EnableTrace.Value = EnableTrace;
|
||||||
|
config.Logger.EnableGuest.Value = EnableGuest;
|
||||||
|
config.Logger.EnableDebug.Value = EnableDebug;
|
||||||
|
config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog;
|
||||||
|
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
||||||
|
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
||||||
|
|
||||||
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||||
|
|
||||||
|
230
Ryujinx.Ava/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs
Normal file
230
Ryujinx.Ava/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
using Avalonia.Media;
|
||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.Fs;
|
||||||
|
using LibHac.Fs.Fsa;
|
||||||
|
using LibHac.FsSystem;
|
||||||
|
using LibHac.Ncm;
|
||||||
|
using LibHac.Tools.Fs;
|
||||||
|
using LibHac.Tools.FsSystem;
|
||||||
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using Ryujinx.Ava.UI.Models;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using System;
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.IO;
|
||||||
|
using Color = Avalonia.Media.Color;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
{
|
||||||
|
internal class UserFirmwareAvatarSelectorViewModel : BaseModel
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<string, byte[]> _avatarStore = new();
|
||||||
|
|
||||||
|
private ObservableCollection<ProfileImageModel> _images;
|
||||||
|
private Color _backgroundColor = Colors.White;
|
||||||
|
|
||||||
|
private int _selectedIndex;
|
||||||
|
private byte[] _selectedImage;
|
||||||
|
|
||||||
|
public UserFirmwareAvatarSelectorViewModel()
|
||||||
|
{
|
||||||
|
_images = new ObservableCollection<ProfileImageModel>();
|
||||||
|
|
||||||
|
LoadImagesFromStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color BackgroundColor
|
||||||
|
{
|
||||||
|
get => _backgroundColor;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_backgroundColor = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
ChangeImageBackground();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<ProfileImageModel> Images
|
||||||
|
{
|
||||||
|
get => _images;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_images = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SelectedIndex
|
||||||
|
{
|
||||||
|
get => _selectedIndex;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_selectedIndex = value;
|
||||||
|
|
||||||
|
if (_selectedIndex == -1)
|
||||||
|
{
|
||||||
|
SelectedImage = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SelectedImage = _images[_selectedIndex].Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] SelectedImage
|
||||||
|
{
|
||||||
|
get => _selectedImage;
|
||||||
|
private set => _selectedImage = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadImagesFromStore()
|
||||||
|
{
|
||||||
|
Images.Clear();
|
||||||
|
|
||||||
|
foreach (var image in _avatarStore)
|
||||||
|
{
|
||||||
|
Images.Add(new ProfileImageModel(image.Key, image.Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ChangeImageBackground()
|
||||||
|
{
|
||||||
|
foreach (var image in Images)
|
||||||
|
{
|
||||||
|
image.BackgroundColor = new SolidColorBrush(BackgroundColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PreloadAvatars(ContentManager contentManager, VirtualFileSystem virtualFileSystem)
|
||||||
|
{
|
||||||
|
if (_avatarStore.Count > 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string contentPath = contentManager.GetInstalledContentPath(0x010000000000080A, StorageId.BuiltInSystem, NcaContentType.Data);
|
||||||
|
string avatarPath = virtualFileSystem.SwitchPathToSystemPath(contentPath);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(avatarPath))
|
||||||
|
{
|
||||||
|
using (IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open))
|
||||||
|
{
|
||||||
|
Nca nca = new(virtualFileSystem.KeySet, ncaFileStream);
|
||||||
|
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
|
||||||
|
|
||||||
|
foreach (DirectoryEntryEx item in romfs.EnumerateEntries())
|
||||||
|
{
|
||||||
|
// TODO: Parse DatabaseInfo.bin and table.bin files for more accuracy.
|
||||||
|
if (item.Type == DirectoryEntryType.File && item.FullPath.Contains("chara") && item.FullPath.Contains("szs"))
|
||||||
|
{
|
||||||
|
using var file = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
romfs.OpenFile(ref file.Ref(), ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
using (MemoryStream stream = new())
|
||||||
|
using (MemoryStream streamPng = new())
|
||||||
|
{
|
||||||
|
file.Get.AsStream().CopyTo(stream);
|
||||||
|
|
||||||
|
stream.Position = 0;
|
||||||
|
|
||||||
|
Image avatarImage = Image.LoadPixelData<Rgba32>(DecompressYaz0(stream), 256, 256);
|
||||||
|
|
||||||
|
avatarImage.SaveAsPng(streamPng);
|
||||||
|
|
||||||
|
_avatarStore.Add(item.FullPath, streamPng.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] DecompressYaz0(Stream stream)
|
||||||
|
{
|
||||||
|
using (BinaryReader reader = new(stream))
|
||||||
|
{
|
||||||
|
reader.ReadInt32(); // Magic
|
||||||
|
|
||||||
|
uint decodedLength = BinaryPrimitives.ReverseEndianness(reader.ReadUInt32());
|
||||||
|
|
||||||
|
reader.ReadInt64(); // Padding
|
||||||
|
|
||||||
|
byte[] input = new byte[stream.Length - stream.Position];
|
||||||
|
stream.Read(input, 0, input.Length);
|
||||||
|
|
||||||
|
uint inputOffset = 0;
|
||||||
|
|
||||||
|
byte[] output = new byte[decodedLength];
|
||||||
|
uint outputOffset = 0;
|
||||||
|
|
||||||
|
ushort mask = 0;
|
||||||
|
byte header = 0;
|
||||||
|
|
||||||
|
while (outputOffset < decodedLength)
|
||||||
|
{
|
||||||
|
if ((mask >>= 1) == 0)
|
||||||
|
{
|
||||||
|
header = input[inputOffset++];
|
||||||
|
mask = 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((header & mask) != 0)
|
||||||
|
{
|
||||||
|
if (outputOffset == output.Length)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
output[outputOffset++] = input[inputOffset++];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
byte byte1 = input[inputOffset++];
|
||||||
|
byte byte2 = input[inputOffset++];
|
||||||
|
|
||||||
|
uint dist = (uint)((byte1 & 0xF) << 8) | byte2;
|
||||||
|
uint position = outputOffset - (dist + 1);
|
||||||
|
|
||||||
|
uint length = (uint)byte1 >> 4;
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
length = (uint)input[inputOffset++] + 0x12;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
length += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint gap = outputOffset - position;
|
||||||
|
uint nonOverlappingLength = length;
|
||||||
|
|
||||||
|
if (nonOverlappingLength > gap)
|
||||||
|
{
|
||||||
|
nonOverlappingLength = gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer.BlockCopy(output, (int)position, output, (int)outputOffset, (int)nonOverlappingLength);
|
||||||
|
outputOffset += nonOverlappingLength;
|
||||||
|
position += nonOverlappingLength;
|
||||||
|
length -= nonOverlappingLength;
|
||||||
|
|
||||||
|
while (length-- > 0)
|
||||||
|
{
|
||||||
|
output[outputOffset++] = output[position++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
{
|
||||||
|
internal class UserProfileImageSelectorViewModel : BaseModel
|
||||||
|
{
|
||||||
|
private bool _firmwareFound;
|
||||||
|
|
||||||
|
public bool FirmwareFound
|
||||||
|
{
|
||||||
|
get => _firmwareFound;
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_firmwareFound = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,215 +1,25 @@
|
|||||||
using Avalonia;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Avalonia.Threading;
|
|
||||||
using FluentAvalonia.UI.Controls;
|
|
||||||
using LibHac.Common;
|
|
||||||
using LibHac.Fs;
|
|
||||||
using LibHac.Fs.Shim;
|
|
||||||
using Ryujinx.Ava.Common.Locale;
|
|
||||||
using Ryujinx.Ava.UI.Controls;
|
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
|
||||||
using UserId = Ryujinx.HLE.HOS.Services.Account.Acc.UserId;
|
|
||||||
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
public class UserProfileViewModel : BaseModel, IDisposable
|
public class UserProfileViewModel : BaseModel, IDisposable
|
||||||
{
|
{
|
||||||
private readonly NavigationDialogHost _owner;
|
|
||||||
|
|
||||||
private UserProfile _selectedProfile;
|
|
||||||
private UserProfile _highlightedProfile;
|
|
||||||
|
|
||||||
public UserProfileViewModel()
|
public UserProfileViewModel()
|
||||||
{
|
{
|
||||||
Profiles = new ObservableCollection<UserProfile>();
|
Profiles = new ObservableCollection<BaseModel>();
|
||||||
LostProfiles = new ObservableCollection<UserProfile>();
|
LostProfiles = new ObservableCollection<UserProfile>();
|
||||||
|
IsEmpty = LostProfiles.IsNullOrEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserProfileViewModel(NavigationDialogHost owner) : this()
|
public ObservableCollection<BaseModel> Profiles { get; set; }
|
||||||
{
|
|
||||||
_owner = owner;
|
|
||||||
|
|
||||||
LoadProfiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableCollection<UserProfile> Profiles { get; set; }
|
|
||||||
|
|
||||||
public ObservableCollection<UserProfile> LostProfiles { get; set; }
|
public ObservableCollection<UserProfile> LostProfiles { get; set; }
|
||||||
|
|
||||||
public UserProfile SelectedProfile
|
public bool IsEmpty { get; set; }
|
||||||
{
|
|
||||||
get => _selectedProfile;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_selectedProfile = value;
|
|
||||||
|
|
||||||
OnPropertyChanged();
|
|
||||||
OnPropertyChanged(nameof(IsHighlightedProfileDeletable));
|
|
||||||
OnPropertyChanged(nameof(IsHighlightedProfileEditable));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsHighlightedProfileEditable => _highlightedProfile != null;
|
|
||||||
|
|
||||||
public bool IsHighlightedProfileDeletable => _highlightedProfile != null && _highlightedProfile.UserId != AccountManager.DefaultUserId;
|
|
||||||
|
|
||||||
public UserProfile HighlightedProfile
|
|
||||||
{
|
|
||||||
get => _highlightedProfile;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_highlightedProfile = value;
|
|
||||||
|
|
||||||
OnPropertyChanged();
|
|
||||||
OnPropertyChanged(nameof(IsHighlightedProfileDeletable));
|
|
||||||
OnPropertyChanged(nameof(IsHighlightedProfileEditable));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() { }
|
public void Dispose() { }
|
||||||
|
|
||||||
public void LoadProfiles()
|
|
||||||
{
|
|
||||||
Profiles.Clear();
|
|
||||||
LostProfiles.Clear();
|
|
||||||
|
|
||||||
var profiles = _owner.AccountManager.GetAllUsers().OrderByDescending(x => x.AccountState == AccountState.Open);
|
|
||||||
|
|
||||||
foreach (var profile in profiles)
|
|
||||||
{
|
|
||||||
Profiles.Add(new UserProfile(profile, _owner));
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectedProfile = Profiles.FirstOrDefault(x => x.UserId == _owner.AccountManager.LastOpenedUser.UserId);
|
|
||||||
|
|
||||||
if (SelectedProfile == null)
|
|
||||||
{
|
|
||||||
SelectedProfile = Profiles.First();
|
|
||||||
|
|
||||||
if (SelectedProfile != null)
|
|
||||||
{
|
|
||||||
_owner.AccountManager.OpenUser(_selectedProfile.UserId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account,
|
|
||||||
default, saveDataId: default, index: default);
|
|
||||||
|
|
||||||
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
|
|
||||||
|
|
||||||
_owner.HorizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
|
|
||||||
|
|
||||||
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
|
||||||
|
|
||||||
HashSet<UserId> lostAccounts = new HashSet<UserId>();
|
|
||||||
|
|
||||||
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];
|
|
||||||
var id = new UserId((long)save.UserId.Id.Low, (long)save.UserId.Id.High);
|
|
||||||
if (Profiles.FirstOrDefault( x=> x.UserId == id) == null)
|
|
||||||
{
|
|
||||||
lostAccounts.Add(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(var account in lostAccounts)
|
|
||||||
{
|
|
||||||
LostProfiles.Add(new UserProfile(new HLE.HOS.Services.Account.Acc.UserProfile(account, "", null), _owner));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddUser()
|
|
||||||
{
|
|
||||||
UserProfile userProfile = null;
|
|
||||||
|
|
||||||
_owner.Navigate(typeof(UserEditor), (this._owner, userProfile, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void ManageSaves()
|
|
||||||
{
|
|
||||||
UserProfile userProfile = _highlightedProfile ?? SelectedProfile;
|
|
||||||
|
|
||||||
SaveManager manager = new SaveManager(userProfile, _owner.HorizonClient, _owner.VirtualFileSystem);
|
|
||||||
|
|
||||||
ContentDialog contentDialog = new ContentDialog
|
|
||||||
{
|
|
||||||
Title = string.Format(LocaleManager.Instance[LocaleKeys.SaveManagerHeading], userProfile.Name),
|
|
||||||
PrimaryButtonText = "",
|
|
||||||
SecondaryButtonText = "",
|
|
||||||
CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose],
|
|
||||||
Content = manager,
|
|
||||||
Padding = new Thickness(0)
|
|
||||||
};
|
|
||||||
|
|
||||||
await contentDialog.ShowAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EditUser()
|
|
||||||
{
|
|
||||||
_owner.Navigate(typeof(UserEditor), (this._owner, _highlightedProfile ?? SelectedProfile, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void DeleteUser()
|
|
||||||
{
|
|
||||||
if (_highlightedProfile != null)
|
|
||||||
{
|
|
||||||
var lastUserId = _owner.AccountManager.LastOpenedUser.UserId;
|
|
||||||
|
|
||||||
if (_highlightedProfile.UserId == lastUserId)
|
|
||||||
{
|
|
||||||
// If we are deleting the currently open profile, then we must open something else before deleting.
|
|
||||||
var profile = Profiles.FirstOrDefault(x => x.UserId != lastUserId);
|
|
||||||
|
|
||||||
if (profile == null)
|
|
||||||
{
|
|
||||||
Dispatcher.UIThread.Post(async () =>
|
|
||||||
{
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionWarningMessage]);
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_owner.AccountManager.OpenUser(profile.UserId);
|
|
||||||
}
|
|
||||||
|
|
||||||
var result =
|
|
||||||
await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionConfirmMessage], "",
|
|
||||||
LocaleManager.Instance[LocaleKeys.InputDialogYes], LocaleManager.Instance[LocaleKeys.InputDialogNo], "");
|
|
||||||
|
|
||||||
if (result == UserResult.Yes)
|
|
||||||
{
|
|
||||||
_owner.AccountManager.DeleteUser(_highlightedProfile.UserId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadProfiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void GoBack()
|
|
||||||
{
|
|
||||||
_owner.GoBack();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RecoverLostAccounts()
|
|
||||||
{
|
|
||||||
_owner.Navigate(typeof(UserRecoverer), (this._owner, this));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
121
Ryujinx.Ava/UI/ViewModels/UserSaveManagerViewModel.cs
Normal file
121
Ryujinx.Ava/UI/ViewModels/UserSaveManagerViewModel.cs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
using DynamicData;
|
||||||
|
using DynamicData.Binding;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.UI.Models;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
{
|
||||||
|
public class UserSaveManagerViewModel : BaseModel
|
||||||
|
{
|
||||||
|
private int _sortIndex;
|
||||||
|
private int _orderIndex;
|
||||||
|
private string _search;
|
||||||
|
private ObservableCollection<SaveModel> _saves = new();
|
||||||
|
private ObservableCollection<SaveModel> _views = new();
|
||||||
|
private AccountManager _accountManager;
|
||||||
|
|
||||||
|
public string SaveManagerHeading =>
|
||||||
|
string.Format(LocaleManager.Instance[LocaleKeys.SaveManagerHeading], _accountManager.LastOpenedUser.Name, _accountManager.LastOpenedUser.UserId);
|
||||||
|
|
||||||
|
public int SortIndex
|
||||||
|
{
|
||||||
|
get => _sortIndex;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_sortIndex = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int OrderIndex
|
||||||
|
{
|
||||||
|
get => _orderIndex;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_orderIndex = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Search
|
||||||
|
{
|
||||||
|
get => _search;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_search = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<SaveModel> Saves
|
||||||
|
{
|
||||||
|
get => _saves;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_saves = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<SaveModel> Views
|
||||||
|
{
|
||||||
|
get => _views;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_views = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserSaveManagerViewModel(AccountManager accountManager)
|
||||||
|
{
|
||||||
|
_accountManager = accountManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Sort()
|
||||||
|
{
|
||||||
|
Saves.AsObservableChangeSet()
|
||||||
|
.Filter(Filter)
|
||||||
|
.Sort(GetComparer())
|
||||||
|
.Bind(out var view).AsObservableList();
|
||||||
|
|
||||||
|
_views.Clear();
|
||||||
|
_views.AddRange(view);
|
||||||
|
OnPropertyChanged(nameof(Views));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Filter(object arg)
|
||||||
|
{
|
||||||
|
if (arg is SaveModel save)
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(_search) || save.Title.ToLower().Contains(_search.ToLower());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -74,7 +74,6 @@
|
|||||||
Margin="5,0,5,0"
|
Margin="5,0,5,0"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
VerticalContentAlignment="Center"
|
|
||||||
DockPanel.Dock="Right"
|
DockPanel.Dock="Right"
|
||||||
KeyUp="SearchBox_OnKeyUp"
|
KeyUp="SearchBox_OnKeyUp"
|
||||||
Text="{Binding SearchText}"
|
Text="{Binding SearchText}"
|
||||||
|
@ -82,9 +82,6 @@
|
|||||||
Width="350"
|
Width="350"
|
||||||
HorizontalContentAlignment="Left"
|
HorizontalContentAlignment="Left"
|
||||||
ToolTip.Tip="{locale:Locale ResolutionScaleTooltip}">
|
ToolTip.Tip="{locale:Locale ResolutionScaleTooltip}">
|
||||||
<ComboBoxItem>
|
|
||||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleCustom}" />
|
|
||||||
</ComboBoxItem>
|
|
||||||
<ComboBoxItem>
|
<ComboBoxItem>
|
||||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleNative}" />
|
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleNative}" />
|
||||||
</ComboBoxItem>
|
</ComboBoxItem>
|
||||||
@ -97,6 +94,9 @@
|
|||||||
<ComboBoxItem>
|
<ComboBoxItem>
|
||||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale4x}" />
|
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale4x}" />
|
||||||
</ComboBoxItem>
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleCustom}" />
|
||||||
|
</ComboBoxItem>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
<ui:NumberBox
|
<ui:NumberBox
|
||||||
Margin="10,0,0,0"
|
Margin="10,0,0,0"
|
||||||
|
@ -47,31 +47,34 @@
|
|||||||
ToolTip.Tip="{locale:Locale ErrorLogTooltip}">
|
ToolTip.Tip="{locale:Locale ErrorLogTooltip}">
|
||||||
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableErrorLogs}" />
|
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableErrorLogs}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
<CheckBox IsChecked="{Binding EnableTrace}"
|
|
||||||
ToolTip.Tip="{locale:Locale TraceLogTooltip}">
|
|
||||||
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableTraceLogs}" />
|
|
||||||
</CheckBox>
|
|
||||||
<CheckBox IsChecked="{Binding EnableGuest}"
|
<CheckBox IsChecked="{Binding EnableGuest}"
|
||||||
ToolTip.Tip="{locale:Locale GuestLogTooltip}">
|
ToolTip.Tip="{locale:Locale GuestLogTooltip}">
|
||||||
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableGuestLogs}" />
|
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableGuestLogs}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Separator Height="1" />
|
<Separator Height="1" />
|
||||||
|
<StackPanel Orientation="Vertical" Spacing="2">
|
||||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabLoggingDeveloperOptions}" />
|
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabLoggingDeveloperOptions}" />
|
||||||
|
<TextBlock Foreground="{DynamicResource SecondaryTextColor}" Text="{locale:Locale SettingsTabLoggingDeveloperOptionsNote}" />
|
||||||
|
</StackPanel>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Margin="10,0,0,0"
|
Margin="10,0,0,0"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Orientation="Vertical"
|
Orientation="Vertical"
|
||||||
Spacing="10">
|
Spacing="10">
|
||||||
<StackPanel Orientation="Vertical">
|
<StackPanel Orientation="Vertical">
|
||||||
<CheckBox IsChecked="{Binding EnableDebug}"
|
<CheckBox IsChecked="{Binding EnableTrace}"
|
||||||
ToolTip.Tip="{locale:Locale DebugLogTooltip}">
|
ToolTip.Tip="{locale:Locale TraceLogTooltip}">
|
||||||
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableDebugLogs}" />
|
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableTraceLogs}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
<CheckBox IsChecked="{Binding EnableFsAccessLog}"
|
<CheckBox IsChecked="{Binding EnableFsAccessLog}"
|
||||||
ToolTip.Tip="{locale:Locale FileAccessLogTooltip}">
|
ToolTip.Tip="{locale:Locale FileAccessLogTooltip}">
|
||||||
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableFsAccessLogs}" />
|
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableFsAccessLogs}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
|
<CheckBox IsChecked="{Binding EnableDebug}"
|
||||||
|
ToolTip.Tip="{locale:Locale DebugLogTooltip}">
|
||||||
|
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableDebugLogs}" />
|
||||||
|
</CheckBox>
|
||||||
<StackPanel Margin="0,10,0,0" Orientation="Horizontal" VerticalAlignment="Stretch">
|
<StackPanel Margin="0,10,0,0" Orientation="Horizontal" VerticalAlignment="Stretch">
|
||||||
<TextBlock VerticalAlignment="Center"
|
<TextBlock VerticalAlignment="Center"
|
||||||
ToolTip.Tip="{locale:Locale FSAccessLogModeTooltip}"
|
ToolTip.Tip="{locale:Locale FSAccessLogModeTooltip}"
|
||||||
|
@ -172,9 +172,9 @@
|
|||||||
</CheckBox>
|
</CheckBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Separator Height="1" />
|
<Separator Height="1" />
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Vertical" Spacing="2">
|
||||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabSystemHacks}" />
|
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabSystemHacks}" />
|
||||||
<TextBlock Text="{locale:Locale SettingsTabSystemHacksNote}" />
|
<TextBlock Foreground="{DynamicResource SecondaryTextColor}" Text="{locale:Locale SettingsTabSystemHacksNote}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Margin="10,0,0,0"
|
Margin="10,0,0,0"
|
||||||
|
@ -1,16 +1,20 @@
|
|||||||
<UserControl
|
<UserControl
|
||||||
x:Class="Ryujinx.Ava.UI.Controls.UserEditor"
|
x:Class="Ryujinx.Ava.UI.Views.User.UserEditorView"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||||
|
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||||
Margin="0"
|
Margin="0"
|
||||||
MinWidth="500"
|
MinWidth="500"
|
||||||
Padding="0"
|
Padding="0"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Focusable="True">
|
Focusable="True"
|
||||||
|
x:CompileBindings="True"
|
||||||
|
x:DataType="models:TempProfile">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
@ -23,35 +27,9 @@
|
|||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<StackPanel
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
VerticalAlignment="Stretch"
|
|
||||||
Orientation="Vertical">
|
|
||||||
<Image
|
|
||||||
Name="ProfileImage"
|
|
||||||
Width="96"
|
|
||||||
Height="96"
|
|
||||||
Margin="0"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
VerticalAlignment="Top"
|
|
||||||
Source="{Binding Image, Converter={StaticResource ByteImage}}" />
|
|
||||||
<Button
|
|
||||||
Name="ChangePictureButton"
|
|
||||||
Margin="5"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Click="ChangePictureButton_Click"
|
|
||||||
Content="{locale:Locale UserProfilesChangeProfileImage}" />
|
|
||||||
<Button
|
|
||||||
Name="AddPictureButton"
|
|
||||||
Margin="5"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Click="ChangePictureButton_Click"
|
|
||||||
Content="{locale:Locale UserProfilesSetProfileImage}" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="1"
|
Grid.Column="0"
|
||||||
Margin="5,10"
|
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Orientation="Vertical"
|
Orientation="Vertical"
|
||||||
Spacing="10">
|
Spacing="10">
|
||||||
@ -61,9 +39,60 @@
|
|||||||
Width="300"
|
Width="300"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
MaxLength="{Binding MaxProfileNameLength}"
|
MaxLength="{Binding MaxProfileNameLength}"
|
||||||
|
Watermark="{locale:Locale ProfileNameSelectionWatermark}"
|
||||||
Text="{Binding Name}" />
|
Text="{Binding Name}" />
|
||||||
<TextBlock Name="IdText" Text="{locale:Locale UserProfilesUserId}" />
|
<TextBlock Name="IdText" Text="{locale:Locale UserProfilesUserId}" />
|
||||||
<TextBlock Name="IdLabel" Text="{Binding UserId}" />
|
<TextBox
|
||||||
|
Name="IdLabel"
|
||||||
|
Width="300"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Text="{Binding UserIdString}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Orientation="Vertical">
|
||||||
|
<Border
|
||||||
|
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||||
|
BorderThickness="1">
|
||||||
|
<Panel>
|
||||||
|
<ui:SymbolIcon
|
||||||
|
FontSize="60"
|
||||||
|
Width="96"
|
||||||
|
Height="96"
|
||||||
|
Margin="0"
|
||||||
|
Foreground="{DynamicResource AppListHoverBackgroundColor}"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Symbol="Camera" />
|
||||||
|
<Image
|
||||||
|
Name="ProfileImage"
|
||||||
|
Width="96"
|
||||||
|
Height="96"
|
||||||
|
Margin="0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Source="{Binding Image, Converter={StaticResource ByteImage}}" />
|
||||||
|
</Panel>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Margin="0 24 0 0"
|
||||||
|
Spacing="10">
|
||||||
|
<Button
|
||||||
|
Width="50"
|
||||||
|
MinWidth="50"
|
||||||
|
Click="BackButton_Click">
|
||||||
|
<ui:SymbolIcon Symbol="Back" />
|
||||||
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
@ -71,16 +100,24 @@
|
|||||||
Grid.ColumnSpan="2"
|
Grid.ColumnSpan="2"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
|
Margin="0 24 0 0"
|
||||||
Spacing="10">
|
Spacing="10">
|
||||||
|
<Button
|
||||||
|
Name="DeleteButton"
|
||||||
|
Click="DeleteButton_Click"
|
||||||
|
Content="{locale:Locale UserProfilesDelete}" />
|
||||||
|
<Button
|
||||||
|
Name="ChangePictureButton"
|
||||||
|
Click="ChangePictureButton_Click"
|
||||||
|
Content="{locale:Locale UserProfilesChangeProfileImage}" />
|
||||||
|
<Button
|
||||||
|
Name="AddPictureButton"
|
||||||
|
Click="ChangePictureButton_Click"
|
||||||
|
Content="{locale:Locale UserProfilesSetProfileImage}" />
|
||||||
<Button
|
<Button
|
||||||
Name="SaveButton"
|
Name="SaveButton"
|
||||||
Click="SaveButton_Click"
|
Click="SaveButton_Click"
|
||||||
Content="{locale:Locale Save}" />
|
Content="{locale:Locale Save}" />
|
||||||
<Button
|
|
||||||
Name="CloseButton"
|
|
||||||
HorizontalAlignment="Right"
|
|
||||||
Click="CloseButton_Click"
|
|
||||||
Content="{locale:Locale Discard}" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
@ -4,13 +4,16 @@ using Avalonia.Interactivity;
|
|||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using FluentAvalonia.UI.Navigation;
|
using FluentAvalonia.UI.Navigation;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Controls;
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
|
using System;
|
||||||
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Controls
|
namespace Ryujinx.Ava.UI.Views.User
|
||||||
{
|
{
|
||||||
public partial class UserEditor : UserControl
|
public partial class UserEditorView : UserControl
|
||||||
{
|
{
|
||||||
private NavigationDialogHost _parent;
|
private NavigationDialogHost _parent;
|
||||||
private UserProfile _profile;
|
private UserProfile _profile;
|
||||||
@ -18,8 +21,9 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
|
|
||||||
public TempProfile TempProfile { get; set; }
|
public TempProfile TempProfile { get; set; }
|
||||||
public uint MaxProfileNameLength => 0x20;
|
public uint MaxProfileNameLength => 0x20;
|
||||||
|
public bool IsDeletable => _profile.UserId != AccountManager.DefaultUserId;
|
||||||
|
|
||||||
public UserEditor()
|
public UserEditorView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
AddHandler(Frame.NavigatedToEvent, (s, e) =>
|
AddHandler(Frame.NavigatedToEvent, (s, e) =>
|
||||||
@ -44,41 +48,84 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - " +
|
||||||
|
$"{ (_isNewUser ? LocaleManager.Instance[LocaleKeys.UserEditorTitleCreate] : LocaleManager.Instance[LocaleKeys.UserEditorTitle])}";
|
||||||
|
|
||||||
DataContext = TempProfile;
|
DataContext = TempProfile;
|
||||||
|
|
||||||
AddPictureButton.IsVisible = _isNewUser;
|
AddPictureButton.IsVisible = _isNewUser;
|
||||||
|
ChangePictureButton.IsVisible = !_isNewUser;
|
||||||
IdLabel.IsVisible = _profile != null;
|
IdLabel.IsVisible = _profile != null;
|
||||||
IdText.IsVisible = _profile != null;
|
IdText.IsVisible = _profile != null;
|
||||||
ChangePictureButton.IsVisible = !_isNewUser;
|
if (!_isNewUser && IsDeletable)
|
||||||
|
{
|
||||||
|
DeleteButton.IsVisible = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DeleteButton.IsVisible = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
private async void BackButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_isNewUser)
|
||||||
|
{
|
||||||
|
if (TempProfile.Name != String.Empty || TempProfile.Image != null)
|
||||||
|
{
|
||||||
|
if (await ContentDialogHelper.CreateChoiceDialog(
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle],
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesMessage],
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesSubMessage]))
|
||||||
{
|
{
|
||||||
_parent?.GoBack();
|
_parent?.GoBack();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_parent?.GoBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_profile.Name != TempProfile.Name || _profile.Image != TempProfile.Image)
|
||||||
|
{
|
||||||
|
if (await ContentDialogHelper.CreateChoiceDialog(
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle],
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesMessage],
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesSubMessage]))
|
||||||
|
{
|
||||||
|
_parent?.GoBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_parent?.GoBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async void SaveButton_Click(object sender, RoutedEventArgs e)
|
private void DeleteButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_parent.DeleteUser(_profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
DataValidationErrors.ClearErrors(NameBox);
|
DataValidationErrors.ClearErrors(NameBox);
|
||||||
bool isInvalid = false;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(TempProfile.Name))
|
if (string.IsNullOrWhiteSpace(TempProfile.Name))
|
||||||
{
|
{
|
||||||
DataValidationErrors.SetError(NameBox, new DataValidationException(LocaleManager.Instance[LocaleKeys.UserProfileEmptyNameError]));
|
DataValidationErrors.SetError(NameBox, new DataValidationException(LocaleManager.Instance[LocaleKeys.UserProfileEmptyNameError]));
|
||||||
|
|
||||||
isInvalid = true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TempProfile.Image == null)
|
if (TempProfile.Image == null)
|
||||||
{
|
{
|
||||||
await ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.UserProfileNoImageError], "");
|
_parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, TempProfile));
|
||||||
|
|
||||||
isInvalid = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isInvalid)
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +151,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
|
|
||||||
public void SelectProfileImage()
|
public void SelectProfileImage()
|
||||||
{
|
{
|
||||||
_parent.Navigate(typeof(ProfileImageSelectionDialog), (_parent, TempProfile));
|
_parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, TempProfile));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ChangePictureButton_Click(object sender, RoutedEventArgs e)
|
private void ChangePictureButton_Click(object sender, RoutedEventArgs e)
|
114
Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml
Normal file
114
Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<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:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Width="528"
|
||||||
|
d:DesignWidth="578"
|
||||||
|
d:DesignHeight="350"
|
||||||
|
x:Class="Ryujinx.Ava.UI.Views.User.UserFirmwareAvatarSelectorView"
|
||||||
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||||
|
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||||
|
x:CompileBindings="True"
|
||||||
|
x:DataType="viewModels:UserFirmwareAvatarSelectorViewModel"
|
||||||
|
Focusable="True">
|
||||||
|
<Design.DataContext>
|
||||||
|
<viewModels:UserFirmwareAvatarSelectorViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
<UserControl.Resources>
|
||||||
|
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||||
|
</UserControl.Resources>
|
||||||
|
<Grid
|
||||||
|
Margin="0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<ListBox
|
||||||
|
Grid.Row="1"
|
||||||
|
BorderThickness="0"
|
||||||
|
SelectedIndex="{Binding SelectedIndex}"
|
||||||
|
Height="400"
|
||||||
|
Items="{Binding Images}"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<ListBox.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<WrapPanel
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Margin="0"
|
||||||
|
HorizontalAlignment="Center" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ListBox.ItemsPanel>
|
||||||
|
<ListBox.Styles>
|
||||||
|
<Style Selector="ListBoxItem">
|
||||||
|
<Setter Property="CornerRadius" Value="4" />
|
||||||
|
<Setter Property="Width" Value="85" />
|
||||||
|
<Setter Property="MaxWidth" Value="85" />
|
||||||
|
<Setter Property="MinWidth" Value="85" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ListBoxItem /template/ Border#SelectionIndicator">
|
||||||
|
<Setter Property="MinHeight" Value="70" />
|
||||||
|
</Style>
|
||||||
|
</ListBox.Styles>
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Panel
|
||||||
|
Background="{Binding BackgroundColor}"
|
||||||
|
Margin="5">
|
||||||
|
<Image Source="{Binding Data, Converter={StaticResource ByteImage}}" />
|
||||||
|
</Panel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="3"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="10"
|
||||||
|
Margin="0 24 0 0"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<Button
|
||||||
|
Width="50"
|
||||||
|
MinWidth="50"
|
||||||
|
Height="35"
|
||||||
|
Click="GoBack">
|
||||||
|
<ui:SymbolIcon Symbol="Back" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="3"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="10"
|
||||||
|
Margin="0 24 0 0"
|
||||||
|
HorizontalAlignment="Right">
|
||||||
|
<ui:ColorPickerButton
|
||||||
|
FlyoutPlacement="Top"
|
||||||
|
IsMoreButtonVisible="False"
|
||||||
|
UseColorPalette="False"
|
||||||
|
UseColorTriangle="False"
|
||||||
|
UseColorWheel="False"
|
||||||
|
ShowAcceptDismissButtons="False"
|
||||||
|
IsAlphaEnabled="False"
|
||||||
|
Color="{Binding BackgroundColor, Mode=TwoWay}"
|
||||||
|
Name="ColorButton">
|
||||||
|
<ui:ColorPickerButton.Styles>
|
||||||
|
<Style Selector="Grid#Root > DockPanel > Grid">
|
||||||
|
<Setter Property="IsVisible" Value="False" />
|
||||||
|
</Style>
|
||||||
|
</ui:ColorPickerButton.Styles>
|
||||||
|
</ui:ColorPickerButton>
|
||||||
|
<Button
|
||||||
|
Content="{locale:Locale AvatarChoose}"
|
||||||
|
Height="35"
|
||||||
|
Name="ChooseButton"
|
||||||
|
Click="ChooseButton_OnClick" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
@ -6,15 +6,20 @@ using Ryujinx.Ava.UI.Controls;
|
|||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.Formats.Png;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Windows
|
namespace Ryujinx.Ava.UI.Views.User
|
||||||
{
|
{
|
||||||
public partial class AvatarWindow : UserControl
|
public partial class UserFirmwareAvatarSelectorView : UserControl
|
||||||
{
|
{
|
||||||
private NavigationDialogHost _parent;
|
private NavigationDialogHost _parent;
|
||||||
private TempProfile _profile;
|
private TempProfile _profile;
|
||||||
|
|
||||||
public AvatarWindow(ContentManager contentManager)
|
public UserFirmwareAvatarSelectorView(ContentManager contentManager)
|
||||||
{
|
{
|
||||||
ContentManager = contentManager;
|
ContentManager = contentManager;
|
||||||
|
|
||||||
@ -23,7 +28,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public AvatarWindow()
|
public UserFirmwareAvatarSelectorView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
@ -43,7 +48,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
ContentManager = _parent.ContentManager;
|
ContentManager = _parent.ContentManager;
|
||||||
if (Program.PreviewerDetached)
|
if (Program.PreviewerDetached)
|
||||||
{
|
{
|
||||||
ViewModel = new AvatarProfileViewModel(() => ViewModel.ReloadImages());
|
ViewModel = new UserFirmwareAvatarSelectorViewModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
DataContext = ViewModel;
|
DataContext = ViewModel;
|
||||||
@ -53,22 +58,28 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
public ContentManager ContentManager { get; private set; }
|
public ContentManager ContentManager { get; private set; }
|
||||||
|
|
||||||
internal AvatarProfileViewModel ViewModel { get; set; }
|
internal UserFirmwareAvatarSelectorViewModel ViewModel { get; set; }
|
||||||
|
|
||||||
private void CloseButton_OnClick(object sender, RoutedEventArgs e)
|
private void GoBack(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
ViewModel.Dispose();
|
|
||||||
|
|
||||||
_parent.GoBack();
|
_parent.GoBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ChooseButton_OnClick(object sender, RoutedEventArgs e)
|
private void ChooseButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (ViewModel.SelectedIndex > -1)
|
if (ViewModel.SelectedImage != null)
|
||||||
{
|
{
|
||||||
_profile.Image = ViewModel.SelectedImage;
|
MemoryStream streamJpg = new();
|
||||||
|
SixLabors.ImageSharp.Image avatarImage = SixLabors.ImageSharp.Image.Load(ViewModel.SelectedImage, new PngDecoder());
|
||||||
|
|
||||||
ViewModel.Dispose();
|
avatarImage.Mutate(x => x.BackgroundColor(new Rgba32(
|
||||||
|
ViewModel.BackgroundColor.R,
|
||||||
|
ViewModel.BackgroundColor.G,
|
||||||
|
ViewModel.BackgroundColor.B,
|
||||||
|
ViewModel.BackgroundColor.A)));
|
||||||
|
avatarImage.SaveAsJpeg(streamJpg);
|
||||||
|
|
||||||
|
_profile.Image = streamJpg.ToArray();
|
||||||
|
|
||||||
_parent.GoBack();
|
_parent.GoBack();
|
||||||
}
|
}
|
63
Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml
Normal file
63
Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<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:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
|
xmlns:viewModles="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||||
|
Focusable="True"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
x:Class="Ryujinx.Ava.UI.Views.User.UserProfileImageSelectorView"
|
||||||
|
x:CompileBindings="True"
|
||||||
|
x:DataType="viewModles:UserProfileImageSelectorViewModel"
|
||||||
|
Width="500"
|
||||||
|
d:DesignWidth="500">
|
||||||
|
<Design.DataContext>
|
||||||
|
<viewModles:UserProfileImageSelectorViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
<Grid
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="70" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="0"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
TextAlignment="Left"
|
||||||
|
Text="{locale:Locale ProfileImageSelectionNote}" />
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="2"
|
||||||
|
Spacing="10"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Button
|
||||||
|
Width="50"
|
||||||
|
MinWidth="50"
|
||||||
|
Click="GoBack">
|
||||||
|
<ui:SymbolIcon Symbol="Back" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="2"
|
||||||
|
Spacing="10"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Button
|
||||||
|
Name="Import"
|
||||||
|
Click="Import_OnClick">
|
||||||
|
<TextBlock Text="{locale:Locale ProfileImageSelectionImportImage}" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
Name="SelectFirmwareImage"
|
||||||
|
IsEnabled="{Binding FirmwareFound}"
|
||||||
|
Click="SelectFirmwareImage_OnClick">
|
||||||
|
<TextBlock Text="{locale:Locale ProfileImageSelectionSelectAvatar}" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
@ -4,25 +4,26 @@ using Avalonia.VisualTree;
|
|||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using FluentAvalonia.UI.Navigation;
|
using FluentAvalonia.UI.Navigation;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.UI.Controls;
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Image = SixLabors.ImageSharp.Image;
|
using Image = SixLabors.ImageSharp.Image;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Controls
|
namespace Ryujinx.Ava.UI.Views.User
|
||||||
{
|
{
|
||||||
public partial class ProfileImageSelectionDialog : UserControl
|
public partial class UserProfileImageSelectorView : UserControl
|
||||||
{
|
{
|
||||||
private ContentManager _contentManager;
|
private ContentManager _contentManager;
|
||||||
private NavigationDialogHost _parent;
|
private NavigationDialogHost _parent;
|
||||||
private TempProfile _profile;
|
private TempProfile _profile;
|
||||||
|
|
||||||
public bool FirmwareFound => _contentManager.GetCurrentFirmwareVersion() != null;
|
internal UserProfileImageSelectorViewModel ViewModel { get; private set; }
|
||||||
|
|
||||||
public ProfileImageSelectionDialog()
|
public UserProfileImageSelectorView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
AddHandler(Frame.NavigatedToEvent, (s, e) =>
|
AddHandler(Frame.NavigatedToEvent, (s, e) =>
|
||||||
@ -40,13 +41,23 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
case NavigationMode.New:
|
case NavigationMode.New:
|
||||||
(_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter;
|
(_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter;
|
||||||
_contentManager = _parent.ContentManager;
|
_contentManager = _parent.ContentManager;
|
||||||
break;
|
|
||||||
case NavigationMode.Back:
|
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {LocaleManager.Instance[LocaleKeys.ProfileImageSelectionHeader]}";
|
||||||
_parent.GoBack();
|
|
||||||
break;
|
if (Program.PreviewerDetached)
|
||||||
|
{
|
||||||
|
DataContext = ViewModel = new UserProfileImageSelectorViewModel();
|
||||||
|
ViewModel.FirmwareFound = _contentManager.GetCurrentFirmwareVersion() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
DataContext = this;
|
break;
|
||||||
|
case NavigationMode.Back:
|
||||||
|
if (_profile.Image != null)
|
||||||
|
{
|
||||||
|
_parent.GoBack();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,17 +84,25 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
string imageFile = image[0];
|
string imageFile = image[0];
|
||||||
|
|
||||||
_profile.Image = ProcessProfileImage(File.ReadAllBytes(imageFile));
|
_profile.Image = ProcessProfileImage(File.ReadAllBytes(imageFile));
|
||||||
}
|
|
||||||
|
|
||||||
|
if (_profile.Image != null)
|
||||||
|
{
|
||||||
_parent.GoBack();
|
_parent.GoBack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GoBack(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_parent.GoBack();
|
||||||
|
}
|
||||||
|
|
||||||
private void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e)
|
private void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (FirmwareFound)
|
if (ViewModel.FirmwareFound)
|
||||||
{
|
{
|
||||||
_parent.Navigate(typeof(AvatarWindow), (_parent, _profile));
|
_parent.Navigate(typeof(UserFirmwareAvatarSelectorView), (_parent, _profile));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
83
Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml
Normal file
83
Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<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="550"
|
||||||
|
d:DesignHeight="450"
|
||||||
|
Width="500"
|
||||||
|
Height="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.Views.User.UserRecovererView"
|
||||||
|
x:CompileBindings="True"
|
||||||
|
x:DataType="viewModels:UserProfileViewModel"
|
||||||
|
Focusable="True">
|
||||||
|
<Design.DataContext>
|
||||||
|
<viewModels:UserProfileViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
<Grid HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Border
|
||||||
|
CornerRadius="5"
|
||||||
|
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||||
|
BorderThickness="1"
|
||||||
|
Grid.Row="0">
|
||||||
|
<Panel>
|
||||||
|
<ListBox
|
||||||
|
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"
|
||||||
|
Click="Recover"
|
||||||
|
CommandParameter="{Binding}"
|
||||||
|
Content="{locale:Locale Recover}"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
<TextBlock
|
||||||
|
IsVisible="{Binding IsEmpty}"
|
||||||
|
TextAlignment="Center"
|
||||||
|
Text="{locale:Locale UserProfilesRecoverEmptyList}"/>
|
||||||
|
</Panel>
|
||||||
|
</Border>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="0 24 0 0"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Button
|
||||||
|
Width="50"
|
||||||
|
MinWidth="50"
|
||||||
|
Click="GoBack">
|
||||||
|
<ui:SymbolIcon Symbol="Back"/>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
51
Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml.cs
Normal file
51
Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using FluentAvalonia.UI.Navigation;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.UI.Controls;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.Views.User
|
||||||
|
{
|
||||||
|
public partial class UserRecovererView : UserControl
|
||||||
|
{
|
||||||
|
private NavigationDialogHost _parent;
|
||||||
|
|
||||||
|
public UserRecovererView()
|
||||||
|
{
|
||||||
|
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 parent = (NavigationDialogHost)arg.Parameter;
|
||||||
|
|
||||||
|
_parent = parent;
|
||||||
|
|
||||||
|
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {LocaleManager.Instance[LocaleKeys.UserProfilesRecoverHeading]}";
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GoBack(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_parent?.GoBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Recover(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_parent?.RecoverLostAccounts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
211
Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml
Normal file
211
Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
<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:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
|
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||||
|
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||||
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignWidth="600"
|
||||||
|
d:DesignHeight="500"
|
||||||
|
Height="450"
|
||||||
|
Width="550"
|
||||||
|
x:Class="Ryujinx.Ava.UI.Views.User.UserSaveManagerView"
|
||||||
|
x:CompileBindings="True"
|
||||||
|
x:DataType="viewModels:UserSaveManagerViewModel"
|
||||||
|
Focusable="True">
|
||||||
|
<Design.DataContext>
|
||||||
|
<viewModels:UserSaveManagerViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
<UserControl.Resources>
|
||||||
|
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||||
|
</UserControl.Resources>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</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.Styles>
|
||||||
|
<Style Selector="ContentControl#ContentPresenter">
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||||
|
</Style>
|
||||||
|
</ComboBox.Styles>
|
||||||
|
</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 OrderDescending}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBox.Styles>
|
||||||
|
<Style Selector="ContentControl#ContentPresenter">
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||||
|
</Style>
|
||||||
|
</ComboBox.Styles>
|
||||||
|
</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"
|
||||||
|
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||||
|
CornerRadius="5"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch">
|
||||||
|
<ListBox
|
||||||
|
Name="SaveList"
|
||||||
|
Items="{Binding Views}"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch">
|
||||||
|
<ListBox.Styles>
|
||||||
|
<Style Selector="ListBoxItem">
|
||||||
|
<Setter Property="Padding" Value="10" />
|
||||||
|
<Setter Property="Margin" Value="5" />
|
||||||
|
<Setter Property="CornerRadius" Value="4" />
|
||||||
|
</Style>
|
||||||
|
</ListBox.Styles>
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="models:SaveModel">
|
||||||
|
<Grid HorizontalAlignment="Stretch">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Column="0"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="5">
|
||||||
|
<Border
|
||||||
|
Height="42"
|
||||||
|
Width="42"
|
||||||
|
Padding="10"
|
||||||
|
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||||
|
BorderThickness="1"
|
||||||
|
IsVisible="{Binding !InGameList}">
|
||||||
|
<ui:SymbolIcon
|
||||||
|
Symbol="Help"
|
||||||
|
FontSize="30"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Border>
|
||||||
|
<Image
|
||||||
|
IsVisible="{Binding InGameList}"
|
||||||
|
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"
|
||||||
|
Click="OpenLocation">
|
||||||
|
<ui:SymbolIcon
|
||||||
|
Symbol="OpenFolder"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Padding="10"
|
||||||
|
MinWidth="0"
|
||||||
|
MinHeight="0"
|
||||||
|
Name="Delete"
|
||||||
|
Click="Delete">
|
||||||
|
<ui:SymbolIcon
|
||||||
|
Symbol="Delete"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</Border>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="2"
|
||||||
|
Margin="0 24 0 0"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Button
|
||||||
|
Width="50"
|
||||||
|
MinWidth="50"
|
||||||
|
Click="GoBack">
|
||||||
|
<ui:SymbolIcon Symbol="Back" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
147
Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml.cs
Normal file
147
Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml.cs
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using FluentAvalonia.UI.Navigation;
|
||||||
|
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.Controls;
|
||||||
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
|
using Ryujinx.Ava.UI.Models;
|
||||||
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UserId = LibHac.Fs.UserId;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.Views.User
|
||||||
|
{
|
||||||
|
public partial class UserSaveManagerView : UserControl
|
||||||
|
{
|
||||||
|
internal UserSaveManagerViewModel ViewModel { get; private set; }
|
||||||
|
|
||||||
|
private AccountManager _accountManager;
|
||||||
|
private HorizonClient _horizonClient;
|
||||||
|
private VirtualFileSystem _virtualFileSystem;
|
||||||
|
private NavigationDialogHost _parent;
|
||||||
|
|
||||||
|
public UserSaveManagerView()
|
||||||
|
{
|
||||||
|
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, AccountManager accountManager, HorizonClient client, VirtualFileSystem virtualFileSystem))arg.Parameter;
|
||||||
|
_accountManager = args.accountManager;
|
||||||
|
_horizonClient = args.client;
|
||||||
|
_virtualFileSystem = args.virtualFileSystem;
|
||||||
|
|
||||||
|
_parent = args.parent;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataContext = ViewModel = new UserSaveManagerViewModel(_accountManager);
|
||||||
|
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {ViewModel.SaveManagerHeading}";
|
||||||
|
|
||||||
|
Task.Run(LoadSaves);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadSaves()
|
||||||
|
{
|
||||||
|
ViewModel.Saves.Clear();
|
||||||
|
var saves = new ObservableCollection<SaveModel>();
|
||||||
|
var saveDataFilter = SaveDataFilter.Make(
|
||||||
|
programId: default,
|
||||||
|
saveType: SaveDataType.Account,
|
||||||
|
new UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.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, _virtualFileSystem);
|
||||||
|
saves.Add(saveModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
ViewModel.Saves = saves;
|
||||||
|
ViewModel.Sort();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GoBack(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_parent?.GoBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenLocation(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Avalonia.Controls.Button button)
|
||||||
|
{
|
||||||
|
if (button.DataContext is SaveModel saveModel)
|
||||||
|
{
|
||||||
|
ApplicationHelper.OpenSaveDir(saveModel.SaveId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Delete(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Avalonia.Controls.Button button)
|
||||||
|
{
|
||||||
|
if (button.DataContext is SaveModel saveModel)
|
||||||
|
{
|
||||||
|
var result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DeleteUserSave],
|
||||||
|
LocaleManager.Instance[LocaleKeys.IrreversibleActionNote],
|
||||||
|
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
||||||
|
LocaleManager.Instance[LocaleKeys.InputDialogNo], "");
|
||||||
|
|
||||||
|
if (result == UserResult.Yes)
|
||||||
|
{
|
||||||
|
_horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, saveModel.SaveId);
|
||||||
|
ViewModel.Saves.Remove(saveModel);
|
||||||
|
ViewModel.Sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
165
Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml
Normal file
165
Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
<UserControl
|
||||||
|
x:Class="Ryujinx.Ava.UI.Views.User.UserSelectorViews"
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||||
|
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||||
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||||
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
d:DesignHeight="450"
|
||||||
|
MinWidth="500"
|
||||||
|
d:DesignWidth="800"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Focusable="True"
|
||||||
|
x:CompileBindings="True"
|
||||||
|
x:DataType="viewModels:UserProfileViewModel">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||||
|
</UserControl.Resources>
|
||||||
|
<Design.DataContext>
|
||||||
|
<viewModels:UserProfileViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Border
|
||||||
|
CornerRadius="5"
|
||||||
|
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||||
|
BorderThickness="1">
|
||||||
|
<ListBox
|
||||||
|
MaxHeight="300"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
SelectionChanged="ProfilesList_SelectionChanged"
|
||||||
|
Background="Transparent"
|
||||||
|
Items="{Binding Profiles}">
|
||||||
|
<ListBox.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<flex:FlexPanel
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
AlignContent="FlexStart"
|
||||||
|
JustifyContent="FlexStart" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ListBox.ItemsPanel>
|
||||||
|
<ListBox.Styles>
|
||||||
|
<Style Selector="ListBoxItem">
|
||||||
|
<Setter Property="Margin" Value="5 5 0 5" />
|
||||||
|
<Setter Property="CornerRadius" Value="5" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Border#SelectionIndicator">
|
||||||
|
<Setter Property="Opacity" Value="0" />
|
||||||
|
</Style>
|
||||||
|
</ListBox.Styles>
|
||||||
|
<ListBox.DataTemplates>
|
||||||
|
<DataTemplate
|
||||||
|
DataType="models:UserProfile">
|
||||||
|
<Grid
|
||||||
|
PointerEnter="Grid_PointerEntered"
|
||||||
|
PointerLeave="Grid_OnPointerExited">
|
||||||
|
<Border
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
ClipToBounds="True"
|
||||||
|
CornerRadius="5"
|
||||||
|
Background="{Binding BackgroundColor}">
|
||||||
|
<StackPanel
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch">
|
||||||
|
<Image
|
||||||
|
Width="96"
|
||||||
|
Height="96"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Source="{Binding Image, Converter={StaticResource ByteImage}}" />
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
MaxWidth="90"
|
||||||
|
Text="{Binding Name}"
|
||||||
|
TextAlignment="Center"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
MaxLines="2"
|
||||||
|
Margin="5" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
<Border
|
||||||
|
Margin="2"
|
||||||
|
Height="24"
|
||||||
|
Width="24"
|
||||||
|
CornerRadius="12"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Background="{DynamicResource ThemeContentBackgroundColor}"
|
||||||
|
IsVisible="{Binding IsPointerOver}">
|
||||||
|
<Button
|
||||||
|
MaxHeight="24"
|
||||||
|
MaxWidth="24"
|
||||||
|
MinHeight="24"
|
||||||
|
MinWidth="24"
|
||||||
|
CornerRadius="12"
|
||||||
|
Padding="0"
|
||||||
|
Click="EditUser">
|
||||||
|
<ui:SymbolIcon Symbol="Edit" />
|
||||||
|
</Button>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
<DataTemplate
|
||||||
|
DataType="viewModels:BaseModel">
|
||||||
|
<Panel
|
||||||
|
Height="118"
|
||||||
|
Width="96">
|
||||||
|
<Button
|
||||||
|
MinWidth="50"
|
||||||
|
MinHeight="50"
|
||||||
|
MaxWidth="50"
|
||||||
|
MaxHeight="50"
|
||||||
|
CornerRadius="25"
|
||||||
|
Margin="10"
|
||||||
|
Padding="0"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Click="AddUser">
|
||||||
|
<ui:SymbolIcon Symbol="Add" />
|
||||||
|
</Button>
|
||||||
|
<Panel.Styles>
|
||||||
|
<Style Selector="Panel">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource ListBoxBackground}"/>
|
||||||
|
</Style>
|
||||||
|
</Panel.Styles>
|
||||||
|
</Panel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.DataTemplates>
|
||||||
|
</ListBox>
|
||||||
|
</Border>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="0 24 0 0"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="10">
|
||||||
|
<Button
|
||||||
|
Click="ManageSaves"
|
||||||
|
Content="{locale:Locale UserProfilesManageSaves}" />
|
||||||
|
<Button
|
||||||
|
Click="RecoverLostAccounts"
|
||||||
|
Content="{locale:Locale UserProfilesRecoverLostAccounts}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="0 24 0 0"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Button
|
||||||
|
Click="Close"
|
||||||
|
Content="{locale:Locale UserProfilesClose}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
128
Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml.cs
Normal file
128
Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml.cs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using FluentAvalonia.UI.Navigation;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.UI.Controls;
|
||||||
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
|
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.Views.User
|
||||||
|
{
|
||||||
|
public partial class UserSelectorViews : UserControl
|
||||||
|
{
|
||||||
|
private NavigationDialogHost _parent;
|
||||||
|
|
||||||
|
public UserProfileViewModel ViewModel { get; set; }
|
||||||
|
|
||||||
|
public UserSelectorViews()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
if (Program.PreviewerDetached)
|
||||||
|
{
|
||||||
|
AddHandler(Frame.NavigatedToEvent, (s, e) =>
|
||||||
|
{
|
||||||
|
NavigatedTo(e);
|
||||||
|
}, RoutingStrategies.Direct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NavigatedTo(NavigationEventArgs arg)
|
||||||
|
{
|
||||||
|
if (Program.PreviewerDetached)
|
||||||
|
{
|
||||||
|
if (arg.NavigationMode == NavigationMode.New)
|
||||||
|
{
|
||||||
|
_parent = (NavigationDialogHost)arg.Parameter;
|
||||||
|
ViewModel = _parent.ViewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg.NavigationMode == NavigationMode.Back)
|
||||||
|
{
|
||||||
|
((ContentDialog)_parent.Parent).Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle];
|
||||||
|
}
|
||||||
|
|
||||||
|
DataContext = ViewModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Grid_PointerEntered(object sender, PointerEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Grid grid)
|
||||||
|
{
|
||||||
|
if (grid.DataContext is UserProfile profile)
|
||||||
|
{
|
||||||
|
profile.IsPointerOver = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Grid_OnPointerExited(object sender, PointerEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Grid grid)
|
||||||
|
{
|
||||||
|
if (grid.DataContext is UserProfile profile)
|
||||||
|
{
|
||||||
|
profile.IsPointerOver = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProfilesList_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is ListBox listBox)
|
||||||
|
{
|
||||||
|
int selectedIndex = listBox.SelectedIndex;
|
||||||
|
|
||||||
|
if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
|
||||||
|
{
|
||||||
|
if (ViewModel.Profiles[selectedIndex] is UserProfile userProfile)
|
||||||
|
{
|
||||||
|
_parent?.AccountManager?.OpenUser(userProfile.UserId);
|
||||||
|
|
||||||
|
foreach (BaseModel profile in ViewModel.Profiles)
|
||||||
|
{
|
||||||
|
if (profile is UserProfile uProfile)
|
||||||
|
{
|
||||||
|
uProfile.UpdateState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddUser(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_parent.AddUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EditUser(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Avalonia.Controls.Button button)
|
||||||
|
{
|
||||||
|
if (button.DataContext is UserProfile userProfile)
|
||||||
|
{
|
||||||
|
_parent.EditUser(userProfile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ManageSaves(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_parent.ManageSaves();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecoverLostAccounts(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_parent.RecoverLostAccounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Close(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
((ContentDialog)_parent.Parent).Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,54 +0,0 @@
|
|||||||
<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:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
|
||||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
|
||||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
|
||||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350"
|
|
||||||
x:Class="Ryujinx.Ava.UI.Windows.AvatarWindow"
|
|
||||||
Margin="0"
|
|
||||||
Padding="0"
|
|
||||||
x:CompileBindings="True"
|
|
||||||
x:DataType="viewModels:AvatarProfileViewModel"
|
|
||||||
Focusable="True">
|
|
||||||
<Design.DataContext>
|
|
||||||
<viewModels:AvatarProfileViewModel />
|
|
||||||
</Design.DataContext>
|
|
||||||
<UserControl.Resources>
|
|
||||||
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
|
||||||
</UserControl.Resources>
|
|
||||||
<Grid Margin="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<ListBox Grid.Row="1" BorderThickness="0" SelectedIndex="{Binding SelectedIndex}" Height="400"
|
|
||||||
Items="{Binding Images}" HorizontalAlignment="Stretch" VerticalAlignment="Center">
|
|
||||||
<ListBox.ItemsPanel>
|
|
||||||
<ItemsPanelTemplate>
|
|
||||||
<WrapPanel Orientation="Horizontal" MaxWidth="700" Margin="0" HorizontalAlignment="Center" />
|
|
||||||
</ItemsPanelTemplate>
|
|
||||||
</ListBox.ItemsPanel>
|
|
||||||
<ListBox.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<Image Margin="5" Height="96" Width="96"
|
|
||||||
Source="{Binding Data, Converter={StaticResource ByteImage}}" />
|
|
||||||
</DataTemplate>
|
|
||||||
</ListBox.ItemTemplate>
|
|
||||||
</ListBox>
|
|
||||||
<ProgressBar Grid.Row="2" IsIndeterminate="{Binding IsIndeterminate}" Value="{Binding ImagesLoaded}" HorizontalAlignment="Stretch" Margin="5"
|
|
||||||
Maximum="{Binding ImageCount}" Minimum="0" />
|
|
||||||
<StackPanel Grid.Row="3" Orientation="Horizontal" Spacing="10" Margin="10" HorizontalAlignment="Center">
|
|
||||||
<Button Content="{locale:Locale AvatarChoose}" Width="200" Name="ChooseButton" Click="ChooseButton_OnClick" />
|
|
||||||
<ui:ColorPickerButton Color="{Binding BackgroundColor, Mode=TwoWay}" Name="ColorButton" />
|
|
||||||
<Button HorizontalAlignment="Right" Content="{locale:Locale Discard}" Click="CloseButton_OnClick"
|
|
||||||
Name="CloseButton"
|
|
||||||
Width="200" />
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</UserControl>
|
|
@ -44,7 +44,8 @@
|
|||||||
<settings:SettingsNetworkView Name="NetworkPage" />
|
<settings:SettingsNetworkView Name="NetworkPage" />
|
||||||
<settings:SettingsLoggingView Name="LoggingPage" />
|
<settings:SettingsLoggingView Name="LoggingPage" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<ui:NavigationView Grid.Row="1"
|
<ui:NavigationView
|
||||||
|
Grid.Row="1"
|
||||||
IsSettingsVisible="False"
|
IsSettingsVisible="False"
|
||||||
Name="NavPanel"
|
Name="NavPanel"
|
||||||
IsBackEnabled="False"
|
IsBackEnabled="False"
|
||||||
@ -54,7 +55,8 @@
|
|||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
OpenPaneLength="200">
|
OpenPaneLength="200">
|
||||||
<ui:NavigationView.MenuItems>
|
<ui:NavigationView.MenuItems>
|
||||||
<ui:NavigationViewItem IsSelected="True"
|
<ui:NavigationViewItem
|
||||||
|
IsSelected="True"
|
||||||
Content="{locale:Locale SettingsTabGeneral}"
|
Content="{locale:Locale SettingsTabGeneral}"
|
||||||
Tag="UiPage"
|
Tag="UiPage"
|
||||||
Icon="New" />
|
Icon="New" />
|
||||||
@ -74,7 +76,8 @@
|
|||||||
Content="{locale:Locale SettingsTabCpu}"
|
Content="{locale:Locale SettingsTabCpu}"
|
||||||
Tag="CpuPage">
|
Tag="CpuPage">
|
||||||
<ui:NavigationViewItem.Icon>
|
<ui:NavigationViewItem.Icon>
|
||||||
<ui:FontIcon FontFamily="avares://Ryujinx.Ava/Assets/Fonts#Segoe Fluent Icons"
|
<ui:FontIcon
|
||||||
|
FontFamily="avares://Ryujinx.Ava/Assets/Fonts#Segoe Fluent Icons"
|
||||||
Glyph="{helpers:GlyphValueConverter Chip}" />
|
Glyph="{helpers:GlyphValueConverter Chip}" />
|
||||||
</ui:NavigationViewItem.Icon>
|
</ui:NavigationViewItem.Icon>
|
||||||
</ui:NavigationViewItem>
|
</ui:NavigationViewItem>
|
||||||
@ -95,6 +98,11 @@
|
|||||||
Tag="LoggingPage"
|
Tag="LoggingPage"
|
||||||
Icon="Document" />
|
Icon="Document" />
|
||||||
</ui:NavigationView.MenuItems>
|
</ui:NavigationView.MenuItems>
|
||||||
|
<ui:NavigationView.Styles>
|
||||||
|
<Style Selector="Grid#PlaceholderGrid">
|
||||||
|
<Setter Property="Height" Value="40" />
|
||||||
|
</Style>
|
||||||
|
</ui:NavigationView.Styles>
|
||||||
</ui:NavigationView>
|
</ui:NavigationView>
|
||||||
<ReversibleStackPanel
|
<ReversibleStackPanel
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
|
@ -9,6 +9,8 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
|
|
||||||
public readonly bool HasFrontFacingBug;
|
public readonly bool HasFrontFacingBug;
|
||||||
public readonly bool HasVectorIndexingBug;
|
public readonly bool HasVectorIndexingBug;
|
||||||
|
public readonly bool NeedsFragmentOutputSpecialization;
|
||||||
|
public readonly bool ReduceShaderPrecision;
|
||||||
|
|
||||||
public readonly bool SupportsAstcCompression;
|
public readonly bool SupportsAstcCompression;
|
||||||
public readonly bool SupportsBc123Compression;
|
public readonly bool SupportsBc123Compression;
|
||||||
@ -49,6 +51,8 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
string vendorName,
|
string vendorName,
|
||||||
bool hasFrontFacingBug,
|
bool hasFrontFacingBug,
|
||||||
bool hasVectorIndexingBug,
|
bool hasVectorIndexingBug,
|
||||||
|
bool needsFragmentOutputSpecialization,
|
||||||
|
bool reduceShaderPrecision,
|
||||||
bool supportsAstcCompression,
|
bool supportsAstcCompression,
|
||||||
bool supportsBc123Compression,
|
bool supportsBc123Compression,
|
||||||
bool supportsBc45Compression,
|
bool supportsBc45Compression,
|
||||||
@ -85,6 +89,8 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
VendorName = vendorName;
|
VendorName = vendorName;
|
||||||
HasFrontFacingBug = hasFrontFacingBug;
|
HasFrontFacingBug = hasFrontFacingBug;
|
||||||
HasVectorIndexingBug = hasVectorIndexingBug;
|
HasVectorIndexingBug = hasVectorIndexingBug;
|
||||||
|
NeedsFragmentOutputSpecialization = needsFragmentOutputSpecialization;
|
||||||
|
ReduceShaderPrecision = reduceShaderPrecision;
|
||||||
SupportsAstcCompression = supportsAstcCompression;
|
SupportsAstcCompression = supportsAstcCompression;
|
||||||
SupportsBc123Compression = supportsBc123Compression;
|
SupportsBc123Compression = supportsBc123Compression;
|
||||||
SupportsBc45Compression = supportsBc45Compression;
|
SupportsBc45Compression = supportsBc45Compression;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using Ryujinx.Common.Memory;
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||||
using Ryujinx.Graphics.Gpu.Shader;
|
using Ryujinx.Graphics.Gpu.Shader;
|
||||||
using Ryujinx.Graphics.Shader;
|
using Ryujinx.Graphics.Shader;
|
||||||
|
|
||||||
@ -10,6 +11,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal class SpecializationStateUpdater
|
internal class SpecializationStateUpdater
|
||||||
{
|
{
|
||||||
|
private readonly GpuContext _context;
|
||||||
private GpuChannelGraphicsState _graphics;
|
private GpuChannelGraphicsState _graphics;
|
||||||
private GpuChannelPoolState _pool;
|
private GpuChannelPoolState _pool;
|
||||||
|
|
||||||
@ -18,6 +20,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
|
|
||||||
private bool _changed;
|
private bool _changed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the specialization state updater class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">GPU context</param>
|
||||||
|
public SpecializationStateUpdater(GpuContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Signal that the specialization state has changed.
|
/// Signal that the specialization state has changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -232,6 +243,42 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the type of the outputs produced by the fragment shader based on the current render target state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rtControl">The render target control register</param>
|
||||||
|
/// <param name="state">The color attachment state</param>
|
||||||
|
public void SetFragmentOutputTypes(RtControl rtControl, ref Array8<RtColorState> state)
|
||||||
|
{
|
||||||
|
bool changed = false;
|
||||||
|
int count = rtControl.UnpackCount();
|
||||||
|
|
||||||
|
for (int index = 0; index < Constants.TotalRenderTargets; index++)
|
||||||
|
{
|
||||||
|
int rtIndex = rtControl.UnpackPermutationIndex(index);
|
||||||
|
|
||||||
|
var colorState = state[rtIndex];
|
||||||
|
|
||||||
|
if (index < count && StateUpdater.IsRtEnabled(colorState))
|
||||||
|
{
|
||||||
|
Format format = colorState.Format.Convert().Format;
|
||||||
|
|
||||||
|
AttributeType type = format.IsInteger() ? (format.IsSint() ? AttributeType.Sint : AttributeType.Uint) : AttributeType.Float;
|
||||||
|
|
||||||
|
if (type != _graphics.FragmentOutputTypes[index])
|
||||||
|
{
|
||||||
|
_graphics.FragmentOutputTypes[index] = type;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed && _context.Capabilities.NeedsFragmentOutputSpecialization)
|
||||||
|
{
|
||||||
|
Signal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0.
|
/// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -138,6 +138,16 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
_dirtyMask = ulong.MaxValue >> ((sizeof(ulong) * 8) - _callbacks.Length);
|
_dirtyMask = ulong.MaxValue >> ((sizeof(ulong) * 8) - _callbacks.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the given register group is dirty without clearing it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="groupIndex">Index of the group to check</param>
|
||||||
|
/// <returns>True if dirty, false otherwise</returns>
|
||||||
|
public bool IsDirty(int groupIndex)
|
||||||
|
{
|
||||||
|
return (_dirtyMask & (1UL << groupIndex)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check all the groups specified by <paramref name="checkMask"/> for modification, and update if modified.
|
/// Check all the groups specified by <paramref name="checkMask"/> for modification, and update if modified.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -20,6 +20,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
public const int ScissorStateIndex = 16;
|
public const int ScissorStateIndex = 16;
|
||||||
public const int VertexBufferStateIndex = 0;
|
public const int VertexBufferStateIndex = 0;
|
||||||
public const int PrimitiveRestartStateIndex = 12;
|
public const int PrimitiveRestartStateIndex = 12;
|
||||||
|
public const int RenderTargetStateIndex = 27;
|
||||||
|
|
||||||
private readonly GpuContext _context;
|
private readonly GpuContext _context;
|
||||||
private readonly GpuChannel _channel;
|
private readonly GpuChannel _channel;
|
||||||
@ -264,6 +265,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
_prevTfEnable = false;
|
_prevTfEnable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_updateTracker.IsDirty(RenderTargetStateIndex))
|
||||||
|
{
|
||||||
|
UpdateRenderTargetSpecialization();
|
||||||
|
}
|
||||||
|
|
||||||
_updateTracker.Update(ulong.MaxValue);
|
_updateTracker.Update(ulong.MaxValue);
|
||||||
|
|
||||||
// If any state that the shader depends on changed,
|
// If any state that the shader depends on changed,
|
||||||
@ -526,12 +532,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates specialization state based on render target state.
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateRenderTargetSpecialization()
|
||||||
|
{
|
||||||
|
_currentSpecState.SetFragmentOutputTypes(_state.State.RtControl, ref _state.State.RtColorState);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if a render target color buffer is used.
|
/// Checks if a render target color buffer is used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="colorState">Color buffer information</param>
|
/// <param name="colorState">Color buffer information</param>
|
||||||
/// <returns>True if the specified buffer is enabled/used, false otherwise</returns>
|
/// <returns>True if the specified buffer is enabled/used, false otherwise</returns>
|
||||||
private static bool IsRtEnabled(RtColorState colorState)
|
internal static bool IsRtEnabled(RtColorState colorState)
|
||||||
{
|
{
|
||||||
// Colors are disabled by writing 0 to the format.
|
// Colors are disabled by writing 0 to the format.
|
||||||
return colorState.Format != 0 && colorState.WidthOrStride != 0;
|
return colorState.Format != 0 && colorState.WidthOrStride != 0;
|
||||||
@ -893,7 +907,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
{
|
{
|
||||||
Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}.");
|
Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}.");
|
||||||
|
|
||||||
format = Format.R32G32B32A32Float;
|
format = vertexAttrib.UnpackType() switch
|
||||||
|
{
|
||||||
|
VertexAttribType.Sint => Format.R32G32B32A32Sint,
|
||||||
|
VertexAttribType.Uint => Format.R32G32B32A32Uint,
|
||||||
|
_ => Format.R32G32B32A32Float
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
vertexAttribs[index] = new VertexAttribDescriptor(
|
vertexAttribs[index] = new VertexAttribDescriptor(
|
||||||
|
@ -71,7 +71,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
|
|
||||||
_i2mClass = new InlineToMemoryClass(context, channel, initializeState: false);
|
_i2mClass = new InlineToMemoryClass(context, channel, initializeState: false);
|
||||||
|
|
||||||
var spec = new SpecializationStateUpdater();
|
var spec = new SpecializationStateUpdater(context);
|
||||||
var drawState = new DrawState();
|
var drawState = new DrawState();
|
||||||
|
|
||||||
_drawManager = new DrawManager(context, channel, _state, drawState, spec);
|
_drawManager = new DrawManager(context, channel, _state, drawState, spec);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||||
using Ryujinx.Graphics.Gpu.Image;
|
using Ryujinx.Graphics.Gpu.Image;
|
||||||
using Ryujinx.Graphics.Gpu.Memory;
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using System;
|
using System;
|
||||||
@ -31,6 +32,11 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal MemoryManager MemoryManager => _memoryManager;
|
internal MemoryManager MemoryManager => _memoryManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Host hardware capabilities from the GPU context.
|
||||||
|
/// </summary>
|
||||||
|
internal ref Capabilities Capabilities => ref _context.Capabilities;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of a GPU channel.
|
/// Creates a new instance of a GPU channel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -67,7 +73,7 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
|
|
||||||
// Since the memory manager changed, make sure we will get pools from addresses of the new memory manager.
|
// Since the memory manager changed, make sure we will get pools from addresses of the new memory manager.
|
||||||
TextureManager.ReloadPools();
|
TextureManager.ReloadPools();
|
||||||
MemoryManager.Physical.BufferCache.QueuePrune();
|
memoryManager.Physical.BufferCache.QueuePrune();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -78,7 +84,9 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
private void MemoryUnmappedHandler(object sender, UnmapEventArgs e)
|
private void MemoryUnmappedHandler(object sender, UnmapEventArgs e)
|
||||||
{
|
{
|
||||||
TextureManager.ReloadPools();
|
TextureManager.ReloadPools();
|
||||||
MemoryManager.Physical.BufferCache.QueuePrune();
|
|
||||||
|
var memoryManager = Volatile.Read(ref _memoryManager);
|
||||||
|
memoryManager?.Physical.BufferCache.QueuePrune();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1431,9 +1431,20 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isGpuThread = _context.IsGpuThread();
|
||||||
|
|
||||||
|
if (isGpuThread)
|
||||||
|
{
|
||||||
|
// No need to wait if we're on the GPU thread, we can just clear the modified flag immediately.
|
||||||
|
handle.Modified = false;
|
||||||
|
}
|
||||||
|
|
||||||
_context.Renderer.BackgroundContextAction(() =>
|
_context.Renderer.BackgroundContextAction(() =>
|
||||||
|
{
|
||||||
|
if (!isGpuThread)
|
||||||
{
|
{
|
||||||
handle.Sync(_context);
|
handle.Sync(_context);
|
||||||
|
}
|
||||||
|
|
||||||
Storage.SignalModifiedDirty();
|
Storage.SignalModifiedDirty();
|
||||||
|
|
||||||
|
@ -231,10 +231,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">The GPU context used to wait for sync</param>
|
/// <param name="context">The GPU context used to wait for sync</param>
|
||||||
public void Sync(GpuContext context)
|
public void Sync(GpuContext context)
|
||||||
{
|
|
||||||
bool needsSync = !context.IsGpuThread();
|
|
||||||
|
|
||||||
if (needsSync)
|
|
||||||
{
|
{
|
||||||
ulong registeredSync = _registeredSync;
|
ulong registeredSync = _registeredSync;
|
||||||
long diff = (long)(context.SyncNumber - registeredSync);
|
long diff = (long)(context.SyncNumber - registeredSync);
|
||||||
@ -254,11 +250,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
// If the difference is <= 0, no data is not ready yet. Flush any data we can without waiting or removing modified flag.
|
// If the difference is <= 0, no data is not ready yet. Flush any data we can without waiting or removing modified flag.
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
Modified = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clears the action registered variable, indicating that the tracking action should be
|
/// Clears the action registered variable, indicating that the tracking action should be
|
||||||
|
@ -107,6 +107,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||||||
return _oldSpecState.GraphicsState.AttributeTypes[location];
|
return _oldSpecState.GraphicsState.AttributeTypes[location];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public AttributeType QueryFragmentOutputType(int location)
|
||||||
|
{
|
||||||
|
return _oldSpecState.GraphicsState.FragmentOutputTypes[location];
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int QueryComputeLocalSizeX() => _oldSpecState.ComputeState.LocalSizeX;
|
public int QueryComputeLocalSizeX() => _oldSpecState.ComputeState.LocalSizeX;
|
||||||
|
|
||||||
|
@ -113,6 +113,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
return _state.GraphicsState.AttributeTypes[location];
|
return _state.GraphicsState.AttributeTypes[location];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public AttributeType QueryFragmentOutputType(int location)
|
||||||
|
{
|
||||||
|
return _state.GraphicsState.FragmentOutputTypes[location];
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int QueryComputeLocalSizeX() => _state.ComputeState.LocalSizeX;
|
public int QueryComputeLocalSizeX() => _state.ComputeState.LocalSizeX;
|
||||||
|
|
||||||
|
@ -112,6 +112,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision;
|
||||||
|
|
||||||
public bool QueryHostHasFrontFacingBug() => _context.Capabilities.HasFrontFacingBug;
|
public bool QueryHostHasFrontFacingBug() => _context.Capabilities.HasFrontFacingBug;
|
||||||
|
|
||||||
public bool QueryHostHasVectorIndexingBug() => _context.Capabilities.HasVectorIndexingBug;
|
public bool QueryHostHasVectorIndexingBug() => _context.Capabilities.HasVectorIndexingBug;
|
||||||
|
@ -87,6 +87,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasUnalignedStorageBuffer;
|
public bool HasUnalignedStorageBuffer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Type of the fragment shader outputs.
|
||||||
|
/// </summary>
|
||||||
|
public Array8<AttributeType> FragmentOutputTypes;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new GPU graphics state.
|
/// Creates a new GPU graphics state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -105,6 +110,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
/// <param name="attributeTypes">Type of the vertex attributes consumed by the shader</param>
|
/// <param name="attributeTypes">Type of the vertex attributes consumed by the shader</param>
|
||||||
/// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param>
|
/// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param>
|
||||||
/// <param name="hasUnalignedStorageBuffer">Indicates that any storage buffer use is unaligned</param>
|
/// <param name="hasUnalignedStorageBuffer">Indicates that any storage buffer use is unaligned</param>
|
||||||
|
/// <param name="fragmentOutputTypes">Type of the fragment shader outputs</param>
|
||||||
public GpuChannelGraphicsState(
|
public GpuChannelGraphicsState(
|
||||||
bool earlyZForce,
|
bool earlyZForce,
|
||||||
PrimitiveTopology topology,
|
PrimitiveTopology topology,
|
||||||
@ -120,7 +126,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
float alphaTestReference,
|
float alphaTestReference,
|
||||||
ref Array32<AttributeType> attributeTypes,
|
ref Array32<AttributeType> attributeTypes,
|
||||||
bool hasConstantBufferDrawParameters,
|
bool hasConstantBufferDrawParameters,
|
||||||
bool hasUnalignedStorageBuffer)
|
bool hasUnalignedStorageBuffer,
|
||||||
|
ref Array8<AttributeType> fragmentOutputTypes)
|
||||||
{
|
{
|
||||||
EarlyZForce = earlyZForce;
|
EarlyZForce = earlyZForce;
|
||||||
Topology = topology;
|
Topology = topology;
|
||||||
@ -137,6 +144,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
AttributeTypes = attributeTypes;
|
AttributeTypes = attributeTypes;
|
||||||
HasConstantBufferDrawParameters = hasConstantBufferDrawParameters;
|
HasConstantBufferDrawParameters = hasConstantBufferDrawParameters;
|
||||||
HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
|
HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
|
||||||
|
FragmentOutputTypes = fragmentOutputTypes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -530,6 +530,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (channel.Capabilities.NeedsFragmentOutputSpecialization && !graphicsState.FragmentOutputTypes.AsSpan().SequenceEqual(GraphicsState.FragmentOutputTypes.AsSpan()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return Matches(channel, ref poolState, checkTextures, isCompute: false);
|
return Matches(channel, ref poolState, checkTextures, isCompute: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +106,8 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
vendorName: GpuVendor,
|
vendorName: GpuVendor,
|
||||||
hasFrontFacingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows,
|
hasFrontFacingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows,
|
||||||
hasVectorIndexingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows,
|
hasVectorIndexingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows,
|
||||||
|
needsFragmentOutputSpecialization: false,
|
||||||
|
reduceShaderPrecision: false,
|
||||||
supportsAstcCompression: HwCapabilities.SupportsAstcCompression,
|
supportsAstcCompression: HwCapabilities.SupportsAstcCompression,
|
||||||
supportsBc123Compression: HwCapabilities.SupportsTextureCompressionS3tc,
|
supportsBc123Compression: HwCapabilities.SupportsTextureCompressionS3tc,
|
||||||
supportsBc45Compression: HwCapabilities.SupportsTextureCompressionRgtc,
|
supportsBc45Compression: HwCapabilities.SupportsTextureCompressionRgtc,
|
||||||
|
@ -346,12 +346,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
{
|
{
|
||||||
string name = context.OperandManager.DeclareLocal(decl);
|
string name = context.OperandManager.DeclareLocal(decl);
|
||||||
|
|
||||||
context.AppendLine(GetVarTypeName(decl.VarType) + " " + name + ";");
|
context.AppendLine(GetVarTypeName(context, decl.VarType) + " " + name + ";");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetVarTypeName(AggregateType type, bool precise = true)
|
public static string GetVarTypeName(CodeGenContext context, AggregateType type, bool precise = true)
|
||||||
{
|
{
|
||||||
|
if (context.Config.GpuAccessor.QueryHostReducedPrecision())
|
||||||
|
{
|
||||||
|
precise = false;
|
||||||
|
}
|
||||||
|
|
||||||
return type switch
|
return type switch
|
||||||
{
|
{
|
||||||
AggregateType.Void => "void",
|
AggregateType.Void => "void",
|
||||||
@ -666,7 +671,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
context.AppendLine($"layout (location = {attr}) out vec4 {name};");
|
string type = context.Config.Stage != ShaderStage.Fragment ? "vec4" :
|
||||||
|
context.Config.GpuAccessor.QueryFragmentOutputType(attr) switch
|
||||||
|
{
|
||||||
|
AttributeType.Sint => "ivec4",
|
||||||
|
AttributeType.Uint => "uvec4",
|
||||||
|
_ => "vec4"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (context.Config.GpuAccessor.QueryHostReducedPrecision() && context.Config.Stage == ShaderStage.Vertex && attr == 0)
|
||||||
|
{
|
||||||
|
context.AppendLine($"layout (location = {attr}) invariant out {type} {name};");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.AppendLine($"layout (location = {attr}) out {type} {name};");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
{
|
{
|
||||||
for (int i = 1; i < info.Functions.Count; i++)
|
for (int i = 1; i < info.Functions.Count; i++)
|
||||||
{
|
{
|
||||||
context.AppendLine($"{GetFunctionSignature(info.Functions[i])};");
|
context.AppendLine($"{GetFunctionSignature(context, info.Functions[i])};");
|
||||||
}
|
}
|
||||||
|
|
||||||
context.AppendLine();
|
context.AppendLine();
|
||||||
@ -44,7 +44,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
{
|
{
|
||||||
context.CurrentFunction = function;
|
context.CurrentFunction = function;
|
||||||
|
|
||||||
context.AppendLine(GetFunctionSignature(function, funcName));
|
context.AppendLine(GetFunctionSignature(context, function, funcName));
|
||||||
context.EnterScope();
|
context.EnterScope();
|
||||||
|
|
||||||
Declarations.DeclareLocals(context, function);
|
Declarations.DeclareLocals(context, function);
|
||||||
@ -54,23 +54,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
context.LeaveScope();
|
context.LeaveScope();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetFunctionSignature(StructuredFunction function, string funcName = null)
|
private static string GetFunctionSignature(CodeGenContext context, StructuredFunction function, string funcName = null)
|
||||||
{
|
{
|
||||||
string[] args = new string[function.InArguments.Length + function.OutArguments.Length];
|
string[] args = new string[function.InArguments.Length + function.OutArguments.Length];
|
||||||
|
|
||||||
for (int i = 0; i < function.InArguments.Length; i++)
|
for (int i = 0; i < function.InArguments.Length; i++)
|
||||||
{
|
{
|
||||||
args[i] = $"{Declarations.GetVarTypeName(function.InArguments[i])} {OperandManager.GetArgumentName(i)}";
|
args[i] = $"{Declarations.GetVarTypeName(context, function.InArguments[i])} {OperandManager.GetArgumentName(i)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < function.OutArguments.Length; i++)
|
for (int i = 0; i < function.OutArguments.Length; i++)
|
||||||
{
|
{
|
||||||
int j = i + function.InArguments.Length;
|
int j = i + function.InArguments.Length;
|
||||||
|
|
||||||
args[j] = $"out {Declarations.GetVarTypeName(function.OutArguments[i])} {OperandManager.GetArgumentName(j)}";
|
args[j] = $"out {Declarations.GetVarTypeName(context, function.OutArguments[i])} {OperandManager.GetArgumentName(j)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"{Declarations.GetVarTypeName(function.ReturnType)} {funcName ?? function.Name}({string.Join(", ", args)})";
|
return $"{Declarations.GetVarTypeName(context, function.ReturnType)} {funcName ?? function.Name}({string.Join(", ", args)})";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PrintBlock(CodeGenContext context, AstBlock block)
|
private static void PrintBlock(CodeGenContext context, AstBlock block)
|
||||||
|
@ -32,7 +32,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
|
|
||||||
if ((outputType & AggregateType.ElementCountMask) != 0)
|
if ((outputType & AggregateType.ElementCountMask) != 0)
|
||||||
{
|
{
|
||||||
return $"{Declarations.GetVarTypeName(outputType, precise: false)}({imageConst})";
|
return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({imageConst})";
|
||||||
}
|
}
|
||||||
|
|
||||||
return imageConst;
|
return imageConst;
|
||||||
@ -513,7 +513,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
|
|
||||||
if ((outputType & AggregateType.ElementCountMask) != 0)
|
if ((outputType & AggregateType.ElementCountMask) != 0)
|
||||||
{
|
{
|
||||||
return $"{Declarations.GetVarTypeName(outputType, precise: false)}({scalarValue})";
|
return $"{Declarations.GetVarTypeName(context, outputType, precise: false)}({scalarValue})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -577,6 +577,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
context.Decorate(spvVar, Decoration.Patch);
|
context.Decorate(spvVar, Decoration.Patch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.Config.GpuAccessor.QueryHostReducedPrecision() && attr == AttributeConsts.PositionX && context.Config.Stage != ShaderStage.Fragment)
|
||||||
|
{
|
||||||
|
context.Decorate(spvVar, Decoration.Invariant);
|
||||||
|
}
|
||||||
|
|
||||||
context.Decorate(spvVar, Decoration.BuiltIn, (LiteralInteger)GetBuiltIn(context, attrInfo.BaseValue));
|
context.Decorate(spvVar, Decoration.BuiltIn, (LiteralInteger)GetBuiltIn(context, attrInfo.BaseValue));
|
||||||
|
|
||||||
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline && isOutAttr)
|
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline && isOutAttr)
|
||||||
|
@ -2194,13 +2194,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
if (operation.Inst.HasFlag(Instruction.FP64))
|
if (operation.Inst.HasFlag(Instruction.FP64))
|
||||||
{
|
{
|
||||||
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2));
|
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2));
|
||||||
|
|
||||||
|
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
|
||||||
|
{
|
||||||
context.Decorate(result, Decoration.NoContraction);
|
context.Decorate(result, Decoration.NoContraction);
|
||||||
|
}
|
||||||
|
|
||||||
return new OperationResult(AggregateType.FP64, result);
|
return new OperationResult(AggregateType.FP64, result);
|
||||||
}
|
}
|
||||||
else if (operation.Inst.HasFlag(Instruction.FP32))
|
else if (operation.Inst.HasFlag(Instruction.FP32))
|
||||||
{
|
{
|
||||||
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2));
|
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2));
|
||||||
|
|
||||||
|
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
|
||||||
|
{
|
||||||
context.Decorate(result, Decoration.NoContraction);
|
context.Decorate(result, Decoration.NoContraction);
|
||||||
|
}
|
||||||
|
|
||||||
return new OperationResult(AggregateType.FP32, result);
|
return new OperationResult(AggregateType.FP32, result);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -2255,13 +2265,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
if (operation.Inst.HasFlag(Instruction.FP64))
|
if (operation.Inst.HasFlag(Instruction.FP64))
|
||||||
{
|
{
|
||||||
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2), context.GetFP64(src3));
|
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2), context.GetFP64(src3));
|
||||||
|
|
||||||
|
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
|
||||||
|
{
|
||||||
context.Decorate(result, Decoration.NoContraction);
|
context.Decorate(result, Decoration.NoContraction);
|
||||||
|
}
|
||||||
|
|
||||||
return new OperationResult(AggregateType.FP64, result);
|
return new OperationResult(AggregateType.FP64, result);
|
||||||
}
|
}
|
||||||
else if (operation.Inst.HasFlag(Instruction.FP32))
|
else if (operation.Inst.HasFlag(Instruction.FP32))
|
||||||
{
|
{
|
||||||
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2), context.GetFP32(src3));
|
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2), context.GetFP32(src3));
|
||||||
|
|
||||||
|
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
|
||||||
|
{
|
||||||
context.Decorate(result, Decoration.NoContraction);
|
context.Decorate(result, Decoration.NoContraction);
|
||||||
|
}
|
||||||
|
|
||||||
return new OperationResult(AggregateType.FP32, result);
|
return new OperationResult(AggregateType.FP32, result);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -114,6 +114,16 @@ namespace Ryujinx.Graphics.Shader
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queries output type for fragment shaders.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="location">Location of the framgent output</param>
|
||||||
|
/// <returns>Output location</returns>
|
||||||
|
AttributeType QueryFragmentOutputType(int location)
|
||||||
|
{
|
||||||
|
return AttributeType.Float;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queries Local Size X for compute shaders.
|
/// Queries Local Size X for compute shaders.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -186,6 +196,15 @@ namespace Ryujinx.Graphics.Shader
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queries host about whether to reduce precision to improve performance.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if precision is limited to vertex position, false otherwise</returns>
|
||||||
|
bool QueryHostReducedPrecision()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queries host about the presence of the FrontFacing built-in variable bug.
|
/// Queries host about the presence of the FrontFacing built-in variable bug.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -128,7 +128,15 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
}
|
}
|
||||||
else if (value >= AttributeConsts.FragmentOutputColorBase && value < AttributeConsts.FragmentOutputColorEnd)
|
else if (value >= AttributeConsts.FragmentOutputColorBase && value < AttributeConsts.FragmentOutputColorEnd)
|
||||||
{
|
{
|
||||||
return new AttributeInfo(value & ~0xf, (value >> 2) & 3, 4, AggregateType.Vector4 | AggregateType.FP32, false);
|
int location = (value - AttributeConsts.FragmentOutputColorBase) / 16;
|
||||||
|
var elemType = config.GpuAccessor.QueryFragmentOutputType(location) switch
|
||||||
|
{
|
||||||
|
AttributeType.Sint => AggregateType.S32,
|
||||||
|
AttributeType.Uint => AggregateType.U32,
|
||||||
|
_ => AggregateType.FP32
|
||||||
|
};
|
||||||
|
|
||||||
|
return new AttributeInfo(value & ~0xf, (value >> 2) & 3, 4, AggregateType.Vector4 | elemType, false);
|
||||||
}
|
}
|
||||||
else if (value == AttributeConsts.SupportBlockViewInverseX || value == AttributeConsts.SupportBlockViewInverseY)
|
else if (value == AttributeConsts.SupportBlockViewInverseX || value == AttributeConsts.SupportBlockViewInverseY)
|
||||||
{
|
{
|
||||||
|
@ -140,6 +140,25 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return _attachments[index];
|
return _attachments[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ComponentType GetAttachmentComponentType(int index)
|
||||||
|
{
|
||||||
|
if (_colors != null && (uint)index < _colors.Length)
|
||||||
|
{
|
||||||
|
var format = _colors[index].Info.Format;
|
||||||
|
|
||||||
|
if (format.IsSint())
|
||||||
|
{
|
||||||
|
return ComponentType.SignedInteger;
|
||||||
|
}
|
||||||
|
else if (format.IsUint())
|
||||||
|
{
|
||||||
|
return ComponentType.UnsignedInteger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ComponentType.Float;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsValidColorAttachment(int bindIndex)
|
public bool IsValidColorAttachment(int bindIndex)
|
||||||
{
|
{
|
||||||
return (uint)bindIndex < Constants.MaxRenderTargets && (_validColorAttachments & (1u << bindIndex)) != 0;
|
return (uint)bindIndex < Constants.MaxRenderTargets && (_validColorAttachments & (1u << bindIndex)) != 0;
|
||||||
|
@ -1,7 +1,20 @@
|
|||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
|
[Flags]
|
||||||
|
enum PortabilitySubsetFlags
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
VertexBufferAlignment4B = 1,
|
||||||
|
NoTriangleFans = 1 << 1,
|
||||||
|
NoPointMode = 1 << 2,
|
||||||
|
No3DImageView = 1 << 3,
|
||||||
|
NoLodBias = 1 << 4
|
||||||
|
}
|
||||||
|
|
||||||
readonly struct HardwareCapabilities
|
readonly struct HardwareCapabilities
|
||||||
{
|
{
|
||||||
public readonly bool SupportsIndexTypeUint8;
|
public readonly bool SupportsIndexTypeUint8;
|
||||||
@ -23,6 +36,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
public readonly uint MaxSubgroupSize;
|
public readonly uint MaxSubgroupSize;
|
||||||
public readonly ShaderStageFlags RequiredSubgroupSizeStages;
|
public readonly ShaderStageFlags RequiredSubgroupSizeStages;
|
||||||
public readonly SampleCountFlags SupportedSampleCounts;
|
public readonly SampleCountFlags SupportedSampleCounts;
|
||||||
|
public readonly PortabilitySubsetFlags PortabilitySubset;
|
||||||
|
|
||||||
public HardwareCapabilities(
|
public HardwareCapabilities(
|
||||||
bool supportsIndexTypeUint8,
|
bool supportsIndexTypeUint8,
|
||||||
@ -43,7 +57,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
uint minSubgroupSize,
|
uint minSubgroupSize,
|
||||||
uint maxSubgroupSize,
|
uint maxSubgroupSize,
|
||||||
ShaderStageFlags requiredSubgroupSizeStages,
|
ShaderStageFlags requiredSubgroupSizeStages,
|
||||||
SampleCountFlags supportedSampleCounts)
|
SampleCountFlags supportedSampleCounts,
|
||||||
|
PortabilitySubsetFlags portabilitySubset)
|
||||||
{
|
{
|
||||||
SupportsIndexTypeUint8 = supportsIndexTypeUint8;
|
SupportsIndexTypeUint8 = supportsIndexTypeUint8;
|
||||||
SupportsCustomBorderColor = supportsCustomBorderColor;
|
SupportsCustomBorderColor = supportsCustomBorderColor;
|
||||||
@ -64,6 +79,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
MaxSubgroupSize = maxSubgroupSize;
|
MaxSubgroupSize = maxSubgroupSize;
|
||||||
RequiredSubgroupSizeStages = requiredSubgroupSizeStages;
|
RequiredSubgroupSizeStages = requiredSubgroupSizeStages;
|
||||||
SupportedSampleCounts = supportedSampleCounts;
|
SupportedSampleCounts = supportedSampleCounts;
|
||||||
|
PortabilitySubset = portabilitySubset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,13 @@ using VkFormat = Silk.NET.Vulkan.Format;
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
|
enum ComponentType
|
||||||
|
{
|
||||||
|
Float,
|
||||||
|
SignedInteger,
|
||||||
|
UnsignedInteger
|
||||||
|
}
|
||||||
|
|
||||||
class HelperShader : IDisposable
|
class HelperShader : IDisposable
|
||||||
{
|
{
|
||||||
private const int UniformBufferAlignment = 256;
|
private const int UniformBufferAlignment = 256;
|
||||||
@ -18,7 +25,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private readonly ISampler _samplerNearest;
|
private readonly ISampler _samplerNearest;
|
||||||
private readonly IProgram _programColorBlit;
|
private readonly IProgram _programColorBlit;
|
||||||
private readonly IProgram _programColorBlitClearAlpha;
|
private readonly IProgram _programColorBlitClearAlpha;
|
||||||
private readonly IProgram _programColorClear;
|
private readonly IProgram _programColorClearF;
|
||||||
|
private readonly IProgram _programColorClearSI;
|
||||||
|
private readonly IProgram _programColorClearUI;
|
||||||
private readonly IProgram _programStrideChange;
|
private readonly IProgram _programStrideChange;
|
||||||
private readonly IProgram _programConvertIndexBuffer;
|
private readonly IProgram _programConvertIndexBuffer;
|
||||||
private readonly IProgram _programConvertIndirectData;
|
private readonly IProgram _programConvertIndirectData;
|
||||||
@ -63,10 +72,22 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
Array.Empty<int>(),
|
Array.Empty<int>(),
|
||||||
Array.Empty<int>());
|
Array.Empty<int>());
|
||||||
|
|
||||||
_programColorClear = gd.CreateProgramWithMinimalLayout(new[]
|
_programColorClearF = gd.CreateProgramWithMinimalLayout(new[]
|
||||||
{
|
{
|
||||||
new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
|
new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
|
||||||
new ShaderSource(ShaderBinaries.ColorClearFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
new ShaderSource(ShaderBinaries.ColorClearFFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
||||||
|
});
|
||||||
|
|
||||||
|
_programColorClearSI = gd.CreateProgramWithMinimalLayout(new[]
|
||||||
|
{
|
||||||
|
new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
|
||||||
|
new ShaderSource(ShaderBinaries.ColorClearSIFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
||||||
|
});
|
||||||
|
|
||||||
|
_programColorClearUI = gd.CreateProgramWithMinimalLayout(new[]
|
||||||
|
{
|
||||||
|
new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
|
||||||
|
new ShaderSource(ShaderBinaries.ColorClearUIFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
||||||
});
|
});
|
||||||
|
|
||||||
var strideChangeBindings = new ShaderBindings(
|
var strideChangeBindings = new ShaderBindings(
|
||||||
@ -242,6 +263,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
int dstWidth,
|
int dstWidth,
|
||||||
int dstHeight,
|
int dstHeight,
|
||||||
VkFormat dstFormat,
|
VkFormat dstFormat,
|
||||||
|
ComponentType type,
|
||||||
Rectangle<int> scissor)
|
Rectangle<int> scissor)
|
||||||
{
|
{
|
||||||
const int ClearColorBufferSize = 16;
|
const int ClearColorBufferSize = 16;
|
||||||
@ -273,7 +295,22 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
scissors[0] = scissor;
|
scissors[0] = scissor;
|
||||||
|
|
||||||
_pipeline.SetProgram(_programColorClear);
|
IProgram program;
|
||||||
|
|
||||||
|
if (type == ComponentType.SignedInteger)
|
||||||
|
{
|
||||||
|
program = _programColorClearSI;
|
||||||
|
}
|
||||||
|
else if (type == ComponentType.UnsignedInteger)
|
||||||
|
{
|
||||||
|
program = _programColorClearUI;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
program = _programColorClearF;
|
||||||
|
}
|
||||||
|
|
||||||
|
_pipeline.SetProgram(program);
|
||||||
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false, dstFormat);
|
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false, dstFormat);
|
||||||
_pipeline.SetRenderTargetColorMasks(new uint[] { componentMask });
|
_pipeline.SetRenderTargetColorMasks(new uint[] { componentMask });
|
||||||
_pipeline.SetViewports(viewports, false);
|
_pipeline.SetViewports(viewports, false);
|
||||||
@ -948,7 +985,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
_programColorBlitClearAlpha.Dispose();
|
_programColorBlitClearAlpha.Dispose();
|
||||||
_programColorBlit.Dispose();
|
_programColorBlit.Dispose();
|
||||||
_programColorClear.Dispose();
|
_programColorClearF.Dispose();
|
||||||
|
_programColorClearSI.Dispose();
|
||||||
|
_programColorClearUI.Dispose();
|
||||||
_programStrideChange.Dispose();
|
_programStrideChange.Dispose();
|
||||||
_programConvertIndexBuffer.Dispose();
|
_programConvertIndexBuffer.Dispose();
|
||||||
_programConvertIndirectData.Dispose();
|
_programConvertIndirectData.Dispose();
|
||||||
|
104
Ryujinx.Graphics.Vulkan/MoltenVK/MVKConfiguration.cs
Normal file
104
Ryujinx.Graphics.Vulkan/MoltenVK/MVKConfiguration.cs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Vulkan.MoltenVK
|
||||||
|
{
|
||||||
|
enum MVKConfigLogLevel : int
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Error = 1,
|
||||||
|
Warning = 2,
|
||||||
|
Info = 3,
|
||||||
|
Debug = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MVKConfigTraceVulkanCalls : int
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Enter = 1,
|
||||||
|
EnterExit = 2,
|
||||||
|
Duration = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MVKConfigAutoGPUCaptureScope : int
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Device = 1,
|
||||||
|
Frame = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
enum MVKConfigAdvertiseExtensions : int
|
||||||
|
{
|
||||||
|
All = 0x00000001,
|
||||||
|
MoltenVK = 0x00000002,
|
||||||
|
WSI = 0x00000004,
|
||||||
|
Portability = 0x00000008
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MVKVkSemaphoreSupportStyle : int
|
||||||
|
{
|
||||||
|
MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE = 0,
|
||||||
|
MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_METAL_EVENTS_WHERE_SAFE = 1,
|
||||||
|
MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_METAL_EVENTS = 2,
|
||||||
|
MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_CALLBACK = 3,
|
||||||
|
MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_MAX_ENUM = 0x7FFFFFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly struct Bool32
|
||||||
|
{
|
||||||
|
uint Value { get; }
|
||||||
|
|
||||||
|
public Bool32(uint value)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bool32(bool value)
|
||||||
|
{
|
||||||
|
Value = value ? 1u : 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator bool(Bool32 val) => val.Value == 1;
|
||||||
|
public static implicit operator Bool32(bool val) => new Bool32(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
struct MVKConfiguration
|
||||||
|
{
|
||||||
|
public Bool32 DebugMode;
|
||||||
|
public Bool32 ShaderConversionFlipVertexY;
|
||||||
|
public Bool32 SynchronousQueueSubmits;
|
||||||
|
public Bool32 PrefillMetalCommandBuffers;
|
||||||
|
public uint MaxActiveMetalCommandBuffersPerQueue;
|
||||||
|
public Bool32 SupportLargeQueryPools;
|
||||||
|
public Bool32 PresentWithCommandBuffer;
|
||||||
|
public Bool32 SwapchainMagFilterUseNearest;
|
||||||
|
public ulong MetalCompileTimeout;
|
||||||
|
public Bool32 PerformanceTracking;
|
||||||
|
public uint PerformanceLoggingFrameCount;
|
||||||
|
public Bool32 DisplayWatermark;
|
||||||
|
public Bool32 SpecializedQueueFamilies;
|
||||||
|
public Bool32 SwitchSystemGPU;
|
||||||
|
public Bool32 FullImageViewSwizzle;
|
||||||
|
public uint DefaultGPUCaptureScopeQueueFamilyIndex;
|
||||||
|
public uint DefaultGPUCaptureScopeQueueIndex;
|
||||||
|
public Bool32 FastMathEnabled;
|
||||||
|
public MVKConfigLogLevel LogLevel;
|
||||||
|
public MVKConfigTraceVulkanCalls TraceVulkanCalls;
|
||||||
|
public Bool32 ForceLowPowerGPU;
|
||||||
|
public Bool32 SemaphoreUseMTLFence;
|
||||||
|
public MVKVkSemaphoreSupportStyle SemaphoreSupportStyle;
|
||||||
|
public MVKConfigAutoGPUCaptureScope AutoGPUCaptureScope;
|
||||||
|
public IntPtr AutoGPUCaptureOutputFilepath;
|
||||||
|
public Bool32 Texture1DAs2D;
|
||||||
|
public Bool32 PreallocateDescriptors;
|
||||||
|
public Bool32 UseCommandPooling;
|
||||||
|
public Bool32 UseMTLHeap;
|
||||||
|
public Bool32 LogActivityPerformanceInline;
|
||||||
|
public uint ApiVersionToAdvertise;
|
||||||
|
public MVKConfigAdvertiseExtensions AdvertiseExtensions;
|
||||||
|
public Bool32 ResumeLostDevice;
|
||||||
|
public Bool32 UseMetalArgumentBuffers;
|
||||||
|
}
|
||||||
|
}
|
31
Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs
Normal file
31
Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Vulkan.MoltenVK
|
||||||
|
{
|
||||||
|
[SupportedOSPlatform("macos")]
|
||||||
|
public static partial class MVKInitialization
|
||||||
|
{
|
||||||
|
[LibraryImport("libMoltenVK.dylib")]
|
||||||
|
private static partial Result vkGetMoltenVKConfigurationMVK(IntPtr unusedInstance, out MVKConfiguration config, in IntPtr configSize);
|
||||||
|
|
||||||
|
[LibraryImport("libMoltenVK.dylib")]
|
||||||
|
private static partial Result vkSetMoltenVKConfigurationMVK(IntPtr unusedInstance, in MVKConfiguration config, in IntPtr configSize);
|
||||||
|
|
||||||
|
public static void Initialize()
|
||||||
|
{
|
||||||
|
var configSize = (IntPtr)Marshal.SizeOf<MVKConfiguration>();
|
||||||
|
|
||||||
|
vkGetMoltenVKConfigurationMVK(IntPtr.Zero, out MVKConfiguration config, configSize);
|
||||||
|
|
||||||
|
config.UseMetalArgumentBuffers = true;
|
||||||
|
|
||||||
|
config.SemaphoreSupportStyle = MVKVkSemaphoreSupportStyle.MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE;
|
||||||
|
config.SynchronousQueueSubmits = false;
|
||||||
|
|
||||||
|
vkSetMoltenVKConfigurationMVK(IntPtr.Zero, config, configSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
using Ryujinx.Graphics.Shader;
|
using Ryujinx.Graphics.Shader;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
@ -50,6 +51,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private Auto<DisposableRenderPass> _renderPass;
|
private Auto<DisposableRenderPass> _renderPass;
|
||||||
private int _writtenAttachmentCount;
|
private int _writtenAttachmentCount;
|
||||||
|
|
||||||
|
private bool _framebufferUsingColorWriteMask;
|
||||||
|
|
||||||
|
private ITexture[] _preMaskColors;
|
||||||
|
private ITexture _preMaskDepthStencil;
|
||||||
|
|
||||||
private readonly DescriptorSetUpdater _descriptorSetUpdater;
|
private readonly DescriptorSetUpdater _descriptorSetUpdater;
|
||||||
|
|
||||||
private IndexBufferState _indexBuffer;
|
private IndexBufferState _indexBuffer;
|
||||||
@ -905,6 +911,12 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_framebufferUsingColorWriteMask)
|
||||||
|
{
|
||||||
|
SetRenderTargetsInternal(_preMaskColors, _preMaskDepthStencil, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
SignalStateChange();
|
SignalStateChange();
|
||||||
|
|
||||||
if (writtenAttachments != _writtenAttachmentCount)
|
if (writtenAttachments != _writtenAttachmentCount)
|
||||||
@ -913,16 +925,23 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_writtenAttachmentCount = writtenAttachments;
|
_writtenAttachmentCount = writtenAttachments;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
|
private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
|
||||||
{
|
{
|
||||||
FramebufferParams?.UpdateModifications();
|
FramebufferParams?.UpdateModifications();
|
||||||
CreateFramebuffer(colors, depthStencil);
|
CreateFramebuffer(colors, depthStencil, filterWriteMasked);
|
||||||
CreateRenderPass();
|
CreateRenderPass();
|
||||||
SignalStateChange();
|
SignalStateChange();
|
||||||
SignalAttachmentChange();
|
SignalAttachmentChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
|
||||||
|
{
|
||||||
|
_framebufferUsingColorWriteMask = false;
|
||||||
|
SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR);
|
||||||
|
}
|
||||||
|
|
||||||
public void SetRenderTargetScale(float scale)
|
public void SetRenderTargetScale(float scale)
|
||||||
{
|
{
|
||||||
_renderScale[0].X = scale;
|
_renderScale[0].X = scale;
|
||||||
@ -1102,7 +1121,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
int vbSize = vertexBuffer.Buffer.Size;
|
int vbSize = vertexBuffer.Buffer.Size;
|
||||||
|
|
||||||
if (Gd.Vendor == Vendor.Amd && vertexBuffer.Stride > 0)
|
if (Gd.Vendor == Vendor.Amd && !Gd.IsMoltenVk && vertexBuffer.Stride > 0)
|
||||||
{
|
{
|
||||||
// AMD has a bug where if offset + stride * count is greater than
|
// AMD has a bug where if offset + stride * count is greater than
|
||||||
// the size, then the last attribute will have the wrong value.
|
// the size, then the last attribute will have the wrong value.
|
||||||
@ -1119,7 +1138,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
buffer.Dispose();
|
buffer.Dispose();
|
||||||
|
|
||||||
if ((vertexBuffer.Stride % FormatExtensions.MaxBufferFormatScalarSize) == 0)
|
if (!Gd.Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.VertexBufferAlignment4B) &&
|
||||||
|
(vertexBuffer.Stride % FormatExtensions.MaxBufferFormatScalarSize) == 0)
|
||||||
{
|
{
|
||||||
buffer = new VertexBufferState(
|
buffer = new VertexBufferState(
|
||||||
vb,
|
vb,
|
||||||
@ -1259,8 +1279,62 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_currentPipelineHandle = 0;
|
_currentPipelineHandle = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil)
|
private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
|
||||||
{
|
{
|
||||||
|
if (filterWriteMasked)
|
||||||
|
{
|
||||||
|
// TBDR GPUs don't work properly if the same attachment is bound to multiple targets,
|
||||||
|
// due to each attachment being a copy of the real attachment, rather than a direct write.
|
||||||
|
|
||||||
|
// Just try to remove duplicate attachments.
|
||||||
|
// Save a copy of the array to rebind when mask changes.
|
||||||
|
|
||||||
|
void maskOut()
|
||||||
|
{
|
||||||
|
if (!_framebufferUsingColorWriteMask)
|
||||||
|
{
|
||||||
|
_preMaskColors = colors.ToArray();
|
||||||
|
_preMaskDepthStencil = depthStencil;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If true, then the framebuffer must be recreated when the mask changes.
|
||||||
|
_framebufferUsingColorWriteMask = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for textures that are masked out.
|
||||||
|
|
||||||
|
for (int i = 0; i < colors.Length; i++)
|
||||||
|
{
|
||||||
|
if (colors[i] == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[i];
|
||||||
|
|
||||||
|
for (int j = 0; j < i; j++)
|
||||||
|
{
|
||||||
|
// Check each binding for a duplicate binding before it.
|
||||||
|
|
||||||
|
if (colors[i] == colors[j])
|
||||||
|
{
|
||||||
|
// Prefer the binding with no write mask.
|
||||||
|
ref var vkBlend2 = ref _newState.Internal.ColorBlendAttachmentState[j];
|
||||||
|
if (vkBlend.ColorWriteMask == 0)
|
||||||
|
{
|
||||||
|
colors[i] = null;
|
||||||
|
maskOut();
|
||||||
|
}
|
||||||
|
else if (vkBlend2.ColorWriteMask == 0)
|
||||||
|
{
|
||||||
|
colors[j] = null;
|
||||||
|
maskOut();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FramebufferParams = new FramebufferParams(Device, colors, depthStencil);
|
FramebufferParams = new FramebufferParams(Device, colors, depthStencil);
|
||||||
UpdatePipelineAttachmentFormats();
|
UpdatePipelineAttachmentFormats();
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
(int)FramebufferParams.Width,
|
(int)FramebufferParams.Width,
|
||||||
(int)FramebufferParams.Height,
|
(int)FramebufferParams.Height,
|
||||||
FramebufferParams.AttachmentFormats[index],
|
FramebufferParams.AttachmentFormats[index],
|
||||||
|
FramebufferParams.GetAttachmentComponentType(index),
|
||||||
ClearScissor);
|
ClearScissor);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
#version 450 core
|
||||||
|
|
||||||
|
layout (location = 0) in vec4 clear_colour;
|
||||||
|
layout (location = 0) out ivec4 colour;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
colour = floatBitsToInt(clear_colour);
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user