using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using StardewModdingAPI.Toolkit.Serialization;
namespace StardewModdingAPI.Utilities
{
/// A set of multi-key bindings which can be triggered by the player.
public class KeybindList
{
/*********
** Accessors
*********/
/// The individual keybinds.
public Keybind[] Keybinds { get; }
/// Whether any keys are bound.
public bool IsBound { get; }
/*********
** Public methods
*********/
/// Construct an instance.
/// The underlying keybinds.
/// See or 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.
public KeybindList(params Keybind[] keybinds)
{
this.Keybinds = keybinds.Where(p => p.IsBound).ToArray();
this.IsBound = this.Keybinds.Any();
}
/// Construct an instance.
/// A single-key binding.
public KeybindList(SButton singleKey)
: this(new Keybind(singleKey)) { }
/// Parse a keybind list from a string, and throw an exception if it's not valid.
/// The keybind string. See remarks on for format details.
/// The format is invalid.
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)}");
}
/// Try to parse a keybind list from a string.
/// The keybind string. See remarks on for format details.
/// The parsed keybind list, if valid.
/// The errors that occurred while parsing the input, if any.
public static bool TryParse(string? input, [NotNullWhen(true)] out KeybindList? parsed, out string[] errors)
{
// empty input
if (string.IsNullOrWhiteSpace(input))
{
parsed = new KeybindList();
errors = Array.Empty();
return true;
}
// parse buttons
var rawErrors = new List();
var keybinds = new List();
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.Distinct().ToArray();
return false;
}
else
{
parsed = new KeybindList(keybinds.ToArray());
errors = Array.Empty();
return true;
}
}
/// Get a keybind list for a single keybind.
/// The buttons that must be down to activate the keybind.
public static KeybindList ForSingle(params SButton[] buttons)
{
return new KeybindList(
new Keybind(buttons)
);
}
/// Get the overall keybind list state relative to the previous tick.
/// States are transitive across keybind. For example, if one keybind is 'released' and another is 'pressed', the state of the keybind list is 'held'.
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;
}
/// Get whether any of the button sets are pressed.
public bool IsDown()
{
SButtonState state = this.GetState();
return state is SButtonState.Pressed or SButtonState.Held;
}
/// Get whether the input binding was just pressed this tick.
public bool JustPressed()
{
return this.GetState() == SButtonState.Pressed;
}
/// Get the keybind which is currently down, if any. If there are multiple keybinds down, the first one is returned.
public Keybind? GetKeybindCurrentlyDown()
{
return this.Keybinds.FirstOrDefault(p => p.GetState().IsDown());
}
/// Get a string representation of the input binding.
/// A keybind list is serialized to a string like LeftControl + S, LeftAlt + S, where each multi-key binding is separated with , and the keys within each keybind are separated with +. The key order is commutative, so LeftControl + S and S + LeftControl are identical.
public override string ToString()
{
return this.Keybinds.Length > 0
? string.Join(", ", this.Keybinds.Select(p => p.ToString()))
: SButton.None.ToString();
}
}
}