aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs
diff options
context:
space:
mode:
authorMary <me@thog.eu>2021-11-28 21:24:17 +0100
committerGitHub <noreply@github.com>2021-11-28 21:24:17 +0100
commit57d3296ba4e5c1fc7ca30376c7ca8eb3041ae2f6 (patch)
tree02e17606a847ff11f68bc7bf123e882f87055b52 /Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs
parent7b040e51b078a16e979ad962ba1265f3be4bcb1d (diff)
infra: Migrate to .NET 6 (#2829)
* infra: Migrate to .NET 6 * Rollback version naming change * Workaround .NET 6 ZipArchive API issues * ci: Switch to VS 2022 for AppVeyor CI is now ready for .NET 6 * Suppress WebClient warning in DoUpdateWithMultipleThreads * Attempt to workaround System.Drawing.Common changes on 6.0.0 * Change keyboard rendering from System.Drawing to ImageSharp * Make the software keyboard renderer multithreaded * Bump ImageSharp version to 1.0.4 to fix a bug in Image.Load * Add fallback fonts to the keyboard renderer * Fix warnings * Address caian's comment * Clean up linux workaround as it's uneeded now * Update readme Co-authored-by: Caian Benedicto <caianbene@gmail.com>
Diffstat (limited to 'Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs')
-rw-r--r--Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs729
1 files changed, 88 insertions, 641 deletions
diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs
index dfd10925..c30ad11b 100644
--- a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs
+++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs
@@ -1,717 +1,164 @@
using Ryujinx.HLE.Ui;
using Ryujinx.Memory;
using System;
-using System.Buffers.Binary;
-using System.Diagnostics;
-using System.Drawing;
-using System.Drawing.Drawing2D;
-using System.Drawing.Imaging;
-using System.Drawing.Text;
-using System.IO;
-using System.Numerics;
-using System.Reflection;
-using System.Runtime.InteropServices;
using System.Threading;
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
/// <summary>
- /// Class that generates the graphics for the software keyboard applet during inline mode.
+ /// Class that manages the renderer base class and its state in a multithreaded context.
/// </summary>
internal class SoftwareKeyboardRenderer : IDisposable
{
- const int TextBoxBlinkThreshold = 8;
- const int TextBoxBlinkSleepMilliseconds = 100;
- const int TextBoxBlinkJoinWaitMilliseconds = 1000;
+ private const int TextBoxBlinkSleepMilliseconds = 100;
+ private const int RendererWaitTimeoutMilliseconds = 100;
- 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 _stateLock = new object();
- private RenderingSurfaceInfo _surfaceInfo;
- private Bitmap _surface = null;
- private object _renderLock = new object();
+ private SoftwareKeyboardUiState _state = new SoftwareKeyboardUiState();
+ private SoftwareKeyboardRendererBase _renderer;
- private string _inputText = "";
- private int _cursorStart = 0;
- private int _cursorEnd = 0;
- private bool _acceptPressed = false;
- private bool _cancelPressed = false;
- private bool _overwriteMode = false;
- private bool _typingEnabled = true;
- private bool _controllerEnabled = true;
-
- private Image _ryujinxLogo = null;
- private Image _padAcceptIcon = null;
- private Image _padCancelIcon = null;
- private Image _keyModeIcon = null;
-
- private float _textBoxOutlineWidth;
- private float _padPressedPenWidth;
-
- private Brush _panelBrush;
- private Brush _disabledBrush;
- private Brush _textNormalBrush;
- private Brush _textSelectedBrush;
- private Brush _textOverCursorBrush;
- private Brush _cursorBrush;
- private Brush _selectionBoxBrush;
- private Brush _keyCapBrush;
- private Brush _keyProgressBrush;
-
- private Pen _gridSeparatorPen;
- private Pen _textBoxOutlinePen;
- private Pen _cursorPen;
- private Pen _selectionBoxPen;
- private Pen _padPressedPen;
-
- private int _inputTextFontSize;
- private int _padButtonFontSize;
- private Font _messageFont;
- private Font _inputTextFont;
- private Font _labelsTextFont;
- private Font _padSymbolFont;
- private Font _keyCapFont;
-
- private float _inputTextCalibrationHeight;
- private float _panelPositionY;
- private RectangleF _panelRectangle;
- private PointF _logoPosition;
- private float _messagePositionY;
-
- private TRef<int> _textBoxBlinkCounter = new TRef<int>(0);
private TimedAction _textBoxBlinkTimedAction = new TimedAction();
+ private TimedAction _renderAction = new TimedAction();
public SoftwareKeyboardRenderer(IHostUiTheme uiTheme)
{
- _surfaceInfo = new RenderingSurfaceInfo(0, 0, 0, 0, 0);
-
- string ryujinxLogoPath = "Ryujinx.Ui.Resources.Logo_Ryujinx.png";
- int ryujinxLogoSize = 32;
-
- _ryujinxLogo = LoadResource(Assembly.GetEntryAssembly(), ryujinxLogoPath, ryujinxLogoSize, ryujinxLogoSize);
-
- string padAcceptIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnA.png";
- string padCancelIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnB.png";
- string keyModeIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_KeyF6.png";
-
- _padAcceptIcon = LoadResource(Assembly.GetExecutingAssembly(), padAcceptIconPath , 0, 0);
- _padCancelIcon = LoadResource(Assembly.GetExecutingAssembly(), padCancelIconPath , 0, 0);
- _keyModeIcon = LoadResource(Assembly.GetExecutingAssembly(), keyModeIconPath , 0, 0);
-
- Color panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255);
- Color panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150);
- Color normalTextColor = ToColor(uiTheme.DefaultForegroundColor);
- Color invertedTextColor = ToColor(uiTheme.DefaultForegroundColor, null, true);
- Color selectedTextColor = ToColor(uiTheme.SelectionForegroundColor);
- Color borderColor = ToColor(uiTheme.DefaultBorderColor);
- Color selectionBackgroundColor = ToColor(uiTheme.SelectionBackgroundColor);
- Color gridSeparatorColor = Color.FromArgb(180, 255, 255, 255);
-
- float cursorWidth = 2;
-
- _textBoxOutlineWidth = 2;
- _padPressedPenWidth = 2;
-
- _panelBrush = new SolidBrush(panelColor);
- _disabledBrush = new SolidBrush(panelTransparentColor);
- _textNormalBrush = new SolidBrush(normalTextColor);
- _textSelectedBrush = new SolidBrush(selectedTextColor);
- _textOverCursorBrush = new SolidBrush(invertedTextColor);
- _cursorBrush = new SolidBrush(normalTextColor);
- _selectionBoxBrush = new SolidBrush(selectionBackgroundColor);
- _keyCapBrush = Brushes.White;
- _keyProgressBrush = new SolidBrush(borderColor);
+ _renderer = new SoftwareKeyboardRendererBase(uiTheme);
- _gridSeparatorPen = new Pen(gridSeparatorColor, 2);
- _textBoxOutlinePen = new Pen(borderColor, _textBoxOutlineWidth);
- _cursorPen = new Pen(normalTextColor, cursorWidth);
- _selectionBoxPen = new Pen(selectionBackgroundColor, cursorWidth);
- _padPressedPen = new Pen(borderColor, _padPressedPenWidth);
-
- _inputTextFontSize = 20;
- _padButtonFontSize = 24;
-
- string font = uiTheme.FontFamily;
-
- _messageFont = new Font(font, 26, FontStyle.Regular, GraphicsUnit.Pixel);
- _inputTextFont = new Font(font, _inputTextFontSize, FontStyle.Regular, GraphicsUnit.Pixel);
- _labelsTextFont = new Font(font, 24, FontStyle.Regular, GraphicsUnit.Pixel);
- _padSymbolFont = new Font(font, _padButtonFontSize, FontStyle.Regular, GraphicsUnit.Pixel);
- _keyCapFont = new Font(font, 15, FontStyle.Regular, GraphicsUnit.Pixel);
-
- // System.Drawing has serious problems measuring strings, so it requires a per-pixel calibration
- // to ensure we are rendering text inside the proper region
- _inputTextCalibrationHeight = CalibrateTextHeight(_inputTextFont);
-
- StartTextBoxBlinker(_textBoxBlinkTimedAction, _textBoxBlinkCounter);
+ StartTextBoxBlinker(_textBoxBlinkTimedAction, _state, _stateLock);
+ StartRenderer(_renderAction, _renderer, _state, _stateLock);
}
- private static void StartTextBoxBlinker(TimedAction timedAction, TRef<int> blinkerCounter)
+ private static void StartTextBoxBlinker(TimedAction timedAction, SoftwareKeyboardUiState state, object stateLock)
{
timedAction.Reset(() =>
{
- // The blinker is on falf of the time and events such as input
- // changes can reset the blinker.
- var value = Volatile.Read(ref blinkerCounter.Value);
- value = (value + 1) % (2 * TextBoxBlinkThreshold);
- Volatile.Write(ref blinkerCounter.Value, value);
-
- }, TextBoxBlinkSleepMilliseconds);
- }
-
- 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.FromArgb(overrideAlpha.GetValueOrDefault(a), r, g, b);
- }
-
- private Image LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight)
- {
- Stream resourceStream = assembly.GetManifestResourceStream(resourcePath);
-
- Debug.Assert(resourceStream != null);
-
- var originalImage = Image.FromStream(resourceStream);
-
- if (newHeight == 0 || newWidth == 0)
- {
- return originalImage;
- }
-
- var newSize = new Rectangle(0, 0, newWidth, newHeight);
- var newImage = new Bitmap(newWidth, newHeight);
-
- using (var graphics = System.Drawing.Graphics.FromImage(newImage))
- using (var wrapMode = new ImageAttributes())
- {
- graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
- graphics.CompositingQuality = CompositingQuality.HighQuality;
- graphics.CompositingMode = CompositingMode.SourceCopy;
- graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
- graphics.SmoothingMode = SmoothingMode.HighQuality;
-
- wrapMode.SetWrapMode(WrapMode.TileFlipXY);
- graphics.DrawImage(originalImage, newSize, 0, 0, originalImage.Width, originalImage.Height, GraphicsUnit.Pixel, wrapMode);
- }
-
- return newImage;
- }
-
-#pragma warning disable CS8632
- public void UpdateTextState(string? inputText, int? cursorStart, int? cursorEnd, bool? overwriteMode, bool? typingEnabled)
-#pragma warning restore CS8632
- {
- lock (_renderLock)
- {
- // Update the parameters that were provided.
- _inputText = inputText != null ? inputText : _inputText;
- _cursorStart = cursorStart.GetValueOrDefault(_cursorStart);
- _cursorEnd = cursorEnd.GetValueOrDefault(_cursorEnd);
- _overwriteMode = overwriteMode.GetValueOrDefault(_overwriteMode);
- _typingEnabled = typingEnabled.GetValueOrDefault(_typingEnabled);
-
- // Reset the cursor blink.
- Volatile.Write(ref _textBoxBlinkCounter.Value, 0);
- }
- }
-
- public void UpdateCommandState(bool? acceptPressed, bool? cancelPressed, bool? controllerEnabled)
- {
- lock (_renderLock)
- {
- // Update the parameters that were provided.
- _acceptPressed = acceptPressed.GetValueOrDefault(_acceptPressed);
- _cancelPressed = cancelPressed.GetValueOrDefault(_cancelPressed);
- _controllerEnabled = controllerEnabled.GetValueOrDefault(_controllerEnabled);
- }
- }
-
- private void Redraw()
- {
- if (_surface == null)
- {
- return;
- }
-
- using (var graphics = CreateGraphics())
- {
- var messageRectangle = MeasureString(graphics, MessageText, _messageFont);
- float messagePositionX = (_panelRectangle.Width - messageRectangle.Width) / 2 - messageRectangle.X;
- float messagePositionY = _messagePositionY - messageRectangle.Y;
- PointF messagePosition = new PointF(messagePositionX, messagePositionY);
-
- graphics.Clear(Color.Transparent);
- graphics.TranslateTransform(0, _panelPositionY);
- graphics.FillRectangle(_panelBrush, _panelRectangle);
- graphics.DrawImage(_ryujinxLogo, _logoPosition);
-
- DrawString(graphics, MessageText, _messageFont, _textNormalBrush, messagePosition);
-
- if (!_typingEnabled)
+ lock (stateLock)
{
- // Just draw a semi-transparent rectangle on top to fade the component with the background.
- // TODO (caian): This will not work if one decides to add make background semi-transparent as well.
- graphics.FillRectangle(_disabledBrush, messagePositionX, messagePositionY, messageRectangle.Width, messageRectangle.Height);
- }
-
- DrawTextBox(graphics);
-
- float halfWidth = _panelRectangle.Width / 2;
-
- PointF acceptButtonPosition = new PointF(halfWidth - 180, 185);
- PointF cancelButtonPosition = new PointF(halfWidth , 185);
- PointF disableButtonPosition = new PointF(halfWidth + 180, 185);
-
- DrawPadButton (graphics, acceptButtonPosition , _padAcceptIcon, AcceptText, _acceptPressed, _controllerEnabled);
- DrawPadButton (graphics, cancelButtonPosition , _padCancelIcon, CancelText, _cancelPressed, _controllerEnabled);
- DrawControllerToggle(graphics, disableButtonPosition, _controllerEnabled);
- }
- }
-
- private void RecreateSurface()
- {
- 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 Bitmap((int)totalWidth, (int)totalHeight, PixelFormat.Format32bppArgb);
- }
-
- private void RecomputeConstants()
- {
- float totalWidth = _surfaceInfo.Width;
- float totalHeight = _surfaceInfo.Height;
-
- float panelHeight = 240;
+ // The blinker is on half of the time and events such as input
+ // changes can reset the blinker.
+ state.TextBoxBlinkCounter = (state.TextBoxBlinkCounter + 1) % (2 * SoftwareKeyboardRendererBase.TextBoxBlinkThreshold);
- _panelPositionY = totalHeight - panelHeight;
- _panelRectangle = new RectangleF(0, 0, totalWidth, panelHeight);
-
- _messagePositionY = 60;
-
- float logoPositionX = (totalWidth - _ryujinxLogo.Width) / 2;
- float logoPositionY = 18;
-
- _logoPosition = new PointF(logoPositionX, logoPositionY);
- }
-
- private StringFormat CreateStringFormat(string text)
- {
- StringFormat format = new StringFormat(StringFormat.GenericTypographic);
- format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces;
- format.SetMeasurableCharacterRanges(new CharacterRange[] { new CharacterRange(0, text.Length) });
-
- return format;
- }
-
- private RectangleF MeasureString(System.Drawing.Graphics graphics, string text, System.Drawing.Font font)
- {
- bool isEmpty = false;
-
- if (string.IsNullOrEmpty(text))
- {
- isEmpty = true;
- text = " ";
- }
-
- var format = CreateStringFormat(text);
- var rectangle = new RectangleF(0, 0, float.PositiveInfinity, float.PositiveInfinity);
- var regions = graphics.MeasureCharacterRanges(text, font, rectangle, format);
-
- Debug.Assert(regions.Length == 1);
-
- rectangle = regions[0].GetBounds(graphics);
-
- if (isEmpty)
- {
- rectangle.Width = 0;
- }
- else
- {
- rectangle.Width += 1.0f;
- }
-
- return rectangle;
+ // Tell the render thread there is something new to render.
+ Monitor.PulseAll(stateLock);
+ }
+ }, TextBoxBlinkSleepMilliseconds);
}
- private float CalibrateTextHeight(Font font)
+ private static void StartRenderer(TimedAction timedAction, SoftwareKeyboardRendererBase renderer, SoftwareKeyboardUiState state, object stateLock)
{
- // This is a pixel-wise calibration that tests the offset of a reference character because Windows text measurement
- // is horrible when compared to other frameworks like Cairo and diverge across systems and fonts.
-
- Debug.Assert(font.Unit == GraphicsUnit.Pixel);
+ SoftwareKeyboardUiState internalState = new SoftwareKeyboardUiState();
- var surfaceSize = (int)Math.Ceiling(2 * font.Size);
+ bool canCreateSurface = false;
+ bool needsUpdate = true;
- string calibrationText = "|";
-
- using (var surface = new Bitmap(surfaceSize, surfaceSize, PixelFormat.Format32bppArgb))
- using (var graphics = CreateGraphics(surface))
+ timedAction.Reset(() =>
{
- var measuredRectangle = MeasureString(graphics, calibrationText, font);
-
- Debug.Assert(measuredRectangle.Right <= surfaceSize);
- Debug.Assert(measuredRectangle.Bottom <= surfaceSize);
-
- var textPosition = new PointF(0, 0);
-
- graphics.Clear(Color.Transparent);
- DrawString(graphics, calibrationText, font, Brushes.White, textPosition);
-
- var lockRectangle = new Rectangle(0, 0, surface.Width, surface.Height);
- var surfaceData = surface.LockBits(lockRectangle, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
- var surfaceBytes = new byte[surfaceData.Stride * surfaceData.Height];
-
- Marshal.Copy(surfaceData.Scan0, surfaceBytes, 0, surfaceBytes.Length);
-
- Point topLeft = new Point();
- Point bottomLeft = new Point();
-
- bool foundTopLeft = false;
-
- for (int y = 0; y < surfaceData.Height; y++)
+ lock (stateLock)
{
- for (int x = 0; x < surfaceData.Stride; x += 4)
+ if (!Monitor.Wait(stateLock, RendererWaitTimeoutMilliseconds))
{
- int position = y * surfaceData.Stride + x;
-
- if (surfaceBytes[position] != 0)
- {
- if (!foundTopLeft)
- {
- topLeft.X = x;
- topLeft.Y = y;
- foundTopLeft = true;
-
- break;
- }
- else
- {
- bottomLeft.X = x;
- bottomLeft.Y = y;
-
- break;
- }
- }
+ return;
}
- }
-
- return bottomLeft.Y - topLeft.Y;
- }
- }
-
- private void DrawString(System.Drawing.Graphics graphics, string text, Font font, Brush brush, PointF point)
- {
- var format = CreateStringFormat(text);
- graphics.DrawString(text, font, brush, point, format);
- }
-
- private System.Drawing.Graphics CreateGraphics()
- {
- return CreateGraphics(_surface);
- }
-
- private System.Drawing.Graphics CreateGraphics(Image surface)
- {
- var graphics = System.Drawing.Graphics.FromImage(surface);
-
- graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
- graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
- graphics.CompositingQuality = CompositingQuality.HighSpeed;
- graphics.CompositingMode = CompositingMode.SourceOver;
- graphics.PixelOffsetMode = PixelOffsetMode.HighSpeed;
- graphics.SmoothingMode = SmoothingMode.HighSpeed;
-
- return graphics;
- }
-
- private void DrawTextBox(System.Drawing.Graphics graphics)
- {
- var inputTextRectangle = MeasureString(graphics, _inputText, _inputTextFont);
-
- float boxWidth = (int)(Math.Max(300, inputTextRectangle.Width + inputTextRectangle.X + 8));
- float boxHeight = 32;
- float boxY = 110;
- float boxX = (int)((_panelRectangle.Width - boxWidth) / 2);
-
- graphics.DrawRectangle(_textBoxOutlinePen, boxX, boxY, boxWidth, boxHeight);
- float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.X;
- float inputTextY = boxY + boxHeight - inputTextRectangle.Bottom - 5;
+ needsUpdate = UpdateStateField(ref state.InputText, ref internalState.InputText);
+ needsUpdate |= UpdateStateField(ref state.CursorBegin, ref internalState.CursorBegin);
+ needsUpdate |= UpdateStateField(ref state.CursorEnd, ref internalState.CursorEnd);
+ needsUpdate |= UpdateStateField(ref state.AcceptPressed, ref internalState.AcceptPressed);
+ needsUpdate |= UpdateStateField(ref state.CancelPressed, ref internalState.CancelPressed);
+ needsUpdate |= UpdateStateField(ref state.OverwriteMode, ref internalState.OverwriteMode);
+ needsUpdate |= UpdateStateField(ref state.TypingEnabled, ref internalState.TypingEnabled);
+ needsUpdate |= UpdateStateField(ref state.ControllerEnabled, ref internalState.ControllerEnabled);
+ needsUpdate |= UpdateStateField(ref state.TextBoxBlinkCounter, ref internalState.TextBoxBlinkCounter);
- var inputTextPosition = new PointF(inputTextX, inputTextY);
+ canCreateSurface = state.SurfaceInfo != null && internalState.SurfaceInfo == null;
- DrawString(graphics, _inputText, _inputTextFont, _textNormalBrush, inputTextPosition);
-
- // Draw the cursor on top of the text and redraw the text with a different color if necessary.
-
- Brush cursorTextBrush;
- Brush cursorBrush;
- Pen cursorPen;
-
- float cursorPositionYBottom = inputTextY + inputTextRectangle.Bottom;
- float cursorPositionYTop = cursorPositionYBottom - _inputTextCalibrationHeight - 2;
- float cursorPositionXLeft;
- float cursorPositionXRight;
-
- bool cursorVisible = false;
-
- if (_cursorStart != _cursorEnd)
- {
- cursorTextBrush = _textSelectedBrush;
- cursorBrush = _selectionBoxBrush;
- cursorPen = _selectionBoxPen;
-
- string textUntilBegin = _inputText.Substring(0, _cursorStart);
- string textUntilEnd = _inputText.Substring(0, _cursorEnd);
-
- RectangleF selectionBeginRectangle = MeasureString(graphics, textUntilBegin, _inputTextFont);
- RectangleF selectionEndRectangle = MeasureString(graphics, textUntilEnd , _inputTextFont);
-
- cursorVisible = true;
- cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.X;
- cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.X;
- }
- else
- {
- cursorTextBrush = _textOverCursorBrush;
- cursorBrush = _cursorBrush;
- cursorPen = _cursorPen;
-
- if (Volatile.Read(ref _textBoxBlinkCounter.Value) < TextBoxBlinkThreshold)
- {
- // Show the blinking cursor.
-
- int cursorStart = Math.Min(_inputText.Length, _cursorStart);
- string textUntilCursor = _inputText.Substring(0, cursorStart);
- RectangleF cursorTextRectangle = MeasureString(graphics, textUntilCursor, _inputTextFont);
-
- cursorVisible = true;
- cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
-
- if (_overwriteMode)
+ if (canCreateSurface)
{
- // The blinking cursor is in overwrite mode so it takes the size of a character.
-
- if (_cursorStart < _inputText.Length)
- {
- textUntilCursor = _inputText.Substring(0, cursorStart + 1);
- cursorTextRectangle = MeasureString(graphics, textUntilCursor, _inputTextFont);
- cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
- }
- else
- {
- cursorPositionXRight = cursorPositionXLeft + _inputTextFontSize / 2;
- }
+ internalState.SurfaceInfo = state.SurfaceInfo;
}
- else
- {
- // The blinking cursor is in insert mode so it is only a line.
- cursorPositionXRight = cursorPositionXLeft;
- }
- }
- else
- {
- cursorPositionXLeft = inputTextX;
- cursorPositionXRight = inputTextX;
}
- }
-
- if (_typingEnabled && cursorVisible)
- {
- float cursorWidth = cursorPositionXRight - cursorPositionXLeft;
- float cursorHeight = cursorPositionYBottom - cursorPositionYTop;
- if (cursorWidth == 0)
+ if (canCreateSurface)
{
- graphics.DrawLine(cursorPen, cursorPositionXLeft, cursorPositionYTop, cursorPositionXLeft, cursorPositionYBottom);
+ renderer.CreateSurface(internalState.SurfaceInfo);
}
- else
- {
- graphics.DrawRectangle(cursorPen, cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
- graphics.FillRectangle(cursorBrush, cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
-
- var cursorRectangle = new RectangleF(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
-
- var oldClip = graphics.Clip;
- graphics.Clip = new Region(cursorRectangle);
-
- DrawString(graphics, _inputText, _inputTextFont, cursorTextBrush, inputTextPosition);
- graphics.Clip = oldClip;
+ if (needsUpdate)
+ {
+ renderer.DrawMutableElements(internalState);
+ renderer.CopyImageToBuffer();
+ needsUpdate = false;
}
- }
- else if (!_typingEnabled)
- {
- // Just draw a semi-transparent rectangle on top to fade the component with the background.
- // TODO (caian): This will not work if one decides to add make background semi-transparent as well.
- graphics.FillRectangle(_disabledBrush, boxX - _textBoxOutlineWidth, boxY - _textBoxOutlineWidth,
- boxWidth + 2* _textBoxOutlineWidth, boxHeight + 2* _textBoxOutlineWidth);
- }
+ });
}
- private void DrawPadButton(System.Drawing.Graphics graphics, PointF point, Image icon, string label, bool pressed, bool enabled)
+ private static bool UpdateStateField<T>(ref T source, ref T destination) where T : IEquatable<T>
{
- // Use relative positions so we can center the the entire drawing later.
-
- float iconX = 0;
- float iconY = 0;
- float iconWidth = icon.Width;
- float iconHeight = icon.Height;
-
- var labelRectangle = MeasureString(graphics, label, _labelsTextFont);
-
- float labelPositionX = iconWidth + 8 - labelRectangle.X;
- float labelPositionY = (iconHeight - labelRectangle.Height) / 2 - labelRectangle.Y - 1;
-
- float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.X;
- float fullHeight = iconHeight;
-
- // Convert all relative positions into absolute.
-
- float originX = (int)(point.X - fullWidth / 2);
- float originY = (int)(point.Y - fullHeight / 2);
-
- iconX += originX;
- iconY += originY;
-
- var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
-
- graphics.DrawImageUnscaled(icon, (int)iconX, (int)iconY);
-
- DrawString(graphics, label, _labelsTextFont, _textNormalBrush, labelPosition);
-
- GraphicsPath frame = new GraphicsPath();
- frame.AddRectangle(new RectangleF(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth,
- fullWidth + 4 * _padPressedPenWidth, fullHeight + 4 * _padPressedPenWidth));
-
- if (enabled)
+ if (!source.Equals(destination))
{
- if (pressed)
- {
- graphics.DrawPath(_padPressedPen, frame);
- }
+ destination = source;
+ return true;
}
- else
- {
- // Just draw a semi-transparent rectangle on top to fade the component with the background.
- // TODO (caian): This will not work if one decides to add make background semi-transparent as well.
- graphics.FillPath(_disabledBrush, frame);
- }
- }
-
- private void DrawControllerToggle(System.Drawing.Graphics graphics, PointF point, bool enabled)
- {
- var labelRectangle = MeasureString(graphics, ControllerToggleText, _labelsTextFont);
-
- // Use relative positions so we can center the the entire drawing later.
-
- float keyWidth = _keyModeIcon.Width;
- float keyHeight = _keyModeIcon.Height;
-
- float labelPositionX = keyWidth + 8 - labelRectangle.X;
- float labelPositionY = -labelRectangle.Y - 1;
-
- float keyX = 0;
- float keyY = (int)((labelPositionY + labelRectangle.Height - keyHeight) / 2);
-
- float fullWidth = labelPositionX + labelRectangle.Width;
- float fullHeight = Math.Max(labelPositionY + labelRectangle.Height, keyHeight);
-
- // Convert all relative positions into absolute.
- float originX = (int)(point.X - fullWidth / 2);
- float originY = (int)(point.Y - fullHeight / 2);
-
- keyX += originX;
- keyY += originY;
-
- var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
- var overlayPosition = new Point((int)keyX, (int)keyY);
-
- graphics.DrawImageUnscaled(_keyModeIcon, overlayPosition);
-
- DrawString(graphics, ControllerToggleText, _labelsTextFont, _textNormalBrush, labelPosition);
+ return false;
}
- private bool TryCopyTo(IVirtualMemoryManager destination, ulong position)
+#pragma warning disable CS8632
+ public void UpdateTextState(string? inputText, int? cursorBegin, int? cursorEnd, bool? overwriteMode, bool? typingEnabled)
+#pragma warning restore CS8632
{
- if (_surface == null)
+ lock (_stateLock)
{
- return false;
- }
-
- Rectangle lockRectangle = new Rectangle(0, 0, _surface.Width, _surface.Height);
- BitmapData surfaceData = _surface.LockBits(lockRectangle, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
-
- Debug.Assert(surfaceData.Stride == _surfaceInfo.Pitch);
- Debug.Assert(surfaceData.Stride * surfaceData.Height == _surfaceInfo.Size);
-
- // Convert the pixel format used in System.Drawing to the one required by a Switch Surface.
- int dataLength = surfaceData.Stride * surfaceData.Height;
-
- byte[] data = new byte[dataLength];
- Span<uint> dataConvert = MemoryMarshal.Cast<byte, uint>(data);
+ // Update the parameters that were provided.
+ _state.InputText = inputText != null ? inputText : _state.InputText;
+ _state.CursorBegin = cursorBegin.GetValueOrDefault(_state.CursorBegin);
+ _state.CursorEnd = cursorEnd.GetValueOrDefault(_state.CursorEnd);
+ _state.OverwriteMode = overwriteMode.GetValueOrDefault(_state.OverwriteMode);
+ _state.TypingEnabled = typingEnabled.GetValueOrDefault(_state.TypingEnabled);
- Marshal.Copy(surfaceData.Scan0, data, 0, dataLength);
+ // Reset the cursor blink.
+ _state.TextBoxBlinkCounter = 0;
- for (int i = 0; i < dataConvert.Length; i++)
- {
- dataConvert[i] = BitOperations.RotateRight(BinaryPrimitives.ReverseEndianness(dataConvert[i]), 8);
+ // Tell the render thread there is something new to render.
+ Monitor.PulseAll(_stateLock);
}
+ }
- try
- {
- destination.Write(position, data);
- }
- finally
+ public void UpdateCommandState(bool? acceptPressed, bool? cancelPressed, bool? controllerEnabled)
+ {
+ lock (_stateLock)
{
- _surface.UnlockBits(surfaceData);
- }
+ // Update the parameters that were provided.
+ _state.AcceptPressed = acceptPressed.GetValueOrDefault(_state.AcceptPressed);
+ _state.CancelPressed = cancelPressed.GetValueOrDefault(_state.CancelPressed);
+ _state.ControllerEnabled = controllerEnabled.GetValueOrDefault(_state.ControllerEnabled);
- return true;
+ // Tell the render thread there is something new to render.
+ Monitor.PulseAll(_stateLock);
+ }
}
- internal bool DrawTo(RenderingSurfaceInfo surfaceInfo, IVirtualMemoryManager destination, ulong position)
+ public void SetSurfaceInfo(RenderingSurfaceInfo surfaceInfo)
{
- lock (_renderLock)
+ lock (_stateLock)
{
- if (!_surfaceInfo.Equals(surfaceInfo))
- {
- _surfaceInfo = surfaceInfo;
- RecreateSurface();
- RecomputeConstants();
- }
-
- Redraw();
+ _state.SurfaceInfo = surfaceInfo;
- return TryCopyTo(destination, position);
+ // Tell the render thread there is something new to render.
+ Monitor.PulseAll(_stateLock);
}
}
+ internal bool DrawTo(IVirtualMemoryManager destination, ulong position)
+ {
+ return _renderer.WriteBufferToMemory(destination, position);
+ }
+
public void Dispose()
{
_textBoxBlinkTimedAction.RequestCancel();
+ _renderAction.RequestCancel();
}
}
}