streamlined TextInputWrapper into MlemPlatform and included link opening

This commit is contained in:
Ell 2021-04-23 00:17:46 +02:00
parent 1123b815b3
commit b48ed479a0
12 changed files with 211 additions and 315 deletions

View File

@ -30,16 +30,12 @@ namespace Demos.Android {
if (Build.VERSION.SdkInt >= BuildVersionCodes.P)
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();
// reset MlemGame width and height to use device's aspect ratio
this.game.GraphicsDeviceManager.ResetWidthAndHeight(this.game.Window);
this.game.OnLoadContent += game => {
// disable mouse handling for android to make emulator behavior more coherent
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)));
};
// disable mouse handling for android to make emulator behavior more coherent
this.game.OnLoadContent += game => game.InputHandler.HandleMouse = false;
// set the game to fullscreen to cause the status bar to be hidden
this.game.GraphicsDeviceManager.IsFullScreen = true;
this.view = this.game.Services.GetService(typeof(View)) as View;

View File

@ -5,7 +5,7 @@ namespace Demos.DesktopGL {
public static class Program {
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();
game.Run();
}

View File

@ -36,24 +36,23 @@ protected override void Draw(GameTime gameTime) {
### 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.
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:
```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:
```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
TextInputWrapper.Current = new TextInputWrapper.Primitive();
```
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();
MlemPlatform.Current = new MlemPlatform.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
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:

View File

@ -5,7 +5,7 @@ namespace TemplateNamespace {
public static class Program {
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();
game.Run();
}

View File

@ -334,10 +334,10 @@ namespace MLEM.Ui.Elements {
public GenericCallback OnTouchExit;
/// <summary>
/// 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.
/// 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>
public TextInputCallback OnTextInput;
/// <summary>

View File

@ -178,7 +178,7 @@ namespace MLEM.Ui.Elements {
this.textScale = textScale;
this.OnPressed += e => {
foreach (var code in token.AppliedCodes.OfType<LinkCode>())
this.System?.LinkBehavior?.Invoke(code);
MlemPlatform.Current.OpenLinkOrFile(code.Match.Groups[1].Value);
};
}

View File

@ -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 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>
public class TextField : Element {
@ -141,10 +141,10 @@ namespace MLEM.Ui.Elements {
if (font != null)
this.Font.Set(font);
TextInputWrapper.EnsureExists();
MlemPlatform.EnsureExists();
this.OnPressed += async e => {
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)
this.SetText(result.Replace('\n', ' '), true);
};

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Xna.Framework;
@ -89,12 +88,6 @@ namespace MLEM.Ui {
/// </summary>
public TextFormatter TextFormatter;
/// <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 ui controls are also the place to change bindings for controller and keyboard input.
/// </summary>
@ -190,8 +183,8 @@ namespace MLEM.Ui {
};
}
if (TextInputWrapper.Current != null)
TextInputWrapper.Current.AddListener(game.Window, (sender, key, character) => this.ApplyToAll(e => e.OnTextInput?.Invoke(e, key, character)));
if (MlemPlatform.Current != null)
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.OnTouchedElementChanged = e => this.ApplyToAll(t => t.OnTouchedElementChanged?.Invoke(t, e));
this.OnSelectedElementChanged = e => this.ApplyToAll(t => t.OnSelectedElementChanged?.Invoke(t, e));

View File

@ -21,6 +21,6 @@
</ItemGroup>
<ItemGroup>
<None Include="../Media/Logo.png" Pack="true" PackagePath=""/>
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
</ItemGroup>
</Project>

188
MLEM/Misc/MlemPlatform.cs Normal file
View 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 =&gt; 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) {
}
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -5,7 +5,7 @@ namespace Sandbox {
internal static class Program {
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();
game.Run();
}