/ src / Ryujinx.HLE / HOS / Applets / SoftwareKeyboard / SoftwareKeyboardRenderer.cs
SoftwareKeyboardRenderer.cs
  1  using Ryujinx.HLE.UI;
  2  using Ryujinx.Memory;
  3  using System;
  4  using System.Threading;
  5  
  6  namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
  7  {
  8      /// <summary>
  9      /// Class that manages the renderer base class and its state in a multithreaded context.
 10      /// </summary>
 11      internal class SoftwareKeyboardRenderer : IDisposable
 12      {
 13          private const int TextBoxBlinkSleepMilliseconds = 100;
 14          private const int RendererWaitTimeoutMilliseconds = 100;
 15  
 16          private readonly object _stateLock = new();
 17  
 18          private readonly SoftwareKeyboardUIState _state = new();
 19          private readonly SoftwareKeyboardRendererBase _renderer;
 20  
 21          private readonly TimedAction _textBoxBlinkTimedAction = new();
 22          private readonly TimedAction _renderAction = new();
 23  
 24          public SoftwareKeyboardRenderer(IHostUITheme uiTheme)
 25          {
 26              _renderer = new SoftwareKeyboardRendererBase(uiTheme);
 27  
 28              StartTextBoxBlinker(_textBoxBlinkTimedAction, _state, _stateLock);
 29              StartRenderer(_renderAction, _renderer, _state, _stateLock);
 30          }
 31  
 32          private static void StartTextBoxBlinker(TimedAction timedAction, SoftwareKeyboardUIState state, object stateLock)
 33          {
 34              timedAction.Reset(() =>
 35              {
 36                  lock (stateLock)
 37                  {
 38                      // The blinker is on half of the time and events such as input
 39                      // changes can reset the blinker.
 40                      state.TextBoxBlinkCounter = (state.TextBoxBlinkCounter + 1) % (2 * SoftwareKeyboardRendererBase.TextBoxBlinkThreshold);
 41  
 42                      // Tell the render thread there is something new to render.
 43                      Monitor.PulseAll(stateLock);
 44                  }
 45              }, TextBoxBlinkSleepMilliseconds);
 46          }
 47  
 48          private static void StartRenderer(TimedAction timedAction, SoftwareKeyboardRendererBase renderer, SoftwareKeyboardUIState state, object stateLock)
 49          {
 50              SoftwareKeyboardUIState internalState = new();
 51  
 52              bool canCreateSurface = false;
 53              bool needsUpdate = true;
 54  
 55              timedAction.Reset(() =>
 56              {
 57                  lock (stateLock)
 58                  {
 59                      if (!Monitor.Wait(stateLock, RendererWaitTimeoutMilliseconds))
 60                      {
 61                          return;
 62                      }
 63  
 64  #pragma warning disable IDE0055 // Disable formatting
 65                      needsUpdate  = UpdateStateField(ref state.InputText,           ref internalState.InputText);
 66                      needsUpdate |= UpdateStateField(ref state.CursorBegin,         ref internalState.CursorBegin);
 67                      needsUpdate |= UpdateStateField(ref state.CursorEnd,           ref internalState.CursorEnd);
 68                      needsUpdate |= UpdateStateField(ref state.AcceptPressed,       ref internalState.AcceptPressed);
 69                      needsUpdate |= UpdateStateField(ref state.CancelPressed,       ref internalState.CancelPressed);
 70                      needsUpdate |= UpdateStateField(ref state.OverwriteMode,       ref internalState.OverwriteMode);
 71                      needsUpdate |= UpdateStateField(ref state.TypingEnabled,       ref internalState.TypingEnabled);
 72                      needsUpdate |= UpdateStateField(ref state.ControllerEnabled,   ref internalState.ControllerEnabled);
 73                      needsUpdate |= UpdateStateField(ref state.TextBoxBlinkCounter, ref internalState.TextBoxBlinkCounter);
 74  #pragma warning restore IDE0055
 75  
 76                      canCreateSurface = state.SurfaceInfo != null && internalState.SurfaceInfo == null;
 77  
 78                      if (canCreateSurface)
 79                      {
 80                          internalState.SurfaceInfo = state.SurfaceInfo;
 81                      }
 82                  }
 83  
 84                  if (canCreateSurface)
 85                  {
 86                      renderer.CreateSurface(internalState.SurfaceInfo);
 87                  }
 88  
 89                  if (needsUpdate)
 90                  {
 91                      renderer.DrawMutableElements(internalState);
 92                      renderer.CopyImageToBuffer();
 93                      needsUpdate = false;
 94                  }
 95              });
 96          }
 97  
 98          private static bool UpdateStateField<T>(ref T source, ref T destination) where T : IEquatable<T>
 99          {
100              if (!source.Equals(destination))
101              {
102                  destination = source;
103                  return true;
104              }
105  
106              return false;
107          }
108  
109          public void UpdateTextState(string inputText, int? cursorBegin, int? cursorEnd, bool? overwriteMode, bool? typingEnabled)
110          {
111              lock (_stateLock)
112              {
113                  // Update the parameters that were provided.
114                  _state.InputText = inputText ?? _state.InputText;
115                  _state.CursorBegin = Math.Max(0, cursorBegin.GetValueOrDefault(_state.CursorBegin));
116                  _state.CursorEnd = Math.Min(cursorEnd.GetValueOrDefault(_state.CursorEnd), _state.InputText.Length);
117                  _state.OverwriteMode = overwriteMode.GetValueOrDefault(_state.OverwriteMode);
118                  _state.TypingEnabled = typingEnabled.GetValueOrDefault(_state.TypingEnabled);
119  
120                  var begin = _state.CursorBegin;
121                  var end = _state.CursorEnd;
122                  _state.CursorBegin = Math.Min(begin, end);
123                  _state.CursorEnd = Math.Max(begin, end);
124  
125                  // Reset the cursor blink.
126                  _state.TextBoxBlinkCounter = 0;
127  
128                  // Tell the render thread there is something new to render.
129                  Monitor.PulseAll(_stateLock);
130              }
131          }
132  
133          public void UpdateCommandState(bool? acceptPressed, bool? cancelPressed, bool? controllerEnabled)
134          {
135              lock (_stateLock)
136              {
137                  // Update the parameters that were provided.
138                  _state.AcceptPressed = acceptPressed.GetValueOrDefault(_state.AcceptPressed);
139                  _state.CancelPressed = cancelPressed.GetValueOrDefault(_state.CancelPressed);
140                  _state.ControllerEnabled = controllerEnabled.GetValueOrDefault(_state.ControllerEnabled);
141  
142                  // Tell the render thread there is something new to render.
143                  Monitor.PulseAll(_stateLock);
144              }
145          }
146  
147          public void SetSurfaceInfo(RenderingSurfaceInfo surfaceInfo)
148          {
149              lock (_stateLock)
150              {
151                  _state.SurfaceInfo = surfaceInfo;
152  
153                  // Tell the render thread there is something new to render.
154                  Monitor.PulseAll(_stateLock);
155              }
156          }
157  
158          internal bool DrawTo(IVirtualMemoryManager destination, ulong position)
159          {
160              return _renderer.WriteBufferToMemory(destination, position);
161          }
162  
163          public void Dispose()
164          {
165              _textBoxBlinkTimedAction.RequestCancel();
166              _renderAction.RequestCancel();
167          }
168      }
169  }