add multi-key binding API (#744)
This commit is contained in:
parent
5676d94fe6
commit
ff16a6567b
|
@ -12,6 +12,7 @@
|
||||||
* Improved game path detection in the installer. The installer now prefers the path registered by Steam or GOG Galaxy, and can also now detect the default install path for manual GOG installs.
|
* Improved game path detection in the installer. The installer now prefers the path registered by Steam or GOG Galaxy, and can also now detect the default install path for manual GOG installs.
|
||||||
|
|
||||||
* For modders:
|
* For modders:
|
||||||
|
* Added [API for multi-key bindings](https://stardewcommunitywiki.com/Modding:Modder_Guide/APIs/Input#KeybindList).
|
||||||
* Improved multiplayer APIs:
|
* Improved multiplayer APIs:
|
||||||
* `PerScreen<T>` now lets you get/set the value for any screen, get all active values, or clear all values.
|
* `PerScreen<T>` now lets you get/set the value for any screen, get all active values, or clear all values.
|
||||||
* Peer data for the multiplayer API/events now includes `IsSplitScreen` and `ScreenID` fields.
|
* Peer data for the multiplayer API/events now includes `IsSplitScreen` and `ScreenID` fields.
|
||||||
|
@ -22,9 +23,9 @@
|
||||||
* Fixed quarry bridge not fixed if the mountain map was reloaded.
|
* Fixed quarry bridge not fixed if the mountain map was reloaded.
|
||||||
* Added an option to disable rewriting mods for compatibility (thanks to Bpendragon!). This prevents older mods from loading but bypasses a Visual Studio debugger crash.
|
* Added an option to disable rewriting mods for compatibility (thanks to Bpendragon!). This prevents older mods from loading but bypasses a Visual Studio debugger crash.
|
||||||
* Game errors shown in the chatbox are now logged.
|
* Game errors shown in the chatbox are now logged.
|
||||||
|
* Moved vanilla error-handling into a new Error Handler mod. This simplifies the core SMAPI logic, and lets users disable it if needed.
|
||||||
|
|
||||||
* For the Error Handler mod:
|
* For the Error Handler mod:
|
||||||
* Added in SMAPI 3.9. This has vanilla error-handling that was previously added by SMAPI directly. That simplifies the core SMAPI logic, and lets players or modders disable it if needed.
|
|
||||||
* Added a detailed message for the _Input string was not in a correct format_ error when the game fails to parse an item text description.
|
* Added a detailed message for the _Input string was not in a correct format_ error when the game fails to parse an item text description.
|
||||||
|
|
||||||
* For the web UI:
|
* For the web UI:
|
||||||
|
|
|
@ -18,10 +18,10 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
|
||||||
** Accessors
|
** Accessors
|
||||||
*********/
|
*********/
|
||||||
/// <summary>Get whether this converter can read JSON.</summary>
|
/// <summary>Get whether this converter can read JSON.</summary>
|
||||||
public override bool CanRead => true;
|
public override bool CanRead { get; } = true;
|
||||||
|
|
||||||
/// <summary>Get whether this converter can write JSON.</summary>
|
/// <summary>Get whether this converter can write JSON.</summary>
|
||||||
public override bool CanWrite => true;
|
public override bool CanWrite { get; } = true;
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
|
|
|
@ -39,6 +39,8 @@
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Hangfire/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Hangfire/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=initializers/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=initializers/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Junimo/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Junimo/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Keybind/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=keybinds/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=modder/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=modder/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=modders/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=modders/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mongo/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mongo/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
|
|
@ -208,6 +208,7 @@ namespace StardewModdingAPI.Framework
|
||||||
{
|
{
|
||||||
JsonConverter[] converters = {
|
JsonConverter[] converters = {
|
||||||
new ColorConverter(),
|
new ColorConverter(),
|
||||||
|
new KeybindConverter(),
|
||||||
new PointConverter(),
|
new PointConverter(),
|
||||||
new Vector2Converter(),
|
new Vector2Converter(),
|
||||||
new RectangleConverter()
|
new RectangleConverter()
|
||||||
|
|
|
@ -11,6 +11,7 @@ using StardewModdingAPI.Framework.Input;
|
||||||
using StardewModdingAPI.Framework.Reflection;
|
using StardewModdingAPI.Framework.Reflection;
|
||||||
using StardewModdingAPI.Framework.StateTracking.Snapshots;
|
using StardewModdingAPI.Framework.StateTracking.Snapshots;
|
||||||
using StardewModdingAPI.Framework.Utilities;
|
using StardewModdingAPI.Framework.Utilities;
|
||||||
|
using StardewModdingAPI.Utilities;
|
||||||
using StardewValley;
|
using StardewValley;
|
||||||
using StardewValley.BellsAndWhistles;
|
using StardewValley.BellsAndWhistles;
|
||||||
using StardewValley.Locations;
|
using StardewValley.Locations;
|
||||||
|
@ -124,6 +125,18 @@ namespace StardewModdingAPI.Framework
|
||||||
this.OnUpdating = onUpdating;
|
this.OnUpdating = onUpdating;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Get the current input state for a button.</summary>
|
||||||
|
/// <param name="button">The button to check.</param>
|
||||||
|
/// <remarks>This is intended for use by <see cref="Keybind"/> and shouldn't be used directly in most cases.</remarks>
|
||||||
|
internal static SButtonState GetInputState(SButton button)
|
||||||
|
{
|
||||||
|
SInputState input = Game1.input as SInputState;
|
||||||
|
if (input == null)
|
||||||
|
throw new InvalidOperationException("SMAPI's input state is not in a ready state yet.");
|
||||||
|
|
||||||
|
return input.GetState(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
** Protected methods
|
** Protected methods
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using StardewModdingAPI.Toolkit.Serialization;
|
||||||
|
using StardewModdingAPI.Utilities;
|
||||||
|
|
||||||
|
namespace StardewModdingAPI.Framework.Serialization
|
||||||
|
{
|
||||||
|
/// <summary>Handles deserialization of <see cref="Keybind"/> and <see cref="KeybindList"/> models.</summary>
|
||||||
|
internal class KeybindConverter : JsonConverter
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Accessors
|
||||||
|
*********/
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool CanRead { get; } = true;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool CanWrite { get; } = true;
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Get whether this instance can convert the specified object type.</summary>
|
||||||
|
/// <param name="objectType">The object type.</param>
|
||||||
|
public override bool CanConvert(Type objectType)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
typeof(Keybind).IsAssignableFrom(objectType)
|
||||||
|
|| typeof(KeybindList).IsAssignableFrom(objectType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Reads the JSON representation of the object.</summary>
|
||||||
|
/// <param name="reader">The JSON reader.</param>
|
||||||
|
/// <param name="objectType">The object type.</param>
|
||||||
|
/// <param name="existingValue">The object being read.</param>
|
||||||
|
/// <param name="serializer">The calling serializer.</param>
|
||||||
|
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
string path = reader.Path;
|
||||||
|
|
||||||
|
// validate JSON type
|
||||||
|
if (reader.TokenType != JsonToken.String)
|
||||||
|
throw new SParseException($"Can't parse {nameof(KeybindList)} from {reader.TokenType} node (path: {reader.Path}).");
|
||||||
|
|
||||||
|
// parse raw value
|
||||||
|
string str = JToken.Load(reader).Value<string>();
|
||||||
|
if (objectType == typeof(Keybind))
|
||||||
|
{
|
||||||
|
return Keybind.TryParse(str, out Keybind parsed, out string[] errors)
|
||||||
|
? parsed
|
||||||
|
: throw new SParseException($"Can't parse {nameof(Keybind)} from invalid value '{str}' (path: {path}).\n{string.Join("\n", errors)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (objectType == typeof(KeybindList))
|
||||||
|
{
|
||||||
|
return KeybindList.TryParse(str, out KeybindList parsed, out string[] errors)
|
||||||
|
? parsed
|
||||||
|
: throw new SParseException($"Can't parse {nameof(KeybindList)} from invalid value '{str}' (path: {path}).\n{string.Join("\n", errors)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SParseException($"Can't parse unexpected type {objectType} from {reader.TokenType} node (path: {reader.Path}).");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Writes the JSON representation of the object.</summary>
|
||||||
|
/// <param name="writer">The JSON writer.</param>
|
||||||
|
/// <param name="value">The value.</param>
|
||||||
|
/// <param name="serializer">The calling serializer.</param>
|
||||||
|
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
writer.WriteValue(value?.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Private methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Read a JSON string.</summary>
|
||||||
|
/// <param name="str">The JSON string value.</param>
|
||||||
|
/// <param name="path">The path to the current JSON node.</param>
|
||||||
|
protected KeybindList ReadString(string str, string path)
|
||||||
|
{
|
||||||
|
return KeybindList.TryParse(str, out KeybindList parsed, out string[] errors)
|
||||||
|
? parsed
|
||||||
|
: throw new SParseException($"Can't parse {nameof(KeybindList)} from invalid value '{str}' (path: {path}).\n{string.Join("\n", errors)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
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
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** 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(SGame.GetInputState).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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue