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 }