aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/Ui
diff options
context:
space:
mode:
authorCaian Benedicto <caianbene@gmail.com>2021-10-12 16:54:21 -0300
committerGitHub <noreply@github.com>2021-10-12 21:54:21 +0200
commit380b95bc59e7dc419f89df951cdc086e792cb0ff (patch)
tree59a636b48db991d8e13132d7d3f41464d9b04b24 /Ryujinx.HLE/Ui
parent69093cf2d69490862aff974f170cee63a0016fd0 (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.cs4
-rw-r--r--Ryujinx.HLE/Ui/IDynamicTextInputHandler.cs16
-rw-r--r--Ryujinx.HLE/Ui/IHostUiHandler.cs51
-rw-r--r--Ryujinx.HLE/Ui/IHostUiTheme.cs13
-rw-r--r--Ryujinx.HLE/Ui/Input/NpadButtonHandler.cs6
-rw-r--r--Ryujinx.HLE/Ui/Input/NpadReader.cs137
-rw-r--r--Ryujinx.HLE/Ui/KeyPressedHandler.cs6
-rw-r--r--Ryujinx.HLE/Ui/KeyReleasedHandler.cs6
-rw-r--r--Ryujinx.HLE/Ui/RenderingSurfaceInfo.cs34
-rw-r--r--Ryujinx.HLE/Ui/ThemeColor.cs18
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;
+ }
+ }
+}