| using System; | |
| using System.Linq; | |
| using System.Text; | |
| using UnityEngine.InputSystem.Layouts; | |
| using UnityEngine.InputSystem.Utilities; | |
| ////REVIEW: do we really need overridable processors and interactions? | |
| // Downsides to the current approach: | |
| // - Being able to address entire batches of controls through a single control is awesome. Especially | |
| // when combining it type-kind of queries (e.g. "<MyDevice>/<Button>"). However, it complicates things | |
| // in quite a few areas. There's quite a few bits in InputActionState that could be simplified if a | |
| // binding simply maps to a control. | |
| namespace UnityEngine.InputSystem | |
| { | |
| /// <summary> | |
| /// A mapping of controls to an action. | |
| /// </summary> | |
| /// <remarks> | |
| /// Each binding represents a value received from controls (see <see cref="InputControl"/>). | |
| /// There are two main types of bindings: "normal" bindings and "composite" bindings. | |
| /// | |
| /// Normal bindings directly bind to control(s) by means of <see cref="path"/> which is a "control path" | |
| /// (see <see cref="InputControlPath"/> for details about how to form paths). At runtime, the | |
| /// path of such a binding may match none, one, or multiple controls. Each control matched by the | |
| /// path will feed input into the binding. | |
| /// | |
| /// Composite bindings do not bind to controls themselves. Instead, they receive their input | |
| /// from their "part" bindings and then return a value representing a "composition" of those | |
| /// inputs. What composition specifically is performed depends on the type of the composite. | |
| /// <see cref="Composites.AxisComposite"/>, for example, will return a floating-point axis value | |
| /// computed from the state of two buttons. | |
| /// | |
| /// The action that is triggered by a binding is determined by its <see cref="action"/> property. | |
| /// The resolution to an <see cref="InputAction"/> depends on where the binding is used. For example, | |
| /// bindings that are part of <see cref="InputActionMap.bindings"/> will resolve action names to | |
| /// actions in the same <see cref="InputActionMap"/>. | |
| /// | |
| /// A binding can also be used as a form of search mask or filter. In this use, <see cref="path"/>, | |
| /// <see cref="action"/>, and <see cref="groups"/> become search criteria that are matched | |
| /// against other bindings. See <see cref="Matches(InputBinding)"/> for details. This use | |
| /// is employed in places such as <see cref="InputActionRebindingExtensions"/> as well as in | |
| /// binding masks on actions (<see cref="InputAction.bindingMask"/>), action maps (<see | |
| /// cref="InputActionMap.bindingMask"/>), and assets (<see cref="InputActionAsset.bindingMask"/>). | |
| /// </remarks> | |
| [] | |
| public struct InputBinding : IEquatable<InputBinding> | |
| { | |
| /// <summary> | |
| /// Character that is used to separate elements in places such as <see cref="groups"/>, | |
| /// <see cref="interactions"/>, and <see cref="processors"/>. | |
| /// </summary> | |
| /// Some strings on bindings represent lists of elements. An example is <see cref="groups"/> | |
| /// which may associate a binding with several binding groups, each one delimited by the | |
| /// separator. | |
| /// | |
| /// <remarks> | |
| /// <example> | |
| /// <code> | |
| /// // A binding that belongs to the "Keyboard&Mouse" and "Gamepad" group. | |
| /// new InputBinding | |
| /// { | |
| /// path = "*/{PrimaryAction}, | |
| /// groups = "Keyboard&Mouse;Gamepad" | |
| /// }; | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| public const char Separator = ';'; | |
| internal const string kSeparatorString = ";"; | |
| /// <summary> | |
| /// Optional name for the binding. | |
| /// </summary> | |
| /// <value>Name of the binding.</value> | |
| /// <remarks> | |
| /// For bindings that are part of composites (see <see cref="isPartOfComposite"/>), this is | |
| /// the name of the field on the binding composite object that should be initialized with | |
| /// the control target of the binding. | |
| /// </remarks> | |
| public string name | |
| { | |
| get => m_Name; | |
| set => m_Name = value; | |
| } | |
| /// <summary> | |
| /// Unique ID of the binding. | |
| /// </summary> | |
| /// <value>Unique ID of the binding.</value> | |
| /// <remarks> | |
| /// This can be used, for example, when storing binding overrides in local user configurations. | |
| /// Using the binding ID, an override can remain associated with one specific binding. | |
| /// </remarks> | |
| public Guid id | |
| { | |
| get | |
| { | |
| ////REVIEW: this is inconsistent with InputActionMap and InputAction which generate IDs, if necessary | |
| if (string.IsNullOrEmpty(m_Id)) | |
| return default; | |
| return new Guid(m_Id); | |
| } | |
| set => m_Id = value.ToString(); | |
| } | |
| /// <summary> | |
| /// Control path being bound to. | |
| /// </summary> | |
| /// <value>Path of control(s) to source input from.</value> | |
| /// <remarks> | |
| /// Bindings reference <see cref="InputControl"/>s using a regular expression-like | |
| /// language. See <see cref="InputControlPath"/> for details. | |
| /// | |
| /// If the binding is a composite (<see cref="isComposite"/>), the path is the composite | |
| /// string instead. For example, for a <see cref="Composites.Vector2Composite"/>, the | |
| /// path could be something like <c>"Vector2(normalize=false)"</c>. | |
| /// | |
| /// The path of a binding may be non-destructively override at runtime using <see cref="overridePath"/> | |
| /// which unlike this property is not serialized. <see cref="effectivePath"/> represents the | |
| /// final, effective path. | |
| /// </remarks> | |
| /// <example> | |
| /// <code> | |
| /// // A binding that references the left mouse button. | |
| /// new InputBinding { path = "<Mouse>/leftButton" } | |
| /// </code> | |
| /// </example> | |
| /// <seealso cref="overridePath"/> | |
| /// <seealso cref="InputControlPath"/> | |
| /// <seealso cref="InputControlPath.Parse"/> | |
| /// <seealso cref="InputControl.path"/> | |
| /// <seealso cref="InputSystem.FindControl"/> | |
| public string path | |
| { | |
| get => m_Path; | |
| set => m_Path = value; | |
| } | |
| /// <summary> | |
| /// If the binding is overridden, this is the overriding path. | |
| /// Otherwise it is <c>null</c>. | |
| /// </summary> | |
| /// <value>Path to override the <see cref="path"/> property with.</value> | |
| /// <remarks> | |
| /// Unlike the <see cref="path"/> property, the value of the override path is not serialized. | |
| /// If set, it will take precedence and determine the result of <see cref="effectivePath"/>. | |
| /// | |
| /// This property can be set to an empty string to disable the binding. During resolution, | |
| /// bindings with an empty <see cref="effectivePath"/> will get skipped. | |
| /// | |
| /// To set the override on an existing binding, use the methods supplied by <see cref="InputActionRebindingExtensions"/> | |
| /// such as <see cref="InputActionRebindingExtensions.ApplyBindingOverride(InputAction,string,string,string)"/>. | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// // Override the binding to <Gamepad>/buttonSouth on | |
| /// // myAction with a binding to <Gamepad>/buttonNorth. | |
| /// myAction.ApplyBindingOverride( | |
| /// new InputBinding | |
| /// { | |
| /// path = "<Gamepad>/buttonSouth", | |
| /// overridePath = "<Gamepad>/buttonNorth" | |
| /// }); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <seealso cref="path"/> | |
| /// <seealso cref="overrideInteractions"/> | |
| /// <seealso cref="overrideProcessors"/> | |
| /// <seealso cref="hasOverrides"/> | |
| /// <seealso cref="InputActionRebindingExtensions.SaveBindingOverridesAsJson(IInputActionCollection2)"/> | |
| /// <seealso cref="InputActionRebindingExtensions.LoadBindingOverridesFromJson(IInputActionCollection2,string,bool)"/> | |
| /// <seealso cref="InputActionRebindingExtensions.ApplyBindingOverride(InputAction,int,InputBinding)"/> | |
| public string overridePath | |
| { | |
| get => m_OverridePath; | |
| set => m_OverridePath = value; | |
| } | |
| /// <summary> | |
| /// Optional list of interactions and their parameters. | |
| /// </summary> | |
| /// <value>Interactions to put on the binding.</value> | |
| /// <remarks> | |
| /// Each element in the list is a name of an interaction (as registered with | |
| /// <see cref="InputSystem.RegisterInteraction{T}"/>) followed by an optional | |
| /// list of parameters. | |
| /// | |
| /// For example, <c>"slowTap(duration=1.2,pressPoint=0.123)"</c> is one element | |
| /// that puts a <see cref="Interactions.SlowTapInteraction"/> on the binding and | |
| /// sets <see cref="Interactions.SlowTapInteraction.duration"/> to 1.2 and | |
| /// <see cref="Interactions.SlowTapInteraction.pressPoint"/> to 0.123. | |
| /// | |
| /// Multiple interactions can be put on a binding by separating them with a comma. | |
| /// For example, <c>"tap,slowTap(duration=1.2)"</c> puts both a | |
| /// <see cref="Interactions.TapInteraction"/> and <see cref="Interactions.SlowTapInteraction"/> | |
| /// on the binding. See <see cref="IInputInteraction"/> for why the order matters. | |
| /// </remarks> | |
| /// <seealso cref="IInputInteraction"/> | |
| /// <seealso cref="overrideInteractions"/> | |
| /// <seealso cref="hasOverrides"/> | |
| /// <seealso cref="InputActionRebindingExtensions.SaveBindingOverridesAsJson(IInputActionCollection2)"/> | |
| /// <seealso cref="InputActionRebindingExtensions.LoadBindingOverridesFromJson(IInputActionCollection2,string,bool)"/> | |
| /// <seealso cref="InputActionRebindingExtensions.ApplyBindingOverride(InputAction,int,InputBinding)"/> | |
| public string interactions | |
| { | |
| get => m_Interactions; | |
| set => m_Interactions = value; | |
| } | |
| /// <summary> | |
| /// Interaction settings to override <see cref="interactions"/> with. | |
| /// </summary> | |
| /// <value>Override string for <see cref="interactions"/> or <c>null</c>.</value> | |
| /// <remarks> | |
| /// If this is not <c>null</c>, it replaces the value of <see cref="interactions"/>. | |
| /// </remarks> | |
| /// <seealso cref="effectiveInteractions"/> | |
| /// <seealso cref="interactions"/> | |
| /// <seealso cref="overridePath"/> | |
| /// <seealso cref="overrideProcessors"/> | |
| /// <seealso cref="hasOverrides"/> | |
| /// <seealso cref="InputActionRebindingExtensions.SaveBindingOverridesAsJson(IInputActionCollection2)"/> | |
| /// <seealso cref="InputActionRebindingExtensions.LoadBindingOverridesFromJson(IInputActionCollection2,string,bool)"/> | |
| /// <seealso cref="InputActionRebindingExtensions.ApplyBindingOverride(InputAction,int,InputBinding)"/> | |
| public string overrideInteractions | |
| { | |
| get => m_OverrideInteractions; | |
| set => m_OverrideInteractions = value; | |
| } | |
| /// <summary> | |
| /// Optional list of processors to apply to control values. | |
| /// </summary> | |
| /// <value>Value processors to apply to the binding.</value> | |
| /// <remarks> | |
| /// This string has the same format as <see cref="InputControlAttribute.processors"/>. | |
| /// </remarks> | |
| /// <seealso cref="InputProcessor{TValue}"/> | |
| /// <seealso cref="overrideProcessors"/> | |
| public string processors | |
| { | |
| get => m_Processors; | |
| set => m_Processors = value; | |
| } | |
| /// <summary> | |
| /// Processor settings to override <see cref="processors"/> with. | |
| /// </summary> | |
| /// <value>Override string for <see cref="processors"/> or <c>null</c>.</value> | |
| /// <remarks> | |
| /// If this is not <c>null</c>, it replaces the value of <see cref="processors"/>. | |
| /// </remarks> | |
| /// <seealso cref="effectiveProcessors"/> | |
| /// <seealso cref="processors"/> | |
| /// <seealso cref="overridePath"/> | |
| /// <seealso cref="overrideInteractions"/> | |
| /// <seealso cref="hasOverrides"/> | |
| public string overrideProcessors | |
| { | |
| get => m_OverrideProcessors; | |
| set => m_OverrideProcessors = value; | |
| } | |
| /// <summary> | |
| /// Optional list of binding groups that the binding belongs to. | |
| /// </summary> | |
| /// <value>List of binding groups or <c>null</c>.</value> | |
| /// <remarks> | |
| /// This is used, for example, to divide bindings into <see cref="InputControlScheme"/>s. | |
| /// Each control scheme is associated with a unique binding group through <see | |
| /// cref="InputControlScheme.bindingGroup"/>. | |
| /// | |
| /// A binding may be associated with multiple groups by listing each group name | |
| /// separate by a semicolon (<see cref="Separator"/>). | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// new InputBinding | |
| /// { | |
| /// path = "*/{PrimaryAction}, | |
| /// // Associate the binding both with the "KeyboardMouse" and | |
| /// // the "Gamepad" group. | |
| /// groups = "KeyboardMouse;Gamepad", | |
| /// } | |
| /// </code> | |
| /// </example> | |
| /// | |
| /// Note that the system places no restriction on what binding groups are used | |
| /// for in practice. Their use by <see cref="InputControlScheme"/> is only one | |
| /// possible one, but which groups to apply and how to use them is ultimately | |
| /// up to you. | |
| /// </remarks> | |
| /// <seealso cref="InputControlScheme.bindingGroup"/> | |
| public string groups | |
| { | |
| get => m_Groups; | |
| set => m_Groups = value; | |
| } | |
| /// <summary> | |
| /// Name or ID of the action triggered by the binding. | |
| /// </summary> | |
| /// <remarks> | |
| /// This is null if the binding does not trigger an action. | |
| /// | |
| /// For InputBindings that are used as masks, this can be a "mapName/actionName" combination | |
| /// or "mapName/*" to match all actions in the given map. | |
| /// </remarks> | |
| /// <seealso cref="InputAction.name"/> | |
| /// <seealso cref="InputAction.id"/> | |
| public string action | |
| { | |
| get => m_Action; | |
| set => m_Action = value; | |
| } | |
| /// <summary> | |
| /// Whether the binding is a composite. | |
| /// </summary> | |
| /// <value>True if the binding is a composite.</value> | |
| /// <remarks> | |
| /// Composite bindings to not bind to controls to themselves but rather source their | |
| /// input from one or more "part binding" (see <see cref="isPartOfComposite"/>). | |
| /// | |
| /// See <see cref="InputBindingComposite{TValue}"/> for more details. | |
| /// </remarks> | |
| /// <seealso cref="InputBindingComposite{TValue}"/> | |
| public bool isComposite | |
| { | |
| get => (m_Flags & Flags.Composite) == Flags.Composite; | |
| set | |
| { | |
| if (value) | |
| m_Flags |= Flags.Composite; | |
| else | |
| m_Flags &= ~Flags.Composite; | |
| } | |
| } | |
| /// <summary> | |
| /// Whether the binding is a "part binding" of a composite. | |
| /// </summary> | |
| /// <value>True if the binding is part of a composite.</value> | |
| /// <remarks> | |
| /// The bindings that make up a composite are laid out sequentially in <see cref="InputActionMap.bindings"/>. | |
| /// First comes the composite itself which is flagged with <see cref="isComposite"/>. It mentions | |
| /// the composite and its parameters in its <see cref="path"/> property. After the composite itself come | |
| /// the part bindings. All subsequent bindings marked as <c>isPartOfComposite</c> will be associated | |
| /// with the composite. | |
| /// </remarks> | |
| /// <seealso cref="isComposite"/> | |
| /// <seealso cref="InputBindingComposite{TValue}"/> | |
| public bool isPartOfComposite | |
| { | |
| get => (m_Flags & Flags.PartOfComposite) == Flags.PartOfComposite; | |
| set | |
| { | |
| if (value) | |
| m_Flags |= Flags.PartOfComposite; | |
| else | |
| m_Flags &= ~Flags.PartOfComposite; | |
| } | |
| } | |
| /// <summary> | |
| /// True if any of the override properties, that is, <see cref="overridePath"/>, <see cref="overrideProcessors"/>, | |
| /// and/or <see cref="overrideInteractions"/>, are set (not <c>null</c>). | |
| /// </summary> | |
| public bool hasOverrides => overridePath != null || overrideProcessors != null || overrideInteractions != null; | |
| /// <summary> | |
| /// Initialize a new binding. | |
| /// </summary> | |
| /// <param name="path">Path for the binding.</param> | |
| /// <param name="action">Action to trigger from the binding.</param> | |
| /// <param name="groups">Semicolon-separated list of binding <see cref="InputBinding.groups"/> the binding is associated with.</param> | |
| /// <param name="processors">Comma-separated list of <see cref="InputBinding.processors"/> to apply to the binding.</param> | |
| /// <param name="interactions">Comma-separated list of <see cref="InputBinding.interactions"/> to apply to the | |
| /// binding.</param> | |
| /// <param name="name">Optional name for the binding.</param> | |
| public InputBinding(string path, string action = null, string groups = null, string processors = null, | |
| string interactions = null, string name = null) | |
| { | |
| m_Path = path; | |
| m_Action = action; | |
| m_Groups = groups; | |
| m_Processors = processors; | |
| m_Interactions = interactions; | |
| m_Name = name; | |
| m_Id = default; | |
| m_Flags = default; | |
| m_OverridePath = default; | |
| m_OverrideInteractions = default; | |
| m_OverrideProcessors = default; | |
| } | |
| public string GetNameOfComposite() | |
| { | |
| if (!isComposite) | |
| return null; | |
| return NameAndParameters.Parse(effectivePath).name; | |
| } | |
| internal void GenerateId() | |
| { | |
| m_Id = Guid.NewGuid().ToString(); | |
| } | |
| internal void RemoveOverrides() | |
| { | |
| m_OverridePath = null; | |
| m_OverrideInteractions = null; | |
| m_OverrideProcessors = null; | |
| } | |
| public static InputBinding MaskByGroup(string group) | |
| { | |
| return new InputBinding {groups = group}; | |
| } | |
| public static InputBinding MaskByGroups(params string[] groups) | |
| { | |
| return new InputBinding {groups = string.Join(kSeparatorString, groups.Where(x => !string.IsNullOrEmpty(x)))}; | |
| } | |
| [] private string m_Name; | |
| [] internal string m_Id; | |
| [ | |
| ] | |
| [] private string m_Path; | |
| [] private string m_Interactions; | |
| [] private string m_Processors; | |
| [] internal string m_Groups; | |
| [] private string m_Action; | |
| [] internal Flags m_Flags; | |
| [] private string m_OverridePath; | |
| [] private string m_OverrideInteractions; | |
| [] private string m_OverrideProcessors; | |
| /// <summary> | |
| /// This is the bindings path which is effectively being used. | |
| /// </summary> | |
| /// <remarks> | |
| /// This is either <see cref="overridePath"/> if that is set, or <see cref="path"/> otherwise. | |
| /// </remarks> | |
| public string effectivePath => overridePath ?? path; | |
| /// <summary> | |
| /// This is the interaction config which is effectively being used. | |
| /// </summary> | |
| /// <remarks> | |
| /// This is either <see cref="overrideInteractions"/> if that is set, or <see cref="interactions"/> otherwise. | |
| /// </remarks> | |
| public string effectiveInteractions => overrideInteractions ?? interactions; | |
| /// <summary> | |
| /// This is the processor config which is effectively being used. | |
| /// </summary> | |
| /// <remarks> | |
| /// This is either <see cref="overrideProcessors"/> if that is set, or <see cref="processors"/> otherwise. | |
| /// </remarks> | |
| public string effectiveProcessors => overrideProcessors ?? processors; | |
| internal bool isEmpty => | |
| string.IsNullOrEmpty(effectivePath) && string.IsNullOrEmpty(action) && | |
| string.IsNullOrEmpty(groups); | |
| /// <summary> | |
| /// Check whether the binding is equivalent to the given binding. | |
| /// </summary> | |
| /// <param name="other">Another binding.</param> | |
| /// <returns>True if the two bindings are equivalent.</returns> | |
| /// <remarks> | |
| /// Bindings are equivalent if their <see cref="effectivePath"/>, <see cref="effectiveInteractions"/>, | |
| /// and <see cref="effectiveProcessors"/>, plus their <see cref="action"/> and <see cref="groups"/> | |
| /// properties are the same. Note that the string comparisons ignore both case and culture. | |
| /// </remarks> | |
| public bool Equals(InputBinding other) | |
| { | |
| return string.Equals(effectivePath, other.effectivePath, StringComparison.InvariantCultureIgnoreCase) && | |
| string.Equals(effectiveInteractions, other.effectiveInteractions, StringComparison.InvariantCultureIgnoreCase) && | |
| string.Equals(effectiveProcessors, other.effectiveProcessors, StringComparison.InvariantCultureIgnoreCase) && | |
| string.Equals(groups, other.groups, StringComparison.InvariantCultureIgnoreCase) && | |
| string.Equals(action, other.action, StringComparison.InvariantCultureIgnoreCase); | |
| } | |
| /// <summary> | |
| /// Compare the binding to the given object. | |
| /// </summary> | |
| /// <param name="obj">An object. May be <c>null</c>.</param> | |
| /// <returns>True if the given object is an <c>InputBinding</c> that equals this one.</returns> | |
| /// <seealso cref="Equals(InputBinding)"/> | |
| public override bool Equals(object obj) | |
| { | |
| if (ReferenceEquals(null, obj)) | |
| return false; | |
| return obj is InputBinding binding && Equals(binding); | |
| } | |
| /// <summary> | |
| /// Compare the two bindings for equality. | |
| /// </summary> | |
| /// <param name="left">The first binding.</param> | |
| /// <param name="right">The second binding.</param> | |
| /// <returns>True if the two bindings are equal.</returns> | |
| /// <seealso cref="Equals(InputBinding)"/> | |
| public static bool operator==(InputBinding left, InputBinding right) | |
| { | |
| return left.Equals(right); | |
| } | |
| /// <summary> | |
| /// Compare the two bindings for inequality. | |
| /// </summary> | |
| /// <param name="left">The first binding.</param> | |
| /// <param name="right">The second binding.</param> | |
| /// <returns>True if the two bindings are not equal.</returns> | |
| /// <seealso cref="Equals(InputBinding)"/> | |
| public static bool operator!=(InputBinding left, InputBinding right) | |
| { | |
| return !(left == right); | |
| } | |
| /// <summary> | |
| /// Compute a hash code for the binding. | |
| /// </summary> | |
| /// <returns>A hash code.</returns> | |
| public override int GetHashCode() | |
| { | |
| unchecked | |
| { | |
| var hashCode = (effectivePath != null ? effectivePath.GetHashCode() : 0); | |
| hashCode = (hashCode * 397) ^ (effectiveInteractions != null ? effectiveInteractions.GetHashCode() : 0); | |
| hashCode = (hashCode * 397) ^ (effectiveProcessors != null ? effectiveProcessors.GetHashCode() : 0); | |
| hashCode = (hashCode * 397) ^ (groups != null ? groups.GetHashCode() : 0); | |
| hashCode = (hashCode * 397) ^ (action != null ? action.GetHashCode() : 0); | |
| return hashCode; | |
| } | |
| } | |
| /// <summary> | |
| /// Return a string representation of the binding useful for debugging. | |
| /// </summary> | |
| /// <returns>A string representation of the binding.</returns> | |
| /// <example> | |
| /// <code> | |
| /// var binding = new InputBinding | |
| /// { | |
| /// action = "fire", | |
| /// path = "<Gamepad>/buttonSouth", | |
| /// groups = "Gamepad" | |
| /// }; | |
| /// | |
| /// // Returns "fire: <Gamepad>/buttonSouth [Gamepad]". | |
| /// binding.ToString(); | |
| /// </code> | |
| /// </example> | |
| public override string ToString() | |
| { | |
| var builder = new StringBuilder(); | |
| // Add action. | |
| if (!string.IsNullOrEmpty(action)) | |
| { | |
| builder.Append(action); | |
| builder.Append(':'); | |
| } | |
| // Add path. | |
| var path = effectivePath; | |
| if (!string.IsNullOrEmpty(path)) | |
| builder.Append(path); | |
| // Add groups. | |
| if (!string.IsNullOrEmpty(groups)) | |
| { | |
| builder.Append('['); | |
| builder.Append(groups); | |
| builder.Append(']'); | |
| } | |
| return builder.ToString(); | |
| } | |
| /// <summary> | |
| /// A set of flags to turn individual default behaviors of <see cref="InputBinding.ToDisplayString(DisplayStringOptions,InputControl)"/> off. | |
| /// </summary> | |
| [] | |
| public enum DisplayStringOptions | |
| { | |
| /// <summary> | |
| /// Do not use short names of controls as set up by <see cref="InputControlAttribute.shortDisplayName"/> | |
| /// and the <c>"shortDisplayName"</c> property in JSON. This will, for example, not use LMB instead of "left Button" | |
| /// on <see cref="Mouse.leftButton"/>. | |
| /// </summary> | |
| DontUseShortDisplayNames = 1 << 0, | |
| /// <summary> | |
| /// By default device names are omitted from display strings. With this option, they are included instead. | |
| /// For example, <c>"A"</c> will be <c>"A [Gamepad]"</c> instead. | |
| /// </summary> | |
| DontOmitDevice = 1 << 1, | |
| /// <summary> | |
| /// By default, interactions on bindings are included in the resulting display string. For example, a binding to | |
| /// the gamepad's A button that has a "Hold" interaction on it, would come out as "Hold A". This can be suppressed | |
| /// with this option in which case the same setup would come out as just "A". | |
| /// </summary> | |
| DontIncludeInteractions = 1 << 2, | |
| /// <summary> | |
| /// By default, <see cref="effectivePath"/> is used for generating a display name. Using this option, the display | |
| /// string can be forced to <see cref="path"/> instead. | |
| /// </summary> | |
| IgnoreBindingOverrides = 1 << 3, | |
| } | |
| /// <summary> | |
| /// Turn the binding into a string suitable for display in a UI. | |
| /// </summary> | |
| /// <param name="options">Optional set of formatting options.</param> | |
| /// <param name="control">Optional control to which the binding has been resolved. If this is supplied, | |
| /// the resulting string can reflect things such as the current keyboard layout or hardware/platform-specific | |
| /// naming of controls (e.g. Xbox vs PS4 controllers as opposed to naming things generically based on the | |
| /// <see cref="Gamepad"/> layout).</param> | |
| /// <returns>A string representation of the binding suitable for display in a UI.</returns> | |
| /// <remarks> | |
| /// This method works only for bindings that are not composites. If the method is called on a binding | |
| /// that is a composite (<see cref="isComposite"/> is true), an empty string will be returned. To automatically | |
| /// handle composites, use <see cref="InputActionRebindingExtensions.GetBindingDisplayString(InputAction,DisplayStringOptions,string)"/> | |
| /// instead. | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// var gamepadBinding = new InputBinding("<Gamepad>/buttonSouth"); | |
| /// var mouseBinding = new InputBinding("<Mouse>/leftButton"); | |
| /// var keyboardBinding = new InputBinding("<Keyboard>/a"); | |
| /// | |
| /// // Prints "A" except on PS4 where it prints "Cross". | |
| /// Debug.Log(gamepadBinding.ToDisplayString()); | |
| /// | |
| /// // Prints "LMB". | |
| /// Debug.Log(mouseBinding.ToDisplayString()); | |
| /// | |
| /// // Print "Left Button". | |
| /// Debug.Log(mouseBinding.ToDisplayString(DisplayStringOptions.DontUseShortDisplayNames)); | |
| /// | |
| /// // Prints the character associated with the "A" key on the current keyboard layout. | |
| /// Debug.Log(keyboardBinding, control: Keyboard.current); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <seealso cref="InputControlPath.ToHumanReadableString(string,InputControlPath.HumanReadableStringOptions,InputControl)"/> | |
| /// <seealso cref="InputActionRebindingExtensions.GetBindingDisplayString(InputAction,int,InputBinding.DisplayStringOptions)"/> | |
| public string ToDisplayString(DisplayStringOptions options = default, InputControl control = default) | |
| { | |
| return ToDisplayString(out var _, out var _, options, control); | |
| } | |
| /// <summary> | |
| /// Turn the binding into a string suitable for display in a UI. | |
| /// </summary> | |
| /// <param name="options">Optional set of formatting options.</param> | |
| /// <param name="control">Optional control to which the binding has been resolved. If this is supplied, | |
| /// the resulting string can reflect things such as the current keyboard layout or hardware/platform-specific | |
| /// naming of controls (e.g. Xbox vs PS4 controllers as opposed to naming things generically based on the | |
| /// <see cref="Gamepad"/> layout).</param> | |
| /// <returns>A string representation of the binding suitable for display in a UI.</returns> | |
| /// <remarks> | |
| /// This method is the same as <see cref="ToDisplayString(DisplayStringOptions,InputControl)"/> except that it | |
| /// will also return the name of the device layout and path of the control, if applicable to the binding. This is | |
| /// useful when needing more context on the resulting display string, for example to decide on an icon to display | |
| /// instead of the textual display string. | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// var displayString = new InputBinding("<Gamepad>/dpad/up") | |
| /// .ToDisplayString(out deviceLayout, out controlPath); | |
| /// | |
| /// // Will print "D-Pad Up". | |
| /// Debug.Log(displayString); | |
| /// | |
| /// // Will print "Gamepad". | |
| /// Debug.Log(deviceLayout); | |
| /// | |
| /// // Will print "dpad/up". | |
| /// Debug.Log(controlPath); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <seealso cref="InputControlPath.ToHumanReadableString(string,out string,out string,InputControlPath.HumanReadableStringOptions,InputControl)"/> | |
| /// <seealso cref="InputActionRebindingExtensions.GetBindingDisplayString(InputAction,int,out string,out string,InputBinding.DisplayStringOptions)"/> | |
| public string ToDisplayString(out string deviceLayoutName, out string controlPath, DisplayStringOptions options = default, | |
| InputControl control = default) | |
| { | |
| if (isComposite) | |
| { | |
| deviceLayoutName = null; | |
| controlPath = null; | |
| return string.Empty; | |
| } | |
| var readableStringOptions = default(InputControlPath.HumanReadableStringOptions); | |
| if ((options & DisplayStringOptions.DontOmitDevice) == 0) | |
| readableStringOptions |= InputControlPath.HumanReadableStringOptions.OmitDevice; | |
| if ((options & DisplayStringOptions.DontUseShortDisplayNames) == 0) | |
| readableStringOptions |= InputControlPath.HumanReadableStringOptions.UseShortNames; | |
| var pathToUse = (options & DisplayStringOptions.IgnoreBindingOverrides) != 0 | |
| ? path | |
| : effectivePath; | |
| var result = InputControlPath.ToHumanReadableString(pathToUse, out deviceLayoutName, out controlPath, readableStringOptions, control); | |
| if (!string.IsNullOrEmpty(effectiveInteractions) && (options & DisplayStringOptions.DontIncludeInteractions) == 0) | |
| { | |
| var interactionString = string.Empty; | |
| var parsedInteractions = NameAndParameters.ParseMultiple(effectiveInteractions); | |
| foreach (var element in parsedInteractions) | |
| { | |
| var interaction = element.name; | |
| var interactionDisplayName = InputInteraction.GetDisplayName(interaction); | |
| // An interaction can avoid being mentioned explicitly by just setting its display | |
| // name to an empty string. | |
| if (string.IsNullOrEmpty(interactionDisplayName)) | |
| continue; | |
| ////TODO: this will need support for localization | |
| if (!string.IsNullOrEmpty(interactionString)) | |
| interactionString = $"{interactionString} or {interactionDisplayName}"; | |
| else | |
| interactionString = interactionDisplayName; | |
| } | |
| if (!string.IsNullOrEmpty(interactionString)) | |
| result = $"{interactionString} {result}"; | |
| } | |
| return result; | |
| } | |
| internal bool TriggersAction(InputAction action) | |
| { | |
| // Match both name and ID on binding. | |
| return string.Compare(action.name, this.action, StringComparison.InvariantCultureIgnoreCase) == 0 | |
| || this.action == action.m_Id; | |
| } | |
| ////TODO: also support matching by name (taking the binding tree into account so that components | |
| //// of composites can be referenced through their parent) | |
| /// <summary> | |
| /// Check whether <paramref name="binding"/> matches the mask | |
| /// represented by the current binding. | |
| /// </summary> | |
| /// <param name="binding">An input binding.</param> | |
| /// <returns>True if <paramref name="binding"/> is matched by the mask represented | |
| /// by <c>this</c>.</returns> | |
| /// <remarks> | |
| /// In this method, the current binding acts as a "mask". When used this way, only | |
| /// three properties of the binding are taken into account: <see cref="path"/>, | |
| /// <see cref="groups"/>, and <see cref="action"/>. | |
| /// | |
| /// For each of these properties, the method checks whether they are set on the current | |
| /// binding and, if so, matches them against the respective property in <paramref name="binding"/>. | |
| /// | |
| /// The way this matching works is that the value of the property in the current binding is | |
| /// allowed to be a semicolon-separated list where each element specifies one possible value | |
| /// that will produce a match. | |
| /// | |
| /// Note that all comparisons are case-insensitive. | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// // Create a couple bindings which we can test against. | |
| /// var keyboardBinding = new InputBinding | |
| /// { | |
| /// path = "<Keyboard>/space", | |
| /// groups = "Keyboard", | |
| /// action = "Fire" | |
| /// }; | |
| /// var gamepadBinding = new InputBinding | |
| /// { | |
| /// path = "<Gamepad>/buttonSouth", | |
| /// groups = "Gamepad", | |
| /// action = "Jump" | |
| /// }; | |
| /// var touchBinding = new InputBinding | |
| /// { | |
| /// path = "<Touchscreen>/*/tap", | |
| /// groups = "Touch", | |
| /// action = "Jump" | |
| /// }; | |
| /// | |
| /// // Example 1: Match any binding in the "Keyboard" or "Gamepad" group. | |
| /// var mask1 = new InputBinding | |
| /// { | |
| /// // We put two elements in the list here and separate them with a semicolon. | |
| /// groups = "Keyboard;Gamepad" | |
| /// }; | |
| /// | |
| /// mask1.Matches(keyboardBinding); // True | |
| /// mask1.Matches(gamepadBinding); // True | |
| /// mask1.Matches(touchBinding); // False | |
| /// | |
| /// // Example 2: Match any binding to the "Jump" or the "Roll" action | |
| /// // (the latter we don't actually have a binding for) | |
| /// var mask2 = new InputBinding | |
| /// { | |
| /// action = "Jump;Roll" | |
| /// }; | |
| /// | |
| /// mask2.Matches(keyboardBinding); // False | |
| /// mask2.Matches(gamepadBinding); // True | |
| /// mask2.Matches(touchBinding); // True | |
| /// | |
| /// // Example: Match any binding to the space or enter key in the | |
| /// // "Keyboard" group. | |
| /// var mask3 = new InputBinding | |
| /// { | |
| /// path = "<Keyboard>/space;<Keyboard>/enter", | |
| /// groups = "Keyboard" | |
| /// }; | |
| /// | |
| /// mask3.Matches(keyboardBinding); // True | |
| /// mask3.Matches(gamepadBinding); // False | |
| /// mask3.Matches(touchBinding); // False | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| public bool Matches(InputBinding binding) | |
| { | |
| return Matches(ref binding); | |
| } | |
| // Internally we pass by reference to not unnecessarily copy the struct. | |
| internal bool Matches(ref InputBinding binding, MatchOptions options = default) | |
| { | |
| // Match name. | |
| if (name != null) | |
| { | |
| if (binding.name == null | |
| || !StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(name, binding.name, Separator)) | |
| return false; | |
| } | |
| // Match path. | |
| if (path != null) | |
| { | |
| ////REVIEW: should this use binding.effectivePath? | |
| ////REVIEW: should this support matching by prefix only? should this use control path matching instead of just string comparisons? | |
| ////TODO: handle things like ignoring leading '/' | |
| if (binding.path == null | |
| || !StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(path, binding.path, Separator)) | |
| return false; | |
| } | |
| // Match action. | |
| if (action != null) | |
| { | |
| ////TODO: handle "map/action" format | |
| ////TODO: handle "map/*" format | |
| ////REVIEW: this will not be able to handle cases where one binding references an action by ID and the other by name but both do mean the same action | |
| if (binding.action == null | |
| || !StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(action, binding.action, Separator)) | |
| return false; | |
| } | |
| // Match groups. | |
| if (groups != null) | |
| { | |
| var haveGroupsOnBinding = !string.IsNullOrEmpty(binding.groups); | |
| if (!haveGroupsOnBinding && (options & MatchOptions.EmptyGroupMatchesAny) == 0) | |
| return false; | |
| if (haveGroupsOnBinding | |
| && !StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(groups, binding.groups, Separator)) | |
| return false; | |
| } | |
| // Match ID. | |
| if (!string.IsNullOrEmpty(m_Id)) | |
| { | |
| if (binding.id != id) | |
| return false; | |
| } | |
| return true; | |
| } | |
| [] | |
| internal enum MatchOptions | |
| { | |
| EmptyGroupMatchesAny = 1 << 0, | |
| } | |
| [] | |
| internal enum Flags | |
| { | |
| None = 0, | |
| Composite = 1 << 2, | |
| PartOfComposite = 1 << 3, | |
| } | |
| } | |
| } | |