From 251261f3d756538a1243827d0aaad5396c2ab91f Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Fri, 12 Jun 2020 17:09:35 +0200 Subject: [PATCH] added a more generic keybind and input handling system --- MLEM.Ui/Elements/Slider.cs | 4 +- MLEM.Ui/UiControls.cs | 60 +++++++------------ MLEM/Input/GenericInput.cs | 116 +++++++++++++++++++++++++++++++++++++ MLEM/Input/InputHandler.cs | 48 +++++++-------- MLEM/Input/Keybind.cs | 105 +++++++++++++++++++++++++++++++++ build.cake | 2 +- 6 files changed, 268 insertions(+), 67 deletions(-) create mode 100644 MLEM/Input/GenericInput.cs create mode 100644 MLEM/Input/Keybind.cs diff --git a/MLEM.Ui/Elements/Slider.cs b/MLEM.Ui/Elements/Slider.cs index 885bd2e..8ea92f0 100644 --- a/MLEM.Ui/Elements/Slider.cs +++ b/MLEM.Ui/Elements/Slider.cs @@ -32,9 +32,9 @@ namespace MLEM.Ui.Elements { base.Update(time); 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; - } 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; } } diff --git a/MLEM.Ui/UiControls.cs b/MLEM.Ui/UiControls.cs index 6250685..618dda5 100644 --- a/MLEM.Ui/UiControls.cs +++ b/MLEM.Ui/UiControls.cs @@ -54,39 +54,32 @@ namespace MLEM.Ui { /// /// A list of , and/or that act as the buttons on the keyboard which perform the action. /// If the is held, these buttons perform . - /// To easily add more elements to this list, use . /// - public object[] KeyboardButtons = {Keys.Space, Keys.Enter}; + public readonly Keybind KeyboardButtons = new Keybind().Add(Keys.Space).Add(Keys.Enter); /// - /// A list of , and/or that act as the buttons on a gamepad that perform the action. - /// To easily add more elements to this list, use . + /// AA that acts as the buttons on a gamepad that perform the action. /// - public object[] GamepadButtons = {Buttons.A}; + public readonly Keybind GamepadButtons = new Keybind().Add(Buttons.A); /// - /// A list of , and/or that act as the buttons on a gamepad that perform the action. - /// To easily add more elements to this list, use . + /// A that acts as the buttons on a gamepad that perform the action. /// - public object[] SecondaryGamepadButtons = {Buttons.X}; + public readonly Keybind SecondaryGamepadButtons = new Keybind().Add(Buttons.X); /// - /// A list of A list of , and/or that act as the buttons that select a that is above the currently selected element. - /// To easily add more elements to this list, use . + /// A that acts as the buttons that select a that is above the currently selected element. /// - public object[] UpButtons = {Buttons.DPadUp, Buttons.LeftThumbstickUp}; + public readonly Keybind UpButtons = new Keybind().Add(Buttons.DPadUp).Add(Buttons.LeftThumbstickUp); /// - /// A list of A list of , and/or that act as the buttons that select a that is below the currently selected element. - /// To easily add more elements to this list, use . + /// A that acts as the buttons that select a that is below the currently selected element. /// - public object[] DownButtons = {Buttons.DPadDown, Buttons.LeftThumbstickDown}; + public readonly Keybind DownButtons = new Keybind().Add(Buttons.DPadDown).Add(Buttons.LeftThumbstickDown); /// - /// A list of A list of , and/or that act as the buttons that select a that is to the left of the currently selected element. - /// To easily add more elements to this list, use . + /// A that acts as the buttons that select a that is to the left of the currently selected element. /// - public object[] LeftButtons = {Buttons.DPadLeft, Buttons.LeftThumbstickLeft}; + public readonly Keybind LeftButtons = new Keybind().Add(Buttons.DPadLeft).Add(Buttons.LeftThumbstickLeft); /// - /// A list of A list of , and/or that act as the buttons that select a that is to the right of the currently selected element. - /// To easily add more elements to this list, use . + /// A that acts as the buttons that select a that is to the right of the currently selected element. /// - public object[] RightButtons = {Buttons.DPadRight, Buttons.LeftThumbstickRight}; + public readonly Keybind RightButtons = new Keybind().Add(Buttons.DPadRight).Add(Buttons.LeftThumbstickRight); /// /// The zero-based index of the used for gamepad input. /// If this index is lower than 0, every connected gamepad will trigger input. @@ -162,7 +155,7 @@ namespace MLEM.Ui { // KEYBOARD INPUT 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.Input.IsModifierKeyDown(ModifierKey.Shift)) { // secondary action on element using space or enter @@ -215,19 +208,19 @@ namespace MLEM.Ui { // GAMEPAD INPUT 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) 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) 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); - } else if (this.LeftButtons.Any(this.IsAnyPressed)) { + } else if (this.LeftButtons.IsPressed(this.Input, this.GamepadIndex)) { this.HandleGamepadNextElement(Direction2.Left); - } else if (this.RightButtons.Any(this.IsAnyPressed)) { + } else if (this.RightButtons.IsPressed(this.Input, this.GamepadIndex)) { this.HandleGamepadNextElement(Direction2.Right); - } else if (this.UpButtons.Any(this.IsAnyPressed)) { + } else if (this.UpButtons.IsPressed(this.Input, this.GamepadIndex)) { 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) { this.IsAutoNavMode = true; RectangleF searchArea = default; @@ -417,14 +406,5 @@ namespace MLEM.Ui { this.SelectElement(this.ActiveRoot, next); } - /// - /// A helper function to add , or to an array of controls. - /// - /// The controls to add to - /// The additional controls to add to the controls list - public static void AddButtons(ref object[] controls, params object[] additional) { - controls = controls.Concat(additional).ToArray(); - } - } } \ No newline at end of file diff --git a/MLEM/Input/GenericInput.cs b/MLEM/Input/GenericInput.cs new file mode 100644 index 0000000..0addb76 --- /dev/null +++ b/MLEM/Input/GenericInput.cs @@ -0,0 +1,116 @@ +using System; +using System.Runtime.Serialization; +using Microsoft.Xna.Framework.Input; + +namespace MLEM.Input { + /// + /// A generic input represents any kind of input key. + /// This includes for keyboard keys, for mouse buttons and for gamepad buttons. + /// For creating and extracting inputs from a generic input, the implicit operators and can be used. + /// Note that this type is serializable using . + /// + [DataContract] + public struct GenericInput { + + /// + /// The of this generic input's current . + /// + [DataMember] + public readonly InputType Type; + [DataMember] + private readonly int value; + + private GenericInput(InputType type, int value) { + this.Type = type; + this.value = value; + } + + /// + /// Converts a to a generic input. + /// + /// The keys to convert + /// The resulting generic input + public static implicit operator GenericInput(Keys keys) { + return new GenericInput(InputType.Keyboard, (int) keys); + } + + /// + /// Converts a to a generic input. + /// + /// The button to convert + /// The resulting generic input + public static implicit operator GenericInput(MouseButton button) { + return new GenericInput(InputType.Mouse, (int) button); + } + + /// + /// Converts a to a generic input. + /// + /// The buttons to convert + /// The resulting generic input + public static implicit operator GenericInput(Buttons buttons) { + return new GenericInput(InputType.Gamepad, (int) buttons); + } + + /// + /// Converts a generic input to a . + /// + /// The input to convert + /// The resulting keys + /// If the given generic input's is not + public static implicit operator Keys(GenericInput input) { + if (input.Type != InputType.Keyboard) + throw new ArgumentException(); + return (Keys) input.value; + } + + /// + /// Converts a generic input to a . + /// + /// The input to convert + /// The resulting button + /// If the given generic input's is not + public static implicit operator MouseButton(GenericInput input) { + if (input.Type != InputType.Mouse) + throw new ArgumentException(); + return (MouseButton) input.value; + } + + /// + /// Converts a generic input to a . + /// + /// The input to convert + /// The resulting buttons + /// If the given generic input's is not + public static implicit operator Buttons(GenericInput input) { + if (input.Type != InputType.Gamepad) + throw new ArgumentException(); + return (Buttons) input.value; + } + + /// + /// A type of input button. + /// + [DataContract] + public enum InputType { + + /// + /// A type representing + /// + [EnumMember] + Mouse, + /// + /// A type representing + /// + [EnumMember] + Keyboard, + /// + /// A type representing + /// + [EnumMember] + Gamepad + + } + + } +} \ No newline at end of file diff --git a/MLEM/Input/InputHandler.cs b/MLEM/Input/InputHandler.cs index 36740b5..8a3d6c1 100644 --- a/MLEM/Input/InputHandler.cs +++ b/MLEM/Input/InputHandler.cs @@ -432,13 +432,13 @@ namespace MLEM.Input { /// The index of the gamepad to query (if applicable), or -1 for any gamepad /// Whether the given control is down /// If the passed control isn't of a supported type - public bool IsDown(object control, int index = -1) { - if (control is Keys key) - return this.IsKeyDown(key); - if (control is Buttons button) - return this.IsGamepadButtonDown(button, index); - if (control is MouseButton mouse) - return this.IsMouseButtonDown(mouse); + public bool IsDown(GenericInput control, int index = -1) { + if (control.Type == GenericInput.InputType.Keyboard) + return this.IsKeyDown(control); + if (control.Type == GenericInput.InputType.Gamepad) + return this.IsGamepadButtonDown(control, index); + if (control.Type == GenericInput.InputType.Mouse) + return this.IsMouseButtonDown(control); throw new ArgumentException(nameof(control)); } @@ -450,13 +450,13 @@ namespace MLEM.Input { /// The index of the gamepad to query (if applicable), or -1 for any gamepad /// Whether the given control is down /// If the passed control isn't of a supported type - public bool IsUp(object control, int index = -1) { - if (control is Keys key) - return this.IsKeyUp(key); - if (control is Buttons button) - return this.IsGamepadButtonUp(button, index); - if (control is MouseButton mouse) - return this.IsMouseButtonUp(mouse); + public bool IsUp(GenericInput control, int index = -1) { + if (control.Type == GenericInput.InputType.Keyboard) + return this.IsKeyUp(control); + if (control.Type == GenericInput.InputType.Gamepad) + return this.IsGamepadButtonUp(control, index); + if (control.Type == GenericInput.InputType.Mouse) + return this.IsMouseButtonUp(control); throw new ArgumentException(nameof(control)); } @@ -468,28 +468,28 @@ namespace MLEM.Input { /// The index of the gamepad to query (if applicable), or -1 for any gamepad /// Whether the given control is down /// If the passed control isn't of a supported type - public bool IsPressed(object control, int index = -1) { - if (control is Keys key) - return this.IsKeyPressed(key); - if (control is Buttons button) - return this.IsGamepadButtonPressed(button, index); - if (control is MouseButton mouse) - return this.IsMouseButtonPressed(mouse); + public bool IsPressed(GenericInput control, int index = -1) { + if (control.Type == GenericInput.InputType.Keyboard) + return this.IsKeyPressed(control); + if (control.Type == GenericInput.InputType.Gamepad) + return this.IsGamepadButtonPressed(control, index); + if (control.Type == GenericInput.InputType.Mouse) + return this.IsMouseButtonPressed(control); throw new ArgumentException(nameof(control)); } /// - public bool IsAnyDown(params object[] control) { + public bool IsAnyDown(params GenericInput[] control) { return control.Any(c => this.IsDown(c)); } /// - public bool IsAnyUp(params object[] control) { + public bool IsAnyUp(params GenericInput[] control) { return control.Any(c => this.IsUp(c)); } /// - public bool IsAnyPressed(params object[] control) { + public bool IsAnyPressed(params GenericInput[] control) { return control.Any(c => this.IsPressed(c)); } diff --git a/MLEM/Input/Keybind.cs b/MLEM/Input/Keybind.cs new file mode 100644 index 0000000..61318e8 --- /dev/null +++ b/MLEM/Input/Keybind.cs @@ -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 { + /// + /// 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 . + /// + [DataContract] + public class Keybind { + + [DataMember] + private readonly List combinations = new List(); + + /// + /// Adds a new key combination to this keybind that can optionally be pressed for the keybind to trigger. + /// + /// The key to be pressed. + /// The modifier keys that have to be held down. + /// This keybind, for chaining + public Keybind Add(GenericInput key, params GenericInput[] modifiers) { + this.combinations.Add(new Combination(key, modifiers)); + return this; + } + + /// + public Keybind Add(GenericInput key, ModifierKey modifier) { + return this.Add(key, modifier.GetKeys().Select(m => (GenericInput) m).ToArray()); + } + + /// + /// Clears this keybind, removing all active combinations. + /// + /// This keybind, for chaining + public Keybind Clear() { + this.combinations.Clear(); + return this; + } + + /// + /// Copies all of the combinations from the given keybind into this keybind. + /// Note that this doesn't this keybind, so combinations will be merged rather than replaced. + /// + /// The keybind to copy from + /// This keybind, for chaining + public Keybind CopyFrom(Keybind other) { + this.combinations.AddRange(other.combinations); + return this; + } + + /// + /// Returns whether this keybind is considered to be down. + /// See for more information. + /// + /// The input handler to query the keys with + /// The index of the gamepad to query, or -1 to query all gamepads + /// Whether this keybind is considered to be down + public bool IsDown(InputHandler handler, int gamepadIndex = -1) { + return this.combinations.Any(c => c.IsDown(handler, gamepadIndex)); + } + + /// + /// Returns whether this keybind is considered to be pressed. + /// See for more information. + /// + /// The input handler to query the keys with + /// The index of the gamepad to query, or -1 to query all gamepads + /// Whether this keybind is considered to be pressed + 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)); + } + + } + + } +} \ No newline at end of file diff --git a/build.cake b/build.cake index f8fea57..3f479d5 100644 --- a/build.cake +++ b/build.cake @@ -2,7 +2,7 @@ #tool docfx.console&version=2.51.0 // 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 branch = Argument("branch", "master"); var config = Argument("configuration", "Release");