1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-11-26 14:38:34 +01:00

added a more generic keybind and input handling system

This commit is contained in:
Ellpeck 2020-06-12 17:09:35 +02:00
parent 601423407d
commit 251261f3d7
6 changed files with 268 additions and 67 deletions

View file

@ -32,9 +32,9 @@ namespace MLEM.Ui.Elements {
base.Update(time); base.Update(time);
if (this.IsSelected) { if (this.IsSelected) {
if (this.Controls.LeftButtons.Any(b => this.Input.IsPressed(b, this.Controls.GamepadIndex))) { if (this.Controls.LeftButtons.IsPressed(this.Input, this.Controls.GamepadIndex)) {
this.CurrentValue -= this.StepPerScroll; this.CurrentValue -= this.StepPerScroll;
} else if (this.Controls.RightButtons.Any(b => this.Input.IsPressed(b, this.Controls.GamepadIndex))) { } else if (this.Controls.RightButtons.IsPressed(this.Input, this.Controls.GamepadIndex)) {
this.CurrentValue += this.StepPerScroll; this.CurrentValue += this.StepPerScroll;
} }
} }

View file

@ -54,39 +54,32 @@ namespace MLEM.Ui {
/// <summary> /// <summary>
/// A list of <see cref="Keys"/>, <see cref="Buttons"/> and/or <see cref="MouseButton"/> that act as the buttons on the keyboard which perform the <see cref="Element.OnPressed"/> action. /// A list of <see cref="Keys"/>, <see cref="Buttons"/> and/or <see cref="MouseButton"/> that act as the buttons on the keyboard which perform the <see cref="Element.OnPressed"/> action.
/// If the <see cref="ModifierKey.Shift"/> is held, these buttons perform <see cref="Element.OnSecondaryPressed"/>. /// If the <see cref="ModifierKey.Shift"/> is held, these buttons perform <see cref="Element.OnSecondaryPressed"/>.
/// To easily add more elements to this list, use <see cref="AddButtons"/>.
/// </summary> /// </summary>
public object[] KeyboardButtons = {Keys.Space, Keys.Enter}; public readonly Keybind KeyboardButtons = new Keybind().Add(Keys.Space).Add(Keys.Enter);
/// <summary> /// <summary>
/// A list of <see cref="Keys"/>, <see cref="Buttons"/> and/or <see cref="MouseButton"/> that act as the buttons on a gamepad that perform the <see cref="Element.OnPressed"/> action. /// AA <see cref="Keybind"/> that acts as the buttons on a gamepad that perform the <see cref="Element.OnPressed"/> action.
/// To easily add more elements to this list, use <see cref="AddButtons"/>.
/// </summary> /// </summary>
public object[] GamepadButtons = {Buttons.A}; public readonly Keybind GamepadButtons = new Keybind().Add(Buttons.A);
/// <summary> /// <summary>
/// A list of <see cref="Keys"/>, <see cref="Buttons"/> and/or <see cref="MouseButton"/> that act as the buttons on a gamepad that perform the <see cref="Element.OnSecondaryPressed"/> action. /// A <see cref="Keybind"/> that acts as the buttons on a gamepad that perform the <see cref="Element.OnSecondaryPressed"/> action.
/// To easily add more elements to this list, use <see cref="AddButtons"/>.
/// </summary> /// </summary>
public object[] SecondaryGamepadButtons = {Buttons.X}; public readonly Keybind SecondaryGamepadButtons = new Keybind().Add(Buttons.X);
/// <summary> /// <summary>
/// A list of A list of <see cref="Keys"/>, <see cref="Buttons"/> and/or <see cref="MouseButton"/> that act as the buttons that select a <see cref="Element"/> that is above the currently selected element. /// A <see cref="Keybind"/> that acts as the buttons that select a <see cref="Element"/> that is above the currently selected element.
/// To easily add more elements to this list, use <see cref="AddButtons"/>.
/// </summary> /// </summary>
public object[] UpButtons = {Buttons.DPadUp, Buttons.LeftThumbstickUp}; public readonly Keybind UpButtons = new Keybind().Add(Buttons.DPadUp).Add(Buttons.LeftThumbstickUp);
/// <summary> /// <summary>
/// A list of A list of <see cref="Keys"/>, <see cref="Buttons"/> and/or <see cref="MouseButton"/> that act as the buttons that select a <see cref="Element"/> that is below the currently selected element. /// A <see cref="Keybind"/> that acts as the buttons that select a <see cref="Element"/> that is below the currently selected element.
/// To easily add more elements to this list, use <see cref="AddButtons"/>.
/// </summary> /// </summary>
public object[] DownButtons = {Buttons.DPadDown, Buttons.LeftThumbstickDown}; public readonly Keybind DownButtons = new Keybind().Add(Buttons.DPadDown).Add(Buttons.LeftThumbstickDown);
/// <summary> /// <summary>
/// A list of A list of <see cref="Keys"/>, <see cref="Buttons"/> and/or <see cref="MouseButton"/> that act as the buttons that select a <see cref="Element"/> that is to the left of the currently selected element. /// A <see cref="Keybind"/> that acts as the buttons that select a <see cref="Element"/> that is to the left of the currently selected element.
/// To easily add more elements to this list, use <see cref="AddButtons"/>.
/// </summary> /// </summary>
public object[] LeftButtons = {Buttons.DPadLeft, Buttons.LeftThumbstickLeft}; public readonly Keybind LeftButtons = new Keybind().Add(Buttons.DPadLeft).Add(Buttons.LeftThumbstickLeft);
/// <summary> /// <summary>
/// A list of A list of <see cref="Keys"/>, <see cref="Buttons"/> and/or <see cref="MouseButton"/> that act as the buttons that select a <see cref="Element"/> that is to the right of the currently selected element. /// A <see cref="Keybind"/> that acts as the buttons that select a <see cref="Element"/> that is to the right of the currently selected element.
/// To easily add more elements to this list, use <see cref="AddButtons"/>.
/// </summary> /// </summary>
public object[] RightButtons = {Buttons.DPadRight, Buttons.LeftThumbstickRight}; public readonly Keybind RightButtons = new Keybind().Add(Buttons.DPadRight).Add(Buttons.LeftThumbstickRight);
/// <summary> /// <summary>
/// The zero-based index of the <see cref="GamePad"/> used for gamepad input. /// The zero-based index of the <see cref="GamePad"/> used for gamepad input.
/// If this index is lower than 0, every connected gamepad will trigger input. /// If this index is lower than 0, every connected gamepad will trigger input.
@ -162,7 +155,7 @@ namespace MLEM.Ui {
// KEYBOARD INPUT // KEYBOARD INPUT
if (this.HandleKeyboard) { if (this.HandleKeyboard) {
if (this.KeyboardButtons.Any(this.IsAnyPressed)) { if (this.KeyboardButtons.IsPressed(this.Input, this.GamepadIndex)) {
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) { if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) {
if (this.Input.IsModifierKeyDown(ModifierKey.Shift)) { if (this.Input.IsModifierKeyDown(ModifierKey.Shift)) {
// secondary action on element using space or enter // secondary action on element using space or enter
@ -215,19 +208,19 @@ namespace MLEM.Ui {
// GAMEPAD INPUT // GAMEPAD INPUT
if (this.HandleGamepad) { if (this.HandleGamepad) {
if (this.GamepadButtons.Any(this.IsAnyPressed)) { if (this.GamepadButtons.IsPressed(this.Input, this.GamepadIndex)) {
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed)
this.System.OnElementPressed?.Invoke(this.SelectedElement); this.System.OnElementPressed?.Invoke(this.SelectedElement);
} else if (this.SecondaryGamepadButtons.Any(this.IsAnyPressed)) { } else if (this.SecondaryGamepadButtons.IsPressed(this.Input, this.GamepadIndex)) {
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed)
this.System.OnElementSecondaryPressed?.Invoke(this.SelectedElement); this.System.OnElementSecondaryPressed?.Invoke(this.SelectedElement);
} else if (this.DownButtons.Any(this.IsAnyPressed)) { } else if (this.DownButtons.IsPressed(this.Input, this.GamepadIndex)) {
this.HandleGamepadNextElement(Direction2.Down); this.HandleGamepadNextElement(Direction2.Down);
} else if (this.LeftButtons.Any(this.IsAnyPressed)) { } else if (this.LeftButtons.IsPressed(this.Input, this.GamepadIndex)) {
this.HandleGamepadNextElement(Direction2.Left); this.HandleGamepadNextElement(Direction2.Left);
} else if (this.RightButtons.Any(this.IsAnyPressed)) { } else if (this.RightButtons.IsPressed(this.Input, this.GamepadIndex)) {
this.HandleGamepadNextElement(Direction2.Right); this.HandleGamepadNextElement(Direction2.Right);
} else if (this.UpButtons.Any(this.IsAnyPressed)) { } else if (this.UpButtons.IsPressed(this.Input, this.GamepadIndex)) {
this.HandleGamepadNextElement(Direction2.Up); this.HandleGamepadNextElement(Direction2.Up);
} }
} }
@ -383,10 +376,6 @@ namespace MLEM.Ui {
} }
} }
private bool IsAnyPressed(object button) {
return this.Input.IsPressed(button, this.GamepadIndex);
}
private void HandleGamepadNextElement(Direction2 dir) { private void HandleGamepadNextElement(Direction2 dir) {
this.IsAutoNavMode = true; this.IsAutoNavMode = true;
RectangleF searchArea = default; RectangleF searchArea = default;
@ -417,14 +406,5 @@ namespace MLEM.Ui {
this.SelectElement(this.ActiveRoot, next); this.SelectElement(this.ActiveRoot, next);
} }
/// <summary>
/// A helper function to add <see cref="Keys"/>, <see cref="Buttons"/> or <see cref="MouseButton"/> to an array of controls.
/// </summary>
/// <param name="controls">The controls to add to</param>
/// <param name="additional">The additional controls to add to the controls list</param>
public static void AddButtons(ref object[] controls, params object[] additional) {
controls = controls.Concat(additional).ToArray();
}
} }
} }

116
MLEM/Input/GenericInput.cs Normal file
View file

@ -0,0 +1,116 @@
using System;
using System.Runtime.Serialization;
using Microsoft.Xna.Framework.Input;
namespace MLEM.Input {
/// <summary>
/// A generic input represents any kind of input key.
/// This includes <see cref="Keys"/> for keyboard keys, <see cref="MouseButton"/> for mouse buttons and <see cref="Buttons"/> for gamepad buttons.
/// For creating and extracting inputs from a generic input, the implicit operators and <see cref="Type"/> can be used.
/// Note that this type is serializable using <see cref="DataContractAttribute"/>.
/// </summary>
[DataContract]
public struct GenericInput {
/// <summary>
/// The <see cref="InputType"/> of this generic input's current <see cref="value"/>.
/// </summary>
[DataMember]
public readonly InputType Type;
[DataMember]
private readonly int value;
private GenericInput(InputType type, int value) {
this.Type = type;
this.value = value;
}
/// <summary>
/// Converts a <see cref="Keys"/> to a generic input.
/// </summary>
/// <param name="keys">The keys to convert</param>
/// <returns>The resulting generic input</returns>
public static implicit operator GenericInput(Keys keys) {
return new GenericInput(InputType.Keyboard, (int) keys);
}
/// <summary>
/// Converts a <see cref="MouseButton"/> to a generic input.
/// </summary>
/// <param name="button">The button to convert</param>
/// <returns>The resulting generic input</returns>
public static implicit operator GenericInput(MouseButton button) {
return new GenericInput(InputType.Mouse, (int) button);
}
/// <summary>
/// Converts a <see cref="Buttons"/> to a generic input.
/// </summary>
/// <param name="buttons">The buttons to convert</param>
/// <returns>The resulting generic input</returns>
public static implicit operator GenericInput(Buttons buttons) {
return new GenericInput(InputType.Gamepad, (int) buttons);
}
/// <summary>
/// Converts a generic input to a <see cref="Keys"/>.
/// </summary>
/// <param name="input">The input to convert</param>
/// <returns>The resulting keys</returns>
/// <exception cref="ArgumentException">If the given generic input's <see cref="Type"/> is not <see cref="InputType.Keyboard"/></exception>
public static implicit operator Keys(GenericInput input) {
if (input.Type != InputType.Keyboard)
throw new ArgumentException();
return (Keys) input.value;
}
/// <summary>
/// Converts a generic input to a <see cref="MouseButton"/>.
/// </summary>
/// <param name="input">The input to convert</param>
/// <returns>The resulting button</returns>
/// <exception cref="ArgumentException">If the given generic input's <see cref="Type"/> is not <see cref="InputType.Mouse"/></exception>
public static implicit operator MouseButton(GenericInput input) {
if (input.Type != InputType.Mouse)
throw new ArgumentException();
return (MouseButton) input.value;
}
/// <summary>
/// Converts a generic input to a <see cref="Buttons"/>.
/// </summary>
/// <param name="input">The input to convert</param>
/// <returns>The resulting buttons</returns>
/// <exception cref="ArgumentException">If the given generic input's <see cref="Type"/> is not <see cref="InputType.Gamepad"/></exception>
public static implicit operator Buttons(GenericInput input) {
if (input.Type != InputType.Gamepad)
throw new ArgumentException();
return (Buttons) input.value;
}
/// <summary>
/// A type of input button.
/// </summary>
[DataContract]
public enum InputType {
/// <summary>
/// A type representing <see cref="MouseButton"/>
/// </summary>
[EnumMember]
Mouse,
/// <summary>
/// A type representing <see cref="Keys"/>
/// </summary>
[EnumMember]
Keyboard,
/// <summary>
/// A type representing <see cref="Buttons"/>
/// </summary>
[EnumMember]
Gamepad
}
}
}

View file

@ -432,13 +432,13 @@ namespace MLEM.Input {
/// <param name="index">The index of the gamepad to query (if applicable), or -1 for any gamepad</param> /// <param name="index">The index of the gamepad to query (if applicable), or -1 for any gamepad</param>
/// <returns>Whether the given control is down</returns> /// <returns>Whether the given control is down</returns>
/// <exception cref="ArgumentException">If the passed control isn't of a supported type</exception> /// <exception cref="ArgumentException">If the passed control isn't of a supported type</exception>
public bool IsDown(object control, int index = -1) { public bool IsDown(GenericInput control, int index = -1) {
if (control is Keys key) if (control.Type == GenericInput.InputType.Keyboard)
return this.IsKeyDown(key); return this.IsKeyDown(control);
if (control is Buttons button) if (control.Type == GenericInput.InputType.Gamepad)
return this.IsGamepadButtonDown(button, index); return this.IsGamepadButtonDown(control, index);
if (control is MouseButton mouse) if (control.Type == GenericInput.InputType.Mouse)
return this.IsMouseButtonDown(mouse); return this.IsMouseButtonDown(control);
throw new ArgumentException(nameof(control)); throw new ArgumentException(nameof(control));
} }
@ -450,13 +450,13 @@ namespace MLEM.Input {
/// <param name="index">The index of the gamepad to query (if applicable), or -1 for any gamepad</param> /// <param name="index">The index of the gamepad to query (if applicable), or -1 for any gamepad</param>
/// <returns>Whether the given control is down</returns> /// <returns>Whether the given control is down</returns>
/// <exception cref="ArgumentException">If the passed control isn't of a supported type</exception> /// <exception cref="ArgumentException">If the passed control isn't of a supported type</exception>
public bool IsUp(object control, int index = -1) { public bool IsUp(GenericInput control, int index = -1) {
if (control is Keys key) if (control.Type == GenericInput.InputType.Keyboard)
return this.IsKeyUp(key); return this.IsKeyUp(control);
if (control is Buttons button) if (control.Type == GenericInput.InputType.Gamepad)
return this.IsGamepadButtonUp(button, index); return this.IsGamepadButtonUp(control, index);
if (control is MouseButton mouse) if (control.Type == GenericInput.InputType.Mouse)
return this.IsMouseButtonUp(mouse); return this.IsMouseButtonUp(control);
throw new ArgumentException(nameof(control)); throw new ArgumentException(nameof(control));
} }
@ -468,28 +468,28 @@ namespace MLEM.Input {
/// <param name="index">The index of the gamepad to query (if applicable), or -1 for any gamepad</param> /// <param name="index">The index of the gamepad to query (if applicable), or -1 for any gamepad</param>
/// <returns>Whether the given control is down</returns> /// <returns>Whether the given control is down</returns>
/// <exception cref="ArgumentException">If the passed control isn't of a supported type</exception> /// <exception cref="ArgumentException">If the passed control isn't of a supported type</exception>
public bool IsPressed(object control, int index = -1) { public bool IsPressed(GenericInput control, int index = -1) {
if (control is Keys key) if (control.Type == GenericInput.InputType.Keyboard)
return this.IsKeyPressed(key); return this.IsKeyPressed(control);
if (control is Buttons button) if (control.Type == GenericInput.InputType.Gamepad)
return this.IsGamepadButtonPressed(button, index); return this.IsGamepadButtonPressed(control, index);
if (control is MouseButton mouse) if (control.Type == GenericInput.InputType.Mouse)
return this.IsMouseButtonPressed(mouse); return this.IsMouseButtonPressed(control);
throw new ArgumentException(nameof(control)); throw new ArgumentException(nameof(control));
} }
/// <inheritdoc cref="IsDown"/> /// <inheritdoc cref="IsDown"/>
public bool IsAnyDown(params object[] control) { public bool IsAnyDown(params GenericInput[] control) {
return control.Any(c => this.IsDown(c)); return control.Any(c => this.IsDown(c));
} }
/// <inheritdoc cref="IsUp"/> /// <inheritdoc cref="IsUp"/>
public bool IsAnyUp(params object[] control) { public bool IsAnyUp(params GenericInput[] control) {
return control.Any(c => this.IsUp(c)); return control.Any(c => this.IsUp(c));
} }
/// <inheritdoc cref="IsPressed"/> /// <inheritdoc cref="IsPressed"/>
public bool IsAnyPressed(params object[] control) { public bool IsAnyPressed(params GenericInput[] control) {
return control.Any(c => this.IsPressed(c)); return control.Any(c => this.IsPressed(c));
} }

105
MLEM/Input/Keybind.cs Normal file
View file

@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using Microsoft.Xna.Framework.Input;
namespace MLEM.Input {
/// <summary>
/// A keybind represents a generic way to trigger input.
/// A keybind is made up of multiple key combinations, one of which has to be pressed for the keybind to be triggered.
/// Note that this type is serializable using <see cref="DataContractAttribute"/>.
/// </summary>
[DataContract]
public class Keybind {
[DataMember]
private readonly List<Combination> combinations = new List<Combination>();
/// <summary>
/// Adds a new key combination to this keybind that can optionally be pressed for the keybind to trigger.
/// </summary>
/// <param name="key">The key to be pressed.</param>
/// <param name="modifiers">The modifier keys that have to be held down.</param>
/// <returns>This keybind, for chaining</returns>
public Keybind Add(GenericInput key, params GenericInput[] modifiers) {
this.combinations.Add(new Combination(key, modifiers));
return this;
}
/// <inheritdoc cref="Add(MLEM.Input.GenericInput,MLEM.Input.GenericInput[])"/>
public Keybind Add(GenericInput key, ModifierKey modifier) {
return this.Add(key, modifier.GetKeys().Select(m => (GenericInput) m).ToArray());
}
/// <summary>
/// Clears this keybind, removing all active combinations.
/// </summary>
/// <returns>This keybind, for chaining</returns>
public Keybind Clear() {
this.combinations.Clear();
return this;
}
/// <summary>
/// Copies all of the combinations from the given keybind into this keybind.
/// Note that this doesn't <see cref="Clear"/> this keybind, so combinations will be merged rather than replaced.
/// </summary>
/// <param name="other">The keybind to copy from</param>
/// <returns>This keybind, for chaining</returns>
public Keybind CopyFrom(Keybind other) {
this.combinations.AddRange(other.combinations);
return this;
}
/// <summary>
/// Returns whether this keybind is considered to be down.
/// See <see cref="InputHandler.IsDown"/> for more information.
/// </summary>
/// <param name="handler">The input handler to query the keys with</param>
/// <param name="gamepadIndex">The index of the gamepad to query, or -1 to query all gamepads</param>
/// <returns>Whether this keybind is considered to be down</returns>
public bool IsDown(InputHandler handler, int gamepadIndex = -1) {
return this.combinations.Any(c => c.IsDown(handler, gamepadIndex));
}
/// <summary>
/// Returns whether this keybind is considered to be pressed.
/// See <see cref="InputHandler.IsPressed"/> for more information.
/// </summary>
/// <param name="handler">The input handler to query the keys with</param>
/// <param name="gamepadIndex">The index of the gamepad to query, or -1 to query all gamepads</param>
/// <returns>Whether this keybind is considered to be pressed</returns>
public bool IsPressed(InputHandler handler, int gamepadIndex = -1) {
return this.combinations.Any(c => c.IsPressed(handler, gamepadIndex));
}
[DataContract]
private class Combination {
[DataMember]
private readonly GenericInput[] modifiers;
[DataMember]
private readonly GenericInput key;
public Combination(GenericInput key, GenericInput[] modifiers) {
this.modifiers = modifiers;
this.key = key;
}
internal bool IsDown(InputHandler handler, int gamepadIndex = -1) {
return this.IsModifierDown(handler, gamepadIndex) && handler.IsDown(this.key, gamepadIndex);
}
internal bool IsPressed(InputHandler handler, int gamepadIndex = -1) {
return this.IsModifierDown(handler, gamepadIndex) && handler.IsPressed(this.key, gamepadIndex);
}
private bool IsModifierDown(InputHandler handler, int gamepadIndex = -1) {
return this.modifiers.Length <= 0 || this.modifiers.Any(m => handler.IsDown(m, gamepadIndex));
}
}
}
}

View file

@ -2,7 +2,7 @@
#tool docfx.console&version=2.51.0 #tool docfx.console&version=2.51.0
// this is the upcoming version, for prereleases // this is the upcoming version, for prereleases
var version = Argument("version", "3.3.3"); var version = Argument("version", "4.0.0");
var target = Argument("target", "Default"); var target = Argument("target", "Default");
var branch = Argument("branch", "master"); var branch = Argument("branch", "master");
var config = Argument("configuration", "Release"); var config = Argument("configuration", "Release");