summaryrefslogtreecommitdiff
path: root/src/SMAPI/Utilities/KeybindList.cs
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2021-01-19 21:20:25 -0500
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2021-01-19 21:20:25 -0500
commitff16a6567b6137b1aafed3470406d5f5884a5bdc (patch)
tree3b19d821d3f45d266914ec9fbccaa3a375c3fd0a /src/SMAPI/Utilities/KeybindList.cs
parent5676d94fe655c42e50c27b5eae72b9c96cfc2476 (diff)
downloadSMAPI-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.cs152
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();
+ }
+ }
+}