diff options
| author | Caian Benedicto <caianbene@gmail.com> | 2021-10-12 16:54:21 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-10-12 21:54:21 +0200 |
| commit | 380b95bc59e7dc419f89df951cdc086e792cb0ff (patch) | |
| tree | 59a636b48db991d8e13132d7d3f41464d9b04b24 /Ryujinx.HLE/Ui | |
| parent | 69093cf2d69490862aff974f170cee63a0016fd0 (diff) | |
Inline software keyboard without input pop up dialog (#2180)
* Initial implementation
* Refactor dynamic text input keys out to facilitate configuration via UI
* Fix code styling
* Add per applet indirect layer handles
* Remove static functions from SoftwareKeyboardRenderer
* Remove inline keyboard reset delay
* Remove inline keyboard V2 responses
* Add inline keyboard soft-lock recovering
* Add comments
* Forward accept and cancel key names to the keyboard and add soft-lock prevention line
* Add dummy window to handle paste events
* Rework inline keyboard state machine and graphics
* Implement IHostUiHandler interfaces on headless WindowBase class
* Add inline keyboard assets
* Fix coding style
* Fix coding style
* Change mode cycling shortcut to F6
* Fix invalid calc size error in games using extended calc
* Remove unnecessary namespaces
Diffstat (limited to 'Ryujinx.HLE/Ui')
| -rw-r--r-- | Ryujinx.HLE/Ui/DynamicTextChangedHandler.cs | 4 | ||||
| -rw-r--r-- | Ryujinx.HLE/Ui/IDynamicTextInputHandler.cs | 16 | ||||
| -rw-r--r-- | Ryujinx.HLE/Ui/IHostUiHandler.cs | 51 | ||||
| -rw-r--r-- | Ryujinx.HLE/Ui/IHostUiTheme.cs | 13 | ||||
| -rw-r--r-- | Ryujinx.HLE/Ui/Input/NpadButtonHandler.cs | 6 | ||||
| -rw-r--r-- | Ryujinx.HLE/Ui/Input/NpadReader.cs | 137 | ||||
| -rw-r--r-- | Ryujinx.HLE/Ui/KeyPressedHandler.cs | 6 | ||||
| -rw-r--r-- | Ryujinx.HLE/Ui/KeyReleasedHandler.cs | 6 | ||||
| -rw-r--r-- | Ryujinx.HLE/Ui/RenderingSurfaceInfo.cs | 34 | ||||
| -rw-r--r-- | Ryujinx.HLE/Ui/ThemeColor.cs | 18 |
10 files changed, 291 insertions, 0 deletions
diff --git a/Ryujinx.HLE/Ui/DynamicTextChangedHandler.cs b/Ryujinx.HLE/Ui/DynamicTextChangedHandler.cs new file mode 100644 index 00000000..c571fb68 --- /dev/null +++ b/Ryujinx.HLE/Ui/DynamicTextChangedHandler.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.HLE.Ui +{ + public delegate void DynamicTextChangedHandler(string text, int cursorBegin, int cursorEnd, bool overwriteMode); +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Ui/IDynamicTextInputHandler.cs b/Ryujinx.HLE/Ui/IDynamicTextInputHandler.cs new file mode 100644 index 00000000..6e7b4c49 --- /dev/null +++ b/Ryujinx.HLE/Ui/IDynamicTextInputHandler.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.HLE.Ui +{ + public interface IDynamicTextInputHandler : IDisposable + { + event DynamicTextChangedHandler TextChangedEvent; + event KeyPressedHandler KeyPressedEvent; + event KeyReleasedHandler KeyReleasedEvent; + + bool TextProcessingEnabled { get; set; } + + void SetText(string text, int cursorBegin); + void SetText(string text, int cursorBegin, int cursorEnd); + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Ui/IHostUiHandler.cs b/Ryujinx.HLE/Ui/IHostUiHandler.cs new file mode 100644 index 00000000..91d8be85 --- /dev/null +++ b/Ryujinx.HLE/Ui/IHostUiHandler.cs @@ -0,0 +1,51 @@ +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; + +namespace Ryujinx.HLE.Ui +{ + public interface IHostUiHandler + { + /// <summary> + /// Displays an Input Dialog box to the user and blocks until text is entered. + /// </summary> + /// <param name="userText">Text that the user entered. Set to `null` on internal errors</param> + /// <returns>True when OK is pressed, False otherwise. Also returns True on internal errors</returns> + bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText); + + /// <summary> + /// Displays a Message Dialog box to the user and blocks until it is closed. + /// </summary> + /// <returns>True when OK is pressed, False otherwise.</returns> + bool DisplayMessageDialog(string title, string message); + + /// <summary> + /// Displays a Message Dialog box specific to Controller Applet and blocks until it is closed. + /// </summary> + /// <returns>True when OK is pressed, False otherwise.</returns> + bool DisplayMessageDialog(ControllerAppletUiArgs args); + + /// <summary> + /// Tell the UI that we need to transisition to another program. + /// </summary> + /// <param name="device">The device instance.</param> + /// <param name="kind">The program kind.</param> + /// <param name="value">The value associated to the <paramref name="kind"/>.</param> + void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value); + + /// Displays a Message Dialog box specific to Error Applet and blocks until it is closed. + /// </summary> + /// <returns>False when OK is pressed, True when another button (Details) is pressed.</returns> + bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText); + + /// <summary> + /// Creates a handler to process keyboard inputs into text strings. + /// </summary> + /// <returns>An instance of the text handler.</returns> + IDynamicTextInputHandler CreateDynamicTextInputHandler(); + + /// <summary> + /// Gets fonts and colors used by the host. + /// </summary> + IHostUiTheme HostUiTheme { get; } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Ui/IHostUiTheme.cs b/Ryujinx.HLE/Ui/IHostUiTheme.cs new file mode 100644 index 00000000..6404c80c --- /dev/null +++ b/Ryujinx.HLE/Ui/IHostUiTheme.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.Ui +{ + public interface IHostUiTheme + { + string FontFamily { get; } + + ThemeColor DefaultBackgroundColor { get; } + ThemeColor DefaultForegroundColor { get; } + ThemeColor DefaultBorderColor { get; } + ThemeColor SelectionBackgroundColor { get; } + ThemeColor SelectionForegroundColor { get; } + } +} diff --git a/Ryujinx.HLE/Ui/Input/NpadButtonHandler.cs b/Ryujinx.HLE/Ui/Input/NpadButtonHandler.cs new file mode 100644 index 00000000..cd41f5c8 --- /dev/null +++ b/Ryujinx.HLE/Ui/Input/NpadButtonHandler.cs @@ -0,0 +1,6 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; + +namespace Ryujinx.HLE.Ui.Input +{ + delegate void NpadButtonHandler(int npadIndex, NpadButton button); +} diff --git a/Ryujinx.HLE/Ui/Input/NpadReader.cs b/Ryujinx.HLE/Ui/Input/NpadReader.cs new file mode 100644 index 00000000..bc3fb396 --- /dev/null +++ b/Ryujinx.HLE/Ui/Input/NpadReader.cs @@ -0,0 +1,137 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; +using System; + +namespace Ryujinx.HLE.Ui.Input +{ + /// <summary> + /// Class that converts Hid entries for the Npad into pressed / released events. + /// </summary> + class NpadReader + { + private readonly Switch _device; + private NpadCommonState[] _lastStates; + + public event NpadButtonHandler NpadButtonUpEvent; + public event NpadButtonHandler NpadButtonDownEvent; + + public NpadReader(Switch device) + { + _device = device; + _lastStates = new NpadCommonState[_device.Hid.SharedMemory.Npads.Length]; + } + + public NpadButton GetCurrentButtonsOfNpad(int npadIndex) + { + return _lastStates[npadIndex].Buttons; + } + + public NpadButton GetCurrentButtonsOfAllNpads() + { + NpadButton buttons = 0; + + foreach (var state in _lastStates) + { + buttons |= state.Buttons; + } + + return buttons; + } + + private ref RingLifo<NpadCommonState> GetCommonStateLifo(ref NpadInternalState npad) + { + switch (npad.StyleSet) + { + case NpadStyleTag.FullKey: + return ref npad.FullKey; + case NpadStyleTag.Handheld: + return ref npad.Handheld; + case NpadStyleTag.JoyDual: + return ref npad.JoyDual; + case NpadStyleTag.JoyLeft: + return ref npad.JoyLeft; + case NpadStyleTag.JoyRight: + return ref npad.JoyRight; + case NpadStyleTag.Palma: + return ref npad.Palma; + default: + return ref npad.SystemExt; + } + } + + public void Update(bool supressEvents=false) + { + ref var npads = ref _device.Hid.SharedMemory.Npads; + + // Process each input individually. + for (int npadIndex = 0; npadIndex < npads.Length; npadIndex++) + { + UpdateNpad(npadIndex, supressEvents); + } + } + + private void UpdateNpad(int npadIndex, bool supressEvents) + { + const int MaxEntries = 1024; + + ref var npadState = ref _device.Hid.SharedMemory.Npads[npadIndex]; + ref var lastEntry = ref _lastStates[npadIndex]; + + var fullKeyEntries = GetCommonStateLifo(ref npadState.InternalState).ReadEntries(MaxEntries); + + int firstEntryNum; + + // Scan the LIFO for the first entry that is newer that what's already processed. + for (firstEntryNum = fullKeyEntries.Length - 1; firstEntryNum >= 0 && fullKeyEntries[firstEntryNum].Object.SamplingNumber <= lastEntry.SamplingNumber; firstEntryNum--) ; + + if (firstEntryNum == -1) + { + return; + } + + for (; firstEntryNum >= 0; firstEntryNum--) + { + var entry = fullKeyEntries[firstEntryNum]; + + // The interval of valid entries should be contiguous. + if (entry.SamplingNumber < lastEntry.SamplingNumber) + { + break; + } + + if (!supressEvents) + { + ProcessNpadButtons(npadIndex, entry.Object.Buttons); + } + + lastEntry = entry.Object; + } + } + + private void ProcessNpadButtons(int npadIndex, NpadButton buttons) + { + NpadButton lastButtons = _lastStates[npadIndex].Buttons; + + for (ulong buttonMask = 1; buttonMask != 0; buttonMask <<= 1) + { + NpadButton currentButton = (NpadButton)buttonMask & buttons; + NpadButton lastButton = (NpadButton)buttonMask & lastButtons; + + if (lastButton != 0) + { + if (currentButton == 0) + { + NpadButtonUpEvent?.Invoke(npadIndex, lastButton); + } + } + else + { + if (currentButton != 0) + { + NpadButtonDownEvent?.Invoke(npadIndex, currentButton); + } + } + } + } + } +} diff --git a/Ryujinx.HLE/Ui/KeyPressedHandler.cs b/Ryujinx.HLE/Ui/KeyPressedHandler.cs new file mode 100644 index 00000000..096bf731 --- /dev/null +++ b/Ryujinx.HLE/Ui/KeyPressedHandler.cs @@ -0,0 +1,6 @@ +using Ryujinx.Common.Configuration.Hid; + +namespace Ryujinx.HLE.Ui +{ + public delegate bool KeyPressedHandler(Key key); +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Ui/KeyReleasedHandler.cs b/Ryujinx.HLE/Ui/KeyReleasedHandler.cs new file mode 100644 index 00000000..4faaa529 --- /dev/null +++ b/Ryujinx.HLE/Ui/KeyReleasedHandler.cs @@ -0,0 +1,6 @@ +using Ryujinx.Common.Configuration.Hid; + +namespace Ryujinx.HLE.Ui +{ + public delegate bool KeyReleasedHandler(Key key); +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Ui/RenderingSurfaceInfo.cs b/Ryujinx.HLE/Ui/RenderingSurfaceInfo.cs new file mode 100644 index 00000000..0903ffdd --- /dev/null +++ b/Ryujinx.HLE/Ui/RenderingSurfaceInfo.cs @@ -0,0 +1,34 @@ +using Ryujinx.HLE.HOS.Services.SurfaceFlinger; + +namespace Ryujinx.HLE.Ui +{ + /// <summary> + /// Information about the indirect layer that is being drawn to. + /// </summary> + class RenderingSurfaceInfo + { + public ColorFormat ColorFormat { get; } + public uint Width { get; } + public uint Height { get; } + public uint Pitch { get; } + public uint Size { get; } + + public RenderingSurfaceInfo(ColorFormat colorFormat, uint width, uint height, uint pitch, uint size) + { + ColorFormat = colorFormat; + Width = width; + Height = height; + Pitch = pitch; + Size = size; + } + + public bool Equals(RenderingSurfaceInfo other) + { + return ColorFormat == other.ColorFormat && + Width == other.Width && + Height == other.Height && + Pitch == other.Pitch && + Size == other.Size; + } + } +} diff --git a/Ryujinx.HLE/Ui/ThemeColor.cs b/Ryujinx.HLE/Ui/ThemeColor.cs new file mode 100644 index 00000000..1a42b167 --- /dev/null +++ b/Ryujinx.HLE/Ui/ThemeColor.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.Ui +{ + public struct ThemeColor + { + public float A { get; } + public float R { get; } + public float G { get; } + public float B { get; } + + public ThemeColor(float a, float r, float g, float b) + { + A = a; + R = r; + G = g; + B = b; + } + } +} |
