forked from Mirror/Ryujinx
Replace ImageSharp with SkiaSharp everywhere (#7030)
* replace ImageSharp with SkiaSharp for inline keyboard applet rendering * fix avalonia inline keyboard input * remove image sharp from gtk3 project * add skiasharp linux assets * fix whitespace * fix format * fix ico image offset when saving shortcut to windows
This commit is contained in:
parent
3c61d560c3
commit
e0acde04bb
14 changed files with 296 additions and 256 deletions
|
@ -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" />
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
{
|
||||||
|
bitmap.Dispose();
|
||||||
|
bitmap = resized;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return image;
|
return bitmap;
|
||||||
}
|
|
||||||
|
|
||||||
private static void SetGraphicsOptions(IImageProcessingContext context)
|
|
||||||
{
|
|
||||||
context.GetGraphicsOptions().Antialias = true;
|
|
||||||
context.GetDrawingOptions().GraphicsOptions.Antialias = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
float halfWidth = _panelRectangle.Width / 2;
|
||||||
context.Fill(_panelBrush, _panelRectangle);
|
float buttonsY = _panelRectangle.Top + 185;
|
||||||
context.DrawImage(_ryujinxLogo, _logoPosition, 1);
|
|
||||||
|
|
||||||
float halfWidth = _panelRectangle.Width / 2;
|
SKPoint disableButtonPosition = new(halfWidth + 180, buttonsY);
|
||||||
float buttonsY = _panelRectangle.Y + 185;
|
|
||||||
|
|
||||||
PointF disableButtonPosition = new(halfWidth + 180, buttonsY);
|
DrawControllerToggle(canvas, disableButtonPosition);
|
||||||
|
|
||||||
DrawControllerToggle(context, 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
|
||||||
|
{
|
||||||
|
paint.MeasureText(text, ref bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
FontRectangle rectangle = TextMeasurer.MeasureSize(text, options);
|
return bounds;
|
||||||
|
|
||||||
return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RectangleF MeasureString(ReadOnlySpan<char> text, Font font)
|
private static SKRect MeasureString(ReadOnlySpan<char> 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
|
||||||
|
{
|
||||||
|
paint.MeasureText(text, ref bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
FontRectangle rectangle = TextMeasurer.MeasureSize(text, options);
|
return bounds;
|
||||||
|
|
||||||
return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawTextBox(IImageProcessingContext context, SoftwareKeyboardUIState state)
|
private void DrawTextBox(SKCanvas canvas, SoftwareKeyboardUIState state)
|
||||||
{
|
{
|
||||||
var inputTextRectangle = MeasureString(state.InputText, _inputTextFont);
|
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.X + 8));
|
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)
|
||||||
{
|
{
|
||||||
return;
|
if (!_surface.ReadPixels(_imageInfo, (nint)bufferPtr, _imageInfo.RowBytes, 0, 0))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_bufferData = MemoryMarshal.AsBytes(pixels.Span).ToArray();
|
_bufferData = buffer;
|
||||||
Span<uint> dataConvert = MemoryMarshal.Cast<byte, uint>(_bufferData);
|
|
||||||
|
|
||||||
Debug.Assert(_bufferData.Length == _surfaceInfo.Size);
|
Debug.Assert(buffer.Length == _surfaceInfo.Size);
|
||||||
|
|
||||||
for (int i = 0; i < dataConvert.Length; i++)
|
|
||||||
{
|
|
||||||
dataConvert[i] = BitOperations.RotateRight(dataConvert[i], 8);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -24,8 +25,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>
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Reference in a new issue