diff --git a/Demos.Android/Activity1.cs b/Demos.Android/Activity1.cs index 4ae7af1..39139b6 100644 --- a/Demos.Android/Activity1.cs +++ b/Demos.Android/Activity1.cs @@ -5,6 +5,7 @@ using Android.Net; using Android.OS; using Android.Views; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; using MLEM.Extensions; using MLEM.Misc; @@ -29,7 +30,7 @@ namespace Demos.Android { if (Build.VERSION.SdkInt >= BuildVersionCodes.P) this.Window.Attributes.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.ShortEdges; - TextInputWrapper.Current = new TextInputWrapper.Mobile(); + TextInputWrapper.Current = new TextInputWrapper.Mobile(KeyboardInput.Show); this.game = new GameImpl(); // reset MlemGame width and height to use device's aspect ratio this.game.GraphicsDeviceManager.ResetWidthAndHeight(this.game.Window); diff --git a/Docs/articles/ui.md b/Docs/articles/ui.md index 1889cf8..c1e78e1 100644 --- a/Docs/articles/ui.md +++ b/Docs/articles/ui.md @@ -34,7 +34,9 @@ protected override void Draw(GameTime gameTime) { ``` ### Text Input -Text input is a bit weird in MonoGame. On Desktop devices, you have the `Window.TextInput` event that gets called automatically with the correct characters for the keys that you're pressing, even for non-American keyboards. However, this function doesn't just *not work* on other devices, it doesn't exist there at all. So, to make MLEM.Ui compatible with all devices without publishing a separate version for each MonoGame system, you have to set up the text input wrapper yourself, based on the system you're using MLEM.Ui with. This has to be done *before* initializing your `UiSystem`. +On desktop devices, MonoGame provides the `Window.TextInput` event that gets called automatically with the correct characters for the keys that you're pressing, even for non-American keyboards. However, this function doesn't exist on other devices. Similarly, MonoGame provides the `KeyboardInput` class for showing an on-screen keyboard on mobile devices and consoles, but not on desktop. + +To make MLEM.Ui compatible with all devices without publishing a separate version for each MonoGame platform, you have to set up the text input wrapper yourself, based on the system you're using MLEM.Ui with. This has to be done *before* initializing your `UiSystem`. DesktopGL: ```cs @@ -42,7 +44,7 @@ TextInputWrapper.Current = new TextInputWrapper.DesktopGl((w ``` Mobile devices and consoles: ```cs -TextInputWrapper.Current = new TextInputWrapper.Mobile(); +TextInputWrapper.Current = new TextInputWrapper.Mobile(KeyboardInput.Show); ``` Other systems. Note that, for this implementation, its `Update()` method also has to be called every game update tick. It only supports an American keyboard layout due to the way that it is implemented: ```cs diff --git a/MLEM.Data/MLEM.Data.csproj b/MLEM.Data/MLEM.Data.csproj index 9b65d51..ee8146b 100644 --- a/MLEM.Data/MLEM.Data.csproj +++ b/MLEM.Data/MLEM.Data.csproj @@ -15,13 +15,14 @@ + + all - + all - all diff --git a/MLEM.Extended/MLEM.Extended.csproj b/MLEM.Extended/MLEM.Extended.csproj index b795e50..9c2eea3 100644 --- a/MLEM.Extended/MLEM.Extended.csproj +++ b/MLEM.Extended/MLEM.Extended.csproj @@ -24,7 +24,7 @@ all - + all diff --git a/MLEM.Startup/MLEM.Startup.csproj b/MLEM.Startup/MLEM.Startup.csproj index 2e15174..deddf6f 100644 --- a/MLEM.Startup/MLEM.Startup.csproj +++ b/MLEM.Startup/MLEM.Startup.csproj @@ -17,7 +17,8 @@ - + + all diff --git a/MLEM.Ui/Elements/TextField.cs b/MLEM.Ui/Elements/TextField.cs index 279a41b..64b5976 100644 --- a/MLEM.Ui/Elements/TextField.cs +++ b/MLEM.Ui/Elements/TextField.cs @@ -17,7 +17,7 @@ namespace MLEM.Ui.Elements { /// /// A text field element for use inside of a . /// A text field is a selectable element that can be typed in, as well as copied and pasted from. - /// If is enabled, then this text field will automatically open an on-screen keyboard when pressed using . + /// If is enabled, then this text field will automatically open an on-screen keyboard when pressed using MonoGame's KeyboardInput class. /// public class TextField : Element { @@ -104,11 +104,11 @@ namespace MLEM.Ui.Elements { /// public Rule InputRule; /// - /// The title of the field on mobile devices and consoles + /// The title of the KeyboardInput field on mobile devices and consoles /// public string MobileTitle; /// - /// The description of the field on mobile devices and consoles + /// The description of the KeyboardInput field on mobile devices and consoles /// public string MobileDescription; private int caretPos; @@ -142,14 +142,12 @@ namespace MLEM.Ui.Elements { this.Font.Set(font); TextInputWrapper.EnsureExists(); - if (TextInputWrapper.Current.RequiresOnScreenKeyboard()) { + if (TextInputWrapper.Current.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); - } + var title = this.MobileTitle ?? this.PlaceholderText; + var result = await TextInputWrapper.Current.OpenOnScreenKeyboard(title, this.MobileDescription, this.Text, false); + if (result != null) + this.SetText(result.Replace('\n', ' '), true); }; } this.OnTextInput += (element, key, character) => { diff --git a/MLEM.Ui/MLEM.Ui.csproj b/MLEM.Ui/MLEM.Ui.csproj index 74b2a5d..84b7f94 100644 --- a/MLEM.Ui/MLEM.Ui.csproj +++ b/MLEM.Ui/MLEM.Ui.csproj @@ -15,10 +15,11 @@ - + + + all - diff --git a/MLEM/Input/InputHandler.cs b/MLEM/Input/InputHandler.cs index 3da5667..c456902 100644 --- a/MLEM/Input/InputHandler.cs +++ b/MLEM/Input/InputHandler.cs @@ -453,6 +453,7 @@ namespace MLEM.Input { return true; } } + sample = default; return false; } diff --git a/MLEM/MLEM.csproj b/MLEM/MLEM.csproj index c3134c2..4bf3ee1 100644 --- a/MLEM/MLEM.csproj +++ b/MLEM/MLEM.csproj @@ -15,7 +15,7 @@ - + all diff --git a/MLEM/Misc/TextInputWrapper.cs b/MLEM/Misc/TextInputWrapper.cs index d3905b8..a7fb3f1 100644 --- a/MLEM/Misc/TextInputWrapper.cs +++ b/MLEM/Misc/TextInputWrapper.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; using MLEM.Input; @@ -20,20 +21,24 @@ namespace MLEM.Misc { /// public static TextInputWrapper Current; - /// - /// 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"); - } - /// /// Determines if this text input wrapper requires an on-screen keyboard. /// - /// If this text input wrapper requires an on-screen keyboard - public abstract bool RequiresOnScreenKeyboard(); + public abstract bool RequiresOnScreenKeyboard { get; } + + /// + /// Opens the on-screen keyboard for this text input wrapper. + /// Note that, if is false, this method should not be called. + /// + /// 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. + /// Thrown if there is no on-screen keyboard to open, or when is false + public virtual Task OpenOnScreenKeyboard(string title, string description, string defaultText, bool usePasswordMode) { + throw new InvalidOperationException(); + } /// /// Adds a text input listener to this text input wrapper. @@ -43,6 +48,15 @@ namespace MLEM.Misc { /// 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 /// @@ -58,12 +72,15 @@ namespace MLEM.Misc { /// /// This listener is initialized as follows: /// - /// new TextInputWrapper.DesktopGl{TextInputEventArgs}((w, c) => w.TextInput += c); + /// new TextInputWrapper.DesktopGl{TextInputEventArgs}((w, c) => w.TextInput += c) /// /// /// public class DesktopGl : TextInputWrapper { + /// + public override bool RequiresOnScreenKeyboard => false; + private FieldInfo key; private FieldInfo character; private readonly Action> addListener; @@ -76,11 +93,6 @@ namespace MLEM.Misc { this.addListener = addListener; } - /// - public override bool RequiresOnScreenKeyboard() { - return false; - } - /// public override void AddListener(GameWindow window, TextInputCallback callback) { this.addListener(window, (sender, args) => { @@ -96,19 +108,48 @@ namespace MLEM.Misc { /// /// A text input wrapper for mobile platforms as well as consoles. - /// This text input wrapper performs no actions itself, as it signals that an on-screen keyboard is required. + /// 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 { /// - public override bool RequiresOnScreenKeyboard() { - return true; + public override bool RequiresOnScreenKeyboard => true; + + 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); + } /// @@ -118,9 +159,7 @@ namespace MLEM.Misc { public class None : TextInputWrapper { /// - public override bool RequiresOnScreenKeyboard() { - return false; - } + public override bool RequiresOnScreenKeyboard => false; /// public override void AddListener(GameWindow window, TextInputCallback callback) { @@ -136,6 +175,9 @@ namespace MLEM.Misc { /// public class Primitive : TextInputWrapper { + /// + public override bool RequiresOnScreenKeyboard => false; + private TextInputCallback callback; /// @@ -152,11 +194,6 @@ namespace MLEM.Misc { } } - /// - public override bool RequiresOnScreenKeyboard() { - return false; - } - /// public override void AddListener(GameWindow window, TextInputCallback callback) { this.callback += callback;