SoftwareKeyboardRenderer.cs 30 KB

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