diff options
author | Mary <me@thog.eu> | 2021-11-28 21:24:17 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-28 21:24:17 +0100 |
commit | 57d3296ba4e5c1fc7ca30376c7ca8eb3041ae2f6 (patch) | |
tree | 02e17606a847ff11f68bc7bf123e882f87055b52 /Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs | |
parent | 7b040e51b078a16e979ad962ba1265f3be4bcb1d (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.cs | 729 |
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(); } } } |