1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-11-25 22:18:34 +01:00

updated to MonoGame 8 and added support for opening the on-screen keyboard to TextInputWrapper

This commit is contained in:
Ell 2021-04-22 19:26:07 +02:00
parent 5e26155ec5
commit cf9bcc7ae4
10 changed files with 90 additions and 48 deletions

View file

@ -5,6 +5,7 @@ using Android.Net;
using Android.OS; using Android.OS;
using Android.Views; using Android.Views;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using MLEM.Extensions; using MLEM.Extensions;
using MLEM.Misc; using MLEM.Misc;
@ -29,7 +30,7 @@ namespace Demos.Android {
if (Build.VERSION.SdkInt >= BuildVersionCodes.P) if (Build.VERSION.SdkInt >= BuildVersionCodes.P)
this.Window.Attributes.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.ShortEdges; this.Window.Attributes.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.ShortEdges;
TextInputWrapper.Current = new TextInputWrapper.Mobile(); TextInputWrapper.Current = new TextInputWrapper.Mobile(KeyboardInput.Show);
this.game = new GameImpl(); this.game = new GameImpl();
// reset MlemGame width and height to use device's aspect ratio // reset MlemGame width and height to use device's aspect ratio
this.game.GraphicsDeviceManager.ResetWidthAndHeight(this.game.Window); this.game.GraphicsDeviceManager.ResetWidthAndHeight(this.game.Window);

View file

@ -34,7 +34,9 @@ protected override void Draw(GameTime gameTime) {
``` ```
### Text Input ### 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: DesktopGL:
```cs ```cs
@ -42,7 +44,7 @@ TextInputWrapper.Current = new TextInputWrapper.DesktopGl<TextInputEventArgs>((w
``` ```
Mobile devices and consoles: Mobile devices and consoles:
```cs ```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: 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 ```cs

View file

@ -15,13 +15,14 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Lidgren.Network" Version="1.0.2"> <PackageReference Include="Lidgren.Network" Version="1.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="MonoGame.Framework.Portable" Version="3.7.1.189"> <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Newtonsoft.Json.Bson" Version="1.0.2"> <PackageReference Include="Newtonsoft.Json.Bson" Version="1.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>

View file

@ -24,7 +24,7 @@
<PackageReference Include="FontStashSharp.MonoGame" Version="0.9.2"> <PackageReference Include="FontStashSharp.MonoGame" Version="0.9.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="MonoGame.Framework.Portable" Version="3.7.1.189"> <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>

View file

@ -17,7 +17,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Coroutine" Version="2.1.0" /> <PackageReference Include="Coroutine" Version="2.1.0" />
<PackageReference Include="MonoGame.Framework.Portable" Version="3.7.1.189">
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>

View file

@ -17,7 +17,7 @@ namespace MLEM.Ui.Elements {
/// <summary> /// <summary>
/// A text field element for use inside of a <see cref="UiSystem"/>. /// A text field element for use inside of a <see cref="UiSystem"/>.
/// A text field is a selectable element that can be typed in, as well as copied and pasted from. /// A text field is a selectable element that can be typed in, as well as copied and pasted from.
/// If <see cref="TextInputWrapper.RequiresOnScreenKeyboard"/> is enabled, then this text field will automatically open an on-screen keyboard when pressed using <see cref="KeyboardInput"/>. /// If <see cref="TextInputWrapper.RequiresOnScreenKeyboard"/> is enabled, then this text field will automatically open an on-screen keyboard when pressed using MonoGame's <c>KeyboardInput</c> class.
/// </summary> /// </summary>
public class TextField : Element { public class TextField : Element {
@ -104,11 +104,11 @@ namespace MLEM.Ui.Elements {
/// </summary> /// </summary>
public Rule InputRule; public Rule InputRule;
/// <summary> /// <summary>
/// The title of the <see cref="KeyboardInput"/> field on mobile devices and consoles /// The title of the <c>KeyboardInput</c> field on mobile devices and consoles
/// </summary> /// </summary>
public string MobileTitle; public string MobileTitle;
/// <summary> /// <summary>
/// The description of the <see cref="KeyboardInput"/> field on mobile devices and consoles /// The description of the <c>KeyboardInput</c> field on mobile devices and consoles
/// </summary> /// </summary>
public string MobileDescription; public string MobileDescription;
private int caretPos; private int caretPos;
@ -142,14 +142,12 @@ namespace MLEM.Ui.Elements {
this.Font.Set(font); this.Font.Set(font);
TextInputWrapper.EnsureExists(); TextInputWrapper.EnsureExists();
if (TextInputWrapper.Current.RequiresOnScreenKeyboard()) { if (TextInputWrapper.Current.RequiresOnScreenKeyboard) {
this.OnPressed += async e => { this.OnPressed += async e => {
if (!KeyboardInput.IsVisible) {
var title = this.MobileTitle ?? this.PlaceholderText; var title = this.MobileTitle ?? this.PlaceholderText;
var result = await KeyboardInput.Show(title, this.MobileDescription, this.Text); var result = await TextInputWrapper.Current.OpenOnScreenKeyboard(title, this.MobileDescription, this.Text, false);
if (result != null) if (result != null)
this.SetText(result.Replace('\n', ' '), true); this.SetText(result.Replace('\n', ' '), true);
}
}; };
} }
this.OnTextInput += (element, key, character) => { this.OnTextInput += (element, key, character) => {

View file

@ -15,10 +15,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MonoGame.Framework.Portable" Version="3.7.1.189"> <PackageReference Include="TextCopy" Version="4.3.0" />
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="TextCopy" Version="4.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -453,6 +453,7 @@ namespace MLEM.Input {
return true; return true;
} }
} }
sample = default;
return false; return false;
} }

View file

@ -15,7 +15,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MonoGame.Framework.Portable" Version="3.7.1.189"> <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
using MLEM.Input; using MLEM.Input;
@ -20,20 +21,24 @@ namespace MLEM.Misc {
/// <exception cref="InvalidOperationException"></exception> /// <exception cref="InvalidOperationException"></exception>
public static TextInputWrapper Current; public static TextInputWrapper Current;
/// <summary>
/// Ensures that <see cref="Current"/> is set to a valid <see cref="TextInputWrapper"/> value by throwing an <see cref="InvalidOperationException"/> exception if <see cref="Current"/> is null.
/// </summary>
/// <exception cref="InvalidOperationException">If <see cref="Current"/> is null</exception>
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");
}
/// <summary> /// <summary>
/// Determines if this text input wrapper requires an on-screen keyboard. /// Determines if this text input wrapper requires an on-screen keyboard.
/// </summary> /// </summary>
/// <returns>If this text input wrapper requires an on-screen keyboard</returns> public abstract bool RequiresOnScreenKeyboard { get; }
public abstract bool RequiresOnScreenKeyboard();
/// <summary>
/// Opens the on-screen keyboard for this text input wrapper.
/// Note that, if <see cref="RequiresOnScreenKeyboard"/> is false, this method should not be called.
/// </summary>
/// <param name="title">Title of the dialog box.</param>
/// <param name="description">Description of the dialog box.</param>
/// <param name="defaultText">Default text displayed in the input area.</param>
/// <param name="usePasswordMode">If password mode is enabled, the characters entered are not displayed.</param>
/// <returns>Text entered by the player. Null if back was used.</returns>
/// <exception cref="InvalidOperationException">Thrown if there is no on-screen keyboard to open, or when <see cref="RequiresOnScreenKeyboard"/> is false</exception>
public virtual Task<string> OpenOnScreenKeyboard(string title, string description, string defaultText, bool usePasswordMode) {
throw new InvalidOperationException();
}
/// <summary> /// <summary>
/// Adds a text input listener to this text input wrapper. /// Adds a text input listener to this text input wrapper.
@ -43,6 +48,15 @@ namespace MLEM.Misc {
/// <param name="callback">The callback that should be called whenever a character is pressed</param> /// <param name="callback">The callback that should be called whenever a character is pressed</param>
public abstract void AddListener(GameWindow window, TextInputCallback callback); public abstract void AddListener(GameWindow window, TextInputCallback callback);
/// <summary>
/// Ensures that <see cref="Current"/> is set to a valid <see cref="TextInputWrapper"/> value by throwing an <see cref="InvalidOperationException"/> exception if <see cref="Current"/> is null.
/// </summary>
/// <exception cref="InvalidOperationException">If <see cref="Current"/> is null</exception>
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");
}
/// <summary> /// <summary>
/// A delegate method that can be used for <see cref="TextInputWrapper.AddListener"/> /// A delegate method that can be used for <see cref="TextInputWrapper.AddListener"/>
/// </summary> /// </summary>
@ -58,12 +72,15 @@ namespace MLEM.Misc {
/// <example> /// <example>
/// This listener is initialized as follows: /// This listener is initialized as follows:
/// <code> /// <code>
/// new TextInputWrapper.DesktopGl{TextInputEventArgs}((w, c) => w.TextInput += c); /// new TextInputWrapper.DesktopGl{TextInputEventArgs}((w, c) => w.TextInput += c)
/// </code> /// </code>
/// </example> /// </example>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
public class DesktopGl<T> : TextInputWrapper { public class DesktopGl<T> : TextInputWrapper {
/// <inheritdoc />
public override bool RequiresOnScreenKeyboard => false;
private FieldInfo key; private FieldInfo key;
private FieldInfo character; private FieldInfo character;
private readonly Action<GameWindow, EventHandler<T>> addListener; private readonly Action<GameWindow, EventHandler<T>> addListener;
@ -76,11 +93,6 @@ namespace MLEM.Misc {
this.addListener = addListener; this.addListener = addListener;
} }
/// <inheritdoc />
public override bool RequiresOnScreenKeyboard() {
return false;
}
/// <inheritdoc /> /// <inheritdoc />
public override void AddListener(GameWindow window, TextInputCallback callback) { public override void AddListener(GameWindow window, TextInputCallback callback) {
this.addListener(window, (sender, args) => { this.addListener(window, (sender, args) => {
@ -96,19 +108,48 @@ namespace MLEM.Misc {
/// <summary> /// <summary>
/// A text input wrapper for mobile platforms as well as consoles. /// 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 <see cref="Microsoft.Xna.Framework.Input"/> <c>KeyboardInput</c> class on mobile devices.
/// </summary> /// </summary>
/// <example>
/// This listener is initialized as follows:
/// <code>
/// new TextInputWrapper.Mobile(Microsoft.Xna.Framework.Input.KeyboardInput.Show)
/// </code>
/// </example>
public class Mobile : TextInputWrapper { public class Mobile : TextInputWrapper {
/// <inheritdoc /> /// <inheritdoc />
public override bool RequiresOnScreenKeyboard() { public override bool RequiresOnScreenKeyboard => true;
return true;
private readonly OpenOnScreenKeyboardDelegate openOnScreenKeyboard;
/// <summary>
/// Creates a new mobile- and console-based text input wrapper
/// </summary>
/// <param name="openOnScreenKeyboard">The function that is used to display the on-screen keyboard</param>
public Mobile(OpenOnScreenKeyboardDelegate openOnScreenKeyboard) {
this.openOnScreenKeyboard = openOnScreenKeyboard;
}
/// <inheritdoc />
public override Task<string> OpenOnScreenKeyboard(string title, string description, string defaultText, bool usePasswordMode) {
return this.openOnScreenKeyboard(title, description, defaultText, usePasswordMode);
} }
/// <inheritdoc /> /// <inheritdoc />
public override void AddListener(GameWindow window, TextInputCallback callback) { public override void AddListener(GameWindow window, TextInputCallback callback) {
} }
/// <summary>
/// A delegate method used for <see cref="Mobile.OpenOnScreenKeyboard"/>
/// </summary>
/// <param name="title">Title of the dialog box.</param>
/// <param name="description">Description of the dialog box.</param>
/// <param name="defaultText">Default text displayed in the input area.</param>
/// <param name="usePasswordMode">If password mode is enabled, the characters entered are not displayed.</param>
/// <returns>Text entered by the player. Null if back was used.</returns>
public delegate Task<string> OpenOnScreenKeyboardDelegate(string title, string description, string defaultText, bool usePasswordMode);
} }
/// <summary> /// <summary>
@ -118,9 +159,7 @@ namespace MLEM.Misc {
public class None : TextInputWrapper { public class None : TextInputWrapper {
/// <inheritdoc /> /// <inheritdoc />
public override bool RequiresOnScreenKeyboard() { public override bool RequiresOnScreenKeyboard => false;
return false;
}
/// <inheritdoc /> /// <inheritdoc />
public override void AddListener(GameWindow window, TextInputCallback callback) { public override void AddListener(GameWindow window, TextInputCallback callback) {
@ -136,6 +175,9 @@ namespace MLEM.Misc {
/// </summary> /// </summary>
public class Primitive : TextInputWrapper { public class Primitive : TextInputWrapper {
/// <inheritdoc />
public override bool RequiresOnScreenKeyboard => false;
private TextInputCallback callback; private TextInputCallback callback;
/// <summary> /// <summary>
@ -152,11 +194,6 @@ namespace MLEM.Misc {
} }
} }
/// <inheritdoc />
public override bool RequiresOnScreenKeyboard() {
return false;
}
/// <inheritdoc /> /// <inheritdoc />
public override void AddListener(GameWindow window, TextInputCallback callback) { public override void AddListener(GameWindow window, TextInputCallback callback) {
this.callback += callback; this.callback += callback;