diff options
Diffstat (limited to 'src/SMAPI/Utilities/Keybind.cs')
-rw-r--r-- | src/SMAPI/Utilities/Keybind.cs | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/src/SMAPI/Utilities/Keybind.cs b/src/SMAPI/Utilities/Keybind.cs new file mode 100644 index 00000000..dd8d2861 --- /dev/null +++ b/src/SMAPI/Utilities/Keybind.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using StardewModdingAPI.Framework; + +namespace StardewModdingAPI.Utilities +{ + /// <summary>A single multi-key binding which can be triggered by the player.</summary> + /// <remarks>NOTE: this is part of <see cref="KeybindList"/>, and usually shouldn't be used directly.</remarks> + public class Keybind + { + /********* + ** Fields + *********/ + /// <summary>Get the current input state for a button.</summary> + [Obsolete("This property should only be used for unit tests.")] + internal Func<SButton, SButtonState> GetButtonState { get; set; } = SGame.GetInputState; + + + /********* + ** Accessors + *********/ + /// <summary>The buttons that must be down to activate the keybind.</summary> + public SButton[] Buttons { get; } + + /// <summary>Whether any keys are bound.</summary> + public bool IsBound { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="buttons">The buttons that must be down to activate the keybind.</param> + public Keybind(params SButton[] buttons) + { + this.Buttons = buttons; + this.IsBound = buttons.Any(p => p != SButton.None); + } + + /// <summary>Parse a keybind string, if it's valid.</summary> + /// <param name="input">The keybind string. See remarks on <see cref="ToString"/> for format details.</param> + /// <param name="parsed">The parsed keybind, if valid.</param> + /// <param name="errors">The parse errors, if any.</param> + public static bool TryParse(string input, out Keybind parsed, out string[] errors) + { + // empty input + if (string.IsNullOrWhiteSpace(input)) + { + parsed = new Keybind(SButton.None); + errors = new string[0]; + return true; + } + + // parse buttons + string[] rawButtons = input.Split('+'); + SButton[] buttons = new SButton[rawButtons.Length]; + List<string> rawErrors = new List<string>(); + for (int i = 0; i < buttons.Length; i++) + { + string rawButton = rawButtons[i].Trim(); + if (string.IsNullOrWhiteSpace(rawButton)) + rawErrors.Add("Invalid empty button value"); + else if (!Enum.TryParse(rawButton, ignoreCase: true, out SButton button)) + { + string error = $"Invalid button value '{rawButton}'"; + + switch (rawButton.ToLower()) + { + case "shift": + error += $" (did you mean {SButton.LeftShift}?)"; + break; + + case "ctrl": + case "control": + error += $" (did you mean {SButton.LeftControl}?)"; + break; + + case "alt": + error += $" (did you mean {SButton.LeftAlt}?)"; + break; + } + + rawErrors.Add(error); + } + else + buttons[i] = button; + } + + // build result + if (rawErrors.Any()) + { + parsed = null; + errors = rawErrors.ToArray(); + return false; + } + else + { + parsed = new Keybind(buttons); + errors = new string[0]; + return true; + } + } + + /// <summary>Get the keybind state relative to the previous tick.</summary> + public SButtonState GetState() + { + SButtonState[] states = this.Buttons.Select(this.GetButtonState).Distinct().ToArray(); + + // single state + if (states.Length == 1) + return states[0]; + + // if any key has no state, the whole set wasn't enabled last tick + if (states.Contains(SButtonState.None)) + return SButtonState.None; + + // mix of held + pressed => pressed + if (states.All(p => p == SButtonState.Pressed || p == SButtonState.Held)) + return SButtonState.Pressed; + + // mix of held + released => released + if (states.All(p => p == SButtonState.Held || p == SButtonState.Released)) + return SButtonState.Released; + + // not down last tick or now + return SButtonState.None; + } + + /// <summary>Get a string representation of the keybind.</summary> + /// <remarks>A keybind is serialized to a string like <c>LeftControl + S</c>, where each key is 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.Buttons.Length > 0 + ? string.Join(" + ", this.Buttons) + : SButton.None.ToString(); + } + } +} |