Merge remote-tracking branch 'origin/master' into feature/auto-load-extra-files

This commit is contained in:
Jimmy Reichley 2024-08-31 16:14:48 -04:00
commit 401e60e4cb
No known key found for this signature in database
GPG key ID: 67715DC5A329803C
25 changed files with 515 additions and 344 deletions

View file

@ -42,8 +42,8 @@
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.9" /> <PackageVersion Include="SkiaSharp" Version="2.88.7" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" /> <PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.7" />
<PackageVersion Include="SPB" Version="0.0.4-build32" /> <PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" /> <PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
<PackageVersion Include="System.Management" Version="8.0.0" /> <PackageVersion Include="System.Management" Version="8.0.0" />

View file

@ -87,6 +87,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon", "src\Ryuj
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -249,6 +251,10 @@ Global
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.Build.0 = Release|Any CPU {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.Build.0 = Release|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View file

@ -80,9 +80,12 @@ namespace ARMeilleure.Translation
return true; return true;
} }
if (!_disposed)
{
Monitor.Wait(Sync); Monitor.Wait(Sync);
} }
} }
}
result = default; result = default;

View file

@ -39,7 +39,10 @@ namespace Ryujinx.Graphics.Device
{ {
var field = fields[fieldIndex]; var field = fields[fieldIndex];
int sizeOfField = SizeCalculator.SizeOf(field.FieldType); var currentFieldOffset = (int)Marshal.OffsetOf<TState>(field.Name);
var nextFieldOffset = fieldIndex + 1 == fields.Length ? Unsafe.SizeOf<TState>() : (int)Marshal.OffsetOf<TState>(fields[fieldIndex + 1].Name);
int sizeOfField = nextFieldOffset - currentFieldOffset;
for (int i = 0; i < ((sizeOfField + 3) & ~3); i += 4) for (int i = 0; i < ((sizeOfField + 3) & ~3); i += 4)
{ {

View file

@ -1,63 +0,0 @@
using System;
using System.Reflection;
namespace Ryujinx.Graphics.Device
{
public static class SizeCalculator
{
public static int SizeOf(Type type)
{
// Is type a enum type?
if (type.IsEnum)
{
type = type.GetEnumUnderlyingType();
}
// Is type a pointer type?
if (type.IsPointer || type == typeof(IntPtr) || type == typeof(UIntPtr))
{
return IntPtr.Size;
}
// Is type a struct type?
if (type.IsValueType && !type.IsPrimitive)
{
// Check if the struct has a explicit size, if so, return that.
if (type.StructLayoutAttribute.Size != 0)
{
return type.StructLayoutAttribute.Size;
}
// Otherwise we calculate the sum of the sizes of all fields.
int size = 0;
var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
for (int fieldIndex = 0; fieldIndex < fields.Length; fieldIndex++)
{
size += SizeOf(fields[fieldIndex].FieldType);
}
return size;
}
// Primitive types.
return (Type.GetTypeCode(type)) switch
{
TypeCode.SByte => sizeof(sbyte),
TypeCode.Byte => sizeof(byte),
TypeCode.Int16 => sizeof(short),
TypeCode.UInt16 => sizeof(ushort),
TypeCode.Int32 => sizeof(int),
TypeCode.UInt32 => sizeof(uint),
TypeCode.Int64 => sizeof(long),
TypeCode.UInt64 => sizeof(ulong),
TypeCode.Char => sizeof(char),
TypeCode.Single => sizeof(float),
TypeCode.Double => sizeof(double),
TypeCode.Decimal => sizeof(decimal),
TypeCode.Boolean => sizeof(bool),
_ => throw new ArgumentException($"Length for type \"{type.Name}\" is unknown."),
};
}
}
}

View file

@ -79,7 +79,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{ {
var field = fields[fieldIndex]; var field = fields[fieldIndex];
int sizeOfField = SizeCalculator.SizeOf(field.FieldType); var currentFieldOffset = (int)Marshal.OffsetOf<TState>(field.Name);
var nextFieldOffset = fieldIndex + 1 == fields.Length ? Unsafe.SizeOf<TState>() : (int)Marshal.OffsetOf<TState>(fields[fieldIndex + 1].Name);
int sizeOfField = nextFieldOffset - currentFieldOffset;
if (fieldToDelegate.TryGetValue(field.Name, out int entryIndex)) if (fieldToDelegate.TryGetValue(field.Name, out int entryIndex))
{ {

View file

@ -13,7 +13,6 @@ using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper; using Ryujinx.UI.Common.Helper;
using Ryujinx.UI.Common.SystemInfo; using Ryujinx.UI.Common.SystemInfo;
using Ryujinx.UI.Widgets; using Ryujinx.UI.Widgets;
using SixLabors.ImageSharp.Formats.Jpeg;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@ -162,12 +161,6 @@ namespace Ryujinx
}); });
}; };
// Sets ImageSharp Jpeg Encoder Quality.
SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder()
{
Quality = 100,
});
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName); string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName); string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);

View file

@ -30,7 +30,6 @@
<PackageReference Include="OpenTK.Graphics" /> <PackageReference Include="OpenTK.Graphics" />
<PackageReference Include="SPB" /> <PackageReference Include="SPB" />
<PackageReference Include="SharpZipLib" /> <PackageReference Include="SharpZipLib" />
<PackageReference Include="SixLabors.ImageSharp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -13,16 +13,13 @@ using Ryujinx.Input.HLE;
using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper; using Ryujinx.UI.Common.Helper;
using Ryujinx.UI.Widgets; using Ryujinx.UI.Widgets;
using SixLabors.ImageSharp; using SkiaSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Image = SixLabors.ImageSharp.Image;
using Key = Ryujinx.Input.Key; using Key = Ryujinx.Input.Key;
using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter; using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter;
using Switch = Ryujinx.HLE.Switch; using Switch = Ryujinx.HLE.Switch;
@ -404,23 +401,31 @@ namespace Ryujinx.UI
return; return;
} }
Image image = e.IsBgra ? Image.LoadPixelData<Bgra32>(e.Data, e.Width, e.Height) var colorType = e.IsBgra ? SKColorType.Bgra8888 : SKColorType.Rgba8888;
: Image.LoadPixelData<Rgba32>(e.Data, e.Width, e.Height); using var image = new SKBitmap(new SKImageInfo(e.Width, e.Height, colorType, SKAlphaType.Premul));
if (e.FlipX) Marshal.Copy(e.Data, 0, image.GetPixels(), e.Data.Length);
using var surface = SKSurface.Create(image.Info);
var canvas = surface.Canvas;
if (e.FlipX || e.FlipY)
{ {
image.Mutate(x => x.Flip(FlipMode.Horizontal)); canvas.Clear(SKColors.Transparent);
float scaleX = e.FlipX ? -1 : 1;
float scaleY = e.FlipY ? -1 : 1;
var matrix = SKMatrix.CreateScale(scaleX, scaleY, image.Width / 2f, image.Height / 2f);
canvas.SetMatrix(matrix);
} }
canvas.DrawBitmap(image, new SKPoint());
if (e.FlipY) surface.Flush();
{ using var snapshot = surface.Snapshot();
image.Mutate(x => x.Flip(FlipMode.Vertical)); using var encoded = snapshot.Encode(SKEncodedImageFormat.Png, 80);
} using var file = File.OpenWrite(path);
encoded.SaveTo(file);
image.SaveAsPng(path, new PngEncoder()
{
ColorType = PngColorType.Rgb,
});
image.Dispose(); image.Dispose();

View file

@ -9,16 +9,13 @@ using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Configuration;
using SixLabors.ImageSharp; using SkiaSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using Image = SixLabors.ImageSharp.Image; using System.Runtime.InteropServices;
namespace Ryujinx.UI.Windows namespace Ryujinx.UI.Windows
{ {
@ -144,9 +141,11 @@ namespace Ryujinx.UI.Windows
stream.Position = 0; stream.Position = 0;
Image avatarImage = Image.LoadPixelData<Rgba32>(DecompressYaz0(stream), 256, 256); using var avatarImage = new SKBitmap(new SKImageInfo(256, 256, SKColorType.Rgba8888));
var data = DecompressYaz0(stream);
Marshal.Copy(data, 0, avatarImage.GetPixels(), data.Length);
avatarImage.SaveAsPng(streamPng); avatarImage.Encode(streamPng, SKEncodedImageFormat.Png, 80);
_avatarDict.Add(item.FullPath, streamPng.ToArray()); _avatarDict.Add(item.FullPath, streamPng.ToArray());
} }
@ -170,15 +169,23 @@ namespace Ryujinx.UI.Windows
{ {
using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream(); using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream();
Image avatarImage = Image.Load(data, new PngDecoder()); using var avatarImage = SKBitmap.Decode(data);
using var surface = SKSurface.Create(avatarImage.Info);
avatarImage.Mutate(x => x.BackgroundColor(new Rgba32( var background = new SKColor(
(byte)(_backgroundColor.Red * 255), (byte)(_backgroundColor.Red * 255),
(byte)(_backgroundColor.Green * 255), (byte)(_backgroundColor.Green * 255),
(byte)(_backgroundColor.Blue * 255), (byte)(_backgroundColor.Blue * 255),
(byte)(_backgroundColor.Alpha * 255) (byte)(_backgroundColor.Alpha * 255)
))); );
avatarImage.SaveAsJpeg(streamJpg); var canvas = surface.Canvas;
canvas.Clear(background);
canvas.DrawBitmap(avatarImage, new SKPoint());
surface.Flush();
using var snapshot = surface.Snapshot();
using var encoded = snapshot.Encode(SKEncodedImageFormat.Jpeg, 80);
encoded.SaveTo(streamJpg);
return streamJpg.ToArray(); return streamJpg.ToArray();
} }

View file

@ -4,15 +4,13 @@ using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Widgets; using Ryujinx.UI.Widgets;
using SixLabors.ImageSharp; using SkiaSharp;
using SixLabors.ImageSharp.Processing;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Image = SixLabors.ImageSharp.Image;
namespace Ryujinx.UI.Windows namespace Ryujinx.UI.Windows
{ {
@ -177,13 +175,13 @@ namespace Ryujinx.UI.Windows
private void ProcessProfileImage(byte[] buffer) private void ProcessProfileImage(byte[] buffer)
{ {
using Image image = Image.Load(buffer); using var image = SKBitmap.Decode(buffer);
image.Mutate(x => x.Resize(256, 256)); image.Resize(new SKImageInfo(256, 256), SKFilterQuality.High);
using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream(); using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream();
image.SaveAsJpeg(streamJpg); image.Encode(streamJpg, SKEncodedImageFormat.Jpeg, 80);
_bufferImageProfile = streamJpg.ToArray(); _bufferImageProfile = streamJpg.ToArray();
} }

View file

@ -0,0 +1,63 @@
using System.Text;
namespace Ryujinx.HLE.Generators
{
class CodeGenerator
{
private const int IndentLength = 4;
private readonly StringBuilder _sb;
private int _currentIndentCount;
public CodeGenerator()
{
_sb = new StringBuilder();
}
public void EnterScope(string header = null)
{
if (header != null)
{
AppendLine(header);
}
AppendLine("{");
IncreaseIndentation();
}
public void LeaveScope(string suffix = "")
{
DecreaseIndentation();
AppendLine($"}}{suffix}");
}
public void IncreaseIndentation()
{
_currentIndentCount++;
}
public void DecreaseIndentation()
{
if (_currentIndentCount - 1 >= 0)
{
_currentIndentCount--;
}
}
public void AppendLine()
{
_sb.AppendLine();
}
public void AppendLine(string text)
{
_sb.Append(' ', IndentLength * _currentIndentCount);
_sb.AppendLine(text);
}
public override string ToString()
{
return _sb.ToString();
}
}
}

View file

@ -0,0 +1,76 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Linq;
namespace Ryujinx.HLE.Generators
{
[Generator]
public class IpcServiceGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
var syntaxReceiver = (ServiceSyntaxReceiver)context.SyntaxReceiver;
CodeGenerator generator = new CodeGenerator();
generator.AppendLine("using System;");
generator.EnterScope($"namespace Ryujinx.HLE.HOS.Services.Sm");
generator.EnterScope($"partial class IUserInterface");
generator.EnterScope($"public IpcService? GetServiceInstance(Type type, ServiceCtx context, object? parameter = null)");
foreach (var className in syntaxReceiver.Types)
{
if (className.Modifiers.Any(SyntaxKind.AbstractKeyword) || className.Modifiers.Any(SyntaxKind.PrivateKeyword) || !className.AttributeLists.Any(x => x.Attributes.Any(y => y.ToString().StartsWith("Service"))))
continue;
var name = GetFullName(className, context).Replace("global::", "");
if (!name.StartsWith("Ryujinx.HLE.HOS.Services"))
continue;
var constructors = className.ChildNodes().Where(x => x.IsKind(SyntaxKind.ConstructorDeclaration)).Select(y => y as ConstructorDeclarationSyntax);
if (!constructors.Any(x => x.ParameterList.Parameters.Count >= 1))
continue;
if (constructors.Where(x => x.ParameterList.Parameters.Count >= 1).FirstOrDefault().ParameterList.Parameters[0].Type.ToString() == "ServiceCtx")
{
generator.EnterScope($"if (type == typeof({GetFullName(className, context)}))");
if (constructors.Any(x => x.ParameterList.Parameters.Count == 2))
{
var type = constructors.Where(x => x.ParameterList.Parameters.Count == 2).FirstOrDefault().ParameterList.Parameters[1].Type;
var model = context.Compilation.GetSemanticModel(type.SyntaxTree);
var typeSymbol = model.GetSymbolInfo(type).Symbol as INamedTypeSymbol;
var fullName = typeSymbol.ToString();
generator.EnterScope("if (parameter != null)");
generator.AppendLine($"return new {GetFullName(className, context)}(context, ({fullName})parameter);");
generator.LeaveScope();
}
if (constructors.Any(x => x.ParameterList.Parameters.Count == 1))
{
generator.AppendLine($"return new {GetFullName(className, context)}(context);");
}
generator.LeaveScope();
}
}
generator.AppendLine("return null;");
generator.LeaveScope();
generator.LeaveScope();
generator.LeaveScope();
context.AddSource($"IUserInterface.g.cs", generator.ToString());
}
private string GetFullName(ClassDeclarationSyntax syntaxNode, GeneratorExecutionContext context)
{
var typeSymbol = context.Compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetDeclaredSymbol(syntaxNode);
return typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
}
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new ServiceSyntaxReceiver());
}
}
}

View file

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
<IsRoslynComponent>true</IsRoslynComponent>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,24 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
namespace Ryujinx.HLE.Generators
{
internal class ServiceSyntaxReceiver : ISyntaxReceiver
{
public HashSet<ClassDeclarationSyntax> Types = new HashSet<ClassDeclarationSyntax>();
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is ClassDeclarationSyntax classDeclaration)
{
if (classDeclaration.BaseList == null)
{
return;
}
Types.Add(classDeclaration);
}
}
}
}

View file

@ -8,27 +8,24 @@ namespace Ryujinx.HLE.HOS.Applets
{ {
static class AppletManager static class AppletManager
{ {
private static readonly Dictionary<AppletId, Type> _appletMapping;
static AppletManager()
{
_appletMapping = new Dictionary<AppletId, Type>
{
{ AppletId.Error, typeof(ErrorApplet) },
{ AppletId.PlayerSelect, typeof(PlayerSelectApplet) },
{ AppletId.Controller, typeof(ControllerApplet) },
{ AppletId.SoftwareKeyboard, typeof(SoftwareKeyboardApplet) },
{ AppletId.LibAppletWeb, typeof(BrowserApplet) },
{ AppletId.LibAppletShop, typeof(BrowserApplet) },
{ AppletId.LibAppletOff, typeof(BrowserApplet) },
};
}
public static IApplet Create(AppletId applet, Horizon system) public static IApplet Create(AppletId applet, Horizon system)
{ {
if (_appletMapping.TryGetValue(applet, out Type appletClass)) switch (applet)
{ {
return (IApplet)Activator.CreateInstance(appletClass, system); case AppletId.Controller:
return new ControllerApplet(system);
case AppletId.Error:
return new ErrorApplet(system);
case AppletId.PlayerSelect:
return new PlayerSelectApplet(system);
case AppletId.SoftwareKeyboard:
return new SoftwareKeyboardApplet(system);
case AppletId.LibAppletWeb:
return new BrowserApplet(system);
case AppletId.LibAppletShop:
return new BrowserApplet(system);
case AppletId.LibAppletOff:
return new BrowserApplet(system);
} }
throw new NotImplementedException($"{applet} applet is not implemented."); throw new NotImplementedException($"{applet} applet is not implemented.");

View file

@ -112,11 +112,16 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{ {
// Update the parameters that were provided. // Update the parameters that were provided.
_state.InputText = inputText ?? _state.InputText; _state.InputText = inputText ?? _state.InputText;
_state.CursorBegin = cursorBegin.GetValueOrDefault(_state.CursorBegin); _state.CursorBegin = Math.Max(0, cursorBegin.GetValueOrDefault(_state.CursorBegin));
_state.CursorEnd = cursorEnd.GetValueOrDefault(_state.CursorEnd); _state.CursorEnd = Math.Min(cursorEnd.GetValueOrDefault(_state.CursorEnd), _state.InputText.Length);
_state.OverwriteMode = overwriteMode.GetValueOrDefault(_state.OverwriteMode); _state.OverwriteMode = overwriteMode.GetValueOrDefault(_state.OverwriteMode);
_state.TypingEnabled = typingEnabled.GetValueOrDefault(_state.TypingEnabled); _state.TypingEnabled = typingEnabled.GetValueOrDefault(_state.TypingEnabled);
var begin = _state.CursorBegin;
var end = _state.CursorEnd;
_state.CursorBegin = Math.Min(begin, end);
_state.CursorEnd = Math.Max(begin, end);
// Reset the cursor blink. // Reset the cursor blink.
_state.TextBoxBlinkCounter = 0; _state.TextBoxBlinkCounter = 0;

View file

@ -1,14 +1,9 @@
using Ryujinx.HLE.UI; using Ryujinx.HLE.UI;
using Ryujinx.Memory; using Ryujinx.Memory;
using SixLabors.Fonts; using SkiaSharp;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Numerics;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -29,38 +24,39 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
private readonly object _bufferLock = new(); private readonly object _bufferLock = new();
private RenderingSurfaceInfo _surfaceInfo = null; private RenderingSurfaceInfo _surfaceInfo = null;
private Image<Argb32> _surface = null; private SKImageInfo _imageInfo;
private SKSurface _surface = null;
private byte[] _bufferData = null; private byte[] _bufferData = null;
private readonly Image _ryujinxLogo = null; private readonly SKBitmap _ryujinxLogo = null;
private readonly Image _padAcceptIcon = null; private readonly SKBitmap _padAcceptIcon = null;
private readonly Image _padCancelIcon = null; private readonly SKBitmap _padCancelIcon = null;
private readonly Image _keyModeIcon = null; private readonly SKBitmap _keyModeIcon = null;
private readonly float _textBoxOutlineWidth; private readonly float _textBoxOutlineWidth;
private readonly float _padPressedPenWidth; private readonly float _padPressedPenWidth;
private readonly Color _textNormalColor; private readonly SKColor _textNormalColor;
private readonly Color _textSelectedColor; private readonly SKColor _textSelectedColor;
private readonly Color _textOverCursorColor; private readonly SKColor _textOverCursorColor;
private readonly Brush _panelBrush; private readonly SKPaint _panelBrush;
private readonly Brush _disabledBrush; private readonly SKPaint _disabledBrush;
private readonly Brush _cursorBrush; private readonly SKPaint _cursorBrush;
private readonly Brush _selectionBoxBrush; private readonly SKPaint _selectionBoxBrush;
private readonly Pen _textBoxOutlinePen; private readonly SKPaint _textBoxOutlinePen;
private readonly Pen _cursorPen; private readonly SKPaint _cursorPen;
private readonly Pen _selectionBoxPen; private readonly SKPaint _selectionBoxPen;
private readonly Pen _padPressedPen; private readonly SKPaint _padPressedPen;
private readonly int _inputTextFontSize; private readonly int _inputTextFontSize;
private Font _messageFont; private SKFont _messageFont;
private Font _inputTextFont; private SKFont _inputTextFont;
private Font _labelsTextFont; private SKFont _labelsTextFont;
private RectangleF _panelRectangle; private SKRect _panelRectangle;
private Point _logoPosition; private SKPoint _logoPosition;
private float _messagePositionY; private float _messagePositionY;
public SoftwareKeyboardRendererBase(IHostUITheme uiTheme) public SoftwareKeyboardRendererBase(IHostUITheme uiTheme)
@ -78,10 +74,10 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
_padCancelIcon = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, padCancelIconPath, 0, 0); _padCancelIcon = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, padCancelIconPath, 0, 0);
_keyModeIcon = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, keyModeIconPath, 0, 0); _keyModeIcon = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, keyModeIconPath, 0, 0);
Color panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255); var panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255);
Color panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150); var panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150);
Color borderColor = ToColor(uiTheme.DefaultBorderColor); var borderColor = ToColor(uiTheme.DefaultBorderColor);
Color selectionBackgroundColor = ToColor(uiTheme.SelectionBackgroundColor); var selectionBackgroundColor = ToColor(uiTheme.SelectionBackgroundColor);
_textNormalColor = ToColor(uiTheme.DefaultForegroundColor); _textNormalColor = ToColor(uiTheme.DefaultForegroundColor);
_textSelectedColor = ToColor(uiTheme.SelectionForegroundColor); _textSelectedColor = ToColor(uiTheme.SelectionForegroundColor);
@ -92,15 +88,29 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
_textBoxOutlineWidth = 2; _textBoxOutlineWidth = 2;
_padPressedPenWidth = 2; _padPressedPenWidth = 2;
_panelBrush = new SolidBrush(panelColor); _panelBrush = new SKPaint()
_disabledBrush = new SolidBrush(panelTransparentColor); {
_cursorBrush = new SolidBrush(_textNormalColor); Color = panelColor,
_selectionBoxBrush = new SolidBrush(selectionBackgroundColor); IsAntialias = true
};
_disabledBrush = new SKPaint()
{
Color = panelTransparentColor,
IsAntialias = true
};
_cursorBrush = new SKPaint() { Color = _textNormalColor, IsAntialias = true };
_selectionBoxBrush = new SKPaint() { Color = selectionBackgroundColor, IsAntialias = true };
_textBoxOutlinePen = Pens.Solid(borderColor, _textBoxOutlineWidth); _textBoxOutlinePen = new SKPaint()
_cursorPen = Pens.Solid(_textNormalColor, cursorWidth); {
_selectionBoxPen = Pens.Solid(selectionBackgroundColor, cursorWidth); Color = borderColor,
_padPressedPen = Pens.Solid(borderColor, _padPressedPenWidth); StrokeWidth = _textBoxOutlineWidth,
IsStroke = true,
IsAntialias = true
};
_cursorPen = new SKPaint() { Color = _textNormalColor, StrokeWidth = cursorWidth, IsStroke = true, IsAntialias = true };
_selectionBoxPen = new SKPaint() { Color = selectionBackgroundColor, StrokeWidth = cursorWidth, IsStroke = true, IsAntialias = true };
_padPressedPen = new SKPaint() { Color = borderColor, StrokeWidth = _padPressedPenWidth, IsStroke = true, IsAntialias = true };
_inputTextFontSize = 20; _inputTextFontSize = 20;
@ -123,9 +133,10 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{ {
try try
{ {
_messageFont = SystemFonts.CreateFont(fontFamily, 26, FontStyle.Regular); using var typeface = SKTypeface.FromFamilyName(fontFamily, SKFontStyle.Normal);
_inputTextFont = SystemFonts.CreateFont(fontFamily, _inputTextFontSize, FontStyle.Regular); _messageFont = new SKFont(typeface, 26);
_labelsTextFont = SystemFonts.CreateFont(fontFamily, 24, FontStyle.Regular); _inputTextFont = new SKFont(typeface, _inputTextFontSize);
_labelsTextFont = new SKFont(typeface, 24);
return; return;
} }
@ -137,7 +148,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
throw new Exception($"None of these fonts were found in the system: {String.Join(", ", availableFonts)}!"); throw new Exception($"None of these fonts were found in the system: {String.Join(", ", availableFonts)}!");
} }
private static Color ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false) private static SKColor ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false)
{ {
var a = (byte)(color.A * 255); var a = (byte)(color.A * 255);
var r = (byte)(color.R * 255); var r = (byte)(color.R * 255);
@ -151,34 +162,33 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
b = (byte)(255 - b); b = (byte)(255 - b);
} }
return Color.FromRgba(r, g, b, overrideAlpha.GetValueOrDefault(a)); return new SKColor(r, g, b, overrideAlpha.GetValueOrDefault(a));
} }
private static Image LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight) private static SKBitmap LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight)
{ {
Stream resourceStream = assembly.GetManifestResourceStream(resourcePath); Stream resourceStream = assembly.GetManifestResourceStream(resourcePath);
return LoadResource(resourceStream, newWidth, newHeight); return LoadResource(resourceStream, newWidth, newHeight);
} }
private static Image LoadResource(Stream resourceStream, int newWidth, int newHeight) private static SKBitmap LoadResource(Stream resourceStream, int newWidth, int newHeight)
{ {
Debug.Assert(resourceStream != null); Debug.Assert(resourceStream != null);
var image = Image.Load(resourceStream); var bitmap = SKBitmap.Decode(resourceStream);
if (newHeight != 0 && newWidth != 0) if (newHeight != 0 && newWidth != 0)
{ {
image.Mutate(x => x.Resize(newWidth, newHeight, KnownResamplers.Lanczos3)); var resized = bitmap.Resize(new SKImageInfo(newWidth, newHeight), SKFilterQuality.High);
} if (resized != null)
return image;
}
private static void SetGraphicsOptions(IImageProcessingContext context)
{ {
context.GetGraphicsOptions().Antialias = true; bitmap.Dispose();
context.GetDrawingOptions().GraphicsOptions.Antialias = true; bitmap = resized;
}
}
return bitmap;
} }
private void DrawImmutableElements() private void DrawImmutableElements()
@ -187,22 +197,18 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{ {
return; return;
} }
var canvas = _surface.Canvas;
_surface.Mutate(context => canvas.Clear(SKColors.Transparent);
{ canvas.DrawRect(_panelRectangle, _panelBrush);
SetGraphicsOptions(context); canvas.DrawBitmap(_ryujinxLogo, _logoPosition);
context.Clear(Color.Transparent);
context.Fill(_panelBrush, _panelRectangle);
context.DrawImage(_ryujinxLogo, _logoPosition, 1);
float halfWidth = _panelRectangle.Width / 2; float halfWidth = _panelRectangle.Width / 2;
float buttonsY = _panelRectangle.Y + 185; float buttonsY = _panelRectangle.Top + 185;
PointF disableButtonPosition = new(halfWidth + 180, buttonsY); SKPoint disableButtonPosition = new(halfWidth + 180, buttonsY);
DrawControllerToggle(context, disableButtonPosition); DrawControllerToggle(canvas, disableButtonPosition);
});
} }
public void DrawMutableElements(SoftwareKeyboardUIState state) public void DrawMutableElements(SoftwareKeyboardUIState state)
@ -212,40 +218,43 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
return; return;
} }
_surface.Mutate(context => using var paint = new SKPaint(_messageFont)
{ {
var messageRectangle = MeasureString(MessageText, _messageFont); Color = _textNormalColor,
float messagePositionX = (_panelRectangle.Width - messageRectangle.Width) / 2 - messageRectangle.X; IsAntialias = true
float messagePositionY = _messagePositionY - messageRectangle.Y; };
var messagePosition = new PointF(messagePositionX, messagePositionY);
var messageBoundRectangle = new RectangleF(messagePositionX, messagePositionY, messageRectangle.Width, messageRectangle.Height);
SetGraphicsOptions(context); var canvas = _surface.Canvas;
var messageRectangle = MeasureString(MessageText, paint);
float messagePositionX = (_panelRectangle.Width - messageRectangle.Width) / 2 - messageRectangle.Left;
float messagePositionY = _messagePositionY - messageRectangle.Top;
var messagePosition = new SKPoint(messagePositionX, messagePositionY);
var messageBoundRectangle = SKRect.Create(messagePositionX, messagePositionY, messageRectangle.Width, messageRectangle.Height);
context.Fill(_panelBrush, messageBoundRectangle); canvas.DrawRect(messageBoundRectangle, _panelBrush);
context.DrawText(MessageText, _messageFont, _textNormalColor, messagePosition); canvas.DrawText(MessageText, messagePosition.X, messagePosition.Y + _messageFont.Metrics.XHeight + _messageFont.Metrics.Descent, paint);
if (!state.TypingEnabled) if (!state.TypingEnabled)
{ {
// Just draw a semi-transparent rectangle on top to fade the component with the background. // 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. // TODO (caian): This will not work if one decides to add make background semi-transparent as well.
context.Fill(_disabledBrush, messageBoundRectangle); canvas.DrawRect(messageBoundRectangle, _disabledBrush);
} }
DrawTextBox(context, state); DrawTextBox(canvas, state);
float halfWidth = _panelRectangle.Width / 2; float halfWidth = _panelRectangle.Width / 2;
float buttonsY = _panelRectangle.Y + 185; float buttonsY = _panelRectangle.Top + 185;
PointF acceptButtonPosition = new(halfWidth - 180, buttonsY); SKPoint acceptButtonPosition = new(halfWidth - 180, buttonsY);
PointF cancelButtonPosition = new(halfWidth, buttonsY); SKPoint cancelButtonPosition = new(halfWidth, buttonsY);
PointF disableButtonPosition = new(halfWidth + 180, buttonsY); SKPoint disableButtonPosition = new(halfWidth + 180, buttonsY);
DrawPadButton(canvas, acceptButtonPosition, _padAcceptIcon, AcceptText, state.AcceptPressed, state.ControllerEnabled);
DrawPadButton(canvas, cancelButtonPosition, _padCancelIcon, CancelText, state.CancelPressed, state.ControllerEnabled);
DrawPadButton(context, acceptButtonPosition, _padAcceptIcon, AcceptText, state.AcceptPressed, state.ControllerEnabled);
DrawPadButton(context, cancelButtonPosition, _padCancelIcon, CancelText, state.CancelPressed, state.ControllerEnabled);
});
} }
public void CreateSurface(RenderingSurfaceInfo surfaceInfo) public void CreateSurface(RenderingSurfaceInfo surfaceInfo)
@ -268,7 +277,8 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
Debug.Assert(_surfaceInfo.Height <= totalHeight); Debug.Assert(_surfaceInfo.Height <= totalHeight);
Debug.Assert(_surfaceInfo.Pitch * _surfaceInfo.Height <= _surfaceInfo.Size); Debug.Assert(_surfaceInfo.Pitch * _surfaceInfo.Height <= _surfaceInfo.Size);
_surface = new Image<Argb32>((int)totalWidth, (int)totalHeight); _imageInfo = new SKImageInfo((int)totalWidth, (int)totalHeight, SKColorType.Rgba8888);
_surface = SKSurface.Create(_imageInfo);
ComputeConstants(); ComputeConstants();
DrawImmutableElements(); DrawImmutableElements();
@ -282,76 +292,81 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
int panelHeight = 240; int panelHeight = 240;
int panelPositionY = totalHeight - panelHeight; int panelPositionY = totalHeight - panelHeight;
_panelRectangle = new RectangleF(0, panelPositionY, totalWidth, panelHeight); _panelRectangle = SKRect.Create(0, panelPositionY, totalWidth, panelHeight);
_messagePositionY = panelPositionY + 60; _messagePositionY = panelPositionY + 60;
int logoPositionX = (totalWidth - _ryujinxLogo.Width) / 2; int logoPositionX = (totalWidth - _ryujinxLogo.Width) / 2;
int logoPositionY = panelPositionY + 18; int logoPositionY = panelPositionY + 18;
_logoPosition = new Point(logoPositionX, logoPositionY); _logoPosition = new SKPoint(logoPositionX, logoPositionY);
} }
private static RectangleF MeasureString(string text, Font font) private static SKRect MeasureString(string text, SKPaint paint)
{ {
TextOptions options = new(font); SKRect bounds = SKRect.Empty;
if (text == "") if (text == "")
{ {
FontRectangle emptyRectangle = TextMeasurer.MeasureSize(" ", options); paint.MeasureText(" ", ref bounds);
return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height);
} }
else
FontRectangle rectangle = TextMeasurer.MeasureSize(text, options);
return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
}
private static RectangleF MeasureString(ReadOnlySpan<char> text, Font font)
{ {
TextOptions options = new(font); paint.MeasureText(text, ref bounds);
}
return bounds;
}
private static SKRect MeasureString(ReadOnlySpan<char> text, SKPaint paint)
{
SKRect bounds = SKRect.Empty;
if (text == "") if (text == "")
{ {
FontRectangle emptyRectangle = TextMeasurer.MeasureSize(" ", options); paint.MeasureText(" ", ref bounds);
return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height);
} }
else
FontRectangle rectangle = TextMeasurer.MeasureSize(text, options);
return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
}
private void DrawTextBox(IImageProcessingContext context, SoftwareKeyboardUIState state)
{ {
var inputTextRectangle = MeasureString(state.InputText, _inputTextFont); paint.MeasureText(text, ref bounds);
}
float boxWidth = (int)(Math.Max(300, inputTextRectangle.Width + inputTextRectangle.X + 8)); return bounds;
}
private void DrawTextBox(SKCanvas canvas, SoftwareKeyboardUIState state)
{
using var textPaint = new SKPaint(_labelsTextFont)
{
IsAntialias = true,
Color = _textNormalColor
};
var inputTextRectangle = MeasureString(state.InputText, textPaint);
float boxWidth = (int)(Math.Max(300, inputTextRectangle.Width + inputTextRectangle.Left + 8));
float boxHeight = 32; float boxHeight = 32;
float boxY = _panelRectangle.Y + 110; float boxY = _panelRectangle.Top + 110;
float boxX = (int)((_panelRectangle.Width - boxWidth) / 2); float boxX = (int)((_panelRectangle.Width - boxWidth) / 2);
RectangleF boxRectangle = new(boxX, boxY, boxWidth, boxHeight); SKRect boxRectangle = SKRect.Create(boxX, boxY, boxWidth, boxHeight);
RectangleF boundRectangle = new(_panelRectangle.X, boxY - _textBoxOutlineWidth, SKRect boundRectangle = SKRect.Create(_panelRectangle.Left, boxY - _textBoxOutlineWidth,
_panelRectangle.Width, boxHeight + 2 * _textBoxOutlineWidth); _panelRectangle.Width, boxHeight + 2 * _textBoxOutlineWidth);
context.Fill(_panelBrush, boundRectangle); canvas.DrawRect(boundRectangle, _panelBrush);
context.Draw(_textBoxOutlinePen, boxRectangle); canvas.DrawRect(boxRectangle, _textBoxOutlinePen);
float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.X; float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.Left;
float inputTextY = boxY + 5; float inputTextY = boxY + 5;
var inputTextPosition = new PointF(inputTextX, inputTextY); var inputTextPosition = new SKPoint(inputTextX, inputTextY);
canvas.DrawText(state.InputText, inputTextPosition.X, inputTextPosition.Y + (_labelsTextFont.Metrics.XHeight + _labelsTextFont.Metrics.Descent), textPaint);
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. // Draw the cursor on top of the text and redraw the text with a different color if necessary.
Color cursorTextColor; SKColor cursorTextColor;
Brush cursorBrush; SKPaint cursorBrush;
Pen cursorPen; SKPaint cursorPen;
float cursorPositionYTop = inputTextY + 1; float cursorPositionYTop = inputTextY + 1;
float cursorPositionYBottom = cursorPositionYTop + _inputTextFontSize + 1; float cursorPositionYBottom = cursorPositionYTop + _inputTextFontSize + 1;
@ -371,12 +386,12 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
ReadOnlySpan<char> textUntilBegin = state.InputText.AsSpan(0, state.CursorBegin); ReadOnlySpan<char> textUntilBegin = state.InputText.AsSpan(0, state.CursorBegin);
ReadOnlySpan<char> textUntilEnd = state.InputText.AsSpan(0, state.CursorEnd); ReadOnlySpan<char> textUntilEnd = state.InputText.AsSpan(0, state.CursorEnd);
var selectionBeginRectangle = MeasureString(textUntilBegin, _inputTextFont); var selectionBeginRectangle = MeasureString(textUntilBegin, textPaint);
var selectionEndRectangle = MeasureString(textUntilEnd, _inputTextFont); var selectionEndRectangle = MeasureString(textUntilEnd, textPaint);
cursorVisible = true; cursorVisible = true;
cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.X; cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.Left;
cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.X; cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.Left;
} }
else else
{ {
@ -390,10 +405,10 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
int cursorBegin = Math.Min(state.InputText.Length, state.CursorBegin); int cursorBegin = Math.Min(state.InputText.Length, state.CursorBegin);
ReadOnlySpan<char> textUntilCursor = state.InputText.AsSpan(0, cursorBegin); ReadOnlySpan<char> textUntilCursor = state.InputText.AsSpan(0, cursorBegin);
var cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont); var cursorTextRectangle = MeasureString(textUntilCursor, textPaint);
cursorVisible = true; cursorVisible = true;
cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X; cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.Left;
if (state.OverwriteMode) if (state.OverwriteMode)
{ {
@ -402,8 +417,8 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
if (state.CursorBegin < state.InputText.Length) if (state.CursorBegin < state.InputText.Length)
{ {
textUntilCursor = state.InputText.AsSpan(0, cursorBegin + 1); textUntilCursor = state.InputText.AsSpan(0, cursorBegin + 1);
cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont); cursorTextRectangle = MeasureString(textUntilCursor, textPaint);
cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X; cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.Left;
} }
else else
{ {
@ -430,29 +445,32 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
if (cursorWidth == 0) if (cursorWidth == 0)
{ {
PointF[] points = { canvas.DrawLine(new SKPoint(cursorPositionXLeft, cursorPositionYTop),
new PointF(cursorPositionXLeft, cursorPositionYTop), new SKPoint(cursorPositionXLeft, cursorPositionYBottom),
new PointF(cursorPositionXLeft, cursorPositionYBottom), cursorPen);
};
context.DrawLine(cursorPen, points);
} }
else else
{ {
var cursorRectangle = new RectangleF(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight); var cursorRectangle = SKRect.Create(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
context.Draw(cursorPen, cursorRectangle); canvas.DrawRect(cursorRectangle, cursorPen);
context.Fill(cursorBrush, cursorRectangle); canvas.DrawRect(cursorRectangle, cursorBrush);
Image<Argb32> textOverCursor = new((int)cursorRectangle.Width, (int)cursorRectangle.Height); using var textOverCursor = SKSurface.Create(new SKImageInfo((int)cursorRectangle.Width, (int)cursorRectangle.Height, SKColorType.Rgba8888));
textOverCursor.Mutate(context => var textOverCanvas = textOverCursor.Canvas;
var textRelativePosition = new SKPoint(inputTextPosition.X - cursorRectangle.Left, inputTextPosition.Y - cursorRectangle.Top);
using var cursorPaint = new SKPaint(_inputTextFont)
{ {
var textRelativePosition = new PointF(inputTextPosition.X - cursorRectangle.X, inputTextPosition.Y - cursorRectangle.Y); Color = cursorTextColor,
context.DrawText(state.InputText, _inputTextFont, cursorTextColor, textRelativePosition); IsAntialias = true
}); };
var cursorPosition = new Point((int)cursorRectangle.X, (int)cursorRectangle.Y); textOverCanvas.DrawText(state.InputText, textRelativePosition.X, textRelativePosition.Y + _inputTextFont.Metrics.XHeight + _inputTextFont.Metrics.Descent, cursorPaint);
context.DrawImage(textOverCursor, cursorPosition, 1);
var cursorPosition = new SKPoint((int)cursorRectangle.Left, (int)cursorRectangle.Top);
textOverCursor.Flush();
canvas.DrawSurface(textOverCursor, cursorPosition);
} }
} }
else if (!state.TypingEnabled) else if (!state.TypingEnabled)
@ -460,11 +478,11 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
// Just draw a semi-transparent rectangle on top to fade the component with the background. // 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. // TODO (caian): This will not work if one decides to add make background semi-transparent as well.
context.Fill(_disabledBrush, boundRectangle); canvas.DrawRect(boundRectangle, _disabledBrush);
} }
} }
private void DrawPadButton(IImageProcessingContext context, PointF point, Image icon, string label, bool pressed, bool enabled) private void DrawPadButton(SKCanvas canvas, SKPoint point, SKBitmap icon, string label, bool pressed, bool enabled)
{ {
// Use relative positions so we can center the entire drawing later. // Use relative positions so we can center the entire drawing later.
@ -473,12 +491,18 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
float iconWidth = icon.Width; float iconWidth = icon.Width;
float iconHeight = icon.Height; float iconHeight = icon.Height;
var labelRectangle = MeasureString(label, _labelsTextFont); using var paint = new SKPaint(_labelsTextFont)
{
Color = _textNormalColor,
IsAntialias = true
};
float labelPositionX = iconWidth + 8 - labelRectangle.X; var labelRectangle = MeasureString(label, paint);
float labelPositionX = iconWidth + 8 - labelRectangle.Left;
float labelPositionY = 3; float labelPositionY = 3;
float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.X; float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.Left;
float fullHeight = iconHeight; float fullHeight = iconHeight;
// Convert all relative positions into absolute. // Convert all relative positions into absolute.
@ -489,24 +513,24 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
iconX += originX; iconX += originX;
iconY += originY; iconY += originY;
var iconPosition = new Point((int)iconX, (int)iconY); var iconPosition = new SKPoint((int)iconX, (int)iconY);
var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY); var labelPosition = new SKPoint(labelPositionX + originX, labelPositionY + originY);
var selectedRectangle = new RectangleF(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth, var selectedRectangle = SKRect.Create(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth,
fullWidth + 4 * _padPressedPenWidth, fullHeight + 4 * _padPressedPenWidth); fullWidth + 4 * _padPressedPenWidth, fullHeight + 4 * _padPressedPenWidth);
var boundRectangle = new RectangleF(originX, originY, fullWidth, fullHeight); var boundRectangle = SKRect.Create(originX, originY, fullWidth, fullHeight);
boundRectangle.Inflate(4 * _padPressedPenWidth, 4 * _padPressedPenWidth); boundRectangle.Inflate(4 * _padPressedPenWidth, 4 * _padPressedPenWidth);
context.Fill(_panelBrush, boundRectangle); canvas.DrawRect(boundRectangle, _panelBrush);
context.DrawImage(icon, iconPosition, 1); canvas.DrawBitmap(icon, iconPosition);
context.DrawText(label, _labelsTextFont, _textNormalColor, labelPosition); canvas.DrawText(label, labelPosition.X, labelPosition.Y + _labelsTextFont.Metrics.XHeight + _labelsTextFont.Metrics.Descent, paint);
if (enabled) if (enabled)
{ {
if (pressed) if (pressed)
{ {
context.Draw(_padPressedPen, selectedRectangle); canvas.DrawRect(selectedRectangle, _padPressedPen);
} }
} }
else else
@ -514,21 +538,26 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
// Just draw a semi-transparent rectangle on top to fade the component with the background. // 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. // TODO (caian): This will not work if one decides to add make background semi-transparent as well.
context.Fill(_disabledBrush, boundRectangle); canvas.DrawRect(boundRectangle, _disabledBrush);
} }
} }
private void DrawControllerToggle(IImageProcessingContext context, PointF point) private void DrawControllerToggle(SKCanvas canvas, SKPoint point)
{ {
var labelRectangle = MeasureString(ControllerToggleText, _labelsTextFont); using var paint = new SKPaint(_labelsTextFont)
{
IsAntialias = true,
Color = _textNormalColor
};
var labelRectangle = MeasureString(ControllerToggleText, paint);
// Use relative positions so we can center the entire drawing later. // Use relative positions so we can center the entire drawing later.
float keyWidth = _keyModeIcon.Width; float keyWidth = _keyModeIcon.Width;
float keyHeight = _keyModeIcon.Height; float keyHeight = _keyModeIcon.Height;
float labelPositionX = keyWidth + 8 - labelRectangle.X; float labelPositionX = keyWidth + 8 - labelRectangle.Left;
float labelPositionY = -labelRectangle.Y - 1; float labelPositionY = -labelRectangle.Top - 1;
float keyX = 0; float keyX = 0;
float keyY = (int)((labelPositionY + labelRectangle.Height - keyHeight) / 2); float keyY = (int)((labelPositionY + labelRectangle.Height - keyHeight) / 2);
@ -544,14 +573,14 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
keyX += originX; keyX += originX;
keyY += originY; keyY += originY;
var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY); var labelPosition = new SKPoint(labelPositionX + originX, labelPositionY + originY);
var overlayPosition = new Point((int)keyX, (int)keyY); var overlayPosition = new SKPoint((int)keyX, (int)keyY);
context.DrawImage(_keyModeIcon, overlayPosition, 1); canvas.DrawBitmap(_keyModeIcon, overlayPosition);
context.DrawText(ControllerToggleText, _labelsTextFont, _textNormalColor, labelPosition); canvas.DrawText(ControllerToggleText, labelPosition.X, labelPosition.Y + _labelsTextFont.Metrics.XHeight, paint);
} }
public void CopyImageToBuffer() public unsafe void CopyImageToBuffer()
{ {
lock (_bufferLock) lock (_bufferLock)
{ {
@ -561,21 +590,20 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
} }
// Convert the pixel format used in the image to the one used in the Switch surface. // Convert the pixel format used in the image to the one used in the Switch surface.
_surface.Flush();
if (!_surface.DangerousTryGetSinglePixelMemory(out Memory<Argb32> pixels)) var buffer = new byte[_imageInfo.BytesSize];
fixed (byte* bufferPtr = buffer)
{
if (!_surface.ReadPixels(_imageInfo, (nint)bufferPtr, _imageInfo.RowBytes, 0, 0))
{ {
return; return;
} }
_bufferData = MemoryMarshal.AsBytes(pixels.Span).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);
} }
_bufferData = buffer;
Debug.Assert(buffer.Length == _surfaceInfo.Size);
} }
} }

View file

@ -1,10 +1,10 @@
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Caps.Types; using Ryujinx.HLE.HOS.Services.Caps.Types;
using SixLabors.ImageSharp; using SkiaSharp;
using SixLabors.ImageSharp.PixelFormats;
using System; using System;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography; using System.Security.Cryptography;
namespace Ryujinx.HLE.HOS.Services.Caps namespace Ryujinx.HLE.HOS.Services.Caps
@ -118,7 +118,11 @@ namespace Ryujinx.HLE.HOS.Services.Caps
} }
// NOTE: The saved JPEG file doesn't have the limitation in the extra EXIF data. // NOTE: The saved JPEG file doesn't have the limitation in the extra EXIF data.
Image.LoadPixelData<Rgba32>(screenshotData, 1280, 720).SaveAsJpegAsync(filePath); using var bitmap = new SKBitmap(new SKImageInfo(1280, 720, SKColorType.Rgba8888));
Marshal.Copy(screenshotData, 0, bitmap.GetPixels(), screenshotData.Length);
using var data = bitmap.Encode(SKEncodedImageFormat.Jpeg, 80);
using var file = File.OpenWrite(filePath);
data.SaveTo(file);
return ResultCode.Success; return ResultCode.Success;
} }

View file

@ -2,6 +2,7 @@ using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Ipc; using Ryujinx.HLE.HOS.Kernel.Ipc;
using Ryujinx.HLE.HOS.Services.Apm;
using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Common;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -12,7 +13,7 @@ using System.Text;
namespace Ryujinx.HLE.HOS.Services.Sm namespace Ryujinx.HLE.HOS.Services.Sm
{ {
class IUserInterface : IpcService partial class IUserInterface : IpcService
{ {
private static readonly Dictionary<string, Type> _services; private static readonly Dictionary<string, Type> _services;
@ -95,9 +96,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
{ {
ServiceAttribute serviceAttribute = (ServiceAttribute)type.GetCustomAttributes(typeof(ServiceAttribute)).First(service => ((ServiceAttribute)service).Name == name); ServiceAttribute serviceAttribute = (ServiceAttribute)type.GetCustomAttributes(typeof(ServiceAttribute)).First(service => ((ServiceAttribute)service).Name == name);
IpcService service = serviceAttribute.Parameter != null IpcService service = GetServiceInstance(type, context, serviceAttribute.Parameter);
? (IpcService)Activator.CreateInstance(type, context, serviceAttribute.Parameter)
: (IpcService)Activator.CreateInstance(type, context);
service.TrySetServer(_commonServer); service.TrySetServer(_commonServer);
service.Server.AddSessionObj(session.ServerSession, service); service.Server.AddSessionObj(session.ServerSession, service);

View file

@ -2,6 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -11,6 +12,7 @@
<ProjectReference Include="..\Ryujinx.Graphics.Host1x\Ryujinx.Graphics.Host1x.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.Host1x\Ryujinx.Graphics.Host1x.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Nvdec\Ryujinx.Graphics.Nvdec.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.Nvdec\Ryujinx.Graphics.Nvdec.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Vic\Ryujinx.Graphics.Vic.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.Vic\Ryujinx.Graphics.Vic.csproj" />
<ProjectReference Include="..\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
<ProjectReference Include="..\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj" /> <ProjectReference Include="..\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> <ProjectReference Include="..\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\Ryujinx.Horizon\Ryujinx.Horizon.csproj" /> <ProjectReference Include="..\Ryujinx.Horizon\Ryujinx.Horizon.csproj" />
@ -24,8 +26,8 @@
<PackageReference Include="LibHac" /> <PackageReference Include="LibHac" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" /> <PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
<PackageReference Include="MsgPack.Cli" /> <PackageReference Include="MsgPack.Cli" />
<PackageReference Include="SixLabors.ImageSharp" /> <PackageReference Include="SkiaSharp" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" /> <PackageReference Include="SkiaSharp.NativeAssets.Linux" />
<PackageReference Include="NetCoreServer" /> <PackageReference Include="NetCoreServer" />
</ItemGroup> </ItemGroup>

View file

@ -1,10 +1,7 @@
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using ShellLink; using ShellLink;
using SixLabors.ImageSharp; using SkiaSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -21,8 +18,8 @@ namespace Ryujinx.UI.Common.Helper
iconPath += ".ico"; iconPath += ".ico";
MemoryStream iconDataStream = new(iconData); MemoryStream iconDataStream = new(iconData);
var image = Image.Load(iconDataStream); using var image = SKBitmap.Decode(iconDataStream);
image.Mutate(x => x.Resize(128, 128)); image.Resize(new SKImageInfo(128, 128), SKFilterQuality.High);
SaveBitmapAsIcon(image, iconPath); SaveBitmapAsIcon(image, iconPath);
var shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId), iconPath, 0); var shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId), iconPath, 0);
@ -37,8 +34,10 @@ namespace Ryujinx.UI.Common.Helper
var desktopFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.desktop"); var desktopFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.desktop");
iconPath += ".png"; iconPath += ".png";
var image = Image.Load<Rgba32>(iconData); var image = SKBitmap.Decode(iconData);
image.SaveAsPng(iconPath); using var data = image.Encode(SKEncodedImageFormat.Png, 100);
using var file = File.OpenWrite(iconPath);
data.SaveTo(file);
using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop")); using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop"));
outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId)}"); outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId)}");
@ -78,8 +77,10 @@ namespace Ryujinx.UI.Common.Helper
} }
const string IconName = "icon.png"; const string IconName = "icon.png";
var image = Image.Load<Rgba32>(iconData); var image = SKBitmap.Decode(iconData);
image.SaveAsPng(Path.Combine(resourceFolderPath, IconName)); using var data = image.Encode(SKEncodedImageFormat.Png, 100);
using var file = File.OpenWrite(Path.Combine(resourceFolderPath, IconName));
data.SaveTo(file);
// plist file // plist file
using StreamWriter outputFile = new(Path.Combine(contentFolderPath, "Info.plist")); using StreamWriter outputFile = new(Path.Combine(contentFolderPath, "Info.plist"));
@ -148,7 +149,7 @@ namespace Ryujinx.UI.Common.Helper
/// <param name="source">The source bitmap image that will be saved as an .ico file</param> /// <param name="source">The source bitmap image that will be saved as an .ico file</param>
/// <param name="filePath">The location that the new .ico file will be saved too (Make sure to include '.ico' in the path).</param> /// <param name="filePath">The location that the new .ico file will be saved too (Make sure to include '.ico' in the path).</param>
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
private static void SaveBitmapAsIcon(Image source, string filePath) private static void SaveBitmapAsIcon(SKBitmap source, string filePath)
{ {
// Code Modified From https://stackoverflow.com/a/11448060/368354 by Benlitz // Code Modified From https://stackoverflow.com/a/11448060/368354 by Benlitz
byte[] header = { 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 32, 0, 0, 0, 0, 0, 22, 0, 0, 0 }; byte[] header = { 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 32, 0, 0, 0, 0, 0, 22, 0, 0, 0 };
@ -156,13 +157,16 @@ namespace Ryujinx.UI.Common.Helper
fs.Write(header); fs.Write(header);
// Writing actual data // Writing actual data
source.Save(fs, PngFormat.Instance); using var data = source.Encode(SKEncodedImageFormat.Png, 100);
data.SaveTo(fs);
// Getting data length (file length minus header) // Getting data length (file length minus header)
long dataLength = fs.Length - header.Length; long dataLength = fs.Length - header.Length;
// Write it in the correct place // Write it in the correct place
fs.Seek(14, SeekOrigin.Begin); fs.Seek(14, SeekOrigin.Begin);
fs.WriteByte((byte)dataLength); fs.WriteByte((byte)dataLength);
fs.WriteByte((byte)(dataLength >> 8)); fs.WriteByte((byte)(dataLength >> 8));
fs.WriteByte((byte)(dataLength >> 16));
fs.WriteByte((byte)(dataLength >> 24));
} }
} }
} }

View file

@ -41,17 +41,12 @@ namespace Ryujinx.Ava.UI.Applet
private void TextChanged(string text) private void TextChanged(string text)
{ {
TextChangedEvent?.Invoke(text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, true); TextChangedEvent?.Invoke(text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, false);
} }
private void SelectionChanged(int selection) private void SelectionChanged(int selection)
{ {
if (_hiddenTextBox.SelectionEnd < _hiddenTextBox.SelectionStart) TextChangedEvent?.Invoke(_hiddenTextBox.Text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, false);
{
_hiddenTextBox.SelectionStart = _hiddenTextBox.SelectionEnd;
}
TextChangedEvent?.Invoke(_hiddenTextBox.Text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, true);
} }
private void AvaloniaDynamicTextInputHandler_TextInput(object sender, string text) private void AvaloniaDynamicTextInputHandler_TextInput(object sender, string text)

View file

@ -1,11 +1,14 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using System;
namespace Ryujinx.Ava.UI.Helpers namespace Ryujinx.Ava.UI.Helpers
{ {
public class OffscreenTextBox : TextBox public class OffscreenTextBox : TextBox
{ {
protected override Type StyleKeyOverride => typeof(TextBox);
public static RoutedEvent<KeyEventArgs> GetKeyDownRoutedEvent() public static RoutedEvent<KeyEventArgs> GetKeyDownRoutedEvent()
{ {
return KeyDownEvent; return KeyDownEvent;

View file

@ -42,12 +42,10 @@
</Window.KeyBindings> </Window.KeyBindings>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<helpers:OffscreenTextBox Name="HiddenTextBox" Grid.Row="0" /> <helpers:OffscreenTextBox IsEnabled="False" Opacity="0" Name="HiddenTextBox" IsHitTestVisible="False" IsTabStop="False" />
<Grid <Grid
Grid.Row="1"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> VerticalAlignment="Stretch">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>