diff --git a/Demos.Android/Activity1.cs b/Demos.Android/Activity1.cs index 3de36d3..30426cf 100644 --- a/Demos.Android/Activity1.cs +++ b/Demos.Android/Activity1.cs @@ -2,6 +2,7 @@ using Android.App; using Android.Content.PM; using Android.OS; using Android.Views; +using MLEM.Misc; namespace Demos.Android { [Activity(Label = "Demos.Android" @@ -16,6 +17,7 @@ namespace Demos.Android { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); + TextInputWrapper.Current = new TextInputWrapper.Mobile(); var g = new GameImpl(); // disable mouse handling for android to make emulator behavior more coherent g.OnLoadContent += game => game.InputHandler.HandleMouse = false; diff --git a/Demos.DesktopGL/Program.cs b/Demos.DesktopGL/Program.cs index 379d31e..d6abd58 100644 --- a/Demos.DesktopGL/Program.cs +++ b/Demos.DesktopGL/Program.cs @@ -1,9 +1,11 @@ -using System; +using Microsoft.Xna.Framework; +using MLEM.Misc; namespace Demos.DesktopGL { public static class Program { public static void Main() { + TextInputWrapper.Current = new TextInputWrapper.DesktopGl((w, c) => w.TextInput += c); using (var game = new GameImpl()) game.Run(); } diff --git a/MLEM.Ui/Elements/TextField.cs b/MLEM.Ui/Elements/TextField.cs index 8364612..559ba37 100644 --- a/MLEM.Ui/Elements/TextField.cs +++ b/MLEM.Ui/Elements/TextField.cs @@ -7,6 +7,7 @@ using Microsoft.Xna.Framework.Input; using MLEM.Extensions; using MLEM.Font; using MLEM.Input; +using MLEM.Misc; using MLEM.Textures; using MLEM.Ui.Style; @@ -53,22 +54,7 @@ namespace MLEM.Ui.Elements { if (font != null) this.Font.Set(font); - if (WindowExtensions.SupportsTextInput()) { - this.OnTextInput += (element, key, character) => { - if (!this.IsSelected || this.IsHidden) - return; - if (key == Keys.Back) { - if (this.CaretPos > 0) { - this.CaretPos--; - this.RemoveText(this.CaretPos, 1); - } - } else if (key == Keys.Delete) { - this.RemoveText(this.CaretPos, 1); - } else { - this.InsertText(character); - } - }; - } else { + if (TextInputWrapper.Current.RequiresOnScreenKeyboard()) { this.OnPressed += async e => { if (!KeyboardInput.IsVisible) { var title = this.MobileTitle ?? this.PlaceholderText; @@ -78,6 +64,20 @@ namespace MLEM.Ui.Elements { } }; } + this.OnTextInput += (element, key, character) => { + if (!this.IsSelected || this.IsHidden) + return; + if (key == Keys.Back) { + if (this.CaretPos > 0) { + this.CaretPos--; + this.RemoveText(this.CaretPos, 1); + } + } else if (key == Keys.Delete) { + this.RemoveText(this.CaretPos, 1); + } else { + this.InsertText(character); + } + }; this.OnDeselected += e => this.CaretPos = 0; this.OnSelected += e => this.CaretPos = this.text.Length; } diff --git a/MLEM.Ui/UiSystem.cs b/MLEM.Ui/UiSystem.cs index ed91cd1..1187fb9 100644 --- a/MLEM.Ui/UiSystem.cs +++ b/MLEM.Ui/UiSystem.cs @@ -35,7 +35,7 @@ namespace MLEM.Ui { root.Element.ForceUpdateArea(); } } - + private UiStyle style; public UiStyle Style { get => this.style; @@ -81,7 +81,7 @@ namespace MLEM.Ui { root.Element.ForceUpdateArea(); }; - window.AddTextInputListener((sender, key, character) => this.ApplyToAll(e => e.OnTextInput?.Invoke(e, key, character))); + TextInputWrapper.Current.AddListener(window, (sender, 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 3016cf5..0000000 --- a/MLEM/Extensions/WindowExtensions.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Input; - -namespace MLEM.Extensions { - public static class WindowExtensions { - - private static readonly EventInfo TextInput = typeof(GameWindow).GetEvent("TextInput"); - - public static bool AddTextInputListener(this GameWindow window, TextInputCallback callback) { - return new TextInputReflector(callback).AddToWindow(window); - } - - public static bool SupportsTextInput() { - if (TextInput == null) - return false; - // The newest version of DesktopGL.Core made a change where TextInputEventArgs doesn't extend EventArgs - // anymore, making this reflection system incompatible with it. For now, this just disables text input - // meaning that MLEM.Ui text boxes won't work, but at least it won't crash either. - // Let's hope there'll be one last update to DesktopGL that also introduces this change so we can fix this. - if (!typeof(EventArgs).IsAssignableFrom(TextInput.EventHandlerType.GenericTypeArguments.FirstOrDefault())) - return false; - return true; - } - - public delegate void TextInputCallback(object sender, Keys key, char character); - - private class TextInputReflector { - - private static readonly MethodInfo Callback = typeof(TextInputReflector).GetMethod(nameof(OnTextInput), BindingFlags.Instance | BindingFlags.NonPublic); - private static PropertyInfo key; - private static PropertyInfo character; - private readonly TextInputCallback callback; - - public TextInputReflector(TextInputCallback callback) { - this.callback = callback; - } - - public bool AddToWindow(GameWindow window) { - if (!SupportsTextInput()) - return false; - TextInput.AddEventHandler(window, Delegate.CreateDelegate(TextInput.EventHandlerType, this, Callback)); - return true; - } - - private void OnTextInput(object sender, object args) { - if (key == null) - key = args.GetType().GetProperty("Key"); - if (character == null) - character = args.GetType().GetProperty("Character"); - this.callback.Invoke(sender, (Keys) key.GetValue(args), (char) character.GetValue(args)); - } - - } - - } -} \ No newline at end of file diff --git a/MLEM/Misc/TextInputWrapper.cs b/MLEM/Misc/TextInputWrapper.cs new file mode 100644 index 0000000..5068bd2 --- /dev/null +++ b/MLEM/Misc/TextInputWrapper.cs @@ -0,0 +1,173 @@ +using System; +using System.Linq; +using System.Reflection; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using MLEM.Input; + +namespace MLEM.Misc { + public abstract class TextInputWrapper { + + private static TextInputWrapper current; + public static TextInputWrapper Current { + get { + if (current == null) + throw new InvalidOperationException("The TextInputWrapper was not initialized yet. Please do so before running your game like so:\n" + + "DesktopGL: TextInputWrapper.Current = new TextInputWrapper.DesktopGl((w, c) => w.TextInput += c);\n" + + "Mobile and Consoles: TextInputWrapper.Current = new TextInputWrapper.Mobile();\n" + + "Other Systems: TextInputWrapper.Current = new TextInputWrapper.Primitive();\n" + + "Note that the primitive wrapper needs to have its Update() method called."); + return current; + } + set => current = value; + } + + public abstract bool RequiresOnScreenKeyboard(); + + public abstract void AddListener(GameWindow window, TextInputCallback callback); + + public delegate void TextInputCallback(object sender, Keys key, char character); + + public class DesktopGl : TextInputWrapper where T : EventArgs { + + private PropertyInfo key; + private PropertyInfo character; + private readonly Action> addListener; + + public DesktopGl(Action> addListener) { + this.addListener = addListener; + } + + public override bool RequiresOnScreenKeyboard() { + return false; + } + + public override void AddListener(GameWindow window, TextInputCallback callback) { + this.addListener(window, (sender, args) => { + if (this.key == null) + this.key = args.GetType().GetProperty("Key"); + if (this.character == null) + this.character = args.GetType().GetProperty("Character"); + callback.Invoke(sender, (Keys) this.key.GetValue(args), (char) this.character.GetValue(args)); + }); + } + + } + + public class Mobile : TextInputWrapper { + + public override bool RequiresOnScreenKeyboard() { + return true; + } + + public override void AddListener(GameWindow window, TextInputCallback callback) { + } + + } + + public class Primitive : TextInputWrapper { + + private TextInputCallback callback; + + public void Update(InputHandler handler) { + var pressed = handler.KeyboardState.GetPressedKeys().Except(handler.LastKeyboardState.GetPressedKeys()); + var shift = handler.IsModifierKeyDown(ModifierKey.Shift); + foreach (var key in pressed) { + var c = GetChar(key, shift); + if (c.HasValue) + this.callback?.Invoke(this, key, c.Value); + } + } + + public override bool RequiresOnScreenKeyboard() { + return false; + } + + public override void AddListener(GameWindow window, TextInputCallback callback) { + this.callback += callback; + } + + 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 diff --git a/Sandbox/Program.cs b/Sandbox/Program.cs index 5f7b7fa..cd8cb41 100644 --- a/Sandbox/Program.cs +++ b/Sandbox/Program.cs @@ -1,7 +1,11 @@ -namespace Sandbox { +using Microsoft.Xna.Framework; +using MLEM.Misc; + +namespace Sandbox { internal static class Program { private static void Main() { + TextInputWrapper.Current = new TextInputWrapper.DesktopGl((w, c) => w.TextInput += c); using (var game = new GameImpl()) game.Run(); }