mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-12-24 17:29:23 +01:00
updated to MonoGame 8 and added support for opening the on-screen keyboard to TextInputWrapper
This commit is contained in:
parent
5e26155ec5
commit
cf9bcc7ae4
10 changed files with 90 additions and 48 deletions
|
@ -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);
|
||||
|
|
|
@ -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<TextInputEventArgs>((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
|
||||
|
|
|
@ -15,13 +15,14 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
|
||||
<PackageReference Include="Lidgren.Network" Version="1.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MonoGame.Framework.Portable" Version="3.7.1.189">
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json.Bson" Version="1.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<PackageReference Include="FontStashSharp.MonoGame" Version="0.9.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MonoGame.Framework.Portable" Version="3.7.1.189">
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
|
||||
<ItemGroup>
|
||||
<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>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace MLEM.Ui.Elements {
|
|||
/// <summary>
|
||||
/// 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.
|
||||
/// 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>
|
||||
public class TextField : Element {
|
||||
|
||||
|
@ -104,11 +104,11 @@ namespace MLEM.Ui.Elements {
|
|||
/// </summary>
|
||||
public Rule InputRule;
|
||||
/// <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>
|
||||
public string MobileTitle;
|
||||
/// <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>
|
||||
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) => {
|
||||
|
|
|
@ -15,10 +15,11 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<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>
|
||||
</PackageReference>
|
||||
<PackageReference Include="TextCopy" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -453,6 +453,7 @@ namespace MLEM.Input {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
sample = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MonoGame.Framework.Portable" Version="3.7.1.189">
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
|
|
@ -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 {
|
|||
/// <exception cref="InvalidOperationException"></exception>
|
||||
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>
|
||||
/// Determines if this text input wrapper requires an on-screen keyboard.
|
||||
/// </summary>
|
||||
/// <returns>If this text input wrapper requires an on-screen keyboard</returns>
|
||||
public abstract bool RequiresOnScreenKeyboard();
|
||||
public abstract bool RequiresOnScreenKeyboard { get; }
|
||||
|
||||
/// <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>
|
||||
/// 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>
|
||||
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>
|
||||
/// A delegate method that can be used for <see cref="TextInputWrapper.AddListener"/>
|
||||
/// </summary>
|
||||
|
@ -58,12 +72,15 @@ namespace MLEM.Misc {
|
|||
/// <example>
|
||||
/// This listener is initialized as follows:
|
||||
/// <code>
|
||||
/// new TextInputWrapper.DesktopGl{TextInputEventArgs}((w, c) => w.TextInput += c);
|
||||
/// new TextInputWrapper.DesktopGl{TextInputEventArgs}((w, c) => w.TextInput += c)
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class DesktopGl<T> : TextInputWrapper {
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool RequiresOnScreenKeyboard => false;
|
||||
|
||||
private FieldInfo key;
|
||||
private FieldInfo character;
|
||||
private readonly Action<GameWindow, EventHandler<T>> addListener;
|
||||
|
@ -76,11 +93,6 @@ namespace MLEM.Misc {
|
|||
this.addListener = addListener;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool RequiresOnScreenKeyboard() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void AddListener(GameWindow window, TextInputCallback callback) {
|
||||
this.addListener(window, (sender, args) => {
|
||||
|
@ -96,19 +108,48 @@ namespace MLEM.Misc {
|
|||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <example>
|
||||
/// This listener is initialized as follows:
|
||||
/// <code>
|
||||
/// new TextInputWrapper.Mobile(Microsoft.Xna.Framework.Input.KeyboardInput.Show)
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class Mobile : TextInputWrapper {
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool RequiresOnScreenKeyboard() {
|
||||
return true;
|
||||
public override bool RequiresOnScreenKeyboard => 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 />
|
||||
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>
|
||||
|
@ -118,9 +159,7 @@ namespace MLEM.Misc {
|
|||
public class None : TextInputWrapper {
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool RequiresOnScreenKeyboard() {
|
||||
return false;
|
||||
}
|
||||
public override bool RequiresOnScreenKeyboard => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void AddListener(GameWindow window, TextInputCallback callback) {
|
||||
|
@ -136,6 +175,9 @@ namespace MLEM.Misc {
|
|||
/// </summary>
|
||||
public class Primitive : TextInputWrapper {
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool RequiresOnScreenKeyboard => false;
|
||||
|
||||
private TextInputCallback callback;
|
||||
|
||||
/// <summary>
|
||||
|
@ -152,11 +194,6 @@ namespace MLEM.Misc {
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool RequiresOnScreenKeyboard() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void AddListener(GameWindow window, TextInputCallback callback) {
|
||||
this.callback += callback;
|
||||
|
|
Loading…
Reference in a new issue