forked from Mirror/Ryujinx
infra: Migrate to .NET 6 (#2829)
* infra: Migrate to .NET 6 * Rollback version naming change * Workaround .NET 6 ZipArchive API issues * ci: Switch to VS 2022 for AppVeyor CI is now ready for .NET 6 * Suppress WebClient warning in DoUpdateWithMultipleThreads * Attempt to workaround System.Drawing.Common changes on 6.0.0 * Change keyboard rendering from System.Drawing to ImageSharp * Make the software keyboard renderer multithreaded * Bump ImageSharp version to 1.0.4 to fix a bug in Image.Load * Add fallback fonts to the keyboard renderer * Fix warnings * Address caian's comment * Clean up linux workaround as it's uneeded now * Update readme Co-authored-by: Caian Benedicto <caianbene@gmail.com>
This commit is contained in:
parent
7b040e51b0
commit
57d3296ba4
51 changed files with 917 additions and 766 deletions
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
|
@ -50,7 +50,7 @@ jobs:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-dotnet@v1
|
- uses: actions/setup-dotnet@v1
|
||||||
with:
|
with:
|
||||||
dotnet-version: 5.0.x
|
dotnet-version: 6.0.x
|
||||||
- name: Ensure NuGet Source
|
- name: Ensure NuGet Source
|
||||||
uses: fabriciomurta/ensure-nuget-source@v1
|
uses: fabriciomurta/ensure-nuget-source@v1
|
||||||
- name: Get git short hash
|
- name: Get git short hash
|
||||||
|
@ -63,10 +63,10 @@ jobs:
|
||||||
- name: Test
|
- name: Test
|
||||||
run: dotnet test -c "${{ matrix.configuration }}"
|
run: dotnet test -c "${{ matrix.configuration }}"
|
||||||
- name: Publish Ryujinx
|
- name: Publish Ryujinx
|
||||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish /p:Version="1.0.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx
|
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish /p:Version="1.0.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx --self-contained
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
- name: Publish Ryujinx.Headless.SDL2
|
- name: Publish Ryujinx.Headless.SDL2
|
||||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless /p:Version="1.0.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Headless.SDL2
|
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless /p:Version="1.0.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Headless.SDL2 --self-contained
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
- name: Upload Ryujinx artifact
|
- name: Upload Ryujinx artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
<PackageReference Include="Mono.Posix.NETStandard" Version="5.20.1-preview" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -35,7 +35,7 @@ The latest automatic build for Windows, macOS, and Linux can be found on the [Of
|
||||||
|
|
||||||
If you wish to build the emulator yourself you will need to:
|
If you wish to build the emulator yourself you will need to:
|
||||||
|
|
||||||
**Step one:** Install the X64 version of [.NET 5.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/5.0).
|
**Step one:** Install the X64 version of [.NET 6.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/6.0).
|
||||||
|
|
||||||
**Step two (choose one):**
|
**Step two (choose one):**
|
||||||
**(Variant one)**
|
**(Variant one)**
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="5.0.1" />
|
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
||||||
<PackageReference Include="System.Management" Version="5.0.0" />
|
<PackageReference Include="System.Management" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
namespace Ryujinx.Common.System
|
namespace Ryujinx.Common.System
|
||||||
{
|
{
|
||||||
|
@ -19,7 +20,7 @@ namespace Ryujinx.Common.System
|
||||||
public static void Windows()
|
public static void Windows()
|
||||||
{
|
{
|
||||||
// Make process DPI aware for proper window sizing on high-res screens.
|
// Make process DPI aware for proper window sizing on high-res screens.
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.OSVersion.Version.Major >= 6)
|
if (OperatingSystem.IsWindowsVersionAtLeast(6))
|
||||||
{
|
{
|
||||||
SetProcessDPIAware();
|
SetProcessDPIAware();
|
||||||
}
|
}
|
||||||
|
@ -27,16 +28,22 @@ namespace Ryujinx.Common.System
|
||||||
|
|
||||||
public static double GetWindowScaleFactor()
|
public static double GetWindowScaleFactor()
|
||||||
{
|
{
|
||||||
double userDpiScale;
|
double userDpiScale = 96.0;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
userDpiScale = Graphics.FromHwnd(IntPtr.Zero).DpiX;
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
userDpiScale = Graphics.FromHwnd(IntPtr.Zero).DpiX;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Linux support
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: {e.Message}");
|
Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: {e.Message}");
|
||||||
userDpiScale = 96.0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Math.Min(userDpiScale / _standardDpiScale, _maxScaleFactor);
|
return Math.Min(userDpiScale / _standardDpiScale, _maxScaleFactor);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
@ -14,4 +14,8 @@
|
||||||
<ProjectReference Include="..\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj" />
|
<ProjectReference Include="..\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
using Ryujinx.Common;
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
@ -119,7 +119,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Main storage of the cache collection.
|
/// Main storage of the cache collection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private ZipArchive _cacheArchive;
|
private ZipFile _cacheArchive;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates if the cache collection supports modification.
|
/// Indicates if the cache collection supports modification.
|
||||||
|
@ -324,7 +324,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
EnsureArchiveUpToDate();
|
EnsureArchiveUpToDate();
|
||||||
|
|
||||||
// Open the zip in readonly to avoid anyone modifying/corrupting it during normal operations.
|
// Open the zip in readonly to avoid anyone modifying/corrupting it during normal operations.
|
||||||
_cacheArchive = ZipFile.Open(GetArchivePath(), ZipArchiveMode.Read);
|
_cacheArchive = new ZipFile(File.OpenRead(GetArchivePath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -336,7 +336,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
// First close previous opened instance if found.
|
// First close previous opened instance if found.
|
||||||
if (_cacheArchive != null)
|
if (_cacheArchive != null)
|
||||||
{
|
{
|
||||||
_cacheArchive.Dispose();
|
_cacheArchive.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
string archivePath = GetArchivePath();
|
string archivePath = GetArchivePath();
|
||||||
|
@ -355,8 +355,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(archivePath))
|
||||||
|
{
|
||||||
|
using (ZipFile newZip = ZipFile.Create(archivePath))
|
||||||
|
{
|
||||||
|
// Workaround for SharpZipLib issue #395
|
||||||
|
newZip.BeginUpdate();
|
||||||
|
newZip.CommitUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Open the zip in read/write.
|
// Open the zip in read/write.
|
||||||
_cacheArchive = ZipFile.Open(archivePath, ZipArchiveMode.Update);
|
_cacheArchive = new ZipFile(File.Open(archivePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None));
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Gpu, $"Updating cache collection archive {archivePath}...");
|
Logger.Info?.Print(LogClass.Gpu, $"Updating cache collection archive {archivePath}...");
|
||||||
|
|
||||||
|
@ -366,7 +376,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
CacheHelper.EnsureArchiveUpToDate(_cacheDirectory, _cacheArchive, _hashTable);
|
CacheHelper.EnsureArchiveUpToDate(_cacheDirectory, _cacheArchive, _hashTable);
|
||||||
|
|
||||||
// Close the instance to force a flush.
|
// Close the instance to force a flush.
|
||||||
_cacheArchive.Dispose();
|
_cacheArchive.Close();
|
||||||
_cacheArchive = null;
|
_cacheArchive = null;
|
||||||
|
|
||||||
string cacheTempDataPath = GetCacheTempDataPath();
|
string cacheTempDataPath = GetCacheTempDataPath();
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Ryujinx.Common;
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
|
@ -9,7 +10,6 @@ using Ryujinx.Graphics.Shader.Translation;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
@ -192,19 +192,19 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
/// <param name="entry">The given hash</param>
|
/// <param name="entry">The given hash</param>
|
||||||
/// <returns>The cached file if present or null</returns>
|
/// <returns>The cached file if present or null</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static byte[] ReadFromArchive(ZipArchive archive, Hash128 entry)
|
public static byte[] ReadFromArchive(ZipFile archive, Hash128 entry)
|
||||||
{
|
{
|
||||||
if (archive != null)
|
if (archive != null)
|
||||||
{
|
{
|
||||||
ZipArchiveEntry archiveEntry = archive.GetEntry($"{entry}");
|
ZipEntry archiveEntry = archive.GetEntry($"{entry}");
|
||||||
|
|
||||||
if (archiveEntry != null)
|
if (archiveEntry != null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
byte[] result = new byte[archiveEntry.Length];
|
byte[] result = new byte[archiveEntry.Size];
|
||||||
|
|
||||||
using (Stream archiveStream = archiveEntry.Open())
|
using (Stream archiveStream = archive.GetInputStream(archiveEntry))
|
||||||
{
|
{
|
||||||
archiveStream.Read(result);
|
archiveStream.Read(result);
|
||||||
|
|
||||||
|
@ -538,8 +538,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
/// <param name="archive">The archive to use</param>
|
/// <param name="archive">The archive to use</param>
|
||||||
/// <param name="entries">The entries in the cache</param>
|
/// <param name="entries">The entries in the cache</param>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void EnsureArchiveUpToDate(string baseCacheDirectory, ZipArchive archive, HashSet<Hash128> entries)
|
public static void EnsureArchiveUpToDate(string baseCacheDirectory, ZipFile archive, HashSet<Hash128> entries)
|
||||||
{
|
{
|
||||||
|
List<string> filesToDelete = new List<string>();
|
||||||
|
|
||||||
|
archive.BeginUpdate();
|
||||||
|
|
||||||
foreach (Hash128 hash in entries)
|
foreach (Hash128 hash in entries)
|
||||||
{
|
{
|
||||||
string cacheTempFilePath = GenCacheTempFilePath(baseCacheDirectory, hash);
|
string cacheTempFilePath = GenCacheTempFilePath(baseCacheDirectory, hash);
|
||||||
|
@ -548,15 +552,25 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
{
|
{
|
||||||
string cacheHash = $"{hash}";
|
string cacheHash = $"{hash}";
|
||||||
|
|
||||||
ZipArchiveEntry entry = archive.GetEntry(cacheHash);
|
ZipEntry entry = archive.GetEntry(cacheHash);
|
||||||
|
|
||||||
entry?.Delete();
|
if (entry != null)
|
||||||
|
{
|
||||||
|
archive.Delete(entry);
|
||||||
|
}
|
||||||
|
|
||||||
archive.CreateEntryFromFile(cacheTempFilePath, cacheHash);
|
// We enforce deflate compression here to avoid possible incompatibilities on older version of Ryujinx that use System.IO.Compression.
|
||||||
|
archive.Add(new StaticDiskDataSource(cacheTempFilePath), cacheHash, CompressionMethod.Deflated);
|
||||||
File.Delete(cacheTempFilePath);
|
filesToDelete.Add(cacheTempFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
archive.CommitUpdate();
|
||||||
|
|
||||||
|
foreach (string filePath in filesToDelete)
|
||||||
|
{
|
||||||
|
File.Delete(filePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsArchiveReadOnly(string archivePath)
|
public static bool IsArchiveReadOnly(string archivePath)
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
using Ryujinx.Common;
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
{
|
{
|
||||||
|
@ -35,27 +35,36 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class StreamZipEntryDataSource : IStaticDataSource
|
||||||
|
{
|
||||||
|
private readonly ZipFile Archive;
|
||||||
|
private readonly ZipEntry Entry;
|
||||||
|
public StreamZipEntryDataSource(ZipFile archive, ZipEntry entry)
|
||||||
|
{
|
||||||
|
Archive = archive;
|
||||||
|
Entry = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream GetSource()
|
||||||
|
{
|
||||||
|
return Archive.GetInputStream(Entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Move a file with the name of a given hash to another in the cache archive.
|
/// Move a file with the name of a given hash to another in the cache archive.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="archive">The archive in use</param>
|
/// <param name="archive">The archive in use</param>
|
||||||
/// <param name="oldKey">The old key</param>
|
/// <param name="oldKey">The old key</param>
|
||||||
/// <param name="newKey">The new key</param>
|
/// <param name="newKey">The new key</param>
|
||||||
private static void MoveEntry(ZipArchive archive, Hash128 oldKey, Hash128 newKey)
|
private static void MoveEntry(ZipFile archive, Hash128 oldKey, Hash128 newKey)
|
||||||
{
|
{
|
||||||
ZipArchiveEntry oldGuestEntry = archive.GetEntry($"{oldKey}");
|
ZipEntry oldGuestEntry = archive.GetEntry($"{oldKey}");
|
||||||
|
|
||||||
if (oldGuestEntry != null)
|
if (oldGuestEntry != null)
|
||||||
{
|
{
|
||||||
ZipArchiveEntry newGuestEntry = archive.CreateEntry($"{newKey}");
|
archive.Add(new StreamZipEntryDataSource(archive, oldGuestEntry), $"{newKey}", CompressionMethod.Deflated);
|
||||||
|
archive.Delete(oldGuestEntry);
|
||||||
using (Stream oldStream = oldGuestEntry.Open())
|
|
||||||
using (Stream newStream = newGuestEntry.Open())
|
|
||||||
{
|
|
||||||
oldStream.CopyTo(newStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
oldGuestEntry.Delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,8 +90,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
string guestArchivePath = CacheHelper.GetArchivePath(guestBaseCacheDirectory);
|
string guestArchivePath = CacheHelper.GetArchivePath(guestBaseCacheDirectory);
|
||||||
string hostArchivePath = CacheHelper.GetArchivePath(hostBaseCacheDirectory);
|
string hostArchivePath = CacheHelper.GetArchivePath(hostBaseCacheDirectory);
|
||||||
|
|
||||||
ZipArchive guestArchive = ZipFile.Open(guestArchivePath, ZipArchiveMode.Update);
|
ZipFile guestArchive = new ZipFile(File.Open(guestArchivePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None));
|
||||||
ZipArchive hostArchive = ZipFile.Open(hostArchivePath, ZipArchiveMode.Update);
|
ZipFile hostArchive = new ZipFile(File.Open(hostArchivePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None));
|
||||||
|
|
||||||
CacheHelper.EnsureArchiveUpToDate(guestBaseCacheDirectory, guestArchive, guestEntries);
|
CacheHelper.EnsureArchiveUpToDate(guestBaseCacheDirectory, guestArchive, guestEntries);
|
||||||
CacheHelper.EnsureArchiveUpToDate(hostBaseCacheDirectory, hostArchive, hostEntries);
|
CacheHelper.EnsureArchiveUpToDate(hostBaseCacheDirectory, hostArchive, hostEntries);
|
||||||
|
@ -129,8 +138,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
File.WriteAllBytes(guestManifestPath, newGuestManifestContent);
|
File.WriteAllBytes(guestManifestPath, newGuestManifestContent);
|
||||||
File.WriteAllBytes(hostManifestPath, newHostManifestContent);
|
File.WriteAllBytes(hostManifestPath, newHostManifestContent);
|
||||||
|
|
||||||
guestArchive.Dispose();
|
guestArchive.CommitUpdate();
|
||||||
hostArchive.Dispose();
|
hostArchive.CommitUpdate();
|
||||||
|
|
||||||
|
guestArchive.Close();
|
||||||
|
hostArchive.Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -170,7 +170,9 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||||
{
|
{
|
||||||
_npads?.Update();
|
_npads?.Update();
|
||||||
|
|
||||||
return _keyboardRenderer?.DrawTo(surfaceInfo, destination, position) ?? false;
|
_keyboardRenderer?.SetSurfaceInfo(surfaceInfo);
|
||||||
|
|
||||||
|
return _keyboardRenderer?.DrawTo(destination, position) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExecuteForegroundKeyboard()
|
private void ExecuteForegroundKeyboard()
|
||||||
|
|
|
@ -1,717 +1,164 @@
|
||||||
using Ryujinx.HLE.Ui;
|
using Ryujinx.HLE.Ui;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers.Binary;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Drawing.Drawing2D;
|
|
||||||
using System.Drawing.Imaging;
|
|
||||||
using System.Drawing.Text;
|
|
||||||
using System.IO;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class that generates the graphics for the software keyboard applet during inline mode.
|
/// Class that manages the renderer base class and its state in a multithreaded context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class SoftwareKeyboardRenderer : IDisposable
|
internal class SoftwareKeyboardRenderer : IDisposable
|
||||||
{
|
{
|
||||||
const int TextBoxBlinkThreshold = 8;
|
private const int TextBoxBlinkSleepMilliseconds = 100;
|
||||||
const int TextBoxBlinkSleepMilliseconds = 100;
|
private const int RendererWaitTimeoutMilliseconds = 100;
|
||||||
const int TextBoxBlinkJoinWaitMilliseconds = 1000;
|
|
||||||
|
|
||||||
const string MessageText = "Please use the keyboard to input text";
|
private readonly object _stateLock = new object();
|
||||||
const string AcceptText = "Accept";
|
|
||||||
const string CancelText = "Cancel";
|
|
||||||
const string ControllerToggleText = "Toggle input";
|
|
||||||
|
|
||||||
private RenderingSurfaceInfo _surfaceInfo;
|
private SoftwareKeyboardUiState _state = new SoftwareKeyboardUiState();
|
||||||
private Bitmap _surface = null;
|
private SoftwareKeyboardRendererBase _renderer;
|
||||||
private object _renderLock = new object();
|
|
||||||
|
|
||||||
private string _inputText = "";
|
|
||||||
private int _cursorStart = 0;
|
|
||||||
private int _cursorEnd = 0;
|
|
||||||
private bool _acceptPressed = false;
|
|
||||||
private bool _cancelPressed = false;
|
|
||||||
private bool _overwriteMode = false;
|
|
||||||
private bool _typingEnabled = true;
|
|
||||||
private bool _controllerEnabled = true;
|
|
||||||
|
|
||||||
private Image _ryujinxLogo = null;
|
|
||||||
private Image _padAcceptIcon = null;
|
|
||||||
private Image _padCancelIcon = null;
|
|
||||||
private Image _keyModeIcon = null;
|
|
||||||
|
|
||||||
private float _textBoxOutlineWidth;
|
|
||||||
private float _padPressedPenWidth;
|
|
||||||
|
|
||||||
private Brush _panelBrush;
|
|
||||||
private Brush _disabledBrush;
|
|
||||||
private Brush _textNormalBrush;
|
|
||||||
private Brush _textSelectedBrush;
|
|
||||||
private Brush _textOverCursorBrush;
|
|
||||||
private Brush _cursorBrush;
|
|
||||||
private Brush _selectionBoxBrush;
|
|
||||||
private Brush _keyCapBrush;
|
|
||||||
private Brush _keyProgressBrush;
|
|
||||||
|
|
||||||
private Pen _gridSeparatorPen;
|
|
||||||
private Pen _textBoxOutlinePen;
|
|
||||||
private Pen _cursorPen;
|
|
||||||
private Pen _selectionBoxPen;
|
|
||||||
private Pen _padPressedPen;
|
|
||||||
|
|
||||||
private int _inputTextFontSize;
|
|
||||||
private int _padButtonFontSize;
|
|
||||||
private Font _messageFont;
|
|
||||||
private Font _inputTextFont;
|
|
||||||
private Font _labelsTextFont;
|
|
||||||
private Font _padSymbolFont;
|
|
||||||
private Font _keyCapFont;
|
|
||||||
|
|
||||||
private float _inputTextCalibrationHeight;
|
|
||||||
private float _panelPositionY;
|
|
||||||
private RectangleF _panelRectangle;
|
|
||||||
private PointF _logoPosition;
|
|
||||||
private float _messagePositionY;
|
|
||||||
|
|
||||||
private TRef<int> _textBoxBlinkCounter = new TRef<int>(0);
|
|
||||||
private TimedAction _textBoxBlinkTimedAction = new TimedAction();
|
private TimedAction _textBoxBlinkTimedAction = new TimedAction();
|
||||||
|
private TimedAction _renderAction = new TimedAction();
|
||||||
|
|
||||||
public SoftwareKeyboardRenderer(IHostUiTheme uiTheme)
|
public SoftwareKeyboardRenderer(IHostUiTheme uiTheme)
|
||||||
{
|
{
|
||||||
_surfaceInfo = new RenderingSurfaceInfo(0, 0, 0, 0, 0);
|
_renderer = new SoftwareKeyboardRendererBase(uiTheme);
|
||||||
|
|
||||||
string ryujinxLogoPath = "Ryujinx.Ui.Resources.Logo_Ryujinx.png";
|
StartTextBoxBlinker(_textBoxBlinkTimedAction, _state, _stateLock);
|
||||||
int ryujinxLogoSize = 32;
|
StartRenderer(_renderAction, _renderer, _state, _stateLock);
|
||||||
|
|
||||||
_ryujinxLogo = LoadResource(Assembly.GetEntryAssembly(), ryujinxLogoPath, ryujinxLogoSize, ryujinxLogoSize);
|
|
||||||
|
|
||||||
string padAcceptIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnA.png";
|
|
||||||
string padCancelIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnB.png";
|
|
||||||
string keyModeIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_KeyF6.png";
|
|
||||||
|
|
||||||
_padAcceptIcon = LoadResource(Assembly.GetExecutingAssembly(), padAcceptIconPath , 0, 0);
|
|
||||||
_padCancelIcon = LoadResource(Assembly.GetExecutingAssembly(), padCancelIconPath , 0, 0);
|
|
||||||
_keyModeIcon = LoadResource(Assembly.GetExecutingAssembly(), keyModeIconPath , 0, 0);
|
|
||||||
|
|
||||||
Color panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255);
|
|
||||||
Color panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150);
|
|
||||||
Color normalTextColor = ToColor(uiTheme.DefaultForegroundColor);
|
|
||||||
Color invertedTextColor = ToColor(uiTheme.DefaultForegroundColor, null, true);
|
|
||||||
Color selectedTextColor = ToColor(uiTheme.SelectionForegroundColor);
|
|
||||||
Color borderColor = ToColor(uiTheme.DefaultBorderColor);
|
|
||||||
Color selectionBackgroundColor = ToColor(uiTheme.SelectionBackgroundColor);
|
|
||||||
Color gridSeparatorColor = Color.FromArgb(180, 255, 255, 255);
|
|
||||||
|
|
||||||
float cursorWidth = 2;
|
|
||||||
|
|
||||||
_textBoxOutlineWidth = 2;
|
|
||||||
_padPressedPenWidth = 2;
|
|
||||||
|
|
||||||
_panelBrush = new SolidBrush(panelColor);
|
|
||||||
_disabledBrush = new SolidBrush(panelTransparentColor);
|
|
||||||
_textNormalBrush = new SolidBrush(normalTextColor);
|
|
||||||
_textSelectedBrush = new SolidBrush(selectedTextColor);
|
|
||||||
_textOverCursorBrush = new SolidBrush(invertedTextColor);
|
|
||||||
_cursorBrush = new SolidBrush(normalTextColor);
|
|
||||||
_selectionBoxBrush = new SolidBrush(selectionBackgroundColor);
|
|
||||||
_keyCapBrush = Brushes.White;
|
|
||||||
_keyProgressBrush = new SolidBrush(borderColor);
|
|
||||||
|
|
||||||
_gridSeparatorPen = new Pen(gridSeparatorColor, 2);
|
|
||||||
_textBoxOutlinePen = new Pen(borderColor, _textBoxOutlineWidth);
|
|
||||||
_cursorPen = new Pen(normalTextColor, cursorWidth);
|
|
||||||
_selectionBoxPen = new Pen(selectionBackgroundColor, cursorWidth);
|
|
||||||
_padPressedPen = new Pen(borderColor, _padPressedPenWidth);
|
|
||||||
|
|
||||||
_inputTextFontSize = 20;
|
|
||||||
_padButtonFontSize = 24;
|
|
||||||
|
|
||||||
string font = uiTheme.FontFamily;
|
|
||||||
|
|
||||||
_messageFont = new Font(font, 26, FontStyle.Regular, GraphicsUnit.Pixel);
|
|
||||||
_inputTextFont = new Font(font, _inputTextFontSize, FontStyle.Regular, GraphicsUnit.Pixel);
|
|
||||||
_labelsTextFont = new Font(font, 24, FontStyle.Regular, GraphicsUnit.Pixel);
|
|
||||||
_padSymbolFont = new Font(font, _padButtonFontSize, FontStyle.Regular, GraphicsUnit.Pixel);
|
|
||||||
_keyCapFont = new Font(font, 15, FontStyle.Regular, GraphicsUnit.Pixel);
|
|
||||||
|
|
||||||
// System.Drawing has serious problems measuring strings, so it requires a per-pixel calibration
|
|
||||||
// to ensure we are rendering text inside the proper region
|
|
||||||
_inputTextCalibrationHeight = CalibrateTextHeight(_inputTextFont);
|
|
||||||
|
|
||||||
StartTextBoxBlinker(_textBoxBlinkTimedAction, _textBoxBlinkCounter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void StartTextBoxBlinker(TimedAction timedAction, TRef<int> blinkerCounter)
|
private static void StartTextBoxBlinker(TimedAction timedAction, SoftwareKeyboardUiState state, object stateLock)
|
||||||
{
|
{
|
||||||
timedAction.Reset(() =>
|
timedAction.Reset(() =>
|
||||||
{
|
{
|
||||||
// The blinker is on falf of the time and events such as input
|
lock (stateLock)
|
||||||
// changes can reset the blinker.
|
{
|
||||||
var value = Volatile.Read(ref blinkerCounter.Value);
|
// The blinker is on half of the time and events such as input
|
||||||
value = (value + 1) % (2 * TextBoxBlinkThreshold);
|
// changes can reset the blinker.
|
||||||
Volatile.Write(ref blinkerCounter.Value, value);
|
state.TextBoxBlinkCounter = (state.TextBoxBlinkCounter + 1) % (2 * SoftwareKeyboardRendererBase.TextBoxBlinkThreshold);
|
||||||
|
|
||||||
|
// Tell the render thread there is something new to render.
|
||||||
|
Monitor.PulseAll(stateLock);
|
||||||
|
}
|
||||||
}, TextBoxBlinkSleepMilliseconds);
|
}, TextBoxBlinkSleepMilliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false)
|
private static void StartRenderer(TimedAction timedAction, SoftwareKeyboardRendererBase renderer, SoftwareKeyboardUiState state, object stateLock)
|
||||||
{
|
{
|
||||||
var a = (byte)(color.A * 255);
|
SoftwareKeyboardUiState internalState = new SoftwareKeyboardUiState();
|
||||||
var r = (byte)(color.R * 255);
|
|
||||||
var g = (byte)(color.G * 255);
|
|
||||||
var b = (byte)(color.B * 255);
|
|
||||||
|
|
||||||
if (flipRgb)
|
bool canCreateSurface = false;
|
||||||
|
bool needsUpdate = true;
|
||||||
|
|
||||||
|
timedAction.Reset(() =>
|
||||||
{
|
{
|
||||||
r = (byte)(255 - r);
|
lock (stateLock)
|
||||||
g = (byte)(255 - g);
|
{
|
||||||
b = (byte)(255 - b);
|
if (!Monitor.Wait(stateLock, RendererWaitTimeoutMilliseconds))
|
||||||
}
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return Color.FromArgb(overrideAlpha.GetValueOrDefault(a), r, g, b);
|
needsUpdate = UpdateStateField(ref state.InputText, ref internalState.InputText);
|
||||||
|
needsUpdate |= UpdateStateField(ref state.CursorBegin, ref internalState.CursorBegin);
|
||||||
|
needsUpdate |= UpdateStateField(ref state.CursorEnd, ref internalState.CursorEnd);
|
||||||
|
needsUpdate |= UpdateStateField(ref state.AcceptPressed, ref internalState.AcceptPressed);
|
||||||
|
needsUpdate |= UpdateStateField(ref state.CancelPressed, ref internalState.CancelPressed);
|
||||||
|
needsUpdate |= UpdateStateField(ref state.OverwriteMode, ref internalState.OverwriteMode);
|
||||||
|
needsUpdate |= UpdateStateField(ref state.TypingEnabled, ref internalState.TypingEnabled);
|
||||||
|
needsUpdate |= UpdateStateField(ref state.ControllerEnabled, ref internalState.ControllerEnabled);
|
||||||
|
needsUpdate |= UpdateStateField(ref state.TextBoxBlinkCounter, ref internalState.TextBoxBlinkCounter);
|
||||||
|
|
||||||
|
canCreateSurface = state.SurfaceInfo != null && internalState.SurfaceInfo == null;
|
||||||
|
|
||||||
|
if (canCreateSurface)
|
||||||
|
{
|
||||||
|
internalState.SurfaceInfo = state.SurfaceInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canCreateSurface)
|
||||||
|
{
|
||||||
|
renderer.CreateSurface(internalState.SurfaceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsUpdate)
|
||||||
|
{
|
||||||
|
renderer.DrawMutableElements(internalState);
|
||||||
|
renderer.CopyImageToBuffer();
|
||||||
|
needsUpdate = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Image LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight)
|
private static bool UpdateStateField<T>(ref T source, ref T destination) where T : IEquatable<T>
|
||||||
{
|
{
|
||||||
Stream resourceStream = assembly.GetManifestResourceStream(resourcePath);
|
if (!source.Equals(destination))
|
||||||
|
|
||||||
Debug.Assert(resourceStream != null);
|
|
||||||
|
|
||||||
var originalImage = Image.FromStream(resourceStream);
|
|
||||||
|
|
||||||
if (newHeight == 0 || newWidth == 0)
|
|
||||||
{
|
{
|
||||||
return originalImage;
|
destination = source;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newSize = new Rectangle(0, 0, newWidth, newHeight);
|
return false;
|
||||||
var newImage = new Bitmap(newWidth, newHeight);
|
|
||||||
|
|
||||||
using (var graphics = System.Drawing.Graphics.FromImage(newImage))
|
|
||||||
using (var wrapMode = new ImageAttributes())
|
|
||||||
{
|
|
||||||
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
|
||||||
graphics.CompositingQuality = CompositingQuality.HighQuality;
|
|
||||||
graphics.CompositingMode = CompositingMode.SourceCopy;
|
|
||||||
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
|
||||||
graphics.SmoothingMode = SmoothingMode.HighQuality;
|
|
||||||
|
|
||||||
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
|
|
||||||
graphics.DrawImage(originalImage, newSize, 0, 0, originalImage.Width, originalImage.Height, GraphicsUnit.Pixel, wrapMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newImage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable CS8632
|
#pragma warning disable CS8632
|
||||||
public void UpdateTextState(string? inputText, int? cursorStart, int? cursorEnd, bool? overwriteMode, bool? typingEnabled)
|
public void UpdateTextState(string? inputText, int? cursorBegin, int? cursorEnd, bool? overwriteMode, bool? typingEnabled)
|
||||||
#pragma warning restore CS8632
|
#pragma warning restore CS8632
|
||||||
{
|
{
|
||||||
lock (_renderLock)
|
lock (_stateLock)
|
||||||
{
|
{
|
||||||
// Update the parameters that were provided.
|
// Update the parameters that were provided.
|
||||||
_inputText = inputText != null ? inputText : _inputText;
|
_state.InputText = inputText != null ? inputText : _state.InputText;
|
||||||
_cursorStart = cursorStart.GetValueOrDefault(_cursorStart);
|
_state.CursorBegin = cursorBegin.GetValueOrDefault(_state.CursorBegin);
|
||||||
_cursorEnd = cursorEnd.GetValueOrDefault(_cursorEnd);
|
_state.CursorEnd = cursorEnd.GetValueOrDefault(_state.CursorEnd);
|
||||||
_overwriteMode = overwriteMode.GetValueOrDefault(_overwriteMode);
|
_state.OverwriteMode = overwriteMode.GetValueOrDefault(_state.OverwriteMode);
|
||||||
_typingEnabled = typingEnabled.GetValueOrDefault(_typingEnabled);
|
_state.TypingEnabled = typingEnabled.GetValueOrDefault(_state.TypingEnabled);
|
||||||
|
|
||||||
// Reset the cursor blink.
|
// Reset the cursor blink.
|
||||||
Volatile.Write(ref _textBoxBlinkCounter.Value, 0);
|
_state.TextBoxBlinkCounter = 0;
|
||||||
|
|
||||||
|
// Tell the render thread there is something new to render.
|
||||||
|
Monitor.PulseAll(_stateLock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateCommandState(bool? acceptPressed, bool? cancelPressed, bool? controllerEnabled)
|
public void UpdateCommandState(bool? acceptPressed, bool? cancelPressed, bool? controllerEnabled)
|
||||||
{
|
{
|
||||||
lock (_renderLock)
|
lock (_stateLock)
|
||||||
{
|
{
|
||||||
// Update the parameters that were provided.
|
// Update the parameters that were provided.
|
||||||
_acceptPressed = acceptPressed.GetValueOrDefault(_acceptPressed);
|
_state.AcceptPressed = acceptPressed.GetValueOrDefault(_state.AcceptPressed);
|
||||||
_cancelPressed = cancelPressed.GetValueOrDefault(_cancelPressed);
|
_state.CancelPressed = cancelPressed.GetValueOrDefault(_state.CancelPressed);
|
||||||
_controllerEnabled = controllerEnabled.GetValueOrDefault(_controllerEnabled);
|
_state.ControllerEnabled = controllerEnabled.GetValueOrDefault(_state.ControllerEnabled);
|
||||||
|
|
||||||
|
// Tell the render thread there is something new to render.
|
||||||
|
Monitor.PulseAll(_stateLock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Redraw()
|
public void SetSurfaceInfo(RenderingSurfaceInfo surfaceInfo)
|
||||||
{
|
{
|
||||||
if (_surface == null)
|
lock (_stateLock)
|
||||||
{
|
{
|
||||||
return;
|
_state.SurfaceInfo = surfaceInfo;
|
||||||
}
|
|
||||||
|
|
||||||
using (var graphics = CreateGraphics())
|
// Tell the render thread there is something new to render.
|
||||||
{
|
Monitor.PulseAll(_stateLock);
|
||||||
var messageRectangle = MeasureString(graphics, MessageText, _messageFont);
|
|
||||||
float messagePositionX = (_panelRectangle.Width - messageRectangle.Width) / 2 - messageRectangle.X;
|
|
||||||
float messagePositionY = _messagePositionY - messageRectangle.Y;
|
|
||||||
PointF messagePosition = new PointF(messagePositionX, messagePositionY);
|
|
||||||
|
|
||||||
graphics.Clear(Color.Transparent);
|
|
||||||
graphics.TranslateTransform(0, _panelPositionY);
|
|
||||||
graphics.FillRectangle(_panelBrush, _panelRectangle);
|
|
||||||
graphics.DrawImage(_ryujinxLogo, _logoPosition);
|
|
||||||
|
|
||||||
DrawString(graphics, MessageText, _messageFont, _textNormalBrush, messagePosition);
|
|
||||||
|
|
||||||
if (!_typingEnabled)
|
|
||||||
{
|
|
||||||
// Just draw a semi-transparent rectangle on top to fade the component with the background.
|
|
||||||
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
|
|
||||||
graphics.FillRectangle(_disabledBrush, messagePositionX, messagePositionY, messageRectangle.Width, messageRectangle.Height);
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawTextBox(graphics);
|
|
||||||
|
|
||||||
float halfWidth = _panelRectangle.Width / 2;
|
|
||||||
|
|
||||||
PointF acceptButtonPosition = new PointF(halfWidth - 180, 185);
|
|
||||||
PointF cancelButtonPosition = new PointF(halfWidth , 185);
|
|
||||||
PointF disableButtonPosition = new PointF(halfWidth + 180, 185);
|
|
||||||
|
|
||||||
DrawPadButton (graphics, acceptButtonPosition , _padAcceptIcon, AcceptText, _acceptPressed, _controllerEnabled);
|
|
||||||
DrawPadButton (graphics, cancelButtonPosition , _padCancelIcon, CancelText, _cancelPressed, _controllerEnabled);
|
|
||||||
DrawControllerToggle(graphics, disableButtonPosition, _controllerEnabled);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RecreateSurface()
|
internal bool DrawTo(IVirtualMemoryManager destination, ulong position)
|
||||||
{
|
{
|
||||||
Debug.Assert(_surfaceInfo.ColorFormat == Services.SurfaceFlinger.ColorFormat.A8B8G8R8);
|
return _renderer.WriteBufferToMemory(destination, position);
|
||||||
|
|
||||||
// Use the whole area of the image to draw, even the alignment, otherwise it may shear the final
|
|
||||||
// image if the pitch is different.
|
|
||||||
uint totalWidth = _surfaceInfo.Pitch / 4;
|
|
||||||
uint totalHeight = _surfaceInfo.Size / _surfaceInfo.Pitch;
|
|
||||||
|
|
||||||
Debug.Assert(_surfaceInfo.Width <= totalWidth);
|
|
||||||
Debug.Assert(_surfaceInfo.Height <= totalHeight);
|
|
||||||
Debug.Assert(_surfaceInfo.Pitch * _surfaceInfo.Height <= _surfaceInfo.Size);
|
|
||||||
|
|
||||||
_surface = new Bitmap((int)totalWidth, (int)totalHeight, PixelFormat.Format32bppArgb);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RecomputeConstants()
|
|
||||||
{
|
|
||||||
float totalWidth = _surfaceInfo.Width;
|
|
||||||
float totalHeight = _surfaceInfo.Height;
|
|
||||||
|
|
||||||
float panelHeight = 240;
|
|
||||||
|
|
||||||
_panelPositionY = totalHeight - panelHeight;
|
|
||||||
_panelRectangle = new RectangleF(0, 0, totalWidth, panelHeight);
|
|
||||||
|
|
||||||
_messagePositionY = 60;
|
|
||||||
|
|
||||||
float logoPositionX = (totalWidth - _ryujinxLogo.Width) / 2;
|
|
||||||
float logoPositionY = 18;
|
|
||||||
|
|
||||||
_logoPosition = new PointF(logoPositionX, logoPositionY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StringFormat CreateStringFormat(string text)
|
|
||||||
{
|
|
||||||
StringFormat format = new StringFormat(StringFormat.GenericTypographic);
|
|
||||||
format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces;
|
|
||||||
format.SetMeasurableCharacterRanges(new CharacterRange[] { new CharacterRange(0, text.Length) });
|
|
||||||
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
|
|
||||||
private RectangleF MeasureString(System.Drawing.Graphics graphics, string text, System.Drawing.Font font)
|
|
||||||
{
|
|
||||||
bool isEmpty = false;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(text))
|
|
||||||
{
|
|
||||||
isEmpty = true;
|
|
||||||
text = " ";
|
|
||||||
}
|
|
||||||
|
|
||||||
var format = CreateStringFormat(text);
|
|
||||||
var rectangle = new RectangleF(0, 0, float.PositiveInfinity, float.PositiveInfinity);
|
|
||||||
var regions = graphics.MeasureCharacterRanges(text, font, rectangle, format);
|
|
||||||
|
|
||||||
Debug.Assert(regions.Length == 1);
|
|
||||||
|
|
||||||
rectangle = regions[0].GetBounds(graphics);
|
|
||||||
|
|
||||||
if (isEmpty)
|
|
||||||
{
|
|
||||||
rectangle.Width = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rectangle.Width += 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rectangle;
|
|
||||||
}
|
|
||||||
|
|
||||||
private float CalibrateTextHeight(Font font)
|
|
||||||
{
|
|
||||||
// This is a pixel-wise calibration that tests the offset of a reference character because Windows text measurement
|
|
||||||
// is horrible when compared to other frameworks like Cairo and diverge across systems and fonts.
|
|
||||||
|
|
||||||
Debug.Assert(font.Unit == GraphicsUnit.Pixel);
|
|
||||||
|
|
||||||
var surfaceSize = (int)Math.Ceiling(2 * font.Size);
|
|
||||||
|
|
||||||
string calibrationText = "|";
|
|
||||||
|
|
||||||
using (var surface = new Bitmap(surfaceSize, surfaceSize, PixelFormat.Format32bppArgb))
|
|
||||||
using (var graphics = CreateGraphics(surface))
|
|
||||||
{
|
|
||||||
var measuredRectangle = MeasureString(graphics, calibrationText, font);
|
|
||||||
|
|
||||||
Debug.Assert(measuredRectangle.Right <= surfaceSize);
|
|
||||||
Debug.Assert(measuredRectangle.Bottom <= surfaceSize);
|
|
||||||
|
|
||||||
var textPosition = new PointF(0, 0);
|
|
||||||
|
|
||||||
graphics.Clear(Color.Transparent);
|
|
||||||
DrawString(graphics, calibrationText, font, Brushes.White, textPosition);
|
|
||||||
|
|
||||||
var lockRectangle = new Rectangle(0, 0, surface.Width, surface.Height);
|
|
||||||
var surfaceData = surface.LockBits(lockRectangle, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
|
||||||
var surfaceBytes = new byte[surfaceData.Stride * surfaceData.Height];
|
|
||||||
|
|
||||||
Marshal.Copy(surfaceData.Scan0, surfaceBytes, 0, surfaceBytes.Length);
|
|
||||||
|
|
||||||
Point topLeft = new Point();
|
|
||||||
Point bottomLeft = new Point();
|
|
||||||
|
|
||||||
bool foundTopLeft = false;
|
|
||||||
|
|
||||||
for (int y = 0; y < surfaceData.Height; y++)
|
|
||||||
{
|
|
||||||
for (int x = 0; x < surfaceData.Stride; x += 4)
|
|
||||||
{
|
|
||||||
int position = y * surfaceData.Stride + x;
|
|
||||||
|
|
||||||
if (surfaceBytes[position] != 0)
|
|
||||||
{
|
|
||||||
if (!foundTopLeft)
|
|
||||||
{
|
|
||||||
topLeft.X = x;
|
|
||||||
topLeft.Y = y;
|
|
||||||
foundTopLeft = true;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bottomLeft.X = x;
|
|
||||||
bottomLeft.Y = y;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bottomLeft.Y - topLeft.Y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawString(System.Drawing.Graphics graphics, string text, Font font, Brush brush, PointF point)
|
|
||||||
{
|
|
||||||
var format = CreateStringFormat(text);
|
|
||||||
graphics.DrawString(text, font, brush, point, format);
|
|
||||||
}
|
|
||||||
|
|
||||||
private System.Drawing.Graphics CreateGraphics()
|
|
||||||
{
|
|
||||||
return CreateGraphics(_surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
private System.Drawing.Graphics CreateGraphics(Image surface)
|
|
||||||
{
|
|
||||||
var graphics = System.Drawing.Graphics.FromImage(surface);
|
|
||||||
|
|
||||||
graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
|
|
||||||
graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
|
|
||||||
graphics.CompositingQuality = CompositingQuality.HighSpeed;
|
|
||||||
graphics.CompositingMode = CompositingMode.SourceOver;
|
|
||||||
graphics.PixelOffsetMode = PixelOffsetMode.HighSpeed;
|
|
||||||
graphics.SmoothingMode = SmoothingMode.HighSpeed;
|
|
||||||
|
|
||||||
return graphics;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawTextBox(System.Drawing.Graphics graphics)
|
|
||||||
{
|
|
||||||
var inputTextRectangle = MeasureString(graphics, _inputText, _inputTextFont);
|
|
||||||
|
|
||||||
float boxWidth = (int)(Math.Max(300, inputTextRectangle.Width + inputTextRectangle.X + 8));
|
|
||||||
float boxHeight = 32;
|
|
||||||
float boxY = 110;
|
|
||||||
float boxX = (int)((_panelRectangle.Width - boxWidth) / 2);
|
|
||||||
|
|
||||||
graphics.DrawRectangle(_textBoxOutlinePen, boxX, boxY, boxWidth, boxHeight);
|
|
||||||
|
|
||||||
float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.X;
|
|
||||||
float inputTextY = boxY + boxHeight - inputTextRectangle.Bottom - 5;
|
|
||||||
|
|
||||||
var inputTextPosition = new PointF(inputTextX, inputTextY);
|
|
||||||
|
|
||||||
DrawString(graphics, _inputText, _inputTextFont, _textNormalBrush, inputTextPosition);
|
|
||||||
|
|
||||||
// Draw the cursor on top of the text and redraw the text with a different color if necessary.
|
|
||||||
|
|
||||||
Brush cursorTextBrush;
|
|
||||||
Brush cursorBrush;
|
|
||||||
Pen cursorPen;
|
|
||||||
|
|
||||||
float cursorPositionYBottom = inputTextY + inputTextRectangle.Bottom;
|
|
||||||
float cursorPositionYTop = cursorPositionYBottom - _inputTextCalibrationHeight - 2;
|
|
||||||
float cursorPositionXLeft;
|
|
||||||
float cursorPositionXRight;
|
|
||||||
|
|
||||||
bool cursorVisible = false;
|
|
||||||
|
|
||||||
if (_cursorStart != _cursorEnd)
|
|
||||||
{
|
|
||||||
cursorTextBrush = _textSelectedBrush;
|
|
||||||
cursorBrush = _selectionBoxBrush;
|
|
||||||
cursorPen = _selectionBoxPen;
|
|
||||||
|
|
||||||
string textUntilBegin = _inputText.Substring(0, _cursorStart);
|
|
||||||
string textUntilEnd = _inputText.Substring(0, _cursorEnd);
|
|
||||||
|
|
||||||
RectangleF selectionBeginRectangle = MeasureString(graphics, textUntilBegin, _inputTextFont);
|
|
||||||
RectangleF selectionEndRectangle = MeasureString(graphics, textUntilEnd , _inputTextFont);
|
|
||||||
|
|
||||||
cursorVisible = true;
|
|
||||||
cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.X;
|
|
||||||
cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.X;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cursorTextBrush = _textOverCursorBrush;
|
|
||||||
cursorBrush = _cursorBrush;
|
|
||||||
cursorPen = _cursorPen;
|
|
||||||
|
|
||||||
if (Volatile.Read(ref _textBoxBlinkCounter.Value) < TextBoxBlinkThreshold)
|
|
||||||
{
|
|
||||||
// Show the blinking cursor.
|
|
||||||
|
|
||||||
int cursorStart = Math.Min(_inputText.Length, _cursorStart);
|
|
||||||
string textUntilCursor = _inputText.Substring(0, cursorStart);
|
|
||||||
RectangleF cursorTextRectangle = MeasureString(graphics, textUntilCursor, _inputTextFont);
|
|
||||||
|
|
||||||
cursorVisible = true;
|
|
||||||
cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
|
|
||||||
|
|
||||||
if (_overwriteMode)
|
|
||||||
{
|
|
||||||
// The blinking cursor is in overwrite mode so it takes the size of a character.
|
|
||||||
|
|
||||||
if (_cursorStart < _inputText.Length)
|
|
||||||
{
|
|
||||||
textUntilCursor = _inputText.Substring(0, cursorStart + 1);
|
|
||||||
cursorTextRectangle = MeasureString(graphics, textUntilCursor, _inputTextFont);
|
|
||||||
cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cursorPositionXRight = cursorPositionXLeft + _inputTextFontSize / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// The blinking cursor is in insert mode so it is only a line.
|
|
||||||
cursorPositionXRight = cursorPositionXLeft;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cursorPositionXLeft = inputTextX;
|
|
||||||
cursorPositionXRight = inputTextX;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_typingEnabled && cursorVisible)
|
|
||||||
{
|
|
||||||
float cursorWidth = cursorPositionXRight - cursorPositionXLeft;
|
|
||||||
float cursorHeight = cursorPositionYBottom - cursorPositionYTop;
|
|
||||||
|
|
||||||
if (cursorWidth == 0)
|
|
||||||
{
|
|
||||||
graphics.DrawLine(cursorPen, cursorPositionXLeft, cursorPositionYTop, cursorPositionXLeft, cursorPositionYBottom);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
graphics.DrawRectangle(cursorPen, cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
|
|
||||||
graphics.FillRectangle(cursorBrush, cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
|
|
||||||
|
|
||||||
var cursorRectangle = new RectangleF(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
|
|
||||||
|
|
||||||
var oldClip = graphics.Clip;
|
|
||||||
graphics.Clip = new Region(cursorRectangle);
|
|
||||||
|
|
||||||
DrawString(graphics, _inputText, _inputTextFont, cursorTextBrush, inputTextPosition);
|
|
||||||
|
|
||||||
graphics.Clip = oldClip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!_typingEnabled)
|
|
||||||
{
|
|
||||||
// Just draw a semi-transparent rectangle on top to fade the component with the background.
|
|
||||||
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
|
|
||||||
graphics.FillRectangle(_disabledBrush, boxX - _textBoxOutlineWidth, boxY - _textBoxOutlineWidth,
|
|
||||||
boxWidth + 2* _textBoxOutlineWidth, boxHeight + 2* _textBoxOutlineWidth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawPadButton(System.Drawing.Graphics graphics, PointF point, Image icon, string label, bool pressed, bool enabled)
|
|
||||||
{
|
|
||||||
// Use relative positions so we can center the the entire drawing later.
|
|
||||||
|
|
||||||
float iconX = 0;
|
|
||||||
float iconY = 0;
|
|
||||||
float iconWidth = icon.Width;
|
|
||||||
float iconHeight = icon.Height;
|
|
||||||
|
|
||||||
var labelRectangle = MeasureString(graphics, label, _labelsTextFont);
|
|
||||||
|
|
||||||
float labelPositionX = iconWidth + 8 - labelRectangle.X;
|
|
||||||
float labelPositionY = (iconHeight - labelRectangle.Height) / 2 - labelRectangle.Y - 1;
|
|
||||||
|
|
||||||
float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.X;
|
|
||||||
float fullHeight = iconHeight;
|
|
||||||
|
|
||||||
// Convert all relative positions into absolute.
|
|
||||||
|
|
||||||
float originX = (int)(point.X - fullWidth / 2);
|
|
||||||
float originY = (int)(point.Y - fullHeight / 2);
|
|
||||||
|
|
||||||
iconX += originX;
|
|
||||||
iconY += originY;
|
|
||||||
|
|
||||||
var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
|
|
||||||
|
|
||||||
graphics.DrawImageUnscaled(icon, (int)iconX, (int)iconY);
|
|
||||||
|
|
||||||
DrawString(graphics, label, _labelsTextFont, _textNormalBrush, labelPosition);
|
|
||||||
|
|
||||||
GraphicsPath frame = new GraphicsPath();
|
|
||||||
frame.AddRectangle(new RectangleF(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth,
|
|
||||||
fullWidth + 4 * _padPressedPenWidth, fullHeight + 4 * _padPressedPenWidth));
|
|
||||||
|
|
||||||
if (enabled)
|
|
||||||
{
|
|
||||||
if (pressed)
|
|
||||||
{
|
|
||||||
graphics.DrawPath(_padPressedPen, frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Just draw a semi-transparent rectangle on top to fade the component with the background.
|
|
||||||
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
|
|
||||||
graphics.FillPath(_disabledBrush, frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawControllerToggle(System.Drawing.Graphics graphics, PointF point, bool enabled)
|
|
||||||
{
|
|
||||||
var labelRectangle = MeasureString(graphics, ControllerToggleText, _labelsTextFont);
|
|
||||||
|
|
||||||
// Use relative positions so we can center the the entire drawing later.
|
|
||||||
|
|
||||||
float keyWidth = _keyModeIcon.Width;
|
|
||||||
float keyHeight = _keyModeIcon.Height;
|
|
||||||
|
|
||||||
float labelPositionX = keyWidth + 8 - labelRectangle.X;
|
|
||||||
float labelPositionY = -labelRectangle.Y - 1;
|
|
||||||
|
|
||||||
float keyX = 0;
|
|
||||||
float keyY = (int)((labelPositionY + labelRectangle.Height - keyHeight) / 2);
|
|
||||||
|
|
||||||
float fullWidth = labelPositionX + labelRectangle.Width;
|
|
||||||
float fullHeight = Math.Max(labelPositionY + labelRectangle.Height, keyHeight);
|
|
||||||
|
|
||||||
// Convert all relative positions into absolute.
|
|
||||||
|
|
||||||
float originX = (int)(point.X - fullWidth / 2);
|
|
||||||
float originY = (int)(point.Y - fullHeight / 2);
|
|
||||||
|
|
||||||
keyX += originX;
|
|
||||||
keyY += originY;
|
|
||||||
|
|
||||||
var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
|
|
||||||
var overlayPosition = new Point((int)keyX, (int)keyY);
|
|
||||||
|
|
||||||
graphics.DrawImageUnscaled(_keyModeIcon, overlayPosition);
|
|
||||||
|
|
||||||
DrawString(graphics, ControllerToggleText, _labelsTextFont, _textNormalBrush, labelPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryCopyTo(IVirtualMemoryManager destination, ulong position)
|
|
||||||
{
|
|
||||||
if (_surface == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle lockRectangle = new Rectangle(0, 0, _surface.Width, _surface.Height);
|
|
||||||
BitmapData surfaceData = _surface.LockBits(lockRectangle, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
|
|
||||||
|
|
||||||
Debug.Assert(surfaceData.Stride == _surfaceInfo.Pitch);
|
|
||||||
Debug.Assert(surfaceData.Stride * surfaceData.Height == _surfaceInfo.Size);
|
|
||||||
|
|
||||||
// Convert the pixel format used in System.Drawing to the one required by a Switch Surface.
|
|
||||||
int dataLength = surfaceData.Stride * surfaceData.Height;
|
|
||||||
|
|
||||||
byte[] data = new byte[dataLength];
|
|
||||||
Span<uint> dataConvert = MemoryMarshal.Cast<byte, uint>(data);
|
|
||||||
|
|
||||||
Marshal.Copy(surfaceData.Scan0, data, 0, dataLength);
|
|
||||||
|
|
||||||
for (int i = 0; i < dataConvert.Length; i++)
|
|
||||||
{
|
|
||||||
dataConvert[i] = BitOperations.RotateRight(BinaryPrimitives.ReverseEndianness(dataConvert[i]), 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
destination.Write(position, data);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_surface.UnlockBits(surfaceData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal bool DrawTo(RenderingSurfaceInfo surfaceInfo, IVirtualMemoryManager destination, ulong position)
|
|
||||||
{
|
|
||||||
lock (_renderLock)
|
|
||||||
{
|
|
||||||
if (!_surfaceInfo.Equals(surfaceInfo))
|
|
||||||
{
|
|
||||||
_surfaceInfo = surfaceInfo;
|
|
||||||
RecreateSurface();
|
|
||||||
RecomputeConstants();
|
|
||||||
}
|
|
||||||
|
|
||||||
Redraw();
|
|
||||||
|
|
||||||
return TryCopyTo(destination, position);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_textBoxBlinkTimedAction.RequestCancel();
|
_textBoxBlinkTimedAction.RequestCancel();
|
||||||
|
_renderAction.RequestCancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,585 @@
|
||||||
|
using Ryujinx.HLE.Ui;
|
||||||
|
using Ryujinx.Memory;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
using SixLabors.ImageSharp.Drawing.Processing;
|
||||||
|
using SixLabors.Fonts;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Base class that generates the graphics for the software keyboard applet during inline mode.
|
||||||
|
/// </summary>
|
||||||
|
internal class SoftwareKeyboardRendererBase
|
||||||
|
{
|
||||||
|
public const int TextBoxBlinkThreshold = 8;
|
||||||
|
|
||||||
|
const string MessageText = "Please use the keyboard to input text";
|
||||||
|
const string AcceptText = "Accept";
|
||||||
|
const string CancelText = "Cancel";
|
||||||
|
const string ControllerToggleText = "Toggle input";
|
||||||
|
|
||||||
|
private readonly object _bufferLock = new object();
|
||||||
|
|
||||||
|
private RenderingSurfaceInfo _surfaceInfo = null;
|
||||||
|
private Image<Argb32> _surface = null;
|
||||||
|
private byte[] _bufferData = null;
|
||||||
|
|
||||||
|
private Image _ryujinxLogo = null;
|
||||||
|
private Image _padAcceptIcon = null;
|
||||||
|
private Image _padCancelIcon = null;
|
||||||
|
private Image _keyModeIcon = null;
|
||||||
|
|
||||||
|
private float _textBoxOutlineWidth;
|
||||||
|
private float _padPressedPenWidth;
|
||||||
|
|
||||||
|
private Color _textNormalColor;
|
||||||
|
private Color _textSelectedColor;
|
||||||
|
private Color _textOverCursorColor;
|
||||||
|
|
||||||
|
private IBrush _panelBrush;
|
||||||
|
private IBrush _disabledBrush;
|
||||||
|
private IBrush _cursorBrush;
|
||||||
|
private IBrush _selectionBoxBrush;
|
||||||
|
|
||||||
|
private Pen _textBoxOutlinePen;
|
||||||
|
private Pen _cursorPen;
|
||||||
|
private Pen _selectionBoxPen;
|
||||||
|
private Pen _padPressedPen;
|
||||||
|
|
||||||
|
private int _inputTextFontSize;
|
||||||
|
private Font _messageFont;
|
||||||
|
private Font _inputTextFont;
|
||||||
|
private Font _labelsTextFont;
|
||||||
|
|
||||||
|
private RectangleF _panelRectangle;
|
||||||
|
private Point _logoPosition;
|
||||||
|
private float _messagePositionY;
|
||||||
|
|
||||||
|
public SoftwareKeyboardRendererBase(IHostUiTheme uiTheme)
|
||||||
|
{
|
||||||
|
string ryujinxLogoPath = "Ryujinx.Ui.Resources.Logo_Ryujinx.png";
|
||||||
|
int ryujinxLogoSize = 32;
|
||||||
|
|
||||||
|
_ryujinxLogo = LoadResource(Assembly.GetEntryAssembly(), ryujinxLogoPath, ryujinxLogoSize, ryujinxLogoSize);
|
||||||
|
|
||||||
|
string padAcceptIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnA.png";
|
||||||
|
string padCancelIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnB.png";
|
||||||
|
string keyModeIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_KeyF6.png";
|
||||||
|
|
||||||
|
_padAcceptIcon = LoadResource(Assembly.GetExecutingAssembly(), padAcceptIconPath , 0, 0);
|
||||||
|
_padCancelIcon = LoadResource(Assembly.GetExecutingAssembly(), padCancelIconPath , 0, 0);
|
||||||
|
_keyModeIcon = LoadResource(Assembly.GetExecutingAssembly(), keyModeIconPath , 0, 0);
|
||||||
|
|
||||||
|
Color panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255);
|
||||||
|
Color panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150);
|
||||||
|
Color borderColor = ToColor(uiTheme.DefaultBorderColor);
|
||||||
|
Color selectionBackgroundColor = ToColor(uiTheme.SelectionBackgroundColor);
|
||||||
|
|
||||||
|
_textNormalColor = ToColor(uiTheme.DefaultForegroundColor);
|
||||||
|
_textSelectedColor = ToColor(uiTheme.SelectionForegroundColor);
|
||||||
|
_textOverCursorColor = ToColor(uiTheme.DefaultForegroundColor, null, true);
|
||||||
|
|
||||||
|
float cursorWidth = 2;
|
||||||
|
|
||||||
|
_textBoxOutlineWidth = 2;
|
||||||
|
_padPressedPenWidth = 2;
|
||||||
|
|
||||||
|
_panelBrush = new SolidBrush(panelColor);
|
||||||
|
_disabledBrush = new SolidBrush(panelTransparentColor);
|
||||||
|
_cursorBrush = new SolidBrush(_textNormalColor);
|
||||||
|
_selectionBoxBrush = new SolidBrush(selectionBackgroundColor);
|
||||||
|
|
||||||
|
_textBoxOutlinePen = new Pen(borderColor, _textBoxOutlineWidth);
|
||||||
|
_cursorPen = new Pen(_textNormalColor, cursorWidth);
|
||||||
|
_selectionBoxPen = new Pen(selectionBackgroundColor, cursorWidth);
|
||||||
|
_padPressedPen = new Pen(borderColor, _padPressedPenWidth);
|
||||||
|
|
||||||
|
_inputTextFontSize = 20;
|
||||||
|
|
||||||
|
CreateFonts(uiTheme.FontFamily);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateFonts(string uiThemeFontFamily)
|
||||||
|
{
|
||||||
|
// Try a list of fonts in case any of them is not available in the system.
|
||||||
|
|
||||||
|
string[] availableFonts = new string[]
|
||||||
|
{
|
||||||
|
uiThemeFontFamily,
|
||||||
|
"Liberation Sans",
|
||||||
|
"FreeSans",
|
||||||
|
"DejaVu Sans"
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (string fontFamily in availableFonts)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_messageFont = SystemFonts.CreateFont(fontFamily, 26, FontStyle.Regular);
|
||||||
|
_inputTextFont = SystemFonts.CreateFont(fontFamily, _inputTextFontSize, FontStyle.Regular);
|
||||||
|
_labelsTextFont = SystemFonts.CreateFont(fontFamily, 24, FontStyle.Regular);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception($"None of these fonts were found in the system: {String.Join(", ", availableFonts)}!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false)
|
||||||
|
{
|
||||||
|
var a = (byte)(color.A * 255);
|
||||||
|
var r = (byte)(color.R * 255);
|
||||||
|
var g = (byte)(color.G * 255);
|
||||||
|
var b = (byte)(color.B * 255);
|
||||||
|
|
||||||
|
if (flipRgb)
|
||||||
|
{
|
||||||
|
r = (byte)(255 - r);
|
||||||
|
g = (byte)(255 - g);
|
||||||
|
b = (byte)(255 - b);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Color.FromRgba(r, g, b, overrideAlpha.GetValueOrDefault(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Image LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight)
|
||||||
|
{
|
||||||
|
Stream resourceStream = assembly.GetManifestResourceStream(resourcePath);
|
||||||
|
|
||||||
|
Debug.Assert(resourceStream != null);
|
||||||
|
|
||||||
|
var image = Image.Load(resourceStream);
|
||||||
|
|
||||||
|
if (newHeight != 0 && newWidth != 0)
|
||||||
|
{
|
||||||
|
image.Mutate(x => x.Resize(newWidth, newHeight, KnownResamplers.Lanczos3));
|
||||||
|
}
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetGraphicsOptions(IImageProcessingContext context)
|
||||||
|
{
|
||||||
|
context.GetGraphicsOptions().Antialias = true;
|
||||||
|
context.GetShapeGraphicsOptions().GraphicsOptions.Antialias = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawImmutableElements()
|
||||||
|
{
|
||||||
|
if (_surface == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_surface.Mutate(context =>
|
||||||
|
{
|
||||||
|
SetGraphicsOptions(context);
|
||||||
|
|
||||||
|
context.Clear(Color.Transparent);
|
||||||
|
context.Fill(_panelBrush, _panelRectangle);
|
||||||
|
context.DrawImage(_ryujinxLogo, _logoPosition, 1);
|
||||||
|
|
||||||
|
float halfWidth = _panelRectangle.Width / 2;
|
||||||
|
float buttonsY = _panelRectangle.Y + 185;
|
||||||
|
|
||||||
|
PointF disableButtonPosition = new PointF(halfWidth + 180, buttonsY);
|
||||||
|
|
||||||
|
DrawControllerToggle(context, disableButtonPosition);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawMutableElements(SoftwareKeyboardUiState state)
|
||||||
|
{
|
||||||
|
if (_surface == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_surface.Mutate(context =>
|
||||||
|
{
|
||||||
|
var messageRectangle = MeasureString(MessageText, _messageFont);
|
||||||
|
float messagePositionX = (_panelRectangle.Width - messageRectangle.Width) / 2 - messageRectangle.X;
|
||||||
|
float messagePositionY = _messagePositionY - messageRectangle.Y;
|
||||||
|
var messagePosition = new PointF(messagePositionX, messagePositionY);
|
||||||
|
var messageBoundRectangle = new RectangleF(messagePositionX, messagePositionY, messageRectangle.Width, messageRectangle.Height);
|
||||||
|
|
||||||
|
SetGraphicsOptions(context);
|
||||||
|
|
||||||
|
context.Fill(_panelBrush, messageBoundRectangle);
|
||||||
|
|
||||||
|
context.DrawText(MessageText, _messageFont, _textNormalColor, messagePosition);
|
||||||
|
|
||||||
|
if (!state.TypingEnabled)
|
||||||
|
{
|
||||||
|
// Just draw a semi-transparent rectangle on top to fade the component with the background.
|
||||||
|
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
|
||||||
|
|
||||||
|
context.Fill(_disabledBrush, messageBoundRectangle);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawTextBox(context, state);
|
||||||
|
|
||||||
|
float halfWidth = _panelRectangle.Width / 2;
|
||||||
|
float buttonsY = _panelRectangle.Y + 185;
|
||||||
|
|
||||||
|
PointF acceptButtonPosition = new PointF(halfWidth - 180, buttonsY);
|
||||||
|
PointF cancelButtonPosition = new PointF(halfWidth , buttonsY);
|
||||||
|
PointF disableButtonPosition = new PointF(halfWidth + 180, buttonsY);
|
||||||
|
|
||||||
|
DrawPadButton(context, acceptButtonPosition, _padAcceptIcon, AcceptText, state.AcceptPressed, state.ControllerEnabled);
|
||||||
|
DrawPadButton(context, cancelButtonPosition, _padCancelIcon, CancelText, state.CancelPressed, state.ControllerEnabled);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateSurface(RenderingSurfaceInfo surfaceInfo)
|
||||||
|
{
|
||||||
|
if (_surfaceInfo != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_surfaceInfo = surfaceInfo;
|
||||||
|
|
||||||
|
Debug.Assert(_surfaceInfo.ColorFormat == Services.SurfaceFlinger.ColorFormat.A8B8G8R8);
|
||||||
|
|
||||||
|
// Use the whole area of the image to draw, even the alignment, otherwise it may shear the final
|
||||||
|
// image if the pitch is different.
|
||||||
|
uint totalWidth = _surfaceInfo.Pitch / 4;
|
||||||
|
uint totalHeight = _surfaceInfo.Size / _surfaceInfo.Pitch;
|
||||||
|
|
||||||
|
Debug.Assert(_surfaceInfo.Width <= totalWidth);
|
||||||
|
Debug.Assert(_surfaceInfo.Height <= totalHeight);
|
||||||
|
Debug.Assert(_surfaceInfo.Pitch * _surfaceInfo.Height <= _surfaceInfo.Size);
|
||||||
|
|
||||||
|
_surface = new Image<Argb32>((int)totalWidth, (int)totalHeight);
|
||||||
|
|
||||||
|
ComputeConstants();
|
||||||
|
DrawImmutableElements();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComputeConstants()
|
||||||
|
{
|
||||||
|
int totalWidth = (int)_surfaceInfo.Width;
|
||||||
|
int totalHeight = (int)_surfaceInfo.Height;
|
||||||
|
|
||||||
|
int panelHeight = 240;
|
||||||
|
int panelPositionY = totalHeight - panelHeight;
|
||||||
|
|
||||||
|
_panelRectangle = new RectangleF(0, panelPositionY, totalWidth, panelHeight);
|
||||||
|
|
||||||
|
_messagePositionY = panelPositionY + 60;
|
||||||
|
|
||||||
|
int logoPositionX = (totalWidth - _ryujinxLogo.Width) / 2;
|
||||||
|
int logoPositionY = panelPositionY + 18;
|
||||||
|
|
||||||
|
_logoPosition = new Point(logoPositionX, logoPositionY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RectangleF MeasureString(string text, Font font)
|
||||||
|
{
|
||||||
|
RendererOptions options = new RendererOptions(font);
|
||||||
|
FontRectangle rectangle = TextMeasurer.Measure(text == "" ? " " : text, options);
|
||||||
|
|
||||||
|
if (text == "")
|
||||||
|
{
|
||||||
|
return new RectangleF(0, rectangle.Y, 0, rectangle.Height);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawTextBox(IImageProcessingContext context, SoftwareKeyboardUiState state)
|
||||||
|
{
|
||||||
|
var inputTextRectangle = MeasureString(state.InputText, _inputTextFont);
|
||||||
|
|
||||||
|
float boxWidth = (int)(Math.Max(300, inputTextRectangle.Width + inputTextRectangle.X + 8));
|
||||||
|
float boxHeight = 32;
|
||||||
|
float boxY = _panelRectangle.Y + 110;
|
||||||
|
float boxX = (int)((_panelRectangle.Width - boxWidth) / 2);
|
||||||
|
|
||||||
|
RectangleF boxRectangle = new RectangleF(boxX, boxY, boxWidth, boxHeight);
|
||||||
|
|
||||||
|
RectangleF boundRectangle = new RectangleF(_panelRectangle.X, boxY - _textBoxOutlineWidth,
|
||||||
|
_panelRectangle.Width, boxHeight + 2 * _textBoxOutlineWidth);
|
||||||
|
|
||||||
|
context.Fill(_panelBrush, boundRectangle);
|
||||||
|
|
||||||
|
context.Draw(_textBoxOutlinePen, boxRectangle);
|
||||||
|
|
||||||
|
float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.X;
|
||||||
|
float inputTextY = boxY + 5;
|
||||||
|
|
||||||
|
var inputTextPosition = new PointF(inputTextX, inputTextY);
|
||||||
|
|
||||||
|
context.DrawText(state.InputText, _inputTextFont, _textNormalColor, inputTextPosition);
|
||||||
|
|
||||||
|
// Draw the cursor on top of the text and redraw the text with a different color if necessary.
|
||||||
|
|
||||||
|
Color cursorTextColor;
|
||||||
|
IBrush cursorBrush;
|
||||||
|
Pen cursorPen;
|
||||||
|
|
||||||
|
float cursorPositionYTop = inputTextY + 1;
|
||||||
|
float cursorPositionYBottom = cursorPositionYTop + _inputTextFontSize + 1;
|
||||||
|
float cursorPositionXLeft;
|
||||||
|
float cursorPositionXRight;
|
||||||
|
|
||||||
|
bool cursorVisible = false;
|
||||||
|
|
||||||
|
if (state.CursorBegin != state.CursorEnd)
|
||||||
|
{
|
||||||
|
Debug.Assert(state.InputText.Length > 0);
|
||||||
|
|
||||||
|
cursorTextColor = _textSelectedColor;
|
||||||
|
cursorBrush = _selectionBoxBrush;
|
||||||
|
cursorPen = _selectionBoxPen;
|
||||||
|
|
||||||
|
string textUntilBegin = state.InputText.Substring(0, state.CursorBegin);
|
||||||
|
string textUntilEnd = state.InputText.Substring(0, state.CursorEnd);
|
||||||
|
|
||||||
|
var selectionBeginRectangle = MeasureString(textUntilBegin, _inputTextFont);
|
||||||
|
var selectionEndRectangle = MeasureString(textUntilEnd , _inputTextFont);
|
||||||
|
|
||||||
|
cursorVisible = true;
|
||||||
|
cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.X;
|
||||||
|
cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.X;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cursorTextColor = _textOverCursorColor;
|
||||||
|
cursorBrush = _cursorBrush;
|
||||||
|
cursorPen = _cursorPen;
|
||||||
|
|
||||||
|
if (state.TextBoxBlinkCounter < TextBoxBlinkThreshold)
|
||||||
|
{
|
||||||
|
// Show the blinking cursor.
|
||||||
|
|
||||||
|
int cursorBegin = Math.Min(state.InputText.Length, state.CursorBegin);
|
||||||
|
string textUntilCursor = state.InputText.Substring(0, cursorBegin);
|
||||||
|
var cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont);
|
||||||
|
|
||||||
|
cursorVisible = true;
|
||||||
|
cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
|
||||||
|
|
||||||
|
if (state.OverwriteMode)
|
||||||
|
{
|
||||||
|
// The blinking cursor is in overwrite mode so it takes the size of a character.
|
||||||
|
|
||||||
|
if (state.CursorBegin < state.InputText.Length)
|
||||||
|
{
|
||||||
|
textUntilCursor = state.InputText.Substring(0, cursorBegin + 1);
|
||||||
|
cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont);
|
||||||
|
cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cursorPositionXRight = cursorPositionXLeft + _inputTextFontSize / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The blinking cursor is in insert mode so it is only a line.
|
||||||
|
cursorPositionXRight = cursorPositionXLeft;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cursorPositionXLeft = inputTextX;
|
||||||
|
cursorPositionXRight = inputTextX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.TypingEnabled && cursorVisible)
|
||||||
|
{
|
||||||
|
float cursorWidth = cursorPositionXRight - cursorPositionXLeft;
|
||||||
|
float cursorHeight = cursorPositionYBottom - cursorPositionYTop;
|
||||||
|
|
||||||
|
if (cursorWidth == 0)
|
||||||
|
{
|
||||||
|
PointF[] points = new PointF[]
|
||||||
|
{
|
||||||
|
new PointF(cursorPositionXLeft, cursorPositionYTop),
|
||||||
|
new PointF(cursorPositionXLeft, cursorPositionYBottom),
|
||||||
|
};
|
||||||
|
|
||||||
|
context.DrawLines(cursorPen, points);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var cursorRectangle = new RectangleF(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
|
||||||
|
|
||||||
|
context.Draw(cursorPen , cursorRectangle);
|
||||||
|
context.Fill(cursorBrush, cursorRectangle);
|
||||||
|
|
||||||
|
Image<Argb32> textOverCursor = new Image<Argb32>((int)cursorRectangle.Width, (int)cursorRectangle.Height);
|
||||||
|
textOverCursor.Mutate(context =>
|
||||||
|
{
|
||||||
|
var textRelativePosition = new PointF(inputTextPosition.X - cursorRectangle.X, inputTextPosition.Y - cursorRectangle.Y);
|
||||||
|
context.DrawText(state.InputText, _inputTextFont, cursorTextColor, textRelativePosition);
|
||||||
|
});
|
||||||
|
|
||||||
|
var cursorPosition = new Point((int)cursorRectangle.X, (int)cursorRectangle.Y);
|
||||||
|
context.DrawImage(textOverCursor, cursorPosition, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!state.TypingEnabled)
|
||||||
|
{
|
||||||
|
// Just draw a semi-transparent rectangle on top to fade the component with the background.
|
||||||
|
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
|
||||||
|
|
||||||
|
context.Fill(_disabledBrush, boundRectangle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPadButton(IImageProcessingContext context, PointF point, Image icon, string label, bool pressed, bool enabled)
|
||||||
|
{
|
||||||
|
// Use relative positions so we can center the the entire drawing later.
|
||||||
|
|
||||||
|
float iconX = 0;
|
||||||
|
float iconY = 0;
|
||||||
|
float iconWidth = icon.Width;
|
||||||
|
float iconHeight = icon.Height;
|
||||||
|
|
||||||
|
var labelRectangle = MeasureString(label, _labelsTextFont);
|
||||||
|
|
||||||
|
float labelPositionX = iconWidth + 8 - labelRectangle.X;
|
||||||
|
float labelPositionY = 3;
|
||||||
|
|
||||||
|
float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.X;
|
||||||
|
float fullHeight = iconHeight;
|
||||||
|
|
||||||
|
// Convert all relative positions into absolute.
|
||||||
|
|
||||||
|
float originX = (int)(point.X - fullWidth / 2);
|
||||||
|
float originY = (int)(point.Y - fullHeight / 2);
|
||||||
|
|
||||||
|
iconX += originX;
|
||||||
|
iconY += originY;
|
||||||
|
|
||||||
|
var iconPosition = new Point((int)iconX, (int)iconY);
|
||||||
|
var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
|
||||||
|
|
||||||
|
var selectedRectangle = new RectangleF(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth,
|
||||||
|
fullWidth + 4 * _padPressedPenWidth, fullHeight + 4 * _padPressedPenWidth);
|
||||||
|
|
||||||
|
var boundRectangle = new RectangleF(originX, originY, fullWidth, fullHeight);
|
||||||
|
boundRectangle.Inflate(4 * _padPressedPenWidth, 4 * _padPressedPenWidth);
|
||||||
|
|
||||||
|
context.Fill(_panelBrush, boundRectangle);
|
||||||
|
context.DrawImage(icon, iconPosition, 1);
|
||||||
|
context.DrawText(label, _labelsTextFont, _textNormalColor, labelPosition);
|
||||||
|
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
if (pressed)
|
||||||
|
{
|
||||||
|
context.Draw(_padPressedPen, selectedRectangle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Just draw a semi-transparent rectangle on top to fade the component with the background.
|
||||||
|
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
|
||||||
|
|
||||||
|
context.Fill(_disabledBrush, boundRectangle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawControllerToggle(IImageProcessingContext context, PointF point)
|
||||||
|
{
|
||||||
|
var labelRectangle = MeasureString(ControllerToggleText, _labelsTextFont);
|
||||||
|
|
||||||
|
// Use relative positions so we can center the the entire drawing later.
|
||||||
|
|
||||||
|
float keyWidth = _keyModeIcon.Width;
|
||||||
|
float keyHeight = _keyModeIcon.Height;
|
||||||
|
|
||||||
|
float labelPositionX = keyWidth + 8 - labelRectangle.X;
|
||||||
|
float labelPositionY = -labelRectangle.Y - 1;
|
||||||
|
|
||||||
|
float keyX = 0;
|
||||||
|
float keyY = (int)((labelPositionY + labelRectangle.Height - keyHeight) / 2);
|
||||||
|
|
||||||
|
float fullWidth = labelPositionX + labelRectangle.Width;
|
||||||
|
float fullHeight = Math.Max(labelPositionY + labelRectangle.Height, keyHeight);
|
||||||
|
|
||||||
|
// Convert all relative positions into absolute.
|
||||||
|
|
||||||
|
float originX = (int)(point.X - fullWidth / 2);
|
||||||
|
float originY = (int)(point.Y - fullHeight / 2);
|
||||||
|
|
||||||
|
keyX += originX;
|
||||||
|
keyY += originY;
|
||||||
|
|
||||||
|
var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
|
||||||
|
var overlayPosition = new Point((int)keyX, (int)keyY);
|
||||||
|
|
||||||
|
context.DrawImage(_keyModeIcon, overlayPosition, 1);
|
||||||
|
context.DrawText(ControllerToggleText, _labelsTextFont, _textNormalColor, labelPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyImageToBuffer()
|
||||||
|
{
|
||||||
|
lock (_bufferLock)
|
||||||
|
{
|
||||||
|
if (_surface == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the pixel format used in the image to the one used in the Switch surface.
|
||||||
|
|
||||||
|
if (!_surface.TryGetSinglePixelSpan(out Span<Argb32> pixels))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_bufferData = MemoryMarshal.AsBytes(pixels).ToArray();
|
||||||
|
Span<uint> dataConvert = MemoryMarshal.Cast<byte, uint>(_bufferData);
|
||||||
|
|
||||||
|
Debug.Assert(_bufferData.Length == _surfaceInfo.Size);
|
||||||
|
|
||||||
|
for (int i = 0; i < dataConvert.Length; i++)
|
||||||
|
{
|
||||||
|
dataConvert[i] = BitOperations.RotateRight(dataConvert[i], 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WriteBufferToMemory(IVirtualMemoryManager destination, ulong position)
|
||||||
|
{
|
||||||
|
lock (_bufferLock)
|
||||||
|
{
|
||||||
|
if (_bufferData == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
destination.Write(position, _bufferData);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
using Ryujinx.HLE.Ui;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// TODO
|
||||||
|
/// </summary>
|
||||||
|
internal class SoftwareKeyboardUiState
|
||||||
|
{
|
||||||
|
public string InputText = "";
|
||||||
|
public int CursorBegin = 0;
|
||||||
|
public int CursorEnd = 0;
|
||||||
|
public bool AcceptPressed = false;
|
||||||
|
public bool CancelPressed = false;
|
||||||
|
public bool OverwriteMode = false;
|
||||||
|
public bool TypingEnabled = true;
|
||||||
|
public bool ControllerEnabled = true;
|
||||||
|
public int TextBoxBlinkCounter = 0;
|
||||||
|
|
||||||
|
public RenderingSurfaceInfo SurfaceInfo = null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -144,6 +144,20 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||||
}), cancelled);
|
}), cancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Reset(Action action)
|
||||||
|
{
|
||||||
|
// Create a dedicated cancel token for each task.
|
||||||
|
var cancelled = new TRef<bool>(false);
|
||||||
|
|
||||||
|
Reset(new Thread(() =>
|
||||||
|
{
|
||||||
|
while (!Volatile.Read(ref cancelled.Value))
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
}), cancelled);
|
||||||
|
}
|
||||||
|
|
||||||
private static bool SleepWithSubstep(SleepSubstepData substepData, TRef<bool> cancelled)
|
private static bool SleepWithSubstep(SleepSubstepData substepData, TRef<bool> cancelled)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < substepData.SleepCount; i++)
|
for (int i = 0; i < substepData.SleepCount; i++)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Memory;
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.HOS.Services.Mii;
|
using Ryujinx.HLE.HOS.Services.Mii;
|
||||||
using Ryujinx.HLE.HOS.Services.Mii.Types;
|
using Ryujinx.HLE.HOS.Services.Mii.Types;
|
||||||
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
|
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
|
||||||
|
@ -172,7 +173,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
||||||
|
|
||||||
if (File.Exists(filePath))
|
if (File.Exists(filePath))
|
||||||
{
|
{
|
||||||
virtualAmiiboFile = JsonSerializer.Deserialize<VirtualAmiiboFile>(File.ReadAllText(filePath));
|
virtualAmiiboFile = JsonHelper.DeserializeFromFile<VirtualAmiiboFile>(filePath);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
using System;
|
using System.Security.Cryptography;
|
||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Spl
|
namespace Ryujinx.HLE.HOS.Services.Spl
|
||||||
{
|
{
|
||||||
[Service("csrng")]
|
[Service("csrng")]
|
||||||
class IRandomInterface : DisposableIpcService
|
class IRandomInterface : DisposableIpcService
|
||||||
{
|
{
|
||||||
private RNGCryptoServiceProvider _rng;
|
private RandomNumberGenerator _rng;
|
||||||
|
|
||||||
private object _lock = new object();
|
private object _lock = new object();
|
||||||
|
|
||||||
public IRandomInterface(ServiceCtx context)
|
public IRandomInterface(ServiceCtx context)
|
||||||
{
|
{
|
||||||
_rng = new RNGCryptoServiceProvider();
|
_rng = RandomNumberGenerator.Create();
|
||||||
}
|
}
|
||||||
|
|
||||||
[CommandHipc(0)]
|
[CommandHipc(0)]
|
||||||
|
|
|
@ -396,7 +396,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
||||||
|
|
||||||
if (!applet.DrawTo(surfaceInfo, context.Memory, layerBuffPosition))
|
if (!applet.DrawTo(surfaceInfo, context.Memory, layerBuffPosition))
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.ServiceVi, $"Applet did not draw on indirect layer handle {layerHandle}");
|
Logger.Warning?.Print(LogClass.ServiceVi, $"Applet did not draw on indirect layer handle {layerHandle}");
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -21,6 +21,7 @@
|
||||||
<PackageReference Include="Concentus" Version="1.1.7" />
|
<PackageReference Include="Concentus" Version="1.1.7" />
|
||||||
<PackageReference Include="LibHac" Version="0.13.3" />
|
<PackageReference Include="LibHac" Version="0.13.3" />
|
||||||
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
||||||
|
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
|
using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Ui
|
namespace Ryujinx.HLE.Ui
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Information about the indirect layer that is being drawn to.
|
/// Information about the indirect layer that is being drawn to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class RenderingSurfaceInfo
|
class RenderingSurfaceInfo : IEquatable<RenderingSurfaceInfo>
|
||||||
{
|
{
|
||||||
public ColorFormat ColorFormat { get; }
|
public ColorFormat ColorFormat { get; }
|
||||||
public uint Width { get; }
|
public uint Width { get; }
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
<PackageReference Include="Mono.Posix.NETStandard" Version="5.20.1-preview" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<Configurations>Debug;Release</Configurations>
|
<Configurations>Debug;Release</Configurations>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Configurations>Debug;Release</Configurations>
|
<Configurations>Debug;Release</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
|
|
@ -4,7 +4,6 @@ using ICSharpCode.SharpZipLib.Tar;
|
||||||
using ICSharpCode.SharpZipLib.Zip;
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
using Mono.Unix;
|
using Mono.Unix;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Ryujinx.Common.Configuration;
|
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Ui;
|
using Ryujinx.Ui;
|
||||||
using Ryujinx.Ui.Widgets;
|
using Ryujinx.Ui.Widgets;
|
||||||
|
@ -13,6 +12,7 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Net.NetworkInformation;
|
using System.Net.NetworkInformation;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
@ -92,10 +92,10 @@ namespace Ryujinx.Modules
|
||||||
// Get latest version number from Appveyor
|
// Get latest version number from Appveyor
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (WebClient jsonClient = new WebClient())
|
using (HttpClient jsonClient = new HttpClient())
|
||||||
{
|
{
|
||||||
// Fetch latest build information
|
// Fetch latest build information
|
||||||
string fetchedJson = await jsonClient.DownloadStringTaskAsync($"{AppveyorApiUrl}/projects/gdkchan/ryujinx/branch/master");
|
string fetchedJson = await jsonClient.GetStringAsync($"{AppveyorApiUrl}/projects/gdkchan/ryujinx/branch/master");
|
||||||
JObject jsonRoot = JObject.Parse(fetchedJson);
|
JObject jsonRoot = JObject.Parse(fetchedJson);
|
||||||
JToken buildToken = jsonRoot["build"];
|
JToken buildToken = jsonRoot["build"];
|
||||||
|
|
||||||
|
@ -149,15 +149,15 @@ namespace Ryujinx.Modules
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch build size information to learn chunk sizes.
|
// Fetch build size information to learn chunk sizes.
|
||||||
using (WebClient buildSizeClient = new WebClient())
|
using (HttpClient buildSizeClient = new HttpClient())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
buildSizeClient.Headers.Add("Range", "bytes=0-0");
|
buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
|
||||||
await buildSizeClient.DownloadDataTaskAsync(new Uri(_buildUrl));
|
|
||||||
|
|
||||||
string contentRange = buildSizeClient.ResponseHeaders["Content-Range"];
|
HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_buildUrl), HttpCompletionOption.ResponseHeadersRead);
|
||||||
_buildSize = long.Parse(contentRange.Substring(contentRange.IndexOf('/') + 1));
|
|
||||||
|
_buildSize = message.Content.Headers.ContentRange.Length.Value;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -220,7 +220,10 @@ namespace Ryujinx.Modules
|
||||||
|
|
||||||
for (int i = 0; i < ConnectionCount; i++)
|
for (int i = 0; i < ConnectionCount; i++)
|
||||||
{
|
{
|
||||||
|
#pragma warning disable SYSLIB0014
|
||||||
|
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
|
||||||
using (WebClient client = new WebClient())
|
using (WebClient client = new WebClient())
|
||||||
|
#pragma warning restore SYSLIB0014
|
||||||
{
|
{
|
||||||
webClients.Add(client);
|
webClients.Add(client);
|
||||||
|
|
||||||
|
@ -307,31 +310,56 @@ namespace Ryujinx.Modules
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DoUpdateWithSingleThread(UpdateDialog updateDialog, string downloadUrl, string updateFile)
|
private static void DoUpdateWithSingleThreadWorker(UpdateDialog updateDialog, string downloadUrl, string updateFile)
|
||||||
{
|
{
|
||||||
// Single-Threaded Updater
|
using (HttpClient client = new HttpClient())
|
||||||
using (WebClient client = new WebClient())
|
|
||||||
{
|
{
|
||||||
client.DownloadProgressChanged += (_, args) =>
|
// We do not want to timeout while downloading
|
||||||
{
|
client.Timeout = TimeSpan.FromDays(1);
|
||||||
updateDialog.ProgressBar.Value = args.ProgressPercentage;
|
|
||||||
};
|
|
||||||
|
|
||||||
client.DownloadDataCompleted += (_, args) =>
|
using (HttpResponseMessage response = client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result)
|
||||||
|
using (Stream remoteFileStream = response.Content.ReadAsStreamAsync().Result)
|
||||||
{
|
{
|
||||||
File.WriteAllBytes(updateFile, args.Result);
|
using (Stream updateFileStream = File.Open(updateFile, FileMode.Create))
|
||||||
InstallUpdate(updateDialog, updateFile);
|
{
|
||||||
};
|
long totalBytes = response.Content.Headers.ContentLength.Value;
|
||||||
|
long byteWritten = 0;
|
||||||
|
|
||||||
client.DownloadDataAsync(new Uri(downloadUrl));
|
byte[] buffer = new byte[32 * 1024];
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int readSize = remoteFileStream.Read(buffer);
|
||||||
|
|
||||||
|
if (readSize == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
byteWritten += readSize;
|
||||||
|
|
||||||
|
updateDialog.ProgressBar.Value = ((double)byteWritten / totalBytes) * 100;
|
||||||
|
updateFileStream.Write(buffer, 0, readSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InstallUpdate(updateDialog, updateFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void DoUpdateWithSingleThread(UpdateDialog updateDialog, string downloadUrl, string updateFile)
|
||||||
|
{
|
||||||
|
Thread worker = new Thread(() => DoUpdateWithSingleThreadWorker(updateDialog, downloadUrl, updateFile));
|
||||||
|
worker.Name = "Updater.SingleThreadWorker";
|
||||||
|
worker.Start();
|
||||||
|
}
|
||||||
|
|
||||||
private static void SetUnixPermissions()
|
private static void SetUnixPermissions()
|
||||||
{
|
{
|
||||||
string ryuBin = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx");
|
string ryuBin = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx");
|
||||||
|
|
||||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (!OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
UnixFileInfo unixFileInfo = new UnixFileInfo(ryuBin);
|
UnixFileInfo unixFileInfo = new UnixFileInfo(ryuBin);
|
||||||
unixFileInfo.FileAccessPermissions |= FileAccessPermissions.UserExecute;
|
unixFileInfo.FileAccessPermissions |= FileAccessPermissions.UserExecute;
|
||||||
|
|
|
@ -61,6 +61,9 @@ namespace Ryujinx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enforce loading of Mono.Posix.NETStandard to avoid .NET runtime lazy loading it during an update.
|
||||||
|
Assembly.Load("Mono.Posix.NETStandard");
|
||||||
|
|
||||||
// Make process DPI aware for proper window sizing on high-res screens.
|
// Make process DPI aware for proper window sizing on high-res screens.
|
||||||
ForceDpiAware.Windows();
|
ForceDpiAware.Windows();
|
||||||
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
|
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
@ -24,7 +24,8 @@
|
||||||
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
||||||
<PackageReference Include="OpenTK.Graphics" Version="4.5.0" />
|
<PackageReference Include="OpenTK.Graphics" Version="4.5.0" />
|
||||||
<PackageReference Include="SPB" Version="0.0.3-build15" />
|
<PackageReference Include="SPB" Version="0.0.3-build15" />
|
||||||
<PackageReference Include="SharpZipLib" Version="1.3.0" />
|
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||||
|
<PackageReference Include="Mono.Posix.NETStandard" Version="5.20.1-preview" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using Gtk;
|
using Gtk;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.Ui.Widgets;
|
using Ryujinx.Ui.Widgets;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -9,7 +10,6 @@ using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ namespace Ryujinx.Ui.Windows
|
||||||
{
|
{
|
||||||
amiiboJsonString = File.ReadAllText(_amiiboJsonPath);
|
amiiboJsonString = File.ReadAllText(_amiiboJsonPath);
|
||||||
|
|
||||||
if (await NeedsUpdate(JsonSerializer.Deserialize<AmiiboJson>(amiiboJsonString).LastUpdated))
|
if (await NeedsUpdate(JsonHelper.Deserialize<AmiiboJson>(amiiboJsonString).LastUpdated))
|
||||||
{
|
{
|
||||||
amiiboJsonString = await DownloadAmiiboJson();
|
amiiboJsonString = await DownloadAmiiboJson();
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,7 @@ namespace Ryujinx.Ui.Windows
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_amiiboList = JsonSerializer.Deserialize<AmiiboJson>(amiiboJsonString).Amiibo;
|
_amiiboList = JsonHelper.Deserialize<AmiiboJson>(amiiboJsonString).Amiibo;
|
||||||
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
|
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
|
||||||
|
|
||||||
if (LastScannedAmiiboShowAll)
|
if (LastScannedAmiiboShowAll)
|
||||||
|
|
|
@ -2,9 +2,9 @@ version: 1.0.{build}
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
image: Visual Studio 2019
|
image: Visual Studio 2022
|
||||||
environment:
|
environment:
|
||||||
appveyor_dotnet_runtime: net5.0
|
appveyor_dotnet_runtime: net6.0
|
||||||
matrix:
|
matrix:
|
||||||
- config: Release
|
- config: Release
|
||||||
config_name: '-'
|
config_name: '-'
|
||||||
|
@ -12,9 +12,9 @@ build_script:
|
||||||
- ps: >-
|
- ps: >-
|
||||||
dotnet --version
|
dotnet --version
|
||||||
|
|
||||||
dotnet publish -c $env:config -r win-x64 /p:Version=$env:APPVEYOR_BUILD_VERSION /p:DebugType=embedded
|
dotnet publish -c $env:config -r win-x64 /p:Version=$env:APPVEYOR_BUILD_VERSION /p:DebugType=embedded --self-contained
|
||||||
|
|
||||||
dotnet publish -c $env:config -r linux-x64 /p:Version=$env:APPVEYOR_BUILD_VERSION /p:DebugType=embedded
|
dotnet publish -c $env:config -r linux-x64 /p:Version=$env:APPVEYOR_BUILD_VERSION /p:DebugType=embedded --self-contained
|
||||||
|
|
||||||
7z a ryujinx$env:config_name$env:APPVEYOR_BUILD_VERSION-win_x64.zip $env:APPVEYOR_BUILD_FOLDER\Ryujinx\bin\$env:config\$env:appveyor_dotnet_runtime\win-x64\publish\
|
7z a ryujinx$env:config_name$env:APPVEYOR_BUILD_VERSION-win_x64.zip $env:APPVEYOR_BUILD_FOLDER\Ryujinx\bin\$env:config\$env:appveyor_dotnet_runtime\win-x64\publish\
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "5.0.100",
|
"version": "6.0.100",
|
||||||
"rollForward": "latestFeature"
|
"rollForward": "latestFeature"
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue