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 readonly SoftwareKeyboardUIState _state = new(); private readonly SoftwareKeyboardRendererBase _renderer; private readonly TimedAction _textBoxBlinkTimedAction = new(); private readonly TimedAction _renderAction = new(); 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(); bool canCreateSurface = false; bool needsUpdate = true; timedAction.Reset(() => { lock (stateLock) { if (!Monitor.Wait(stateLock, RendererWaitTimeoutMilliseconds)) { return; } #pragma warning disable IDE0055 // Disable formatting 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); #pragma warning restore IDE0055 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; } public void UpdateTextState(string inputText, int? cursorBegin, int? cursorEnd, bool? overwriteMode, bool? typingEnabled) { lock (_stateLock) { // Update the parameters that were provided. _state.InputText = 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(); } } }