using System; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; using MLEM.Input; namespace MLEM.Misc { /// /// A text input wrapper is a wrapper around MonoGame's built-in text input event. /// Since said text input event does not exist on non-Desktop devices, we want to wrap it in a wrapper that is platform-independent for MLEM. /// See subclasses of this wrapper or for more info. /// public abstract class TextInputWrapper { /// /// The current text input wrapper. /// Set this value before starting your game if you want to use text input wrapping. /// /// public static TextInputWrapper Current; /// /// Opens the on-screen keyboard for this text input wrapper. /// Note that, if no on-screen keyboard is required, a null string should be returned. /// /// Title of the dialog box. /// Description of the dialog box. /// Default text displayed in the input area. /// If password mode is enabled, the characters entered are not displayed. /// Text entered by the player. Null if back was used. public abstract Task OpenOnScreenKeyboard(string title, string description, string defaultText, bool usePasswordMode); /// /// Adds a text input listener to this text input wrapper. /// The supplied listener will be called whenever a character is input. /// /// The game's window /// The callback that should be called whenever a character is pressed public abstract void AddListener(GameWindow window, TextInputCallback callback); /// /// Ensures that is set to a valid value by throwing an exception if is null. /// /// If is null public static void EnsureExists() { if (Current == null) throw new InvalidOperationException("The TextInputWrapper was not initialized. For more information, see https://mlem.ellpeck.de/articles/ui.html#text-input"); } /// /// A delegate method that can be used for /// /// The object that sent the event. The used in most cases. /// The key that was pressed /// The character that corresponds to that key public delegate void TextInputCallback(object sender, Keys key, char character); /// /// A text input wrapper for DesktopGL devices. /// This wrapper uses the built-in MonoGame TextInput event, which makes this listener work with any keyboard localization natively. /// /// /// This listener is initialized as follows: /// /// new TextInputWrapper.DesktopGl{TextInputEventArgs}((w, c) => w.TextInput += c) /// /// /// public class DesktopGl : TextInputWrapper { private FieldInfo key; private FieldInfo character; private readonly Action> addListener; /// /// Creates a new DesktopGL-based text input wrapper /// /// The function that is used to add a text input listener public DesktopGl(Action> addListener) { this.addListener = addListener; } /// public override Task OpenOnScreenKeyboard(string title, string description, string defaultText, bool usePasswordMode) { return Task.FromResult(null); } /// public override void AddListener(GameWindow window, TextInputCallback callback) { this.addListener(window, (sender, args) => { if (this.key == null) this.key = args.GetType().GetField("Key"); if (this.character == null) this.character = args.GetType().GetField("Character"); callback.Invoke(sender, (Keys) this.key.GetValue(args), (char) this.character.GetValue(args)); }); } } /// /// A text input wrapper for mobile platforms as well as consoles. /// This text input wrapper opens an on-screen keyboard using the KeyboardInput class on mobile devices. /// /// /// This listener is initialized as follows: /// /// new TextInputWrapper.Mobile(Microsoft.Xna.Framework.Input.KeyboardInput.Show) /// /// public class Mobile : TextInputWrapper { private readonly OpenOnScreenKeyboardDelegate openOnScreenKeyboard; /// /// Creates a new mobile- and console-based text input wrapper /// /// The function that is used to display the on-screen keyboard public Mobile(OpenOnScreenKeyboardDelegate openOnScreenKeyboard) { this.openOnScreenKeyboard = openOnScreenKeyboard; } /// public override Task OpenOnScreenKeyboard(string title, string description, string defaultText, bool usePasswordMode) { return this.openOnScreenKeyboard(title, description, defaultText, usePasswordMode); } /// public override void AddListener(GameWindow window, TextInputCallback callback) { } /// /// A delegate method used for /// /// Title of the dialog box. /// Description of the dialog box. /// Default text displayed in the input area. /// If password mode is enabled, the characters entered are not displayed. /// Text entered by the player. Null if back was used. public delegate Task OpenOnScreenKeyboardDelegate(string title, string description, string defaultText, bool usePasswordMode); } /// /// A text input wrapper that does nothing. /// This can be used if no text input is required for the game. /// public class None : TextInputWrapper { /// public override Task OpenOnScreenKeyboard(string title, string description, string defaultText, bool usePasswordMode) { return Task.FromResult(null); } /// public override void AddListener(GameWindow window, TextInputCallback callback) { } } /// /// A primitive text input wrapper that is locked to the American keyboard localization. /// Only use this text input wrapper if is unavailable for some reason. /// /// Note that, when using this text input wrapper, its method has to be called periodically. /// public class Primitive : TextInputWrapper { private TextInputCallback callback; /// /// Updates this text input wrapper by querying pressed keys and sending corresponding input events. /// /// The input handler to use for text input querying 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 Task OpenOnScreenKeyboard(string title, string description, string defaultText, bool usePasswordMode) { return Task.FromResult(null); } /// 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; } } } }