mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-22 12:58:33 +01:00
streamlined TextInputWrapper into MlemPlatform and included link opening
This commit is contained in:
parent
1123b815b3
commit
b48ed479a0
12 changed files with 211 additions and 315 deletions
|
@ -30,16 +30,12 @@ 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(KeyboardInput.Show);
|
MlemPlatform.Current = new MlemPlatform.Mobile(KeyboardInput.Show, l => this.StartActivity(new Intent(Intent.ActionView, Uri.Parse(l))));
|
||||||
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);
|
||||||
this.game.OnLoadContent += game => {
|
|
||||||
// disable mouse handling for android to make emulator behavior more coherent
|
// disable mouse handling for android to make emulator behavior more coherent
|
||||||
game.InputHandler.HandleMouse = false;
|
this.game.OnLoadContent += game => game.InputHandler.HandleMouse = false;
|
||||||
// make text links be opened properly
|
|
||||||
game.UiSystem.LinkBehavior = l => this.StartActivity(new Intent(Intent.ActionView, Uri.Parse(l.Match.Groups[1].Value)));
|
|
||||||
};
|
|
||||||
// set the game to fullscreen to cause the status bar to be hidden
|
// set the game to fullscreen to cause the status bar to be hidden
|
||||||
this.game.GraphicsDeviceManager.IsFullScreen = true;
|
this.game.GraphicsDeviceManager.IsFullScreen = true;
|
||||||
this.view = this.game.Services.GetService(typeof(View)) as View;
|
this.view = this.game.Services.GetService(typeof(View)) as View;
|
||||||
|
|
|
@ -5,7 +5,7 @@ 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);
|
MlemPlatform.Current = new MlemPlatform.DesktopGl<TextInputEventArgs>((w, c) => w.TextInput += c);
|
||||||
using var game = new GameImpl();
|
using var game = new GameImpl();
|
||||||
game.Run();
|
game.Run();
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,24 +36,23 @@ protected override void Draw(GameTime gameTime) {
|
||||||
### Text Input
|
### Text Input
|
||||||
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.
|
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`.
|
To make MLEM compatible with all devices without publishing a separate version for each MonoGame platform, you have to set up the `MlemPlatform` class based on the system you're using MLEM.Ui with. This has to be done *before* initializing your `UiSystem`.
|
||||||
|
|
||||||
DesktopGL:
|
DesktopGL:
|
||||||
```cs
|
```cs
|
||||||
TextInputWrapper.Current = new TextInputWrapper.DesktopGl<TextInputEventArgs>((w, c) => w.TextInput += c);
|
MlemPlatform.Current = new MlemPlatform.DesktopGl<TextInputEventArgs>((w, c) => w.TextInput += c);
|
||||||
```
|
```
|
||||||
Mobile devices and consoles:
|
Mobile devices and consoles:
|
||||||
```cs
|
```cs
|
||||||
TextInputWrapper.Current = new TextInputWrapper.Mobile(KeyboardInput.Show);
|
MlemPlatform.Current = new MlemPlatform.Mobile(KeyboardInput.Show, l => this.StartActivity(new Intent(Intent.ActionView, Uri.Parse(l))));
|
||||||
```
|
```
|
||||||
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:
|
If you're not using text input, you can just set the platform to a stub one like so:
|
||||||
```cs
|
```cs
|
||||||
TextInputWrapper.Current = new TextInputWrapper.Primitive();
|
MlemPlatform.Current = new MlemPlatform.None();
|
||||||
```
|
|
||||||
If you're not using text input, you can just set the wrapper to a stub one like so:
|
|
||||||
```cs
|
|
||||||
TextInputWrapper.Current = new TextInputWrapper.None();
|
|
||||||
```
|
```
|
||||||
|
Initializing the platform in this way also allows for links in paragraphs to be clickable, causing a browser or explorer window to be opened on desktop or mobile devices.
|
||||||
|
|
||||||
|
For more info on MLEM's platform-related code, you can also check out MlemPlatform's [documentation](https://mlem.ellpeck.de/api/MLEM.Misc.MlemPlatform).
|
||||||
|
|
||||||
## Setting the style
|
## Setting the style
|
||||||
By default, MLEM.Ui's controls look pretty bland, since it doesn't ship with any fonts or textures for any of its controls. To change the style of your ui, simply expand your `new UntexturedStyle(this.SpriteBatch)` call to include fonts and textures of your choosing, for example:
|
By default, MLEM.Ui's controls look pretty bland, since it doesn't ship with any fonts or textures for any of its controls. To change the style of your ui, simply expand your `new UntexturedStyle(this.SpriteBatch)` call to include fonts and textures of your choosing, for example:
|
||||||
|
|
|
@ -5,7 +5,7 @@ namespace TemplateNamespace {
|
||||||
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);
|
MlemPlatform.Current = new MlemPlatform.DesktopGl<TextInputEventArgs>((w, c) => w.TextInput += c);
|
||||||
using var game = new GameImpl();
|
using var game = new GameImpl();
|
||||||
game.Run();
|
game.Run();
|
||||||
}
|
}
|
||||||
|
|
|
@ -334,10 +334,10 @@ namespace MLEM.Ui.Elements {
|
||||||
public GenericCallback OnTouchExit;
|
public GenericCallback OnTouchExit;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that is called when text input is made.
|
/// Event that is called when text input is made.
|
||||||
/// When an element uses this event, it should call <see cref="TextInputWrapper.EnsureExists"/> on construction to ensure that a text input wrapper was set.
|
/// When an element uses this event, it should call <see cref="MlemPlatform.EnsureExists"/> on construction to ensure that the MLEM platform is initialized.
|
||||||
///
|
///
|
||||||
/// Note that this event is called for every element, even if it is not selected.
|
/// Note that this event is called for every element, even if it is not selected.
|
||||||
/// Also note that if the active <see cref="TextInputWrapper"/> uses an on-screen keyboard, this event is never called.
|
/// Also note that if the active <see cref="MlemPlatform"/> uses an on-screen keyboard, this event is never called.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TextInputCallback OnTextInput;
|
public TextInputCallback OnTextInput;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -178,7 +178,7 @@ namespace MLEM.Ui.Elements {
|
||||||
this.textScale = textScale;
|
this.textScale = textScale;
|
||||||
this.OnPressed += e => {
|
this.OnPressed += e => {
|
||||||
foreach (var code in token.AppliedCodes.OfType<LinkCode>())
|
foreach (var code in token.AppliedCodes.OfType<LinkCode>())
|
||||||
this.System?.LinkBehavior?.Invoke(code);
|
MlemPlatform.Current.OpenLinkOrFile(code.Match.Groups[1].Value);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 an on-screen keyboard is required, then this text field will automatically open an on-screen keyboard using <see cref="TextInputWrapper.OpenOnScreenKeyboard"/>
|
/// If an on-screen keyboard is required, then this text field will automatically open an on-screen keyboard using <see cref="MlemPlatform.OpenOnScreenKeyboard"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TextField : Element {
|
public class TextField : Element {
|
||||||
|
|
||||||
|
@ -141,10 +141,10 @@ namespace MLEM.Ui.Elements {
|
||||||
if (font != null)
|
if (font != null)
|
||||||
this.Font.Set(font);
|
this.Font.Set(font);
|
||||||
|
|
||||||
TextInputWrapper.EnsureExists();
|
MlemPlatform.EnsureExists();
|
||||||
this.OnPressed += async e => {
|
this.OnPressed += async e => {
|
||||||
var title = this.MobileTitle ?? this.PlaceholderText;
|
var title = this.MobileTitle ?? this.PlaceholderText;
|
||||||
var result = await TextInputWrapper.Current.OpenOnScreenKeyboard(title, this.MobileDescription, this.Text, false);
|
var result = await MlemPlatform.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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
|
@ -89,12 +88,6 @@ namespace MLEM.Ui {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TextFormatter TextFormatter;
|
public TextFormatter TextFormatter;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The action that should be executed when a <see cref="LinkCode"/> in a paragraph's <see cref="Paragraph.TokenizedText"/> is pressed.
|
|
||||||
/// The actual link stored in the link code is stored in its <see cref="Code.Match"/>'s 1st group.
|
|
||||||
/// By default, the browser is opened with the given link's address.
|
|
||||||
/// </summary>
|
|
||||||
public Action<LinkCode> LinkBehavior = l => Process.Start(new ProcessStartInfo(l.Match.Groups[1].Value) {UseShellExecute = true});
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="UiControls"/> that this ui system is controlled by.
|
/// The <see cref="UiControls"/> that this ui system is controlled by.
|
||||||
/// The ui controls are also the place to change bindings for controller and keyboard input.
|
/// The ui controls are also the place to change bindings for controller and keyboard input.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -190,8 +183,8 @@ namespace MLEM.Ui {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TextInputWrapper.Current != null)
|
if (MlemPlatform.Current != null)
|
||||||
TextInputWrapper.Current.AddListener(game.Window, (sender, key, character) => this.ApplyToAll(e => e.OnTextInput?.Invoke(e, key, character)));
|
MlemPlatform.Current.AddTextInputListener(game.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.OnTouchedElementChanged = e => this.ApplyToAll(t => t.OnTouchedElementChanged?.Invoke(t, e));
|
this.OnTouchedElementChanged = e => this.ApplyToAll(t => t.OnTouchedElementChanged?.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));
|
||||||
|
|
188
MLEM/Misc/MlemPlatform.cs
Normal file
188
MLEM/Misc/MlemPlatform.cs
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
|
||||||
|
namespace MLEM.Misc {
|
||||||
|
/// <summary>
|
||||||
|
/// MlemPlatform is a wrapper around some of MonoGame's platform-dependent behavior to allow for MLEM to stay platform-independent.
|
||||||
|
/// See <see cref="DesktopGl{T}"/>, <see cref="Mobile"/> and <see cref="None"/> for information on the specific platforms.
|
||||||
|
/// The MLEM demos' main classes also make use of this functionality: <see href="https://github.com/Ellpeck/MLEM/blob/main/Demos.DesktopGL/Program.cs#L8"/> and <see href="https://github.com/Ellpeck/MLEM/blob/main/Demos.Android/Activity1.cs#L33"/>.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class MlemPlatform {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current MLEM platform
|
||||||
|
/// Set this value before starting your game if you want to use platform-dependent MLEM features.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException"></exception>
|
||||||
|
public static MlemPlatform Current;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens the on-screen keyboard if one is required by the platform.
|
||||||
|
/// Note that, if no on-screen keyboard is required, a null string should be returned.
|
||||||
|
/// </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 abstract Task<string> OpenOnScreenKeyboard(string title, string description, string defaultText, bool usePasswordMode);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a text input listener to this platform, if supported.
|
||||||
|
/// The supplied listener will be called whenever a character is input.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="window">The game's window</param>
|
||||||
|
/// <param name="callback">The callback that should be called whenever a character is pressed</param>
|
||||||
|
public abstract void AddTextInputListener(GameWindow window, TextInputCallback callback);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A method that should be executed to open a link in the browser or a file explorer
|
||||||
|
/// </summary>
|
||||||
|
public abstract void OpenLinkOrFile(string link);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that <see cref="Current"/> is set to a valid <see cref="MlemPlatform"/> 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("MlemPlatform was not initialized. For more information, see the MlemPlatform class or https://mlem.ellpeck.de/api/MLEM.Misc.MlemPlatform");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A delegate method that can be used for <see cref="MlemPlatform.AddTextInputListener"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender">The object that sent the event. The <see cref="MlemPlatform"/> used in most cases.</param>
|
||||||
|
/// <param name="key">The key that was pressed</param>
|
||||||
|
/// <param name="character">The character that corresponds to that key</param>
|
||||||
|
public delegate void TextInputCallback(object sender, Keys key, char character);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The MLEM DesktopGL platform.
|
||||||
|
/// This platform uses the built-in MonoGame TextInput event, which makes this listener work with any keyboard localization natively.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// This platform is initialized as follows:
|
||||||
|
/// <code>
|
||||||
|
/// new MlemPlatform.DesktopGl{TextInputEventArgs}((w, c) => w.TextInput += c)
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
public class DesktopGl<T> : MlemPlatform {
|
||||||
|
|
||||||
|
private FieldInfo key;
|
||||||
|
private FieldInfo character;
|
||||||
|
private readonly Action<GameWindow, EventHandler<T>> addListener;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new DesktopGL-based platform
|
||||||
|
/// See <see cref="MlemPlatform.DesktopGl{T}"/> class documentation for more detailed information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addListener">The function that is used to add a text input listener</param>
|
||||||
|
public DesktopGl(Action<GameWindow, EventHandler<T>> addListener) {
|
||||||
|
this.addListener = addListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Task<string> OpenOnScreenKeyboard(string title, string description, string defaultText, bool usePasswordMode) {
|
||||||
|
return Task.FromResult<string>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void AddTextInputListener(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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OpenLinkOrFile(string link) {
|
||||||
|
Process.Start(new ProcessStartInfo(link) {UseShellExecute = true});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The MLEM platform for mobile platforms as well as consoles.
|
||||||
|
/// This platform opens an on-screen keyboard using the <see cref="Microsoft.Xna.Framework.Input"/> <c>KeyboardInput</c> class on mobile devices.
|
||||||
|
/// Additionally, it starts a new activity whenever <see cref="OpenLinkOrFile"/> is called.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// This listener is initialized as follows in the game's <c>Activity</c> class:
|
||||||
|
/// <code>
|
||||||
|
/// new MlemPlatform.Mobile(KeyboardInput.Show, l => this.StartActivity(new Intent(Intent.ActionView, Uri.Parse(l))))
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public class Mobile : MlemPlatform {
|
||||||
|
|
||||||
|
private readonly OpenOnScreenKeyboardDelegate openOnScreenKeyboard;
|
||||||
|
private readonly Action<string> openLink;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new mobile- and console-based platform.
|
||||||
|
/// See <see cref="MlemPlatform.Mobile"/> class documentation for more detailed information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="openOnScreenKeyboard">The function that is used to display the on-screen keyboard</param>
|
||||||
|
/// <param name="openLink">The action that is invoked to open the </param>
|
||||||
|
public Mobile(OpenOnScreenKeyboardDelegate openOnScreenKeyboard, Action<string> openLink) {
|
||||||
|
this.openOnScreenKeyboard = openOnScreenKeyboard;
|
||||||
|
this.openLink = openLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 AddTextInputListener(GameWindow window, TextInputCallback callback) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OpenLinkOrFile(string link) {
|
||||||
|
this.openLink(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
/// A MLEM platform implementation that does nothing.
|
||||||
|
/// This can be used if no platform-dependent code is required for the game.
|
||||||
|
/// </summary>
|
||||||
|
public class None : MlemPlatform {
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Task<string> OpenOnScreenKeyboard(string title, string description, string defaultText, bool usePasswordMode) {
|
||||||
|
return Task.FromResult<string>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void AddTextInputListener(GameWindow window, TextInputCallback callback) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OpenLinkOrFile(string link) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,280 +0,0 @@
|
||||||
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 {
|
|
||||||
/// <summary>
|
|
||||||
/// 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 <see href="https://mlem.ellpeck.de/articles/ui.html#text-input"/> for more info.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class TextInputWrapper {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The current text input wrapper.
|
|
||||||
/// Set this value before starting your game if you want to use text input wrapping.
|
|
||||||
/// </summary>
|
|
||||||
/// <exception cref="InvalidOperationException"></exception>
|
|
||||||
public static TextInputWrapper Current;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </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 abstract Task<string> OpenOnScreenKeyboard(string title, string description, string defaultText, bool usePasswordMode);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a text input listener to this text input wrapper.
|
|
||||||
/// The supplied listener will be called whenever a character is input.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="window">The game's window</param>
|
|
||||||
/// <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>
|
|
||||||
/// <param name="sender">The object that sent the event. The <see cref="TextInputWrapper"/> used in most cases.</param>
|
|
||||||
/// <param name="key">The key that was pressed</param>
|
|
||||||
/// <param name="character">The character that corresponds to that key</param>
|
|
||||||
public delegate void TextInputCallback(object sender, Keys key, char character);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
/// <example>
|
|
||||||
/// This listener is initialized as follows:
|
|
||||||
/// <code>
|
|
||||||
/// new TextInputWrapper.DesktopGl{TextInputEventArgs}((w, c) => w.TextInput += c)
|
|
||||||
/// </code>
|
|
||||||
/// </example>
|
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
public class DesktopGl<T> : TextInputWrapper {
|
|
||||||
|
|
||||||
private FieldInfo key;
|
|
||||||
private FieldInfo character;
|
|
||||||
private readonly Action<GameWindow, EventHandler<T>> addListener;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new DesktopGL-based text input wrapper
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="addListener">The function that is used to add a text input listener</param>
|
|
||||||
public DesktopGl(Action<GameWindow, EventHandler<T>> addListener) {
|
|
||||||
this.addListener = addListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Task<string> OpenOnScreenKeyboard(string title, string description, string defaultText, bool usePasswordMode) {
|
|
||||||
return Task.FromResult<string>(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
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));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A text input wrapper for mobile platforms as well as consoles.
|
|
||||||
/// 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 {
|
|
||||||
|
|
||||||
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>
|
|
||||||
/// A text input wrapper that does nothing.
|
|
||||||
/// This can be used if no text input is required for the game.
|
|
||||||
/// </summary>
|
|
||||||
public class None : TextInputWrapper {
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Task<string> OpenOnScreenKeyboard(string title, string description, string defaultText, bool usePasswordMode) {
|
|
||||||
return Task.FromResult<string>(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void AddListener(GameWindow window, TextInputCallback callback) {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A primitive text input wrapper that is locked to the American keyboard localization.
|
|
||||||
/// Only use this text input wrapper if <see cref="TextInputWrapper.DesktopGl{T}"/> is unavailable for some reason.
|
|
||||||
///
|
|
||||||
/// Note that, when using this text input wrapper, its <see cref="Update"/> method has to be called periodically.
|
|
||||||
/// </summary>
|
|
||||||
public class Primitive : TextInputWrapper {
|
|
||||||
|
|
||||||
private TextInputCallback callback;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates this text input wrapper by querying pressed keys and sending corresponding input events.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="handler">The input handler to use for text input querying</param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Task<string> OpenOnScreenKeyboard(string title, string description, string defaultText, bool usePasswordMode) {
|
|
||||||
return Task.FromResult<string>(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,7 +5,7 @@ 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);
|
MlemPlatform.Current = new MlemPlatform.DesktopGl<TextInputEventArgs>((w, c) => w.TextInput += c);
|
||||||
using var game = new GameImpl();
|
using var game = new GameImpl();
|
||||||
game.Run();
|
game.Run();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue