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
This commit is contained in:
parent
69093cf2d6
commit
380b95bc59
47 changed files with 2853 additions and 344 deletions
4
Ryujinx.HLE/Ui/DynamicTextChangedHandler.cs
Normal file
4
Ryujinx.HLE/Ui/DynamicTextChangedHandler.cs
Normal file
|
@ -0,0 +1,4 @@
|
|||
namespace Ryujinx.HLE.Ui
|
||||
{
|
||||
public delegate void DynamicTextChangedHandler(string text, int cursorBegin, int cursorEnd, bool overwriteMode);
|
||||
}
|
16
Ryujinx.HLE/Ui/IDynamicTextInputHandler.cs
Normal file
16
Ryujinx.HLE/Ui/IDynamicTextInputHandler.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
51
Ryujinx.HLE/Ui/IHostUiHandler.cs
Normal file
51
Ryujinx.HLE/Ui/IHostUiHandler.cs
Normal file
|
@ -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; }
|
||||
}
|
||||
}
|
13
Ryujinx.HLE/Ui/IHostUiTheme.cs
Normal file
13
Ryujinx.HLE/Ui/IHostUiTheme.cs
Normal file
|
@ -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; }
|
||||
}
|
||||
}
|
6
Ryujinx.HLE/Ui/Input/NpadButtonHandler.cs
Normal file
6
Ryujinx.HLE/Ui/Input/NpadButtonHandler.cs
Normal file
|
@ -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);
|
||||
}
|
137
Ryujinx.HLE/Ui/Input/NpadReader.cs
Normal file
137
Ryujinx.HLE/Ui/Input/NpadReader.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
6
Ryujinx.HLE/Ui/KeyPressedHandler.cs
Normal file
6
Ryujinx.HLE/Ui/KeyPressedHandler.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
using Ryujinx.Common.Configuration.Hid;
|
||||
|
||||
namespace Ryujinx.HLE.Ui
|
||||
{
|
||||
public delegate bool KeyPressedHandler(Key key);
|
||||
}
|
6
Ryujinx.HLE/Ui/KeyReleasedHandler.cs
Normal file
6
Ryujinx.HLE/Ui/KeyReleasedHandler.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
using Ryujinx.Common.Configuration.Hid;
|
||||
|
||||
namespace Ryujinx.HLE.Ui
|
||||
{
|
||||
public delegate bool KeyReleasedHandler(Key key);
|
||||
}
|
34
Ryujinx.HLE/Ui/RenderingSurfaceInfo.cs
Normal file
34
Ryujinx.HLE/Ui/RenderingSurfaceInfo.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
18
Ryujinx.HLE/Ui/ThemeColor.cs
Normal file
18
Ryujinx.HLE/Ui/ThemeColor.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue