1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-11-22 12:58:33 +01:00

added a new text input system

This commit is contained in:
Ellpeck 2020-02-24 14:03:53 +01:00
parent 719bdc7176
commit 08c4281da1
7 changed files with 201 additions and 79 deletions

View file

@ -2,6 +2,7 @@ using Android.App;
using Android.Content.PM; using Android.Content.PM;
using Android.OS; using Android.OS;
using Android.Views; using Android.Views;
using MLEM.Misc;
namespace Demos.Android { namespace Demos.Android {
[Activity(Label = "Demos.Android" [Activity(Label = "Demos.Android"
@ -16,6 +17,7 @@ namespace Demos.Android {
protected override void OnCreate(Bundle bundle) { protected override void OnCreate(Bundle bundle) {
base.OnCreate(bundle); base.OnCreate(bundle);
TextInputWrapper.Current = new TextInputWrapper.Mobile();
var g = new GameImpl(); var g = new GameImpl();
// disable mouse handling for android to make emulator behavior more coherent // disable mouse handling for android to make emulator behavior more coherent
g.OnLoadContent += game => game.InputHandler.HandleMouse = false; g.OnLoadContent += game => game.InputHandler.HandleMouse = false;

View file

@ -1,9 +1,11 @@
using System; using Microsoft.Xna.Framework;
using MLEM.Misc;
namespace Demos.DesktopGL { namespace Demos.DesktopGL {
public static class Program { public static class Program {
public static void Main() { public static void Main() {
TextInputWrapper.Current = new TextInputWrapper.DesktopGl<TextInputEventArgs>((w, c) => w.TextInput += c);
using (var game = new GameImpl()) using (var game = new GameImpl())
game.Run(); game.Run();
} }

View file

@ -7,6 +7,7 @@ using Microsoft.Xna.Framework.Input;
using MLEM.Extensions; using MLEM.Extensions;
using MLEM.Font; using MLEM.Font;
using MLEM.Input; using MLEM.Input;
using MLEM.Misc;
using MLEM.Textures; using MLEM.Textures;
using MLEM.Ui.Style; using MLEM.Ui.Style;
@ -53,22 +54,7 @@ namespace MLEM.Ui.Elements {
if (font != null) if (font != null)
this.Font.Set(font); this.Font.Set(font);
if (WindowExtensions.SupportsTextInput()) { if (TextInputWrapper.Current.RequiresOnScreenKeyboard()) {
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 {
this.OnPressed += async e => { this.OnPressed += async e => {
if (!KeyboardInput.IsVisible) { if (!KeyboardInput.IsVisible) {
var title = this.MobileTitle ?? this.PlaceholderText; 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.OnDeselected += e => this.CaretPos = 0;
this.OnSelected += e => this.CaretPos = this.text.Length; this.OnSelected += e => this.CaretPos = this.text.Length;
} }

View file

@ -35,7 +35,7 @@ namespace MLEM.Ui {
root.Element.ForceUpdateArea(); root.Element.ForceUpdateArea();
} }
} }
private UiStyle style; private UiStyle style;
public UiStyle Style { public UiStyle Style {
get => this.style; get => this.style;
@ -81,7 +81,7 @@ namespace MLEM.Ui {
root.Element.ForceUpdateArea(); 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.OnMousedElementChanged = e => this.ApplyToAll(t => t.OnMousedElementChanged?.Invoke(t, e));
this.OnSelectedElementChanged = e => this.ApplyToAll(t => t.OnSelectedElementChanged?.Invoke(t, e)); this.OnSelectedElementChanged = e => this.ApplyToAll(t => t.OnSelectedElementChanged?.Invoke(t, e));
this.OnSelectedElementDrawn = (element, time, batch, alpha) => { this.OnSelectedElementDrawn = (element, time, batch, alpha) => {

View file

@ -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));
}
}
}
}

View file

@ -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<TextInputEventArgs>((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<T> : TextInputWrapper where T : EventArgs {
private PropertyInfo key;
private PropertyInfo character;
private readonly Action<GameWindow, EventHandler<T>> addListener;
public DesktopGl(Action<GameWindow, EventHandler<T>> 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;
}
}
}
}

View file

@ -1,7 +1,11 @@
namespace Sandbox { using Microsoft.Xna.Framework;
using MLEM.Misc;
namespace Sandbox {
internal static class Program { internal static class Program {
private static void Main() { private static void Main() {
TextInputWrapper.Current = new TextInputWrapper.DesktopGl<TextInputEventArgs>((w, c) => w.TextInput += c);
using (var game = new GameImpl()) using (var game = new GameImpl())
game.Run(); game.Run();
} }