SoftwareKeyboardRenderer.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. using Ryujinx.HLE.Ui;
  2. using Ryujinx.Memory;
  3. using System;
  4. using System.Buffers.Binary;
  5. using System.Diagnostics;
  6. using System.Drawing;
  7. using System.Drawing.Drawing2D;
  8. using System.Drawing.Imaging;
  9. using System.Drawing.Text;
  10. using System.IO;
  11. using System.Numerics;
  12. using System.Reflection;
  13. using System.Runtime.InteropServices;
  14. using System.Threading;
  15. namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
  16. {
  17. /// <summary>
  18. /// Class that generates the graphics for the software keyboard applet during inline mode.
  19. /// </summary>
  20. internal class SoftwareKeyboardRenderer : IDisposable
  21. {
  22. const int TextBoxBlinkThreshold = 8;
  23. const int TextBoxBlinkSleepMilliseconds = 100;
  24. const int TextBoxBlinkJoinWaitMilliseconds = 1000;
  25. const string MessageText = "Please use the keyboard to input text";
  26. const string AcceptText = "Accept";
  27. const string CancelText = "Cancel";
  28. const string ControllerToggleText = "Toggle input";
  29. private RenderingSurfaceInfo _surfaceInfo;
  30. private Bitmap _surface = null;
  31. private object _renderLock = new object();
  32. private string _inputText = "";
  33. private int _cursorStart = 0;
  34. private int _cursorEnd = 0;
  35. private bool _acceptPressed = false;
  36. private bool _cancelPressed = false;
  37. private bool _overwriteMode = false;
  38. private bool _typingEnabled = true;
  39. private bool _controllerEnabled = true;
  40. private Image _ryujinxLogo = null;
  41. private Image _padAcceptIcon = null;
  42. private Image _padCancelIcon = null;
  43. private Image _keyModeIcon = null;
  44. private float _textBoxOutlineWidth;
  45. private float _padPressedPenWidth;
  46. private Brush _panelBrush;
  47. private Brush _disabledBrush;
  48. private Brush _textNormalBrush;
  49. private Brush _textSelectedBrush;
  50. private Brush _textOverCursorBrush;
  51. private Brush _cursorBrush;
  52. private Brush _selectionBoxBrush;
  53. private Brush _keyCapBrush;
  54. private Brush _keyProgressBrush;
  55. private Pen _gridSeparatorPen;
  56. private Pen _textBoxOutlinePen;
  57. private Pen _cursorPen;
  58. private Pen _selectionBoxPen;
  59. private Pen _padPressedPen;
  60. private int _inputTextFontSize;
  61. private int _padButtonFontSize;
  62. private Font _messageFont;
  63. private Font _inputTextFont;
  64. private Font _labelsTextFont;
  65. private Font _padSymbolFont;
  66. private Font _keyCapFont;
  67. private float _inputTextCalibrationHeight;
  68. private float _panelPositionY;
  69. private RectangleF _panelRectangle;
  70. private PointF _logoPosition;
  71. private float _messagePositionY;
  72. private TRef<int> _textBoxBlinkCounter = new TRef<int>(0);
  73. private TimedAction _textBoxBlinkTimedAction = new TimedAction();
  74. public SoftwareKeyboardRenderer(IHostUiTheme uiTheme)
  75. {
  76. _surfaceInfo = new RenderingSurfaceInfo(0, 0, 0, 0, 0);
  77. string ryujinxLogoPath = "Ryujinx.Ui.Resources.Logo_Ryujinx.png";
  78. int ryujinxLogoSize = 32;
  79. _ryujinxLogo = LoadResource(Assembly.GetEntryAssembly(), ryujinxLogoPath, ryujinxLogoSize, ryujinxLogoSize);
  80. string padAcceptIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnA.png";
  81. string padCancelIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnB.png";
  82. string keyModeIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_KeyF6.png";
  83. _padAcceptIcon = LoadResource(Assembly.GetExecutingAssembly(), padAcceptIconPath , 0, 0);
  84. _padCancelIcon = LoadResource(Assembly.GetExecutingAssembly(), padCancelIconPath , 0, 0);
  85. _keyModeIcon = LoadResource(Assembly.GetExecutingAssembly(), keyModeIconPath , 0, 0);
  86. Color panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255);
  87. Color panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150);
  88. Color normalTextColor = ToColor(uiTheme.DefaultForegroundColor);
  89. Color invertedTextColor = ToColor(uiTheme.DefaultForegroundColor, null, true);
  90. Color selectedTextColor = ToColor(uiTheme.SelectionForegroundColor);
  91. Color borderColor = ToColor(uiTheme.DefaultBorderColor);
  92. Color selectionBackgroundColor = ToColor(uiTheme.SelectionBackgroundColor);
  93. Color gridSeparatorColor = Color.FromArgb(180, 255, 255, 255);
  94. float cursorWidth = 2;
  95. _textBoxOutlineWidth = 2;
  96. _padPressedPenWidth = 2;
  97. _panelBrush = new SolidBrush(panelColor);
  98. _disabledBrush = new SolidBrush(panelTransparentColor);
  99. _textNormalBrush = new SolidBrush(normalTextColor);
  100. _textSelectedBrush = new SolidBrush(selectedTextColor);
  101. _textOverCursorBrush = new SolidBrush(invertedTextColor);
  102. _cursorBrush = new SolidBrush(normalTextColor);
  103. _selectionBoxBrush = new SolidBrush(selectionBackgroundColor);
  104. _keyCapBrush = Brushes.White;
  105. _keyProgressBrush = new SolidBrush(borderColor);
  106. _gridSeparatorPen = new Pen(gridSeparatorColor, 2);
  107. _textBoxOutlinePen = new Pen(borderColor, _textBoxOutlineWidth);
  108. _cursorPen = new Pen(normalTextColor, cursorWidth);
  109. _selectionBoxPen = new Pen(selectionBackgroundColor, cursorWidth);
  110. _padPressedPen = new Pen(borderColor, _padPressedPenWidth);
  111. _inputTextFontSize = 20;
  112. _padButtonFontSize = 24;
  113. string font = uiTheme.FontFamily;
  114. _messageFont = new Font(font, 26, FontStyle.Regular, GraphicsUnit.Pixel);
  115. _inputTextFont = new Font(font, _inputTextFontSize, FontStyle.Regular, GraphicsUnit.Pixel);
  116. _labelsTextFont = new Font(font, 24, FontStyle.Regular, GraphicsUnit.Pixel);
  117. _padSymbolFont = new Font(font, _padButtonFontSize, FontStyle.Regular, GraphicsUnit.Pixel);
  118. _keyCapFont = new Font(font, 15, FontStyle.Regular, GraphicsUnit.Pixel);
  119. // System.Drawing has serious problems measuring strings, so it requires a per-pixel calibration
  120. // to ensure we are rendering text inside the proper region
  121. _inputTextCalibrationHeight = CalibrateTextHeight(_inputTextFont);
  122. StartTextBoxBlinker(_textBoxBlinkTimedAction, _textBoxBlinkCounter);
  123. }
  124. private static void StartTextBoxBlinker(TimedAction timedAction, TRef<int> blinkerCounter)
  125. {
  126. timedAction.Reset(() =>
  127. {
  128. // The blinker is on falf of the time and events such as input
  129. // changes can reset the blinker.
  130. var value = Volatile.Read(ref blinkerCounter.Value);
  131. value = (value + 1) % (2 * TextBoxBlinkThreshold);
  132. Volatile.Write(ref blinkerCounter.Value, value);
  133. }, TextBoxBlinkSleepMilliseconds);
  134. }
  135. private Color ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false)
  136. {
  137. var a = (byte)(color.A * 255);
  138. var r = (byte)(color.R * 255);
  139. var g = (byte)(color.G * 255);
  140. var b = (byte)(color.B * 255);
  141. if (flipRgb)
  142. {
  143. r = (byte)(255 - r);
  144. g = (byte)(255 - g);
  145. b = (byte)(255 - b);
  146. }
  147. return Color.FromArgb(overrideAlpha.GetValueOrDefault(a), r, g, b);
  148. }
  149. private Image LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight)
  150. {
  151. Stream resourceStream = assembly.GetManifestResourceStream(resourcePath);
  152. Debug.Assert(resourceStream != null);
  153. var originalImage = Image.FromStream(resourceStream);
  154. if (newHeight == 0 || newWidth == 0)
  155. {
  156. return originalImage;
  157. }
  158. var newSize = new Rectangle(0, 0, newWidth, newHeight);
  159. var newImage = new Bitmap(newWidth, newHeight);
  160. using (var graphics = System.Drawing.Graphics.FromImage(newImage))
  161. using (var wrapMode = new ImageAttributes())
  162. {
  163. graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
  164. graphics.CompositingQuality = CompositingQuality.HighQuality;
  165. graphics.CompositingMode = CompositingMode.SourceCopy;
  166. graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
  167. graphics.SmoothingMode = SmoothingMode.HighQuality;
  168. wrapMode.SetWrapMode(WrapMode.TileFlipXY);
  169. graphics.DrawImage(originalImage, newSize, 0, 0, originalImage.Width, originalImage.Height, GraphicsUnit.Pixel, wrapMode);
  170. }
  171. return newImage;
  172. }
  173. #pragma warning disable CS8632
  174. public void UpdateTextState(string? inputText, int? cursorStart, int? cursorEnd, bool? overwriteMode, bool? typingEnabled)
  175. #pragma warning restore CS8632
  176. {
  177. lock (_renderLock)
  178. {
  179. // Update the parameters that were provided.
  180. _inputText = inputText != null ? inputText : _inputText;
  181. _cursorStart = cursorStart.GetValueOrDefault(_cursorStart);
  182. _cursorEnd = cursorEnd.GetValueOrDefault(_cursorEnd);
  183. _overwriteMode = overwriteMode.GetValueOrDefault(_overwriteMode);
  184. _typingEnabled = typingEnabled.GetValueOrDefault(_typingEnabled);
  185. // Reset the cursor blink.
  186. Volatile.Write(ref _textBoxBlinkCounter.Value, 0);
  187. }
  188. }
  189. public void UpdateCommandState(bool? acceptPressed, bool? cancelPressed, bool? controllerEnabled)
  190. {
  191. lock (_renderLock)
  192. {
  193. // Update the parameters that were provided.
  194. _acceptPressed = acceptPressed.GetValueOrDefault(_acceptPressed);
  195. _cancelPressed = cancelPressed.GetValueOrDefault(_cancelPressed);
  196. _controllerEnabled = controllerEnabled.GetValueOrDefault(_controllerEnabled);
  197. }
  198. }
  199. private void Redraw()
  200. {
  201. if (_surface == null)
  202. {
  203. return;
  204. }
  205. using (var graphics = CreateGraphics())
  206. {
  207. var messageRectangle = MeasureString(graphics, MessageText, _messageFont);
  208. float messagePositionX = (_panelRectangle.Width - messageRectangle.Width) / 2 - messageRectangle.X;
  209. float messagePositionY = _messagePositionY - messageRectangle.Y;
  210. PointF messagePosition = new PointF(messagePositionX, messagePositionY);
  211. graphics.Clear(Color.Transparent);
  212. graphics.TranslateTransform(0, _panelPositionY);
  213. graphics.FillRectangle(_panelBrush, _panelRectangle);
  214. graphics.DrawImage(_ryujinxLogo, _logoPosition);
  215. DrawString(graphics, MessageText, _messageFont, _textNormalBrush, messagePosition);
  216. if (!_typingEnabled)
  217. {
  218. // Just draw a semi-transparent rectangle on top to fade the component with the background.
  219. // TODO (caian): This will not work if one decides to add make background semi-transparent as well.
  220. graphics.FillRectangle(_disabledBrush, messagePositionX, messagePositionY, messageRectangle.Width, messageRectangle.Height);
  221. }
  222. DrawTextBox(graphics);
  223. float halfWidth = _panelRectangle.Width / 2;
  224. PointF acceptButtonPosition = new PointF(halfWidth - 180, 185);
  225. PointF cancelButtonPosition = new PointF(halfWidth , 185);
  226. PointF disableButtonPosition = new PointF(halfWidth + 180, 185);
  227. DrawPadButton (graphics, acceptButtonPosition , _padAcceptIcon, AcceptText, _acceptPressed, _controllerEnabled);
  228. DrawPadButton (graphics, cancelButtonPosition , _padCancelIcon, CancelText, _cancelPressed, _controllerEnabled);
  229. DrawControllerToggle(graphics, disableButtonPosition, _controllerEnabled);
  230. }
  231. }
  232. private void RecreateSurface()
  233. {
  234. Debug.Assert(_surfaceInfo.ColorFormat == Services.SurfaceFlinger.ColorFormat.A8B8G8R8);
  235. // Use the whole area of the image to draw, even the alignment, otherwise it may shear the final
  236. // image if the pitch is different.
  237. uint totalWidth = _surfaceInfo.Pitch / 4;
  238. uint totalHeight = _surfaceInfo.Size / _surfaceInfo.Pitch;
  239. Debug.Assert(_surfaceInfo.Width <= totalWidth);
  240. Debug.Assert(_surfaceInfo.Height <= totalHeight);
  241. Debug.Assert(_surfaceInfo.Pitch * _surfaceInfo.Height <= _surfaceInfo.Size);
  242. _surface = new Bitmap((int)totalWidth, (int)totalHeight, PixelFormat.Format32bppArgb);
  243. }
  244. private void RecomputeConstants()
  245. {
  246. float totalWidth = _surfaceInfo.Width;
  247. float totalHeight = _surfaceInfo.Height;
  248. float panelHeight = 240;
  249. _panelPositionY = totalHeight - panelHeight;
  250. _panelRectangle = new RectangleF(0, 0, totalWidth, panelHeight);
  251. _messagePositionY = 60;
  252. float logoPositionX = (totalWidth - _ryujinxLogo.Width) / 2;
  253. float logoPositionY = 18;
  254. _logoPosition = new PointF(logoPositionX, logoPositionY);
  255. }
  256. private StringFormat CreateStringFormat(string text)
  257. {
  258. StringFormat format = new StringFormat(StringFormat.GenericTypographic);
  259. format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces;
  260. format.SetMeasurableCharacterRanges(new CharacterRange[] { new CharacterRange(0, text.Length) });
  261. return format;
  262. }
  263. private RectangleF MeasureString(System.Drawing.Graphics graphics, string text, System.Drawing.Font font)
  264. {
  265. bool isEmpty = false;
  266. if (string.IsNullOrEmpty(text))
  267. {
  268. isEmpty = true;
  269. text = " ";
  270. }
  271. var format = CreateStringFormat(text);
  272. var rectangle = new RectangleF(0, 0, float.PositiveInfinity, float.PositiveInfinity);
  273. var regions = graphics.MeasureCharacterRanges(text, font, rectangle, format);
  274. Debug.Assert(regions.Length == 1);
  275. rectangle = regions[0].GetBounds(graphics);
  276. if (isEmpty)
  277. {
  278. rectangle.Width = 0;
  279. }
  280. else
  281. {
  282. rectangle.Width += 1.0f;
  283. }
  284. return rectangle;
  285. }
  286. private float CalibrateTextHeight(Font font)
  287. {
  288. // This is a pixel-wise calibration that tests the offset of a reference character because Windows text measurement
  289. // is horrible when compared to other frameworks like Cairo and diverge across systems and fonts.
  290. Debug.Assert(font.Unit == GraphicsUnit.Pixel);
  291. var surfaceSize = (int)Math.Ceiling(2 * font.Size);
  292. string calibrationText = "|";
  293. using (var surface = new Bitmap(surfaceSize, surfaceSize, PixelFormat.Format32bppArgb))
  294. using (var graphics = CreateGraphics(surface))
  295. {
  296. var measuredRectangle = MeasureString(graphics, calibrationText, font);
  297. Debug.Assert(measuredRectangle.Right <= surfaceSize);
  298. Debug.Assert(measuredRectangle.Bottom <= surfaceSize);
  299. var textPosition = new PointF(0, 0);
  300. graphics.Clear(Color.Transparent);
  301. DrawString(graphics, calibrationText, font, Brushes.White, textPosition);
  302. var lockRectangle = new Rectangle(0, 0, surface.Width, surface.Height);
  303. var surfaceData = surface.LockBits(lockRectangle, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
  304. var surfaceBytes = new byte[surfaceData.Stride * surfaceData.Height];
  305. Marshal.Copy(surfaceData.Scan0, surfaceBytes, 0, surfaceBytes.Length);
  306. Point topLeft = new Point();
  307. Point bottomLeft = new Point();
  308. bool foundTopLeft = false;
  309. for (int y = 0; y < surfaceData.Height; y++)
  310. {
  311. for (int x = 0; x < surfaceData.Stride; x += 4)
  312. {
  313. int position = y * surfaceData.Stride + x;
  314. if (surfaceBytes[position] != 0)
  315. {
  316. if (!foundTopLeft)
  317. {
  318. topLeft.X = x;
  319. topLeft.Y = y;
  320. foundTopLeft = true;
  321. break;
  322. }
  323. else
  324. {
  325. bottomLeft.X = x;
  326. bottomLeft.Y = y;
  327. break;
  328. }
  329. }
  330. }
  331. }
  332. return bottomLeft.Y - topLeft.Y;
  333. }
  334. }
  335. private void DrawString(System.Drawing.Graphics graphics, string text, Font font, Brush brush, PointF point)
  336. {
  337. var format = CreateStringFormat(text);
  338. graphics.DrawString(text, font, brush, point, format);
  339. }
  340. private System.Drawing.Graphics CreateGraphics()
  341. {
  342. return CreateGraphics(_surface);
  343. }
  344. private System.Drawing.Graphics CreateGraphics(Image surface)
  345. {
  346. var graphics = System.Drawing.Graphics.FromImage(surface);
  347. graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
  348. graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
  349. graphics.CompositingQuality = CompositingQuality.HighSpeed;
  350. graphics.CompositingMode = CompositingMode.SourceOver;
  351. graphics.PixelOffsetMode = PixelOffsetMode.HighSpeed;
  352. graphics.SmoothingMode = SmoothingMode.HighSpeed;
  353. return graphics;
  354. }
  355. private void DrawTextBox(System.Drawing.Graphics graphics)
  356. {
  357. var inputTextRectangle = MeasureString(graphics, _inputText, _inputTextFont);
  358. float boxWidth = (int)(Math.Max(300, inputTextRectangle.Width + inputTextRectangle.X + 8));
  359. float boxHeight = 32;
  360. float boxY = 110;
  361. float boxX = (int)((_panelRectangle.Width - boxWidth) / 2);
  362. graphics.DrawRectangle(_textBoxOutlinePen, boxX, boxY, boxWidth, boxHeight);
  363. float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.X;
  364. float inputTextY = boxY + boxHeight - inputTextRectangle.Bottom - 5;
  365. var inputTextPosition = new PointF(inputTextX, inputTextY);
  366. DrawString(graphics, _inputText, _inputTextFont, _textNormalBrush, inputTextPosition);
  367. // Draw the cursor on top of the text and redraw the text with a different color if necessary.
  368. Brush cursorTextBrush;
  369. Brush cursorBrush;
  370. Pen cursorPen;
  371. float cursorPositionYBottom = inputTextY + inputTextRectangle.Bottom;
  372. float cursorPositionYTop = cursorPositionYBottom - _inputTextCalibrationHeight - 2;
  373. float cursorPositionXLeft;
  374. float cursorPositionXRight;
  375. bool cursorVisible = false;
  376. if (_cursorStart != _cursorEnd)
  377. {
  378. cursorTextBrush = _textSelectedBrush;
  379. cursorBrush = _selectionBoxBrush;
  380. cursorPen = _selectionBoxPen;
  381. string textUntilBegin = _inputText.Substring(0, _cursorStart);
  382. string textUntilEnd = _inputText.Substring(0, _cursorEnd);
  383. RectangleF selectionBeginRectangle = MeasureString(graphics, textUntilBegin, _inputTextFont);
  384. RectangleF selectionEndRectangle = MeasureString(graphics, textUntilEnd , _inputTextFont);
  385. cursorVisible = true;
  386. cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.X;
  387. cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.X;
  388. }
  389. else
  390. {
  391. cursorTextBrush = _textOverCursorBrush;
  392. cursorBrush = _cursorBrush;
  393. cursorPen = _cursorPen;
  394. if (Volatile.Read(ref _textBoxBlinkCounter.Value) < TextBoxBlinkThreshold)
  395. {
  396. // Show the blinking cursor.
  397. int cursorStart = Math.Min(_inputText.Length, _cursorStart);
  398. string textUntilCursor = _inputText.Substring(0, cursorStart);
  399. RectangleF cursorTextRectangle = MeasureString(graphics, textUntilCursor, _inputTextFont);
  400. cursorVisible = true;
  401. cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
  402. if (_overwriteMode)
  403. {
  404. // The blinking cursor is in overwrite mode so it takes the size of a character.
  405. if (_cursorStart < _inputText.Length)
  406. {
  407. textUntilCursor = _inputText.Substring(0, cursorStart + 1);
  408. cursorTextRectangle = MeasureString(graphics, textUntilCursor, _inputTextFont);
  409. cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
  410. }
  411. else
  412. {
  413. cursorPositionXRight = cursorPositionXLeft + _inputTextFontSize / 2;
  414. }
  415. }
  416. else
  417. {
  418. // The blinking cursor is in insert mode so it is only a line.
  419. cursorPositionXRight = cursorPositionXLeft;
  420. }
  421. }
  422. else
  423. {
  424. cursorPositionXLeft = inputTextX;
  425. cursorPositionXRight = inputTextX;
  426. }
  427. }
  428. if (_typingEnabled && cursorVisible)
  429. {
  430. float cursorWidth = cursorPositionXRight - cursorPositionXLeft;
  431. float cursorHeight = cursorPositionYBottom - cursorPositionYTop;
  432. if (cursorWidth == 0)
  433. {
  434. graphics.DrawLine(cursorPen, cursorPositionXLeft, cursorPositionYTop, cursorPositionXLeft, cursorPositionYBottom);
  435. }
  436. else
  437. {
  438. graphics.DrawRectangle(cursorPen, cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
  439. graphics.FillRectangle(cursorBrush, cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
  440. var cursorRectangle = new RectangleF(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
  441. var oldClip = graphics.Clip;
  442. graphics.Clip = new Region(cursorRectangle);
  443. DrawString(graphics, _inputText, _inputTextFont, cursorTextBrush, inputTextPosition);
  444. graphics.Clip = oldClip;
  445. }
  446. }
  447. else if (!_typingEnabled)
  448. {
  449. // Just draw a semi-transparent rectangle on top to fade the component with the background.
  450. // TODO (caian): This will not work if one decides to add make background semi-transparent as well.
  451. graphics.FillRectangle(_disabledBrush, boxX - _textBoxOutlineWidth, boxY - _textBoxOutlineWidth,
  452. boxWidth + 2* _textBoxOutlineWidth, boxHeight + 2* _textBoxOutlineWidth);
  453. }
  454. }
  455. private void DrawPadButton(System.Drawing.Graphics graphics, PointF point, Image icon, string label, bool pressed, bool enabled)
  456. {
  457. // Use relative positions so we can center the the entire drawing later.
  458. float iconX = 0;
  459. float iconY = 0;
  460. float iconWidth = icon.Width;
  461. float iconHeight = icon.Height;
  462. var labelRectangle = MeasureString(graphics, label, _labelsTextFont);
  463. float labelPositionX = iconWidth + 8 - labelRectangle.X;
  464. float labelPositionY = (iconHeight - labelRectangle.Height) / 2 - labelRectangle.Y - 1;
  465. float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.X;
  466. float fullHeight = iconHeight;
  467. // Convert all relative positions into absolute.
  468. float originX = (int)(point.X - fullWidth / 2);
  469. float originY = (int)(point.Y - fullHeight / 2);
  470. iconX += originX;
  471. iconY += originY;
  472. var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
  473. graphics.DrawImageUnscaled(icon, (int)iconX, (int)iconY);
  474. DrawString(graphics, label, _labelsTextFont, _textNormalBrush, labelPosition);
  475. GraphicsPath frame = new GraphicsPath();
  476. frame.AddRectangle(new RectangleF(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth,
  477. fullWidth + 4 * _padPressedPenWidth, fullHeight + 4 * _padPressedPenWidth));
  478. if (enabled)
  479. {
  480. if (pressed)
  481. {
  482. graphics.DrawPath(_padPressedPen, frame);
  483. }
  484. }
  485. else
  486. {
  487. // Just draw a semi-transparent rectangle on top to fade the component with the background.
  488. // TODO (caian): This will not work if one decides to add make background semi-transparent as well.
  489. graphics.FillPath(_disabledBrush, frame);
  490. }
  491. }
  492. private void DrawControllerToggle(System.Drawing.Graphics graphics, PointF point, bool enabled)
  493. {
  494. var labelRectangle = MeasureString(graphics, ControllerToggleText, _labelsTextFont);
  495. // Use relative positions so we can center the the entire drawing later.
  496. float keyWidth = _keyModeIcon.Width;
  497. float keyHeight = _keyModeIcon.Height;
  498. float labelPositionX = keyWidth + 8 - labelRectangle.X;
  499. float labelPositionY = -labelRectangle.Y - 1;
  500. float keyX = 0;
  501. float keyY = (int)((labelPositionY + labelRectangle.Height - keyHeight) / 2);
  502. float fullWidth = labelPositionX + labelRectangle.Width;
  503. float fullHeight = Math.Max(labelPositionY + labelRectangle.Height, keyHeight);
  504. // Convert all relative positions into absolute.
  505. float originX = (int)(point.X - fullWidth / 2);
  506. float originY = (int)(point.Y - fullHeight / 2);
  507. keyX += originX;
  508. keyY += originY;
  509. var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
  510. var overlayPosition = new Point((int)keyX, (int)keyY);
  511. graphics.DrawImageUnscaled(_keyModeIcon, overlayPosition);
  512. DrawString(graphics, ControllerToggleText, _labelsTextFont, _textNormalBrush, labelPosition);
  513. }
  514. private bool TryCopyTo(IVirtualMemoryManager destination, ulong position)
  515. {
  516. if (_surface == null)
  517. {
  518. return false;
  519. }
  520. Rectangle lockRectangle = new Rectangle(0, 0, _surface.Width, _surface.Height);
  521. BitmapData surfaceData = _surface.LockBits(lockRectangle, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
  522. Debug.Assert(surfaceData.Stride == _surfaceInfo.Pitch);
  523. Debug.Assert(surfaceData.Stride * surfaceData.Height == _surfaceInfo.Size);
  524. // Convert the pixel format used in System.Drawing to the one required by a Switch Surface.
  525. int dataLength = surfaceData.Stride * surfaceData.Height;
  526. byte[] data = new byte[dataLength];
  527. Span<uint> dataConvert = MemoryMarshal.Cast<byte, uint>(data);
  528. Marshal.Copy(surfaceData.Scan0, data, 0, dataLength);
  529. for (int i = 0; i < dataConvert.Length; i++)
  530. {
  531. dataConvert[i] = BitOperations.RotateRight(BinaryPrimitives.ReverseEndianness(dataConvert[i]), 8);
  532. }
  533. try
  534. {
  535. destination.Write(position, data);
  536. }
  537. finally
  538. {
  539. _surface.UnlockBits(surfaceData);
  540. }
  541. return true;
  542. }
  543. internal bool DrawTo(RenderingSurfaceInfo surfaceInfo, IVirtualMemoryManager destination, ulong position)
  544. {
  545. lock (_renderLock)
  546. {
  547. if (!_surfaceInfo.Equals(surfaceInfo))
  548. {
  549. _surfaceInfo = surfaceInfo;
  550. RecreateSurface();
  551. RecomputeConstants();
  552. }
  553. Redraw();
  554. return TryCopyTo(destination, position);
  555. }
  556. }
  557. public void Dispose()
  558. {
  559. _textBoxBlinkTimedAction.RequestCancel();
  560. }
  561. }
  562. }