2021-11-28 21:24:17 +01:00
|
|
|
|
using Ryujinx.HLE.Ui;
|
|
|
|
|
using Ryujinx.Memory;
|
|
|
|
|
using SixLabors.ImageSharp;
|
|
|
|
|
using SixLabors.ImageSharp.Processing;
|
|
|
|
|
using SixLabors.ImageSharp.Drawing.Processing;
|
|
|
|
|
using SixLabors.Fonts;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Numerics;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
using SixLabors.ImageSharp.PixelFormats;
|
|
|
|
|
|
|
|
|
|
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Base class that generates the graphics for the software keyboard applet during inline mode.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal class SoftwareKeyboardRendererBase
|
|
|
|
|
{
|
|
|
|
|
public const int TextBoxBlinkThreshold = 8;
|
|
|
|
|
|
|
|
|
|
const string MessageText = "Please use the keyboard to input text";
|
|
|
|
|
const string AcceptText = "Accept";
|
|
|
|
|
const string CancelText = "Cancel";
|
|
|
|
|
const string ControllerToggleText = "Toggle input";
|
|
|
|
|
|
|
|
|
|
private readonly object _bufferLock = new object();
|
|
|
|
|
|
|
|
|
|
private RenderingSurfaceInfo _surfaceInfo = null;
|
|
|
|
|
private Image<Argb32> _surface = null;
|
|
|
|
|
private byte[] _bufferData = null;
|
|
|
|
|
|
|
|
|
|
private Image _ryujinxLogo = null;
|
|
|
|
|
private Image _padAcceptIcon = null;
|
|
|
|
|
private Image _padCancelIcon = null;
|
|
|
|
|
private Image _keyModeIcon = null;
|
|
|
|
|
|
|
|
|
|
private float _textBoxOutlineWidth;
|
|
|
|
|
private float _padPressedPenWidth;
|
|
|
|
|
|
|
|
|
|
private Color _textNormalColor;
|
|
|
|
|
private Color _textSelectedColor;
|
|
|
|
|
private Color _textOverCursorColor;
|
|
|
|
|
|
|
|
|
|
private IBrush _panelBrush;
|
|
|
|
|
private IBrush _disabledBrush;
|
|
|
|
|
private IBrush _cursorBrush;
|
|
|
|
|
private IBrush _selectionBoxBrush;
|
|
|
|
|
|
|
|
|
|
private Pen _textBoxOutlinePen;
|
|
|
|
|
private Pen _cursorPen;
|
|
|
|
|
private Pen _selectionBoxPen;
|
|
|
|
|
private Pen _padPressedPen;
|
|
|
|
|
|
|
|
|
|
private int _inputTextFontSize;
|
|
|
|
|
private Font _messageFont;
|
|
|
|
|
private Font _inputTextFont;
|
|
|
|
|
private Font _labelsTextFont;
|
|
|
|
|
|
|
|
|
|
private RectangleF _panelRectangle;
|
|
|
|
|
private Point _logoPosition;
|
|
|
|
|
private float _messagePositionY;
|
|
|
|
|
|
|
|
|
|
public SoftwareKeyboardRendererBase(IHostUiTheme uiTheme)
|
|
|
|
|
{
|
2022-05-15 13:30:15 +02:00
|
|
|
|
int ryujinxLogoSize = 32;
|
2021-11-28 21:24:17 +01:00
|
|
|
|
|
2022-12-06 23:00:25 +01:00
|
|
|
|
string ryujinxIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Logo_Ryujinx.png";
|
|
|
|
|
_ryujinxLogo = LoadResource(Assembly.GetExecutingAssembly(), ryujinxIconPath, ryujinxLogoSize, ryujinxLogoSize);
|
2021-11-28 21:24:17 +01:00
|
|
|
|
|
|
|
|
|
string padAcceptIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnA.png";
|
|
|
|
|
string padCancelIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnB.png";
|
|
|
|
|
string keyModeIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_KeyF6.png";
|
|
|
|
|
|
|
|
|
|
_padAcceptIcon = LoadResource(Assembly.GetExecutingAssembly(), padAcceptIconPath , 0, 0);
|
|
|
|
|
_padCancelIcon = LoadResource(Assembly.GetExecutingAssembly(), padCancelIconPath , 0, 0);
|
|
|
|
|
_keyModeIcon = LoadResource(Assembly.GetExecutingAssembly(), keyModeIconPath , 0, 0);
|
|
|
|
|
|
|
|
|
|
Color panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255);
|
|
|
|
|
Color panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150);
|
|
|
|
|
Color borderColor = ToColor(uiTheme.DefaultBorderColor);
|
|
|
|
|
Color selectionBackgroundColor = ToColor(uiTheme.SelectionBackgroundColor);
|
|
|
|
|
|
|
|
|
|
_textNormalColor = ToColor(uiTheme.DefaultForegroundColor);
|
|
|
|
|
_textSelectedColor = ToColor(uiTheme.SelectionForegroundColor);
|
|
|
|
|
_textOverCursorColor = ToColor(uiTheme.DefaultForegroundColor, null, true);
|
|
|
|
|
|
|
|
|
|
float cursorWidth = 2;
|
|
|
|
|
|
|
|
|
|
_textBoxOutlineWidth = 2;
|
|
|
|
|
_padPressedPenWidth = 2;
|
|
|
|
|
|
|
|
|
|
_panelBrush = new SolidBrush(panelColor);
|
|
|
|
|
_disabledBrush = new SolidBrush(panelTransparentColor);
|
|
|
|
|
_cursorBrush = new SolidBrush(_textNormalColor);
|
|
|
|
|
_selectionBoxBrush = new SolidBrush(selectionBackgroundColor);
|
|
|
|
|
|
|
|
|
|
_textBoxOutlinePen = new Pen(borderColor, _textBoxOutlineWidth);
|
|
|
|
|
_cursorPen = new Pen(_textNormalColor, cursorWidth);
|
|
|
|
|
_selectionBoxPen = new Pen(selectionBackgroundColor, cursorWidth);
|
|
|
|
|
_padPressedPen = new Pen(borderColor, _padPressedPenWidth);
|
|
|
|
|
|
|
|
|
|
_inputTextFontSize = 20;
|
|
|
|
|
|
|
|
|
|
CreateFonts(uiTheme.FontFamily);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void CreateFonts(string uiThemeFontFamily)
|
|
|
|
|
{
|
|
|
|
|
// Try a list of fonts in case any of them is not available in the system.
|
|
|
|
|
|
|
|
|
|
string[] availableFonts = new string[]
|
|
|
|
|
{
|
|
|
|
|
uiThemeFontFamily,
|
|
|
|
|
"Liberation Sans",
|
|
|
|
|
"FreeSans",
|
2022-12-06 23:00:25 +01:00
|
|
|
|
"DejaVu Sans",
|
|
|
|
|
"Lucida Grande"
|
2021-11-28 21:24:17 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
foreach (string fontFamily in availableFonts)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_messageFont = SystemFonts.CreateFont(fontFamily, 26, FontStyle.Regular);
|
|
|
|
|
_inputTextFont = SystemFonts.CreateFont(fontFamily, _inputTextFontSize, FontStyle.Regular);
|
|
|
|
|
_labelsTextFont = SystemFonts.CreateFont(fontFamily, 24, FontStyle.Regular);
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw new Exception($"None of these fonts were found in the system: {String.Join(", ", availableFonts)}!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Color ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false)
|
|
|
|
|
{
|
|
|
|
|
var a = (byte)(color.A * 255);
|
|
|
|
|
var r = (byte)(color.R * 255);
|
|
|
|
|
var g = (byte)(color.G * 255);
|
|
|
|
|
var b = (byte)(color.B * 255);
|
|
|
|
|
|
|
|
|
|
if (flipRgb)
|
|
|
|
|
{
|
|
|
|
|
r = (byte)(255 - r);
|
|
|
|
|
g = (byte)(255 - g);
|
|
|
|
|
b = (byte)(255 - b);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Color.FromRgba(r, g, b, overrideAlpha.GetValueOrDefault(a));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Image LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight)
|
|
|
|
|
{
|
|
|
|
|
Stream resourceStream = assembly.GetManifestResourceStream(resourcePath);
|
|
|
|
|
|
2022-05-15 13:30:15 +02:00
|
|
|
|
return LoadResource(resourceStream, newWidth, newHeight);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Image LoadResource(Stream resourceStream, int newWidth, int newHeight)
|
|
|
|
|
{
|
2021-11-28 21:24:17 +01:00
|
|
|
|
Debug.Assert(resourceStream != null);
|
|
|
|
|
|
|
|
|
|
var image = Image.Load(resourceStream);
|
|
|
|
|
|
|
|
|
|
if (newHeight != 0 && newWidth != 0)
|
|
|
|
|
{
|
|
|
|
|
image.Mutate(x => x.Resize(newWidth, newHeight, KnownResamplers.Lanczos3));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return image;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void SetGraphicsOptions(IImageProcessingContext context)
|
|
|
|
|
{
|
|
|
|
|
context.GetGraphicsOptions().Antialias = true;
|
|
|
|
|
context.GetShapeGraphicsOptions().GraphicsOptions.Antialias = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DrawImmutableElements()
|
|
|
|
|
{
|
|
|
|
|
if (_surface == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_surface.Mutate(context =>
|
|
|
|
|
{
|
|
|
|
|
SetGraphicsOptions(context);
|
|
|
|
|
|
|
|
|
|
context.Clear(Color.Transparent);
|
|
|
|
|
context.Fill(_panelBrush, _panelRectangle);
|
|
|
|
|
context.DrawImage(_ryujinxLogo, _logoPosition, 1);
|
|
|
|
|
|
|
|
|
|
float halfWidth = _panelRectangle.Width / 2;
|
|
|
|
|
float buttonsY = _panelRectangle.Y + 185;
|
|
|
|
|
|
|
|
|
|
PointF disableButtonPosition = new PointF(halfWidth + 180, buttonsY);
|
|
|
|
|
|
|
|
|
|
DrawControllerToggle(context, disableButtonPosition);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void DrawMutableElements(SoftwareKeyboardUiState state)
|
|
|
|
|
{
|
|
|
|
|
if (_surface == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_surface.Mutate(context =>
|
|
|
|
|
{
|
|
|
|
|
var messageRectangle = MeasureString(MessageText, _messageFont);
|
|
|
|
|
float messagePositionX = (_panelRectangle.Width - messageRectangle.Width) / 2 - messageRectangle.X;
|
|
|
|
|
float messagePositionY = _messagePositionY - messageRectangle.Y;
|
|
|
|
|
var messagePosition = new PointF(messagePositionX, messagePositionY);
|
|
|
|
|
var messageBoundRectangle = new RectangleF(messagePositionX, messagePositionY, messageRectangle.Width, messageRectangle.Height);
|
|
|
|
|
|
|
|
|
|
SetGraphicsOptions(context);
|
|
|
|
|
|
|
|
|
|
context.Fill(_panelBrush, messageBoundRectangle);
|
|
|
|
|
|
|
|
|
|
context.DrawText(MessageText, _messageFont, _textNormalColor, messagePosition);
|
|
|
|
|
|
|
|
|
|
if (!state.TypingEnabled)
|
|
|
|
|
{
|
|
|
|
|
// Just draw a semi-transparent rectangle on top to fade the component with the background.
|
|
|
|
|
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
|
|
|
|
|
|
|
|
|
|
context.Fill(_disabledBrush, messageBoundRectangle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DrawTextBox(context, state);
|
|
|
|
|
|
|
|
|
|
float halfWidth = _panelRectangle.Width / 2;
|
|
|
|
|
float buttonsY = _panelRectangle.Y + 185;
|
|
|
|
|
|
|
|
|
|
PointF acceptButtonPosition = new PointF(halfWidth - 180, buttonsY);
|
|
|
|
|
PointF cancelButtonPosition = new PointF(halfWidth , buttonsY);
|
|
|
|
|
PointF disableButtonPosition = new PointF(halfWidth + 180, buttonsY);
|
|
|
|
|
|
|
|
|
|
DrawPadButton(context, acceptButtonPosition, _padAcceptIcon, AcceptText, state.AcceptPressed, state.ControllerEnabled);
|
|
|
|
|
DrawPadButton(context, cancelButtonPosition, _padCancelIcon, CancelText, state.CancelPressed, state.ControllerEnabled);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void CreateSurface(RenderingSurfaceInfo surfaceInfo)
|
|
|
|
|
{
|
|
|
|
|
if (_surfaceInfo != null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_surfaceInfo = surfaceInfo;
|
|
|
|
|
|
|
|
|
|
Debug.Assert(_surfaceInfo.ColorFormat == Services.SurfaceFlinger.ColorFormat.A8B8G8R8);
|
|
|
|
|
|
|
|
|
|
// Use the whole area of the image to draw, even the alignment, otherwise it may shear the final
|
|
|
|
|
// image if the pitch is different.
|
|
|
|
|
uint totalWidth = _surfaceInfo.Pitch / 4;
|
|
|
|
|
uint totalHeight = _surfaceInfo.Size / _surfaceInfo.Pitch;
|
|
|
|
|
|
|
|
|
|
Debug.Assert(_surfaceInfo.Width <= totalWidth);
|
|
|
|
|
Debug.Assert(_surfaceInfo.Height <= totalHeight);
|
|
|
|
|
Debug.Assert(_surfaceInfo.Pitch * _surfaceInfo.Height <= _surfaceInfo.Size);
|
|
|
|
|
|
|
|
|
|
_surface = new Image<Argb32>((int)totalWidth, (int)totalHeight);
|
|
|
|
|
|
|
|
|
|
ComputeConstants();
|
|
|
|
|
DrawImmutableElements();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ComputeConstants()
|
|
|
|
|
{
|
|
|
|
|
int totalWidth = (int)_surfaceInfo.Width;
|
|
|
|
|
int totalHeight = (int)_surfaceInfo.Height;
|
|
|
|
|
|
|
|
|
|
int panelHeight = 240;
|
|
|
|
|
int panelPositionY = totalHeight - panelHeight;
|
|
|
|
|
|
|
|
|
|
_panelRectangle = new RectangleF(0, panelPositionY, totalWidth, panelHeight);
|
|
|
|
|
|
|
|
|
|
_messagePositionY = panelPositionY + 60;
|
|
|
|
|
|
|
|
|
|
int logoPositionX = (totalWidth - _ryujinxLogo.Width) / 2;
|
|
|
|
|
int logoPositionY = panelPositionY + 18;
|
|
|
|
|
|
|
|
|
|
_logoPosition = new Point(logoPositionX, logoPositionY);
|
|
|
|
|
}
|
2023-01-18 23:25:16 +01:00
|
|
|
|
private static RectangleF MeasureString(string text, Font font)
|
2021-11-28 21:24:17 +01:00
|
|
|
|
{
|
|
|
|
|
RendererOptions options = new RendererOptions(font);
|
|
|
|
|
|
|
|
|
|
if (text == "")
|
|
|
|
|
{
|
2023-01-18 23:25:16 +01:00
|
|
|
|
FontRectangle emptyRectangle = TextMeasurer.Measure(" ", options);
|
|
|
|
|
|
|
|
|
|
return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height);
|
2021-11-28 21:24:17 +01:00
|
|
|
|
}
|
2023-01-18 23:25:16 +01:00
|
|
|
|
|
|
|
|
|
FontRectangle rectangle = TextMeasurer.Measure(text, options);
|
|
|
|
|
|
|
|
|
|
return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static RectangleF MeasureString(ReadOnlySpan<char> text, Font font)
|
|
|
|
|
{
|
|
|
|
|
RendererOptions options = new RendererOptions(font);
|
|
|
|
|
|
|
|
|
|
if (text == "")
|
2021-11-28 21:24:17 +01:00
|
|
|
|
{
|
2023-01-18 23:25:16 +01:00
|
|
|
|
FontRectangle emptyRectangle = TextMeasurer.Measure(" ", options);
|
|
|
|
|
return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height);
|
2021-11-28 21:24:17 +01:00
|
|
|
|
}
|
2023-01-18 23:25:16 +01:00
|
|
|
|
|
|
|
|
|
FontRectangle rectangle = TextMeasurer.Measure(text, options);
|
|
|
|
|
|
|
|
|
|
return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
|
2021-11-28 21:24:17 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DrawTextBox(IImageProcessingContext context, SoftwareKeyboardUiState state)
|
|
|
|
|
{
|
|
|
|
|
var inputTextRectangle = MeasureString(state.InputText, _inputTextFont);
|
|
|
|
|
|
|
|
|
|
float boxWidth = (int)(Math.Max(300, inputTextRectangle.Width + inputTextRectangle.X + 8));
|
|
|
|
|
float boxHeight = 32;
|
|
|
|
|
float boxY = _panelRectangle.Y + 110;
|
|
|
|
|
float boxX = (int)((_panelRectangle.Width - boxWidth) / 2);
|
|
|
|
|
|
|
|
|
|
RectangleF boxRectangle = new RectangleF(boxX, boxY, boxWidth, boxHeight);
|
|
|
|
|
|
|
|
|
|
RectangleF boundRectangle = new RectangleF(_panelRectangle.X, boxY - _textBoxOutlineWidth,
|
|
|
|
|
_panelRectangle.Width, boxHeight + 2 * _textBoxOutlineWidth);
|
|
|
|
|
|
|
|
|
|
context.Fill(_panelBrush, boundRectangle);
|
|
|
|
|
|
|
|
|
|
context.Draw(_textBoxOutlinePen, boxRectangle);
|
|
|
|
|
|
|
|
|
|
float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.X;
|
|
|
|
|
float inputTextY = boxY + 5;
|
|
|
|
|
|
|
|
|
|
var inputTextPosition = new PointF(inputTextX, inputTextY);
|
|
|
|
|
|
|
|
|
|
context.DrawText(state.InputText, _inputTextFont, _textNormalColor, inputTextPosition);
|
|
|
|
|
|
|
|
|
|
// Draw the cursor on top of the text and redraw the text with a different color if necessary.
|
|
|
|
|
|
|
|
|
|
Color cursorTextColor;
|
|
|
|
|
IBrush cursorBrush;
|
|
|
|
|
Pen cursorPen;
|
|
|
|
|
|
|
|
|
|
float cursorPositionYTop = inputTextY + 1;
|
|
|
|
|
float cursorPositionYBottom = cursorPositionYTop + _inputTextFontSize + 1;
|
|
|
|
|
float cursorPositionXLeft;
|
|
|
|
|
float cursorPositionXRight;
|
|
|
|
|
|
|
|
|
|
bool cursorVisible = false;
|
|
|
|
|
|
|
|
|
|
if (state.CursorBegin != state.CursorEnd)
|
|
|
|
|
{
|
|
|
|
|
Debug.Assert(state.InputText.Length > 0);
|
|
|
|
|
|
|
|
|
|
cursorTextColor = _textSelectedColor;
|
|
|
|
|
cursorBrush = _selectionBoxBrush;
|
|
|
|
|
cursorPen = _selectionBoxPen;
|
|
|
|
|
|
2023-01-18 23:25:16 +01:00
|
|
|
|
ReadOnlySpan<char> textUntilBegin = state.InputText.AsSpan(0, state.CursorBegin);
|
|
|
|
|
ReadOnlySpan<char> textUntilEnd = state.InputText.AsSpan(0, state.CursorEnd);
|
2021-11-28 21:24:17 +01:00
|
|
|
|
|
|
|
|
|
var selectionBeginRectangle = MeasureString(textUntilBegin, _inputTextFont);
|
|
|
|
|
var selectionEndRectangle = MeasureString(textUntilEnd , _inputTextFont);
|
|
|
|
|
|
|
|
|
|
cursorVisible = true;
|
|
|
|
|
cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.X;
|
|
|
|
|
cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.X;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
cursorTextColor = _textOverCursorColor;
|
|
|
|
|
cursorBrush = _cursorBrush;
|
|
|
|
|
cursorPen = _cursorPen;
|
|
|
|
|
|
|
|
|
|
if (state.TextBoxBlinkCounter < TextBoxBlinkThreshold)
|
|
|
|
|
{
|
|
|
|
|
// Show the blinking cursor.
|
|
|
|
|
|
2023-01-18 23:25:16 +01:00
|
|
|
|
int cursorBegin = Math.Min(state.InputText.Length, state.CursorBegin);
|
|
|
|
|
ReadOnlySpan<char> textUntilCursor = state.InputText.AsSpan(0, cursorBegin);
|
|
|
|
|
var cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont);
|
2021-11-28 21:24:17 +01:00
|
|
|
|
|
|
|
|
|
cursorVisible = true;
|
|
|
|
|
cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
|
|
|
|
|
|
|
|
|
|
if (state.OverwriteMode)
|
|
|
|
|
{
|
|
|
|
|
// The blinking cursor is in overwrite mode so it takes the size of a character.
|
|
|
|
|
|
|
|
|
|
if (state.CursorBegin < state.InputText.Length)
|
|
|
|
|
{
|
2023-01-18 23:25:16 +01:00
|
|
|
|
textUntilCursor = state.InputText.AsSpan(0, cursorBegin + 1);
|
2021-11-28 21:24:17 +01:00
|
|
|
|
cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont);
|
|
|
|
|
cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
cursorPositionXRight = cursorPositionXLeft + _inputTextFontSize / 2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// The blinking cursor is in insert mode so it is only a line.
|
|
|
|
|
cursorPositionXRight = cursorPositionXLeft;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
cursorPositionXLeft = inputTextX;
|
|
|
|
|
cursorPositionXRight = inputTextX;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (state.TypingEnabled && cursorVisible)
|
|
|
|
|
{
|
|
|
|
|
float cursorWidth = cursorPositionXRight - cursorPositionXLeft;
|
|
|
|
|
float cursorHeight = cursorPositionYBottom - cursorPositionYTop;
|
|
|
|
|
|
|
|
|
|
if (cursorWidth == 0)
|
|
|
|
|
{
|
|
|
|
|
PointF[] points = new PointF[]
|
|
|
|
|
{
|
|
|
|
|
new PointF(cursorPositionXLeft, cursorPositionYTop),
|
|
|
|
|
new PointF(cursorPositionXLeft, cursorPositionYBottom),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
context.DrawLines(cursorPen, points);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var cursorRectangle = new RectangleF(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
|
|
|
|
|
|
|
|
|
|
context.Draw(cursorPen , cursorRectangle);
|
|
|
|
|
context.Fill(cursorBrush, cursorRectangle);
|
|
|
|
|
|
|
|
|
|
Image<Argb32> textOverCursor = new Image<Argb32>((int)cursorRectangle.Width, (int)cursorRectangle.Height);
|
|
|
|
|
textOverCursor.Mutate(context =>
|
|
|
|
|
{
|
|
|
|
|
var textRelativePosition = new PointF(inputTextPosition.X - cursorRectangle.X, inputTextPosition.Y - cursorRectangle.Y);
|
|
|
|
|
context.DrawText(state.InputText, _inputTextFont, cursorTextColor, textRelativePosition);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var cursorPosition = new Point((int)cursorRectangle.X, (int)cursorRectangle.Y);
|
|
|
|
|
context.DrawImage(textOverCursor, cursorPosition, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (!state.TypingEnabled)
|
|
|
|
|
{
|
|
|
|
|
// Just draw a semi-transparent rectangle on top to fade the component with the background.
|
|
|
|
|
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
|
|
|
|
|
|
|
|
|
|
context.Fill(_disabledBrush, boundRectangle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DrawPadButton(IImageProcessingContext context, PointF point, Image icon, string label, bool pressed, bool enabled)
|
|
|
|
|
{
|
|
|
|
|
// Use relative positions so we can center the the entire drawing later.
|
|
|
|
|
|
|
|
|
|
float iconX = 0;
|
|
|
|
|
float iconY = 0;
|
|
|
|
|
float iconWidth = icon.Width;
|
|
|
|
|
float iconHeight = icon.Height;
|
|
|
|
|
|
|
|
|
|
var labelRectangle = MeasureString(label, _labelsTextFont);
|
|
|
|
|
|
|
|
|
|
float labelPositionX = iconWidth + 8 - labelRectangle.X;
|
|
|
|
|
float labelPositionY = 3;
|
|
|
|
|
|
|
|
|
|
float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.X;
|
|
|
|
|
float fullHeight = iconHeight;
|
|
|
|
|
|
|
|
|
|
// Convert all relative positions into absolute.
|
|
|
|
|
|
|
|
|
|
float originX = (int)(point.X - fullWidth / 2);
|
|
|
|
|
float originY = (int)(point.Y - fullHeight / 2);
|
|
|
|
|
|
|
|
|
|
iconX += originX;
|
|
|
|
|
iconY += originY;
|
|
|
|
|
|
|
|
|
|
var iconPosition = new Point((int)iconX, (int)iconY);
|
|
|
|
|
var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
|
|
|
|
|
|
|
|
|
|
var selectedRectangle = new RectangleF(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth,
|
|
|
|
|
fullWidth + 4 * _padPressedPenWidth, fullHeight + 4 * _padPressedPenWidth);
|
|
|
|
|
|
|
|
|
|
var boundRectangle = new RectangleF(originX, originY, fullWidth, fullHeight);
|
|
|
|
|
boundRectangle.Inflate(4 * _padPressedPenWidth, 4 * _padPressedPenWidth);
|
|
|
|
|
|
|
|
|
|
context.Fill(_panelBrush, boundRectangle);
|
|
|
|
|
context.DrawImage(icon, iconPosition, 1);
|
|
|
|
|
context.DrawText(label, _labelsTextFont, _textNormalColor, labelPosition);
|
|
|
|
|
|
|
|
|
|
if (enabled)
|
|
|
|
|
{
|
|
|
|
|
if (pressed)
|
|
|
|
|
{
|
|
|
|
|
context.Draw(_padPressedPen, selectedRectangle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Just draw a semi-transparent rectangle on top to fade the component with the background.
|
|
|
|
|
// TODO (caian): This will not work if one decides to add make background semi-transparent as well.
|
|
|
|
|
|
|
|
|
|
context.Fill(_disabledBrush, boundRectangle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DrawControllerToggle(IImageProcessingContext context, PointF point)
|
|
|
|
|
{
|
|
|
|
|
var labelRectangle = MeasureString(ControllerToggleText, _labelsTextFont);
|
|
|
|
|
|
|
|
|
|
// Use relative positions so we can center the the entire drawing later.
|
|
|
|
|
|
|
|
|
|
float keyWidth = _keyModeIcon.Width;
|
|
|
|
|
float keyHeight = _keyModeIcon.Height;
|
|
|
|
|
|
|
|
|
|
float labelPositionX = keyWidth + 8 - labelRectangle.X;
|
|
|
|
|
float labelPositionY = -labelRectangle.Y - 1;
|
|
|
|
|
|
|
|
|
|
float keyX = 0;
|
|
|
|
|
float keyY = (int)((labelPositionY + labelRectangle.Height - keyHeight) / 2);
|
|
|
|
|
|
|
|
|
|
float fullWidth = labelPositionX + labelRectangle.Width;
|
|
|
|
|
float fullHeight = Math.Max(labelPositionY + labelRectangle.Height, keyHeight);
|
|
|
|
|
|
|
|
|
|
// Convert all relative positions into absolute.
|
|
|
|
|
|
|
|
|
|
float originX = (int)(point.X - fullWidth / 2);
|
|
|
|
|
float originY = (int)(point.Y - fullHeight / 2);
|
|
|
|
|
|
|
|
|
|
keyX += originX;
|
|
|
|
|
keyY += originY;
|
|
|
|
|
|
|
|
|
|
var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
|
|
|
|
|
var overlayPosition = new Point((int)keyX, (int)keyY);
|
|
|
|
|
|
|
|
|
|
context.DrawImage(_keyModeIcon, overlayPosition, 1);
|
|
|
|
|
context.DrawText(ControllerToggleText, _labelsTextFont, _textNormalColor, labelPosition);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void CopyImageToBuffer()
|
|
|
|
|
{
|
|
|
|
|
lock (_bufferLock)
|
|
|
|
|
{
|
|
|
|
|
if (_surface == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert the pixel format used in the image to the one used in the Switch surface.
|
|
|
|
|
|
2022-12-01 23:06:55 +01:00
|
|
|
|
if (!_surface.TryGetSinglePixelSpan(out Span<Argb32> pixels))
|
2021-11-28 21:24:17 +01:00
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-01 23:06:55 +01:00
|
|
|
|
_bufferData = MemoryMarshal.AsBytes(pixels).ToArray();
|
2021-11-28 21:24:17 +01:00
|
|
|
|
Span<uint> dataConvert = MemoryMarshal.Cast<byte, uint>(_bufferData);
|
|
|
|
|
|
|
|
|
|
Debug.Assert(_bufferData.Length == _surfaceInfo.Size);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < dataConvert.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
dataConvert[i] = BitOperations.RotateRight(dataConvert[i], 8);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool WriteBufferToMemory(IVirtualMemoryManager destination, ulong position)
|
|
|
|
|
{
|
|
|
|
|
lock (_bufferLock)
|
|
|
|
|
{
|
|
|
|
|
if (_bufferData == null)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
destination.Write(position, _bufferData);
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|