123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852 |
- 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>
- [Serializable]
- 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>
- public string path
- {
- get => m_Path;
- set => m_Path = value;
- }
- /// <summary>
- /// If the binding is overridden, this is the overriding path.
- /// Otherwise it is null.
- /// </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>
- 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"/>
- 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"/>
- 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}"/>
- 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"/>
- 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>
- /// 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();
- }
- 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)))};
- }
- [SerializeField] private string m_Name;
- [SerializeField] internal string m_Id;
- [SerializeField] private string m_Path;
- [SerializeField] private string m_Interactions;
- [SerializeField] private string m_Processors;
- [SerializeField] private string m_Groups;
- [SerializeField] private string m_Action;
- [SerializeField] internal Flags m_Flags;
- [NonSerialized] private string m_OverridePath;
- [NonSerialized] private string m_OverrideInteractions;
- [NonSerialized] 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>
- [Flags]
- 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;
- }
- ////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)
- {
- ////TODO: add matching by ID
- 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;
- }
- 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;
- }
- 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;
- }
- if (!string.IsNullOrEmpty(m_Id))
- {
- if (binding.id != id)
- return false;
- }
- return true;
- }
- [Flags]
- internal enum MatchOptions
- {
- EmptyGroupMatchesAny = 1 << 0,
- }
- [Flags]
- internal enum Flags
- {
- None = 0,
- Composite = 1 << 2,
- PartOfComposite = 1 << 3,
- }
- }
- }
|