diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3289c87..976b78c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ Additions
- Added TextureRegion.OffsetCopy
- Added RectangleF.DistanceSquared and RectangleF.Distance
- Added GamepadExtensions.GetAnalogValue to get the analog value of any gamepad button
+- Added InputHandler.TryGetDownTime
Improvements
- Generify GenericFont's string drawing
@@ -24,6 +25,7 @@ Improvements
- Allow LinkCode to specify a color to draw with
- Allow better control over the order and layout of a Keybind's combinations
- Allow setting a gamepad button deadzone in InputHandler
+- Trigger InputHandler key and gamepad repeats for the most recently pressed input
Fixes
- **Fixed a formatting Code only knowing about the last Token that it is applied in**
@@ -32,6 +34,7 @@ Fixes
- Fixed InputHandler.InputsPressed ignoring repeat events for keyboards and gamepads
Removals
+- **Removed InputHandler.StoreAllActiveInputs and always store all active inputs**
- Renamed GenericFont.OneEmSpace to Emsp (and marked OneEmSpace as obsolete)
### MLEM.Ui
diff --git a/MLEM.Ui/UiControls.cs b/MLEM.Ui/UiControls.cs
index 69cd112..5f5818d 100644
--- a/MLEM.Ui/UiControls.cs
+++ b/MLEM.Ui/UiControls.cs
@@ -4,7 +4,6 @@ using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
-using MLEM.Extensions;
using MLEM.Input;
using MLEM.Misc;
using MLEM.Ui.Elements;
diff --git a/MLEM/Input/GamepadExtensions.cs b/MLEM/Input/GamepadExtensions.cs
index 981458e..c746d12 100644
--- a/MLEM/Input/GamepadExtensions.cs
+++ b/MLEM/Input/GamepadExtensions.cs
@@ -1,4 +1,3 @@
-using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
diff --git a/MLEM/Input/InputHandler.cs b/MLEM/Input/InputHandler.cs
index 8d0bf7b..d5e4491 100644
--- a/MLEM/Input/InputHandler.cs
+++ b/MLEM/Input/InputHandler.cs
@@ -56,10 +56,6 @@ namespace MLEM.Input {
///
public bool HandleGamepadRepeats = true;
///
- /// Set this field to false to enable and being calculated.
- ///
- public bool StoreAllActiveInputs;
- ///
/// This field represents the deadzone that gamepad have when input is queried for them using this input handler.
/// A deadzone is the percentage (between 0 and 1) that an analog value has to exceed for it to be considered down () or pressed ().
/// Querying of analog values is done using .
@@ -68,13 +64,12 @@ namespace MLEM.Input {
///
/// An array of all , and values that are currently down.
- /// Note that this value only gets set if is true.
+ /// Additionally, or can be used to determine the amount of time that a given input has been down for.
///
public GenericInput[] InputsDown { get; private set; } = Array.Empty();
///
/// An array of all , and that are currently considered pressed.
/// An input is considered pressed if it was up in the last update, and is up in the current one.
- /// Note that this value only gets set if is true.
///
public GenericInput[] InputsPressed { get; private set; } = Array.Empty();
///
@@ -141,15 +136,14 @@ namespace MLEM.Input {
private readonly GamePadState[] lastGamepads = new GamePadState[GamePad.MaximumGamePadCount];
private readonly GamePadState[] gamepads = new GamePadState[GamePad.MaximumGamePadCount];
- private readonly DateTime[] heldGamepadButtonStarts = new DateTime[GamePad.MaximumGamePadCount];
private readonly DateTime[] lastGamepadButtonRepeats = new DateTime[GamePad.MaximumGamePadCount];
private readonly bool[] triggerGamepadButtonRepeat = new bool[GamePad.MaximumGamePadCount];
private readonly Buttons?[] heldGamepadButtons = new Buttons?[GamePad.MaximumGamePadCount];
- private readonly List inputsDownAccum = new List();
private readonly List gestures = new List();
private Point ViewportOffset => new Point(-this.Game.GraphicsDevice.Viewport.X, -this.Game.GraphicsDevice.Viewport.Y);
- private DateTime heldKeyStart;
+ private Dictionary<(GenericInput, int), DateTime> inputsDownAccum = new Dictionary<(GenericInput, int), DateTime>();
+ private Dictionary<(GenericInput, int), DateTime> inputsDown = new Dictionary<(GenericInput, int), DateTime>();
private DateTime lastKeyRepeat;
private bool triggerKeyRepeat;
private Keys heldKey;
@@ -162,13 +156,11 @@ namespace MLEM.Input {
/// If mouse input should be handled
/// If gamepad input should be handled
/// If touch input should be handled
- /// Whether all inputs that are currently down and pressed should be calculated each update
- public InputHandler(Game game, bool handleKeyboard = true, bool handleMouse = true, bool handleGamepads = true, bool handleTouch = true, bool storeAllActiveInputs = true) : base(game) {
+ public InputHandler(Game game, bool handleKeyboard = true, bool handleMouse = true, bool handleGamepads = true, bool handleTouch = true) : base(game) {
this.HandleKeyboard = handleKeyboard;
this.HandleMouse = handleMouse;
this.HandleGamepads = handleGamepads;
this.HandleTouch = handleTouch;
- this.StoreAllActiveInputs = storeAllActiveInputs;
this.Gestures = this.gestures.AsReadOnly();
}
@@ -177,42 +169,28 @@ namespace MLEM.Input {
/// Call this in your method.
///
public void Update() {
+ var now = DateTime.UtcNow;
var active = this.Game.IsActive;
if (this.HandleKeyboard) {
this.LastKeyboardState = this.KeyboardState;
this.KeyboardState = active ? Keyboard.GetState() : default;
var pressedKeys = this.KeyboardState.GetPressedKeys();
- if (this.StoreAllActiveInputs) {
- foreach (var pressed in pressedKeys)
- this.inputsDownAccum.Add(pressed);
- }
+ foreach (var pressed in pressedKeys)
+ this.AccumulateDown(pressed, -1);
if (this.HandleKeyboardRepeats) {
this.triggerKeyRepeat = false;
- if (this.heldKey == Keys.None) {
- // if we're not repeating a key, set the first key being held to the repeat key
- // note that modifier keys don't count as that wouldn't really make sense
- var key = pressedKeys.FirstOrDefault(k => !k.IsModifier());
- if (key != Keys.None) {
- this.heldKey = key;
- this.heldKeyStart = DateTime.UtcNow;
- }
- } else {
- // if the repeating key isn't being held anymore, reset
- if (!this.IsKeyDown(this.heldKey)) {
- this.heldKey = Keys.None;
- } else {
- var now = DateTime.UtcNow;
- var holdTime = now - this.heldKeyStart;
- // if we've been holding the key longer than the initial delay...
- if (holdTime >= this.KeyRepeatDelay) {
- var diff = now - this.lastKeyRepeat;
- // and we've been holding it for longer than a repeat...
- if (diff >= this.KeyRepeatRate) {
- this.lastKeyRepeat = now;
- // then trigger a repeat, causing IsKeyPressed to be true once
- this.triggerKeyRepeat = true;
- }
+ // the key that started being held most recently should be the one being repeated
+ this.heldKey = pressedKeys.OrderBy(k => this.GetDownTime(k)).FirstOrDefault();
+ if (this.TryGetDownTime(this.heldKey, out var heldTime)) {
+ // if we've been holding the key longer than the initial delay...
+ if (heldTime >= this.KeyRepeatDelay) {
+ var diff = now - this.lastKeyRepeat;
+ // and we've been holding it for longer than a repeat...
+ if (diff >= this.KeyRepeatRate) {
+ this.lastKeyRepeat = now;
+ // then trigger a repeat, causing IsKeyPressed to be true once
+ this.triggerKeyRepeat = true;
}
}
}
@@ -224,11 +202,9 @@ namespace MLEM.Input {
var state = Mouse.GetState();
if (active && this.Game.GraphicsDevice.Viewport.Bounds.Contains(state.Position)) {
this.MouseState = state;
- if (this.StoreAllActiveInputs) {
- foreach (var button in MouseExtensions.MouseButtons) {
- if (state.GetState(button) == ButtonState.Pressed)
- this.inputsDownAccum.Add(button);
- }
+ foreach (var button in MouseExtensions.MouseButtons) {
+ if (state.GetState(button) == ButtonState.Pressed)
+ this.AccumulateDown(button, -1);
}
} else {
// mouse position and scroll wheel value should be preserved when the mouse is out of bounds
@@ -244,43 +220,29 @@ namespace MLEM.Input {
if (GamePad.GetCapabilities(i).IsConnected) {
if (active) {
this.gamepads[i] = GamePad.GetState(i);
- if (this.StoreAllActiveInputs) {
- foreach (var button in EnumHelper.Buttons) {
- if (this.IsGamepadButtonDown(button, i))
- this.inputsDownAccum.Add(button);
- }
+ foreach (var button in EnumHelper.Buttons) {
+ if (this.IsGamepadButtonDown(button, i))
+ this.AccumulateDown(button, i);
}
}
- } else {
- if (this.ConnectedGamepads > i)
- this.ConnectedGamepads = i;
+ } else if (this.ConnectedGamepads > i) {
+ this.ConnectedGamepads = i;
}
}
if (this.HandleGamepadRepeats) {
for (var i = 0; i < this.ConnectedGamepads; i++) {
this.triggerGamepadButtonRepeat[i] = false;
-
- if (!this.heldGamepadButtons[i].HasValue) {
- foreach (var b in EnumHelper.Buttons) {
- if (this.IsGamepadButtonDown(b, i)) {
- this.heldGamepadButtons[i] = b;
- this.heldGamepadButtonStarts[i] = DateTime.UtcNow;
- break;
- }
- }
- } else {
- if (!this.IsGamepadButtonDown(this.heldGamepadButtons[i].Value, i)) {
- this.heldGamepadButtons[i] = null;
- } else {
- var now = DateTime.UtcNow;
- var holdTime = now - this.heldGamepadButtonStarts[i];
- if (holdTime >= this.KeyRepeatDelay) {
- var diff = now - this.lastGamepadButtonRepeats[i];
- if (diff >= this.KeyRepeatRate) {
- this.lastGamepadButtonRepeats[i] = now;
- this.triggerGamepadButtonRepeat[i] = true;
- }
+ this.heldGamepadButtons[i] = EnumHelper.Buttons
+ .Where(b => this.IsGamepadButtonDown(b, i))
+ .OrderBy(b => this.GetDownTime(b, i))
+ .Cast().FirstOrDefault();
+ if (this.heldGamepadButtons[i].HasValue && this.TryGetDownTime(this.heldGamepadButtons[i].Value, out var heldTime, i)) {
+ if (heldTime >= this.KeyRepeatDelay) {
+ var diff = now - this.lastGamepadButtonRepeats[i];
+ if (diff >= this.KeyRepeatRate) {
+ this.lastGamepadButtonRepeats[i] = now;
+ this.triggerGamepadButtonRepeat[i] = true;
}
}
}
@@ -308,15 +270,16 @@ namespace MLEM.Input {
this.gestures.Add(TouchPanel.ReadGesture());
}
- if (this.StoreAllActiveInputs) {
- if (this.inputsDownAccum.Count <= 0) {
- this.InputsPressed = Array.Empty();
- this.InputsDown = Array.Empty();
- } else {
- this.InputsPressed = this.inputsDownAccum.Where(this.IsPressed).ToArray();
- this.InputsDown = this.inputsDownAccum.ToArray();
- this.inputsDownAccum.Clear();
- }
+ if (this.inputsDownAccum.Count <= 0) {
+ this.InputsPressed = Array.Empty();
+ this.InputsDown = Array.Empty();
+ this.inputsDown.Clear();
+ } else {
+ this.InputsPressed = this.inputsDownAccum.Keys.Where(kv => this.IsPressed(kv.Item1, kv.Item2)).Select(kv => kv.Item1).ToArray();
+ this.InputsDown = this.inputsDownAccum.Keys.Select(kv => kv.Item1).ToArray();
+ // swapping these collections means that we don't have to keep moving entries between them
+ (this.inputsDown, this.inputsDownAccum) = (this.inputsDownAccum, this.inputsDown);
+ this.inputsDownAccum.Clear();
}
}
@@ -652,6 +615,38 @@ namespace MLEM.Input {
return false;
}
+ ///
+ /// Tries to retrieve the amount of time that a given has been held down for.
+ /// If the input is currently down, this method returns true and the amount of time that it has been down for is stored in .
+ ///
+ /// The input whose down time to query.
+ /// The resulting down time, or if the input is not being held.
+ /// The index of the gamepad to query (if applicable), or -1 for any gamepad.
+ /// Whether the input is currently being held.
+ public bool TryGetDownTime(GenericInput input, out TimeSpan downTime, int index = -1) {
+ if (this.inputsDown.TryGetValue((input, index), out var start)) {
+ downTime = DateTime.UtcNow - start;
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// Returns the amount of time that a given has been held down for.
+ /// If this input isn't currently own, this method returns .
+ ///
+ /// The input whose down time to query.
+ /// The index of the gamepad to query (if applicable), or -1 for any gamepad.
+ /// The resulting down time, or if the input is not being held.
+ public TimeSpan GetDownTime(GenericInput input, int index = -1) {
+ this.TryGetDownTime(input, out var time, index);
+ return time;
+ }
+
+ private void AccumulateDown(GenericInput input, int index) {
+ this.inputsDownAccum.Add((input, index), this.inputsDown.TryGetValue((input, index), out var start) ? start : DateTime.UtcNow);
+ }
+
///
/// Helper function to enable gestures for a easily.
/// Note that, if other gestures were previously enabled, they will not get overridden.
diff --git a/Sandbox/GameImpl.cs b/Sandbox/GameImpl.cs
index 81f0113..201e9e1 100644
--- a/Sandbox/GameImpl.cs
+++ b/Sandbox/GameImpl.cs
@@ -327,9 +327,9 @@ namespace Sandbox {
}
/*if (Input.InputsDown.Length > 0)
- Console.WriteLine("Down: " + string.Join(", ", Input.InputsDown));
+ Console.WriteLine("Down: " + string.Join(", ", Input.InputsDown));*/
if (Input.InputsPressed.Length > 0)
- Console.WriteLine("Pressed: " + string.Join(", ", Input.InputsPressed));*/
+ Console.WriteLine("Pressed: " + string.Join(", ", Input.InputsPressed));
}
protected override void DoDraw(GameTime gameTime) {