From d511c845b70a8771de7d64369e24ab3f1ed1c325 Mon Sep 17 00:00:00 2001
From: WilliamWsyHK <WilliamWsyHK@users.noreply.github.com>
Date: Sun, 4 Jun 2023 11:30:24 +0800
Subject: Check KeyboardMode in GUI (#4343)

* Update SoftwareKeyboard to send KeyboardMode to UI

* Update GTK UI to check text against KeyboardMode

* Update Ava UI to check text against KeyboardMode

* Restructure input validation

* true when text is not empty

* Add English validation text for SoftwareKeyboardMode

* Add Chinese validation text for SoftwareKeyboardMode

* Update base on feedback

---------

Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
---
 src/Ryujinx.Ava/Assets/Locales/en_US.json          |  3 ++
 src/Ryujinx.Ava/Assets/Locales/zh_CN.json          |  3 ++
 src/Ryujinx.Ava/Assets/Locales/zh_TW.json          |  3 ++
 .../UI/Applet/SwkbdAppletDialog.axaml.cs           | 59 ++++++++++++++++++----
 .../HOS/Applets/SoftwareKeyboard/KeyboardMode.cs   |  2 +-
 .../SoftwareKeyboard/SoftwareKeyboardApplet.cs     |  1 +
 .../SoftwareKeyboard/SoftwareKeyboardUiArgs.cs     |  3 ++
 src/Ryujinx/Ui/Applet/GtkHostUiHandler.cs          |  1 +
 src/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs         | 56 ++++++++++++++++----
 9 files changed, 109 insertions(+), 22 deletions(-)

diff --git a/src/Ryujinx.Ava/Assets/Locales/en_US.json b/src/Ryujinx.Ava/Assets/Locales/en_US.json
index 8f4965e1..79765db1 100644
--- a/src/Ryujinx.Ava/Assets/Locales/en_US.json
+++ b/src/Ryujinx.Ava/Assets/Locales/en_US.json
@@ -544,6 +544,9 @@
   "SwkbdMinCharacters": "Must be at least {0} characters long",
   "SwkbdMinRangeCharacters": "Must be {0}-{1} characters long",
   "SoftwareKeyboard": "Software Keyboard",
+  "SoftwareKeyboardModeNumbersOnly": "Must be numbers only",
+  "SoftwareKeyboardModeAlphabet": "Must be alphabets only",
+  "SoftwareKeyboardModeASCII": "Must be ASCII text only",
   "DialogControllerAppletMessagePlayerRange": "Application requests {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.",
   "DialogControllerAppletMessage": "Application requests exactly {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.",
   "DialogControllerAppletDockModeSet": "Docked mode set. Handheld is also invalid.\n\n",
diff --git a/src/Ryujinx.Ava/Assets/Locales/zh_CN.json b/src/Ryujinx.Ava/Assets/Locales/zh_CN.json
index 25dc3cba..b47dda97 100644
--- a/src/Ryujinx.Ava/Assets/Locales/zh_CN.json
+++ b/src/Ryujinx.Ava/Assets/Locales/zh_CN.json
@@ -527,6 +527,9 @@
   "SwkbdMinCharacters": "至少应为 {0} 个字长",
   "SwkbdMinRangeCharacters": "必须为 {0}-{1} 个字长",
   "SoftwareKeyboard": "软件键盘",
+  "SoftwareKeyboardModeNumbersOnly": "只接受数字",
+  "SoftwareKeyboardModeAlphabet": "只接受英文字母",
+  "SoftwareKeyboardModeASCII": "只接受 ASCII 符号",
   "DialogControllerAppletMessagePlayerRange": "游戏需要 {0} 个玩家并满足以下要求:\n\n手柄类型:{1}\n\n玩家类型:{2}\n\n{3}请打开设置窗口,重新配置手柄输入;或者关闭返回。",
   "DialogControllerAppletMessage": "游戏需要刚好 {0} 个玩家并满足以下要求:\n\n手柄类型:{1}\n\n玩家类型:{2}\n\n{3}请打开设置窗口,重新配置手柄输入;或者关闭返回。",
   "DialogControllerAppletDockModeSet": "目前处于主机模式,无法使用掌机操作方式",
diff --git a/src/Ryujinx.Ava/Assets/Locales/zh_TW.json b/src/Ryujinx.Ava/Assets/Locales/zh_TW.json
index 940282a0..e943691c 100644
--- a/src/Ryujinx.Ava/Assets/Locales/zh_TW.json
+++ b/src/Ryujinx.Ava/Assets/Locales/zh_TW.json
@@ -527,6 +527,9 @@
   "SwkbdMinCharacters": "至少應為 {0} 個字長",
   "SwkbdMinRangeCharacters": "必須為 {0}-{1} 個字長",
   "SoftwareKeyboard": "軟體鍵盤",
+  "SoftwareKeyboardModeNumbersOnly": "只接受數字",
+  "SoftwareKeyboardModeAlphabet": "只接受英文字母",
+  "SoftwareKeyboardModeASCII": "只接受 ASCII 符號",
   "DialogControllerAppletMessagePlayerRange": "本遊戲需要 {0} 個玩家持有:\n\n類型:{1}\n\n玩家:{2}\n\n{3}請打開設定畫面,配置手把,或者關閉本視窗。",
   "DialogControllerAppletMessage": "本遊戲需要剛好 {0} 個玩家持有:\n\n類型:{1}\n\n玩家:{2}\n\n{3}請打開設定畫面,配置手把,或者關閉本視窗。",
   "DialogControllerAppletDockModeSet": "現在處於主機模式,無法使用掌機操作方式\n\n",
diff --git a/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs b/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs
index cb69e96b..04bc4619 100644
--- a/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs
@@ -9,14 +9,17 @@ using Ryujinx.Ava.Common.Locale;
 using Ryujinx.Ava.UI.Helpers;
 using Ryujinx.Ava.UI.Windows;
 using Ryujinx.HLE.HOS.Applets;
+using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
 using System;
+using System.Linq;
 using System.Threading.Tasks;
 
 namespace Ryujinx.Ava.UI.Controls
 {
     internal partial class SwkbdAppletDialog : UserControl
     {
-        private Predicate<int> _checkLength;
+        private Predicate<int> _checkLength = _ => true;
+        private Predicate<string> _checkInput = _ => true;
         private int _inputMax;
         private int _inputMin;
         private string _placeholder;
@@ -35,8 +38,6 @@ namespace Ryujinx.Ava.UI.Controls
             Input.Watermark = _placeholder;
 
             Input.AddHandler(TextInputEvent, Message_TextInput, RoutingStrategies.Tunnel, true);
-
-            SetInputLengthValidation(0, int.MaxValue); // Disable by default.
         }
 
         public SwkbdAppletDialog()
@@ -67,6 +68,7 @@ namespace Ryujinx.Ava.UI.Controls
             string input = string.Empty;
 
             content.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
+            content.SetInputValidation(args.KeyboardMode);
 
             content._host = contentDialog;
             contentDialog.Title = title;
@@ -91,6 +93,12 @@ namespace Ryujinx.Ava.UI.Controls
             return (result, input);
         }
 
+        private void ApplyValidationInfo(string text)
+        {
+            Error.IsVisible = !string.IsNullOrEmpty(text);
+            Error.Text = text;
+        }
+
         public void SetInputLengthValidation(int min, int max)
         {
             _inputMin = Math.Min(min, max);
@@ -99,6 +107,8 @@ namespace Ryujinx.Ava.UI.Controls
             Error.IsVisible = false;
             Error.FontStyle = FontStyle.Italic;
 
+            string validationInfoText = "";
+
             if (_inputMin <= 0 && _inputMax == int.MaxValue) // Disable.
             {
                 Error.IsVisible = false;
@@ -107,21 +117,48 @@ namespace Ryujinx.Ava.UI.Controls
             }
             else if (_inputMin > 0 && _inputMax == int.MaxValue)
             {
-                Error.IsVisible = true;
-
-                Error.Text = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinCharacters, _inputMin);
+                validationInfoText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinCharacters, _inputMin);
 
                 _checkLength = length => _inputMin <= length;
             }
             else
             {
-                Error.IsVisible = true;
-
-                Error.Text = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinRangeCharacters, _inputMin, _inputMax);
+                validationInfoText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinRangeCharacters, _inputMin, _inputMax);
 
                 _checkLength = length => _inputMin <= length && length <= _inputMax;
             }
 
+            ApplyValidationInfo(validationInfoText);
+            Message_TextInput(this, new TextInputEventArgs());
+        }
+
+        private void SetInputValidation(KeyboardMode mode)
+        {
+            string validationInfoText = Error.Text;
+            string localeText;
+            switch (mode)
+            {
+                case KeyboardMode.NumbersOnly:
+                    localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeNumbersOnly);
+                    validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText);
+                    _checkInput = text => text.All(char.IsDigit);
+                    break;
+                case KeyboardMode.Alphabet:
+                    localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeAlphabet);
+                    validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText);
+                    _checkInput = text => text.All(char.IsAsciiLetter);
+                    break;
+                case KeyboardMode.ASCII:
+                    localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeASCII);
+                    validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText);
+                    _checkInput = text => text.All(char.IsAscii);
+                    break;
+                default:
+                    _checkInput = _ => true;
+                    break;
+            }
+
+            ApplyValidationInfo(validationInfoText);
             Message_TextInput(this, new TextInputEventArgs());
         }
 
@@ -129,7 +166,7 @@ namespace Ryujinx.Ava.UI.Controls
         {
             if (_host != null)
             {
-                _host.IsPrimaryButtonEnabled = _checkLength(Message.Length);
+                _host.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message);
             }
         }
 
@@ -141,7 +178,7 @@ namespace Ryujinx.Ava.UI.Controls
             }
             else
             {
-                _host.IsPrimaryButtonEnabled = _checkLength(Message.Length);
+                _host.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message);
             }
         }
     }
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs
index f512050e..01b3c963 100644
--- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs
@@ -3,7 +3,7 @@
     /// <summary>
     /// Identifies the variant of keyboard displayed on screen.
     /// </summary>
-    enum KeyboardMode : uint
+    public enum KeyboardMode : uint
     {
         /// <summary>
         /// A full alpha-numeric keyboard.
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs
index 278ea56c..4b484e80 100644
--- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs
@@ -209,6 +209,7 @@ namespace Ryujinx.HLE.HOS.Applets
                 // Call the configured GUI handler to get user's input.
                 var args = new SoftwareKeyboardUiArgs
                 {
+                    KeyboardMode = _keyboardForegroundConfig.Mode,
                     HeaderText = StripUnicodeControlCodes(_keyboardForegroundConfig.HeaderText),
                     SubtitleText = StripUnicodeControlCodes(_keyboardForegroundConfig.SubtitleText),
                     GuideText = StripUnicodeControlCodes(_keyboardForegroundConfig.GuideText),
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs
index d24adec3..d67a4409 100644
--- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs
@@ -1,7 +1,10 @@
+using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
+
 namespace Ryujinx.HLE.HOS.Applets
 {
     public struct SoftwareKeyboardUiArgs
     {
+        public KeyboardMode KeyboardMode;
         public string HeaderText;
         public string SubtitleText;
         public string InitialText;
diff --git a/src/Ryujinx/Ui/Applet/GtkHostUiHandler.cs b/src/Ryujinx/Ui/Applet/GtkHostUiHandler.cs
index d81cbe3c..b7577b85 100644
--- a/src/Ryujinx/Ui/Applet/GtkHostUiHandler.cs
+++ b/src/Ryujinx/Ui/Applet/GtkHostUiHandler.cs
@@ -106,6 +106,7 @@ namespace Ryujinx.Ui.Applet
                     swkbdDialog.OkButton.Label             = args.SubmitText;
 
                     swkbdDialog.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
+                    swkbdDialog.SetInputValidation(args.KeyboardMode);
 
                     if (swkbdDialog.Run() == (int)ResponseType.Ok)
                     {
diff --git a/src/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs b/src/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs
index 7c14f0e8..28067b75 100644
--- a/src/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs
+++ b/src/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs
@@ -1,5 +1,7 @@
 using Gtk;
+using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
 using System;
+using System.Linq;
 
 namespace Ryujinx.Ui.Applet
 {
@@ -7,8 +9,12 @@ namespace Ryujinx.Ui.Applet
     {
         private int _inputMin;
         private int _inputMax;
+        private KeyboardMode _mode;
 
-        private Predicate<int> _checkLength;
+        private string _validationInfoText = "";
+
+        private Predicate<int> _checkLength = _ => true;
+        private Predicate<string> _checkInput = _ => true;
 
         private readonly Label _validationInfo;
 
@@ -38,8 +44,12 @@ namespace Ryujinx.Ui.Applet
 
             ((Box)MessageArea).PackEnd(_validationInfo, true, true, 0);
             ((Box)MessageArea).PackEnd(InputEntry,      true, true, 4);
+        }
 
-            SetInputLengthValidation(0, int.MaxValue); // Disable by default.
+        private void ApplyValidationInfo()
+        {
+            _validationInfo.Visible = !string.IsNullOrEmpty(_validationInfoText);
+            _validationInfo.Markup = _validationInfoText;
         }
 
         public void SetInputLengthValidation(int min, int max)
@@ -53,23 +63,49 @@ namespace Ryujinx.Ui.Applet
             {
                 _validationInfo.Visible = false;
 
-                _checkLength = (length) => true;
+                _checkLength = _ => true;
             }
             else if (_inputMin > 0 && _inputMax == int.MaxValue)
             {
-                _validationInfo.Visible = true;
-                _validationInfo.Markup  = $"<i>Must be at least {_inputMin} characters long</i>";
+                _validationInfoText = $"<i>Must be at least {_inputMin} characters long.</i> ";
 
-                _checkLength = (length) => _inputMin <= length;
+                _checkLength = length => _inputMin <= length;
             }
             else
             {
-                _validationInfo.Visible = true;
-                _validationInfo.Markup  = $"<i>Must be {_inputMin}-{_inputMax} characters long</i>";
+                _validationInfoText = $"<i>Must be {_inputMin}-{_inputMax} characters long.</i> ";
 
-                _checkLength = (length) => _inputMin <= length && length <= _inputMax;
+                _checkLength = length => _inputMin <= length && length <= _inputMax;
+            }
+
+            ApplyValidationInfo();
+            OnInputChanged(this, EventArgs.Empty);
+        }
+
+        public void SetInputValidation(KeyboardMode mode)
+        {
+            _mode = mode;
+
+            switch (mode)
+            {
+                case KeyboardMode.NumbersOnly:
+                    _validationInfoText += "<i>Must be numbers only.</i>";
+                    _checkInput = text => text.All(char.IsDigit);
+                    break;
+                case KeyboardMode.Alphabet:
+                    _validationInfoText += "<i>Must be alphabets only.</i>";
+                    _checkInput = text => text.All(char.IsAsciiLetter);
+                    break;
+                case KeyboardMode.ASCII:
+                    _validationInfoText += "<i>Must be ASCII text only.</i>";
+                    _checkInput = text => text.All(char.IsAscii);
+                    break;
+                default:
+                    _checkInput = _ => true;
+                    break;
             }
 
+            ApplyValidationInfo();
             OnInputChanged(this, EventArgs.Empty);
         }
 
@@ -83,7 +119,7 @@ namespace Ryujinx.Ui.Applet
 
         private void OnInputChanged(object sender, EventArgs e)
         {
-            OkButton.Sensitive = _checkLength(InputEntry.Text.Length);
+            OkButton.Sensitive = _checkLength(InputEntry.Text.Length) && _checkInput(InputEntry.Text);
         }
     }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2