using Ryujinx.HLE.Ui; using Ryujinx.Memory; using System; using System.Threading; namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard { /// /// Class that manages the renderer base class and its state in a multithreaded context. /// internal class SoftwareKeyboardRenderer : IDisposable { private const int TextBoxBlinkSleepMilliseconds = 100; private const int RendererWaitTimeoutMilliseconds = 100; private readonly object _stateLock = new(); private SoftwareKeyboardUiState _state = new SoftwareKeyboardUiState(); private SoftwareKeyboardRendererBase _renderer; private TimedAction _textBoxBlinkTimedAction = new TimedAction(); private TimedAction _renderAction = new TimedAction(); public SoftwareKeyboardRenderer(IHostUiTheme uiTheme) { _renderer = new SoftwareKeyboardRendererBase(uiTheme); StartTextBoxBlinker(_textBoxBlinkTimedAction, _state, _stateLock); StartRenderer(_renderAction, _renderer, _state, _stateLock); } private static void StartTextBoxBlinker(TimedAction timedAction, SoftwareKeyboardUiState state, object stateLock) { timedAction.Reset(() => { lock (stateLock) { // 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); // Tell the render thread there is something new to render. Monitor.PulseAll(stateLock); } }, TextBoxBlinkSleepMilliseconds); } private static void StartRenderer(TimedAction timedAction, SoftwareKeyboardRendererBase renderer, SoftwareKeyboardUiState state, object stateLock) { SoftwareKeyboardUiState internalState = new SoftwareKeyboardUiState(); bool canCreateSurface = false; bool needsUpdate = true; timedAction.Reset(() => { lock (stateLock) { if (!Monitor.Wait(stateLock, RendererWaitTimeoutMilliseconds)) { return; } 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); canCreateSurface = state.SurfaceInfo != null && internalState.SurfaceInfo == null; if (canCreateSurface) { internalState.SurfaceInfo = state.SurfaceInfo; } } if (canCreateSurface) { renderer.CreateSurface(internalState.SurfaceInfo); } if (needsUpdate) { renderer.DrawMutableElements(internalState); renderer.CopyImageToBuffer(); needsUpdate = false; } }); } private static bool UpdateStateField(ref T source, ref T destination) where T : IEquatable { if (!source.Equals(destination)) { destination = source; return true; } return false; } #pragma warning disable CS8632 public void UpdateTextState(string? inputText, int? cursorBegin, int? cursorEnd, bool? overwriteMode, bool? typingEnabled) #pragma warning restore CS8632 { lock (_stateLock) { // 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); // Reset the cursor blink. _state.TextBoxBlinkCounter = 0; // Tell the render thread there is something new to render. Monitor.PulseAll(_stateLock); } } public void UpdateCommandState(bool? acceptPressed, bool? cancelPressed, bool? controllerEnabled) { lock (_stateLock) { // Update the parameters that were provided. _state.AcceptPressed = acceptPressed.GetValueOrDefault(_state.AcceptPressed); _state.CancelPressed = cancelPressed.GetValueOrDefault(_state.CancelPressed); _state.ControllerEnabled = controllerEnabled.GetValueOrDefault(_state.ControllerEnabled); // Tell the render thread there is something new to render. Monitor.PulseAll(_stateLock); } } public void SetSurfaceInfo(RenderingSurfaceInfo surfaceInfo) { lock (_stateLock) { _state.SurfaceInfo = surfaceInfo; // 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(); } } }