diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2021-01-19 21:20:25 -0500 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2021-01-19 21:20:25 -0500 |
commit | ff16a6567b6137b1aafed3470406d5f5884a5bdc (patch) | |
tree | 3b19d821d3f45d266914ec9fbccaa3a375c3fd0a /src/SMAPI/Utilities/KeybindList.cs | |
parent | 5676d94fe655c42e50c27b5eae72b9c96cfc2476 (diff) | |
download | SMAPI-ff16a6567b6137b1aafed3470406d5f5884a5bdc.tar.gz SMAPI-ff16a6567b6137b1aafed3470406d5f5884a5bdc.tar.bz2 SMAPI-ff16a6567b6137b1aafed3470406d5f5884a5bdc.zip |
add multi-key binding API (#744)
Diffstat (limited to 'src/SMAPI/Utilities/KeybindList.cs')
-rw-r--r-- | src/SMAPI/Utilities/KeybindList.cs | 152 |
1 files changed, 152 insertions, 0 deletions
diff --git a/src/SMAPI/Utilities/KeybindList.cs b/src/SMAPI/Utilities/KeybindList.cs new file mode 100644 index 00000000..f6933af3 --- /dev/null +++ b/src/SMAPI/Utilities/KeybindList.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using StardewModdingAPI.Toolkit.Serialization; + +namespace StardewModdingAPI.Utilities +{ + /// <summary>A set of multi-key bindings which can be triggered by the player.</summary> + public class KeybindList + { + /********* + ** Accessors + *********/ + /// <summary>The individual keybinds.</summary> + public Keybind[] Keybinds { get; } + + /// <summary>Whether any keys are bound.</summary> + public bool IsBound { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="keybinds">The underlying keybinds.</param> + /// <remarks>See <see cref="Parse"/> or <see cref="TryParse"/> to parse it from a string representation. You can also use this type directly in your config or JSON data models, and it'll be parsed by SMAPI.</remarks> + public KeybindList(params Keybind[] keybinds) + { + this.Keybinds = keybinds.Where(p => p.IsBound).ToArray(); + this.IsBound = this.Keybinds.Any(); + } + + /// <summary>Parse a keybind list from a string, and throw an exception if it's not valid.</summary> + /// <param name="input">The keybind string. See remarks on <see cref="ToString"/> for format details.</param> + /// <exception cref="FormatException">The <paramref name="input"/> format is invalid.</exception> + public static KeybindList Parse(string input) + { + return KeybindList.TryParse(input, out KeybindList parsed, out string[] errors) + ? parsed + : throw new SParseException($"Can't parse {nameof(Keybind)} from invalid value '{input}'.\n{string.Join("\n", errors)}"); + } + + /// <summary>Try to parse a keybind list from a string.</summary> + /// <param name="input">The keybind string. See remarks on <see cref="ToString"/> for format details.</param> + /// <param name="parsed">The parsed keybind list, if valid.</param> + /// <param name="errors">The errors that occurred while parsing the input, if any.</param> + public static bool TryParse(string input, out KeybindList parsed, out string[] errors) + { + // empty input + if (string.IsNullOrWhiteSpace(input)) + { + parsed = new KeybindList(); + errors = new string[0]; + return true; + } + + // parse buttons + var rawErrors = new List<string>(); + var keybinds = new List<Keybind>(); + foreach (string rawSet in input.Split(',')) + { + if (string.IsNullOrWhiteSpace(rawSet)) + continue; + + if (!Keybind.TryParse(rawSet, out Keybind keybind, out string[] curErrors)) + rawErrors.AddRange(curErrors); + else + keybinds.Add(keybind); + } + + // build result + if (rawErrors.Any()) + { + parsed = null; + errors = rawErrors.ToArray(); + return false; + } + else + { + parsed = new KeybindList(keybinds.ToArray()); + errors = new string[0]; + return true; + } + } + + /// <summary>Get the overall keybind list state relative to the previous tick.</summary> + /// <remarks>States are transitive across keybind. For example, if one keybind is 'released' and another is 'pressed', the state of the keybind list is 'held'.</remarks> + public SButtonState GetState() + { + bool wasPressed = false; + bool isPressed = false; + + foreach (Keybind keybind in this.Keybinds) + { + switch (keybind.GetState()) + { + case SButtonState.Pressed: + isPressed = true; + break; + + case SButtonState.Held: + wasPressed = true; + isPressed = true; + break; + + case SButtonState.Released: + wasPressed = true; + break; + } + } + + if (wasPressed == isPressed) + { + return wasPressed + ? SButtonState.Held + : SButtonState.None; + } + + return wasPressed + ? SButtonState.Released + : SButtonState.Pressed; + } + + /// <summary>Get whether any of the button sets are pressed.</summary> + public bool IsDown() + { + SButtonState state = this.GetState(); + return state == SButtonState.Pressed || state == SButtonState.Held; + } + + /// <summary>Get whether the input binding was just pressed this tick.</summary> + public bool JustPressed() + { + return this.GetState() == SButtonState.Pressed; + } + + /// <summary>Get the keybind which is currently down, if any. If there are multiple keybinds down, the first one is returned.</summary> + public Keybind GetKeybindCurrentlyDown() + { + return this.Keybinds.FirstOrDefault(p => p.GetState().IsDown()); + } + + /// <summary>Get a string representation of the input binding.</summary> + /// <remarks>A keybind list is serialized to a string like <c>LeftControl + S, LeftAlt + S</c>, where each multi-key binding is separated with <c>,</c> and the keys within each keybind are separated with <c>+</c>. The key order is commutative, so <c>LeftControl + S</c> and <c>S + LeftControl</c> are identical.</remarks> + public override string ToString() + { + return this.Keybinds.Length > 0 + ? string.Join(", ", this.Keybinds.Select(p => p.ToString())) + : SButton.None.ToString(); + } + } +} |