From 053724e4f8bd202a8dd12caa0a0d2328c42c5abd Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Sat, 1 Feb 2020 20:50:42 +0100 Subject: [PATCH] changed text input style to work on all devices including web --- Demos.Android/Activity1.cs | 12 ++- MLEM.Startup/MlemGame.cs | 4 +- MLEM.Ui/Elements/TextField.cs | 30 +++--- MLEM.Ui/UiControls.cs | 2 +- MLEM.Ui/UiSystem.cs | 8 +- MLEM/Extensions/WindowExtensions.cs | 33 ------ MLEM/Input/InputHandler.cs | 16 ++- MLEM/Input/TextInputStyle.cs | 160 ++++++++++++++++++++++++++++ 8 files changed, 208 insertions(+), 57 deletions(-) delete mode 100644 MLEM/Extensions/WindowExtensions.cs create mode 100644 MLEM/Input/TextInputStyle.cs diff --git a/Demos.Android/Activity1.cs b/Demos.Android/Activity1.cs index 3de36d3..633aa43 100644 --- a/Demos.Android/Activity1.cs +++ b/Demos.Android/Activity1.cs @@ -2,23 +2,27 @@ using Android.App; using Android.Content.PM; using Android.OS; using Android.Views; +using MLEM.Input; namespace Demos.Android { [Activity(Label = "Demos.Android" , MainLauncher = true , Icon = "@drawable/icon" - , Theme = "@style/Theme.Splash" , AlwaysRetainTaskState = true , LaunchMode = LaunchMode.SingleInstance - , ScreenOrientation = ScreenOrientation.UserLandscape + , ScreenOrientation = ScreenOrientation.FullUser , ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden | ConfigChanges.ScreenSize)] public class Activity1 : Microsoft.Xna.Framework.AndroidGameActivity { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); var g = new GameImpl(); - // disable mouse handling for android to make emulator behavior more coherent - g.OnLoadContent += game => game.InputHandler.HandleMouse = false; + g.OnLoadContent += game => { + // disable mouse handling for android to make emulator behavior more coherent + game.InputHandler.HandleMouse = false; + // enable android text input style + game.InputHandler.TextInputStyle = new TextInputStyle.Mobile(); + }; this.SetContentView((View) g.Services.GetService(typeof(View))); g.Run(); } diff --git a/MLEM.Startup/MlemGame.cs b/MLEM.Startup/MlemGame.cs index fc4ef53..135a8d5 100644 --- a/MLEM.Startup/MlemGame.cs +++ b/MLEM.Startup/MlemGame.cs @@ -34,9 +34,9 @@ namespace MLEM.Startup { protected override void LoadContent() { this.SpriteBatch = new SpriteBatch(this.GraphicsDevice); - this.InputHandler = new InputHandler(); + this.InputHandler = new InputHandler(this, textInputStyle: new TextInputStyle.DesktopGl()); this.Components.Add(this.InputHandler); - this.UiSystem = new UiSystem(this.Window, this.GraphicsDevice, new UntexturedStyle(this.SpriteBatch), this.InputHandler); + this.UiSystem = new UiSystem(this, this.GraphicsDevice, new UntexturedStyle(this.SpriteBatch), this.InputHandler); this.Components.Add(this.UiSystem); this.OnLoadContent?.Invoke(this); } diff --git a/MLEM.Ui/Elements/TextField.cs b/MLEM.Ui/Elements/TextField.cs index 0cd8601..0a5944c 100644 --- a/MLEM.Ui/Elements/TextField.cs +++ b/MLEM.Ui/Elements/TextField.cs @@ -47,13 +47,27 @@ namespace MLEM.Ui.Elements { } } } + private bool init; public TextField(Anchor anchor, Vector2 size, Rule rule = null, IGenericFont font = null) : base(anchor, size) { this.InputRule = rule ?? DefaultRule; if (font != null) this.Font.Set(font); + } - if (WindowExtensions.SupportsTextInput()) { + public override void ForceUpdateArea() { + if (!this.init) { + this.init = true; + if (this.Input.TextInputStyle.RequiresOnScreenKeyboard()) { + this.OnPressed += async e => { + if (!KeyboardInput.IsVisible) { + var title = this.MobileTitle ?? this.PlaceholderText; + var result = await KeyboardInput.Show(title, this.MobileDescription, this.Text); + if (result != null) + this.SetText(result.Replace('\n', ' '), true); + } + }; + } this.OnTextInput += (element, key, character) => { if (!this.IsSelected || this.IsHidden) return; @@ -68,18 +82,10 @@ namespace MLEM.Ui.Elements { this.InsertText(character); } }; - } else { - this.OnPressed += async e => { - if (!KeyboardInput.IsVisible) { - var title = this.MobileTitle ?? this.PlaceholderText; - var result = await KeyboardInput.Show(title, this.MobileDescription, this.Text); - if (result != null) - this.SetText(result.Replace('\n', ' '), true); - } - }; + this.OnDeselected += e => this.CaretPos = 0; + this.OnSelected += e => this.CaretPos = this.text.Length; } - this.OnDeselected += e => this.CaretPos = 0; - this.OnSelected += e => this.CaretPos = this.text.Length; + base.ForceUpdateArea(); } private void HandleTextChange(bool textChanged = true) { diff --git a/MLEM.Ui/UiControls.cs b/MLEM.Ui/UiControls.cs index 6a41ee2..c413532 100644 --- a/MLEM.Ui/UiControls.cs +++ b/MLEM.Ui/UiControls.cs @@ -36,7 +36,7 @@ namespace MLEM.Ui { public UiControls(UiSystem system, InputHandler inputHandler = null) { this.System = system; - this.Input = inputHandler ?? new InputHandler(); + this.Input = inputHandler ?? new InputHandler(system.Game); this.IsInputOurs = inputHandler == null; // enable all required gestures diff --git a/MLEM.Ui/UiSystem.cs b/MLEM.Ui/UiSystem.cs index bc99c33..53ce04c 100644 --- a/MLEM.Ui/UiSystem.cs +++ b/MLEM.Ui/UiSystem.cs @@ -67,21 +67,21 @@ namespace MLEM.Ui { public RootCallback OnRootAdded; public RootCallback OnRootRemoved; - public UiSystem(GameWindow window, GraphicsDevice device, UiStyle style, InputHandler inputHandler = null) : base(null) { + public UiSystem(Game game, GraphicsDevice device, UiStyle style, InputHandler inputHandler = null) : base(game) { this.Controls = new UiControls(this, inputHandler); this.GraphicsDevice = device; - this.Window = window; + this.Window = game.Window; this.style = style; this.Viewport = device.Viewport.Bounds; this.AutoScaleReferenceSize = this.Viewport.Size; - window.ClientSizeChanged += (sender, args) => { + game.Window.ClientSizeChanged += (sender, args) => { this.Viewport = device.Viewport.Bounds; foreach (var root in this.rootElements) root.Element.ForceUpdateArea(); }; - window.AddTextInputListener((sender, key, character) => this.ApplyToAll(e => e.OnTextInput?.Invoke(e, key, character))); + this.Controls.Input.OnTextInput += (key, character) => this.ApplyToAll(e => e.OnTextInput?.Invoke(e, key, character)); this.OnMousedElementChanged = e => this.ApplyToAll(t => t.OnMousedElementChanged?.Invoke(t, e)); this.OnSelectedElementChanged = e => this.ApplyToAll(t => t.OnSelectedElementChanged?.Invoke(t, e)); this.OnSelectedElementDrawn = (element, time, batch, alpha) => { diff --git a/MLEM/Extensions/WindowExtensions.cs b/MLEM/Extensions/WindowExtensions.cs deleted file mode 100644 index 414a57c..0000000 --- a/MLEM/Extensions/WindowExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Reflection; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Input; - -namespace MLEM.Extensions { - public static class WindowExtensions { - - private static readonly bool TextInputSupported = typeof(GameWindow).GetEvent("TextInput") != null; - - public static bool AddTextInputListener(this GameWindow window, TextInputCallback callback) { - if (!SupportsTextInput()) - return false; - TextInputAdder.Add(window, callback); - return true; - } - - public static bool SupportsTextInput() { - return TextInputSupported; - } - - public delegate void TextInputCallback(object sender, Keys key, char character); - - private static class TextInputAdder { - - public static void Add(GameWindow window, TextInputCallback callback) { - window.TextInput += (sender, args) => callback(sender, args.Key, args.Character); - } - - } - - } -} \ No newline at end of file diff --git a/MLEM/Input/InputHandler.cs b/MLEM/Input/InputHandler.cs index 8ffbed1..e62590d 100644 --- a/MLEM/Input/InputHandler.cs +++ b/MLEM/Input/InputHandler.cs @@ -16,6 +16,16 @@ namespace MLEM.Input { public Keys[] PressedKeys { get; private set; } public bool HandleKeyboard; + private TextInputStyle textInputStyle; + public TextInputStyle TextInputStyle { + get => this.textInputStyle; + set { + this.textInputStyle = value; + value?.Initialize(this); + } + } + public Action OnTextInput; + public MouseState LastMouseState { get; private set; } public MouseState MouseState { get; private set; } public Point MousePosition => this.MouseState.Position; @@ -50,11 +60,12 @@ namespace MLEM.Input { private readonly bool[] triggerGamepadButtonRepeat = new bool[GamePad.MaximumGamePadCount]; private readonly Buttons?[] heldGamepadButtons = new Buttons?[GamePad.MaximumGamePadCount]; - public InputHandler(bool handleKeyboard = true, bool handleMouse = true, bool handleGamepads = true, bool handleTouch = true) : base(null) { + public InputHandler(Game game, bool handleKeyboard = true, bool handleMouse = true, bool handleGamepads = true, bool handleTouch = true, TextInputStyle textInputStyle = null) : base(game) { this.HandleKeyboard = handleKeyboard; this.HandleMouse = handleMouse; this.HandleGamepads = handleGamepads; this.HandleTouch = handleTouch; + this.TextInputStyle = textInputStyle; this.Gestures = this.gestures.AsReadOnly(); } @@ -96,6 +107,9 @@ namespace MLEM.Input { } } + if (this.TextInputStyle != null) + this.TextInputStyle.Update(); + if (this.HandleMouse) { this.LastMouseState = this.MouseState; this.MouseState = Mouse.GetState(); diff --git a/MLEM/Input/TextInputStyle.cs b/MLEM/Input/TextInputStyle.cs new file mode 100644 index 0000000..603c0a1 --- /dev/null +++ b/MLEM/Input/TextInputStyle.cs @@ -0,0 +1,160 @@ +using System; +using System.Linq; +using System.Reflection; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; + +namespace MLEM.Input { + public abstract class TextInputStyle { + + private InputHandler handler; + + public abstract bool RequiresOnScreenKeyboard(); + + public abstract void Update(); + + public virtual void Initialize(InputHandler handler) { + this.handler = handler; + } + + public class DesktopGl : TextInputStyle { + + private static readonly EventInfo TextInput = typeof(GameWindow).GetEvent("TextInput"); + private static readonly MethodInfo Callback = typeof(DesktopGl).GetMethod(nameof(OnTextInput)); + private static PropertyInfo key; + private static PropertyInfo character; + + public override bool RequiresOnScreenKeyboard() { + return false; + } + + public override void Update() { + } + + public override void Initialize(InputHandler handler) { + base.Initialize(handler); + if (TextInput != null) + TextInput.AddEventHandler(handler.Game.Window, Delegate.CreateDelegate(TextInput.EventHandlerType, this, Callback)); + } + + public void OnTextInput(object sender, EventArgs args) { + if (key == null) + key = args.GetType().GetProperty("Key"); + if (character == null) + character = args.GetType().GetProperty("Character"); + this.handler.OnTextInput?.Invoke((Keys) key.GetValue(args), (char) character.GetValue(args)); + } + + } + + public class Mobile : TextInputStyle { + + public override bool RequiresOnScreenKeyboard() { + return true; + } + + public override void Update() { + } + + } + + public class American : TextInputStyle { + + public override bool RequiresOnScreenKeyboard() { + return false; + } + + public override void Update() { + var pressed = this.handler.KeyboardState.GetPressedKeys().Except(this.handler.LastKeyboardState.GetPressedKeys()); + var shift = this.handler.IsModifierKeyDown(ModifierKey.Shift); + foreach (var key in pressed) { + var c = GetChar(key, shift); + if (c.HasValue) + this.handler.OnTextInput?.Invoke(key, c.Value); + } + } + + private static char? GetChar(Keys key, bool shift) { + if (key == Keys.A) return shift ? 'A' : 'a'; + if (key == Keys.B) return shift ? 'B' : 'b'; + if (key == Keys.C) return shift ? 'C' : 'c'; + if (key == Keys.D) return shift ? 'D' : 'd'; + if (key == Keys.E) return shift ? 'E' : 'e'; + if (key == Keys.F) return shift ? 'F' : 'f'; + if (key == Keys.G) return shift ? 'G' : 'g'; + if (key == Keys.H) return shift ? 'H' : 'h'; + if (key == Keys.I) return shift ? 'I' : 'i'; + if (key == Keys.J) return shift ? 'J' : 'j'; + if (key == Keys.K) return shift ? 'K' : 'k'; + if (key == Keys.L) return shift ? 'L' : 'l'; + if (key == Keys.M) return shift ? 'M' : 'm'; + if (key == Keys.N) return shift ? 'N' : 'n'; + if (key == Keys.O) return shift ? 'O' : 'o'; + if (key == Keys.P) return shift ? 'P' : 'p'; + if (key == Keys.Q) return shift ? 'Q' : 'q'; + if (key == Keys.R) return shift ? 'R' : 'r'; + if (key == Keys.S) return shift ? 'S' : 's'; + if (key == Keys.T) return shift ? 'T' : 't'; + if (key == Keys.U) return shift ? 'U' : 'u'; + if (key == Keys.V) return shift ? 'V' : 'v'; + if (key == Keys.W) return shift ? 'W' : 'w'; + if (key == Keys.X) return shift ? 'X' : 'x'; + if (key == Keys.Y) return shift ? 'Y' : 'y'; + if (key == Keys.Z) return shift ? 'Z' : 'z'; + if (key == Keys.D0 && !shift || key == Keys.NumPad0) return '0'; + if (key == Keys.D1 && !shift || key == Keys.NumPad1) return '1'; + if (key == Keys.D2 && !shift || key == Keys.NumPad2) return '2'; + if (key == Keys.D3 && !shift || key == Keys.NumPad3) return '3'; + if (key == Keys.D4 && !shift || key == Keys.NumPad4) return '4'; + if (key == Keys.D5 && !shift || key == Keys.NumPad5) return '5'; + if (key == Keys.D6 && !shift || key == Keys.NumPad6) return '6'; + if (key == Keys.D7 && !shift || key == Keys.NumPad7) return '7'; + if (key == Keys.D8 && !shift || key == Keys.NumPad8) return '8'; + if (key == Keys.D9 && !shift || key == Keys.NumPad9) return '9'; + if (key == Keys.D0 && shift) return ')'; + if (key == Keys.D1 && shift) return '!'; + if (key == Keys.D2 && shift) return '@'; + if (key == Keys.D3 && shift) return '#'; + if (key == Keys.D4 && shift) return '$'; + if (key == Keys.D5 && shift) return '%'; + if (key == Keys.D6 && shift) return '^'; + if (key == Keys.D7 && shift) return '&'; + if (key == Keys.D8 && shift) return '*'; + if (key == Keys.D9 && shift) return '('; + if (key == Keys.Space) return ' '; + if (key == Keys.Tab) return '\t'; + if (key == Keys.Add) return '+'; + if (key == Keys.Decimal) return '.'; + if (key == Keys.Divide) return '/'; + if (key == Keys.Multiply) return '*'; + if (key == Keys.OemBackslash) return '\\'; + if (key == Keys.OemComma && !shift) return ','; + if (key == Keys.OemComma && shift) return '<'; + if (key == Keys.OemOpenBrackets && !shift) return '['; + if (key == Keys.OemOpenBrackets && shift) return '{'; + if (key == Keys.OemCloseBrackets && !shift) return ']'; + if (key == Keys.OemCloseBrackets && shift) return '}'; + if (key == Keys.OemPeriod && !shift) return '.'; + if (key == Keys.OemPeriod && shift) return '>'; + if (key == Keys.OemPipe && !shift) return '\\'; + if (key == Keys.OemPipe && shift) return '|'; + if (key == Keys.OemPlus && !shift) return '='; + if (key == Keys.OemPlus && shift) return '+'; + if (key == Keys.OemMinus && !shift) return '-'; + if (key == Keys.OemMinus && shift) return '_'; + if (key == Keys.OemQuestion && !shift) return '/'; + if (key == Keys.OemQuestion && shift) return '?'; + if (key == Keys.OemQuotes && !shift) return '\''; + if (key == Keys.OemQuotes && shift) return '"'; + if (key == Keys.OemSemicolon && !shift) return ';'; + if (key == Keys.OemSemicolon && shift) return ':'; + if (key == Keys.OemTilde && !shift) return '`'; + if (key == Keys.OemTilde && shift) return '~'; + if (key == Keys.Subtract) return '-'; + return null; + } + + } + + } +} \ No newline at end of file