A game about forced loneliness, made by TACStudios
1using System; 2using System.Linq; 3using System.Text; 4using UnityEngine.InputSystem.Layouts; 5using UnityEngine.InputSystem.Utilities; 6 7////REVIEW: do we really need overridable processors and interactions? 8 9// Downsides to the current approach: 10// - Being able to address entire batches of controls through a single control is awesome. Especially 11// when combining it type-kind of queries (e.g. "<MyDevice>/<Button>"). However, it complicates things 12// in quite a few areas. There's quite a few bits in InputActionState that could be simplified if a 13// binding simply maps to a control. 14 15namespace UnityEngine.InputSystem 16{ 17 /// <summary> 18 /// A mapping of controls to an action. 19 /// </summary> 20 /// <remarks> 21 /// Each binding represents a value received from controls (see <see cref="InputControl"/>). 22 /// There are two main types of bindings: "normal" bindings and "composite" bindings. 23 /// 24 /// Normal bindings directly bind to control(s) by means of <see cref="path"/> which is a "control path" 25 /// (see <see cref="InputControlPath"/> for details about how to form paths). At runtime, the 26 /// path of such a binding may match none, one, or multiple controls. Each control matched by the 27 /// path will feed input into the binding. 28 /// 29 /// Composite bindings do not bind to controls themselves. Instead, they receive their input 30 /// from their "part" bindings and then return a value representing a "composition" of those 31 /// inputs. What composition specifically is performed depends on the type of the composite. 32 /// <see cref="Composites.AxisComposite"/>, for example, will return a floating-point axis value 33 /// computed from the state of two buttons. 34 /// 35 /// The action that is triggered by a binding is determined by its <see cref="action"/> property. 36 /// The resolution to an <see cref="InputAction"/> depends on where the binding is used. For example, 37 /// bindings that are part of <see cref="InputActionMap.bindings"/> will resolve action names to 38 /// actions in the same <see cref="InputActionMap"/>. 39 /// 40 /// A binding can also be used as a form of search mask or filter. In this use, <see cref="path"/>, 41 /// <see cref="action"/>, and <see cref="groups"/> become search criteria that are matched 42 /// against other bindings. See <see cref="Matches(InputBinding)"/> for details. This use 43 /// is employed in places such as <see cref="InputActionRebindingExtensions"/> as well as in 44 /// binding masks on actions (<see cref="InputAction.bindingMask"/>), action maps (<see 45 /// cref="InputActionMap.bindingMask"/>), and assets (<see cref="InputActionAsset.bindingMask"/>). 46 /// </remarks> 47 [Serializable] 48 public struct InputBinding : IEquatable<InputBinding> 49 { 50 /// <summary> 51 /// Character that is used to separate elements in places such as <see cref="groups"/>, 52 /// <see cref="interactions"/>, and <see cref="processors"/>. 53 /// </summary> 54 /// Some strings on bindings represent lists of elements. An example is <see cref="groups"/> 55 /// which may associate a binding with several binding groups, each one delimited by the 56 /// separator. 57 /// 58 /// <remarks> 59 /// <example> 60 /// <code> 61 /// // A binding that belongs to the "Keyboard&amp;Mouse" and "Gamepad" group. 62 /// new InputBinding 63 /// { 64 /// path = "*/{PrimaryAction}, 65 /// groups = "Keyboard&amp;Mouse;Gamepad" 66 /// }; 67 /// </code> 68 /// </example> 69 /// </remarks> 70 public const char Separator = ';'; 71 72 internal const string kSeparatorString = ";"; 73 74 /// <summary> 75 /// Optional name for the binding. 76 /// </summary> 77 /// <value>Name of the binding.</value> 78 /// <remarks> 79 /// For bindings that are part of composites (see <see cref="isPartOfComposite"/>), this is 80 /// the name of the field on the binding composite object that should be initialized with 81 /// the control target of the binding. 82 /// </remarks> 83 public string name 84 { 85 get => m_Name; 86 set => m_Name = value; 87 } 88 89 /// <summary> 90 /// Unique ID of the binding. 91 /// </summary> 92 /// <value>Unique ID of the binding.</value> 93 /// <remarks> 94 /// This can be used, for example, when storing binding overrides in local user configurations. 95 /// Using the binding ID, an override can remain associated with one specific binding. 96 /// </remarks> 97 public Guid id 98 { 99 get 100 { 101 ////REVIEW: this is inconsistent with InputActionMap and InputAction which generate IDs, if necessary 102 if (string.IsNullOrEmpty(m_Id)) 103 return default; 104 return new Guid(m_Id); 105 } 106 set => m_Id = value.ToString(); 107 } 108 109 /// <summary> 110 /// Control path being bound to. 111 /// </summary> 112 /// <value>Path of control(s) to source input from.</value> 113 /// <remarks> 114 /// Bindings reference <see cref="InputControl"/>s using a regular expression-like 115 /// language. See <see cref="InputControlPath"/> for details. 116 /// 117 /// If the binding is a composite (<see cref="isComposite"/>), the path is the composite 118 /// string instead. For example, for a <see cref="Composites.Vector2Composite"/>, the 119 /// path could be something like <c>"Vector2(normalize=false)"</c>. 120 /// 121 /// The path of a binding may be non-destructively override at runtime using <see cref="overridePath"/> 122 /// which unlike this property is not serialized. <see cref="effectivePath"/> represents the 123 /// final, effective path. 124 /// </remarks> 125 /// <example> 126 /// <code> 127 /// // A binding that references the left mouse button. 128 /// new InputBinding { path = "&lt;Mouse&gt;/leftButton" } 129 /// </code> 130 /// </example> 131 /// <seealso cref="overridePath"/> 132 /// <seealso cref="InputControlPath"/> 133 /// <seealso cref="InputControlPath.Parse"/> 134 /// <seealso cref="InputControl.path"/> 135 /// <seealso cref="InputSystem.FindControl"/> 136 public string path 137 { 138 get => m_Path; 139 set => m_Path = value; 140 } 141 142 /// <summary> 143 /// If the binding is overridden, this is the overriding path. 144 /// Otherwise it is <c>null</c>. 145 /// </summary> 146 /// <value>Path to override the <see cref="path"/> property with.</value> 147 /// <remarks> 148 /// Unlike the <see cref="path"/> property, the value of the override path is not serialized. 149 /// If set, it will take precedence and determine the result of <see cref="effectivePath"/>. 150 /// 151 /// This property can be set to an empty string to disable the binding. During resolution, 152 /// bindings with an empty <see cref="effectivePath"/> will get skipped. 153 /// 154 /// To set the override on an existing binding, use the methods supplied by <see cref="InputActionRebindingExtensions"/> 155 /// such as <see cref="InputActionRebindingExtensions.ApplyBindingOverride(InputAction,string,string,string)"/>. 156 /// 157 /// <example> 158 /// <code> 159 /// // Override the binding to &lt;Gamepad&gt;/buttonSouth on 160 /// // myAction with a binding to &lt;Gamepad&gt;/buttonNorth. 161 /// myAction.ApplyBindingOverride( 162 /// new InputBinding 163 /// { 164 /// path = "&lt;Gamepad&gt;/buttonSouth", 165 /// overridePath = "&lt;Gamepad&gt;/buttonNorth" 166 /// }); 167 /// </code> 168 /// </example> 169 /// </remarks> 170 /// <seealso cref="path"/> 171 /// <seealso cref="overrideInteractions"/> 172 /// <seealso cref="overrideProcessors"/> 173 /// <seealso cref="hasOverrides"/> 174 /// <seealso cref="InputActionRebindingExtensions.SaveBindingOverridesAsJson(IInputActionCollection2)"/> 175 /// <seealso cref="InputActionRebindingExtensions.LoadBindingOverridesFromJson(IInputActionCollection2,string,bool)"/> 176 /// <seealso cref="InputActionRebindingExtensions.ApplyBindingOverride(InputAction,int,InputBinding)"/> 177 public string overridePath 178 { 179 get => m_OverridePath; 180 set => m_OverridePath = value; 181 } 182 183 /// <summary> 184 /// Optional list of interactions and their parameters. 185 /// </summary> 186 /// <value>Interactions to put on the binding.</value> 187 /// <remarks> 188 /// Each element in the list is a name of an interaction (as registered with 189 /// <see cref="InputSystem.RegisterInteraction{T}"/>) followed by an optional 190 /// list of parameters. 191 /// 192 /// For example, <c>"slowTap(duration=1.2,pressPoint=0.123)"</c> is one element 193 /// that puts a <see cref="Interactions.SlowTapInteraction"/> on the binding and 194 /// sets <see cref="Interactions.SlowTapInteraction.duration"/> to 1.2 and 195 /// <see cref="Interactions.SlowTapInteraction.pressPoint"/> to 0.123. 196 /// 197 /// Multiple interactions can be put on a binding by separating them with a comma. 198 /// For example, <c>"tap,slowTap(duration=1.2)"</c> puts both a 199 /// <see cref="Interactions.TapInteraction"/> and <see cref="Interactions.SlowTapInteraction"/> 200 /// on the binding. See <see cref="IInputInteraction"/> for why the order matters. 201 /// </remarks> 202 /// <seealso cref="IInputInteraction"/> 203 /// <seealso cref="overrideInteractions"/> 204 /// <seealso cref="hasOverrides"/> 205 /// <seealso cref="InputActionRebindingExtensions.SaveBindingOverridesAsJson(IInputActionCollection2)"/> 206 /// <seealso cref="InputActionRebindingExtensions.LoadBindingOverridesFromJson(IInputActionCollection2,string,bool)"/> 207 /// <seealso cref="InputActionRebindingExtensions.ApplyBindingOverride(InputAction,int,InputBinding)"/> 208 public string interactions 209 { 210 get => m_Interactions; 211 set => m_Interactions = value; 212 } 213 214 /// <summary> 215 /// Interaction settings to override <see cref="interactions"/> with. 216 /// </summary> 217 /// <value>Override string for <see cref="interactions"/> or <c>null</c>.</value> 218 /// <remarks> 219 /// If this is not <c>null</c>, it replaces the value of <see cref="interactions"/>. 220 /// </remarks> 221 /// <seealso cref="effectiveInteractions"/> 222 /// <seealso cref="interactions"/> 223 /// <seealso cref="overridePath"/> 224 /// <seealso cref="overrideProcessors"/> 225 /// <seealso cref="hasOverrides"/> 226 /// <seealso cref="InputActionRebindingExtensions.SaveBindingOverridesAsJson(IInputActionCollection2)"/> 227 /// <seealso cref="InputActionRebindingExtensions.LoadBindingOverridesFromJson(IInputActionCollection2,string,bool)"/> 228 /// <seealso cref="InputActionRebindingExtensions.ApplyBindingOverride(InputAction,int,InputBinding)"/> 229 public string overrideInteractions 230 { 231 get => m_OverrideInteractions; 232 set => m_OverrideInteractions = value; 233 } 234 235 /// <summary> 236 /// Optional list of processors to apply to control values. 237 /// </summary> 238 /// <value>Value processors to apply to the binding.</value> 239 /// <remarks> 240 /// This string has the same format as <see cref="InputControlAttribute.processors"/>. 241 /// </remarks> 242 /// <seealso cref="InputProcessor{TValue}"/> 243 /// <seealso cref="overrideProcessors"/> 244 public string processors 245 { 246 get => m_Processors; 247 set => m_Processors = value; 248 } 249 250 /// <summary> 251 /// Processor settings to override <see cref="processors"/> with. 252 /// </summary> 253 /// <value>Override string for <see cref="processors"/> or <c>null</c>.</value> 254 /// <remarks> 255 /// If this is not <c>null</c>, it replaces the value of <see cref="processors"/>. 256 /// </remarks> 257 /// <seealso cref="effectiveProcessors"/> 258 /// <seealso cref="processors"/> 259 /// <seealso cref="overridePath"/> 260 /// <seealso cref="overrideInteractions"/> 261 /// <seealso cref="hasOverrides"/> 262 public string overrideProcessors 263 { 264 get => m_OverrideProcessors; 265 set => m_OverrideProcessors = value; 266 } 267 268 /// <summary> 269 /// Optional list of binding groups that the binding belongs to. 270 /// </summary> 271 /// <value>List of binding groups or <c>null</c>.</value> 272 /// <remarks> 273 /// This is used, for example, to divide bindings into <see cref="InputControlScheme"/>s. 274 /// Each control scheme is associated with a unique binding group through <see 275 /// cref="InputControlScheme.bindingGroup"/>. 276 /// 277 /// A binding may be associated with multiple groups by listing each group name 278 /// separate by a semicolon (<see cref="Separator"/>). 279 /// 280 /// <example> 281 /// <code> 282 /// new InputBinding 283 /// { 284 /// path = "*/{PrimaryAction}, 285 /// // Associate the binding both with the "KeyboardMouse" and 286 /// // the "Gamepad" group. 287 /// groups = "KeyboardMouse;Gamepad", 288 /// } 289 /// </code> 290 /// </example> 291 /// 292 /// Note that the system places no restriction on what binding groups are used 293 /// for in practice. Their use by <see cref="InputControlScheme"/> is only one 294 /// possible one, but which groups to apply and how to use them is ultimately 295 /// up to you. 296 /// </remarks> 297 /// <seealso cref="InputControlScheme.bindingGroup"/> 298 public string groups 299 { 300 get => m_Groups; 301 set => m_Groups = value; 302 } 303 304 /// <summary> 305 /// Name or ID of the action triggered by the binding. 306 /// </summary> 307 /// <remarks> 308 /// This is null if the binding does not trigger an action. 309 /// 310 /// For InputBindings that are used as masks, this can be a "mapName/actionName" combination 311 /// or "mapName/*" to match all actions in the given map. 312 /// </remarks> 313 /// <seealso cref="InputAction.name"/> 314 /// <seealso cref="InputAction.id"/> 315 public string action 316 { 317 get => m_Action; 318 set => m_Action = value; 319 } 320 321 /// <summary> 322 /// Whether the binding is a composite. 323 /// </summary> 324 /// <value>True if the binding is a composite.</value> 325 /// <remarks> 326 /// Composite bindings to not bind to controls to themselves but rather source their 327 /// input from one or more "part binding" (see <see cref="isPartOfComposite"/>). 328 /// 329 /// See <see cref="InputBindingComposite{TValue}"/> for more details. 330 /// </remarks> 331 /// <seealso cref="InputBindingComposite{TValue}"/> 332 public bool isComposite 333 { 334 get => (m_Flags & Flags.Composite) == Flags.Composite; 335 set 336 { 337 if (value) 338 m_Flags |= Flags.Composite; 339 else 340 m_Flags &= ~Flags.Composite; 341 } 342 } 343 344 /// <summary> 345 /// Whether the binding is a "part binding" of a composite. 346 /// </summary> 347 /// <value>True if the binding is part of a composite.</value> 348 /// <remarks> 349 /// The bindings that make up a composite are laid out sequentially in <see cref="InputActionMap.bindings"/>. 350 /// First comes the composite itself which is flagged with <see cref="isComposite"/>. It mentions 351 /// the composite and its parameters in its <see cref="path"/> property. After the composite itself come 352 /// the part bindings. All subsequent bindings marked as <c>isPartOfComposite</c> will be associated 353 /// with the composite. 354 /// </remarks> 355 /// <seealso cref="isComposite"/> 356 /// <seealso cref="InputBindingComposite{TValue}"/> 357 public bool isPartOfComposite 358 { 359 get => (m_Flags & Flags.PartOfComposite) == Flags.PartOfComposite; 360 set 361 { 362 if (value) 363 m_Flags |= Flags.PartOfComposite; 364 else 365 m_Flags &= ~Flags.PartOfComposite; 366 } 367 } 368 369 /// <summary> 370 /// True if any of the override properties, that is, <see cref="overridePath"/>, <see cref="overrideProcessors"/>, 371 /// and/or <see cref="overrideInteractions"/>, are set (not <c>null</c>). 372 /// </summary> 373 public bool hasOverrides => overridePath != null || overrideProcessors != null || overrideInteractions != null; 374 375 /// <summary> 376 /// Initialize a new binding. 377 /// </summary> 378 /// <param name="path">Path for the binding.</param> 379 /// <param name="action">Action to trigger from the binding.</param> 380 /// <param name="groups">Semicolon-separated list of binding <see cref="InputBinding.groups"/> the binding is associated with.</param> 381 /// <param name="processors">Comma-separated list of <see cref="InputBinding.processors"/> to apply to the binding.</param> 382 /// <param name="interactions">Comma-separated list of <see cref="InputBinding.interactions"/> to apply to the 383 /// binding.</param> 384 /// <param name="name">Optional name for the binding.</param> 385 public InputBinding(string path, string action = null, string groups = null, string processors = null, 386 string interactions = null, string name = null) 387 { 388 m_Path = path; 389 m_Action = action; 390 m_Groups = groups; 391 m_Processors = processors; 392 m_Interactions = interactions; 393 m_Name = name; 394 m_Id = default; 395 m_Flags = default; 396 m_OverridePath = default; 397 m_OverrideInteractions = default; 398 m_OverrideProcessors = default; 399 } 400 401 public string GetNameOfComposite() 402 { 403 if (!isComposite) 404 return null; 405 return NameAndParameters.Parse(effectivePath).name; 406 } 407 408 internal void GenerateId() 409 { 410 m_Id = Guid.NewGuid().ToString(); 411 } 412 413 internal void RemoveOverrides() 414 { 415 m_OverridePath = null; 416 m_OverrideInteractions = null; 417 m_OverrideProcessors = null; 418 } 419 420 public static InputBinding MaskByGroup(string group) 421 { 422 return new InputBinding {groups = group}; 423 } 424 425 public static InputBinding MaskByGroups(params string[] groups) 426 { 427 return new InputBinding {groups = string.Join(kSeparatorString, groups.Where(x => !string.IsNullOrEmpty(x)))}; 428 } 429 430 [SerializeField] private string m_Name; 431 [SerializeField] internal string m_Id; 432 [Tooltip("Path of the control to bind to. Matched at runtime to controls from InputDevices present at the time.\n\nCan either be " 433 + "graphically from the control picker dropdown UI or edited manually in text mode by clicking the 'T' button. Internally, both " 434 + "methods result in control path strings that look like, for example, \"<Gamepad>/buttonSouth\".")] 435 [SerializeField] private string m_Path; 436 [SerializeField] private string m_Interactions; 437 [SerializeField] private string m_Processors; 438 [SerializeField] internal string m_Groups; 439 [SerializeField] private string m_Action; 440 [SerializeField] internal Flags m_Flags; 441 442 [NonSerialized] private string m_OverridePath; 443 [NonSerialized] private string m_OverrideInteractions; 444 [NonSerialized] private string m_OverrideProcessors; 445 446 /// <summary> 447 /// This is the bindings path which is effectively being used. 448 /// </summary> 449 /// <remarks> 450 /// This is either <see cref="overridePath"/> if that is set, or <see cref="path"/> otherwise. 451 /// </remarks> 452 public string effectivePath => overridePath ?? path; 453 454 /// <summary> 455 /// This is the interaction config which is effectively being used. 456 /// </summary> 457 /// <remarks> 458 /// This is either <see cref="overrideInteractions"/> if that is set, or <see cref="interactions"/> otherwise. 459 /// </remarks> 460 public string effectiveInteractions => overrideInteractions ?? interactions; 461 462 /// <summary> 463 /// This is the processor config which is effectively being used. 464 /// </summary> 465 /// <remarks> 466 /// This is either <see cref="overrideProcessors"/> if that is set, or <see cref="processors"/> otherwise. 467 /// </remarks> 468 public string effectiveProcessors => overrideProcessors ?? processors; 469 470 internal bool isEmpty => 471 string.IsNullOrEmpty(effectivePath) && string.IsNullOrEmpty(action) && 472 string.IsNullOrEmpty(groups); 473 474 /// <summary> 475 /// Check whether the binding is equivalent to the given binding. 476 /// </summary> 477 /// <param name="other">Another binding.</param> 478 /// <returns>True if the two bindings are equivalent.</returns> 479 /// <remarks> 480 /// Bindings are equivalent if their <see cref="effectivePath"/>, <see cref="effectiveInteractions"/>, 481 /// and <see cref="effectiveProcessors"/>, plus their <see cref="action"/> and <see cref="groups"/> 482 /// properties are the same. Note that the string comparisons ignore both case and culture. 483 /// </remarks> 484 public bool Equals(InputBinding other) 485 { 486 return string.Equals(effectivePath, other.effectivePath, StringComparison.InvariantCultureIgnoreCase) && 487 string.Equals(effectiveInteractions, other.effectiveInteractions, StringComparison.InvariantCultureIgnoreCase) && 488 string.Equals(effectiveProcessors, other.effectiveProcessors, StringComparison.InvariantCultureIgnoreCase) && 489 string.Equals(groups, other.groups, StringComparison.InvariantCultureIgnoreCase) && 490 string.Equals(action, other.action, StringComparison.InvariantCultureIgnoreCase); 491 } 492 493 /// <summary> 494 /// Compare the binding to the given object. 495 /// </summary> 496 /// <param name="obj">An object. May be <c>null</c>.</param> 497 /// <returns>True if the given object is an <c>InputBinding</c> that equals this one.</returns> 498 /// <seealso cref="Equals(InputBinding)"/> 499 public override bool Equals(object obj) 500 { 501 if (ReferenceEquals(null, obj)) 502 return false; 503 504 return obj is InputBinding binding && Equals(binding); 505 } 506 507 /// <summary> 508 /// Compare the two bindings for equality. 509 /// </summary> 510 /// <param name="left">The first binding.</param> 511 /// <param name="right">The second binding.</param> 512 /// <returns>True if the two bindings are equal.</returns> 513 /// <seealso cref="Equals(InputBinding)"/> 514 public static bool operator==(InputBinding left, InputBinding right) 515 { 516 return left.Equals(right); 517 } 518 519 /// <summary> 520 /// Compare the two bindings for inequality. 521 /// </summary> 522 /// <param name="left">The first binding.</param> 523 /// <param name="right">The second binding.</param> 524 /// <returns>True if the two bindings are not equal.</returns> 525 /// <seealso cref="Equals(InputBinding)"/> 526 public static bool operator!=(InputBinding left, InputBinding right) 527 { 528 return !(left == right); 529 } 530 531 /// <summary> 532 /// Compute a hash code for the binding. 533 /// </summary> 534 /// <returns>A hash code.</returns> 535 public override int GetHashCode() 536 { 537 unchecked 538 { 539 var hashCode = (effectivePath != null ? effectivePath.GetHashCode() : 0); 540 hashCode = (hashCode * 397) ^ (effectiveInteractions != null ? effectiveInteractions.GetHashCode() : 0); 541 hashCode = (hashCode * 397) ^ (effectiveProcessors != null ? effectiveProcessors.GetHashCode() : 0); 542 hashCode = (hashCode * 397) ^ (groups != null ? groups.GetHashCode() : 0); 543 hashCode = (hashCode * 397) ^ (action != null ? action.GetHashCode() : 0); 544 return hashCode; 545 } 546 } 547 548 /// <summary> 549 /// Return a string representation of the binding useful for debugging. 550 /// </summary> 551 /// <returns>A string representation of the binding.</returns> 552 /// <example> 553 /// <code> 554 /// var binding = new InputBinding 555 /// { 556 /// action = "fire", 557 /// path = "&lt;Gamepad&gt;/buttonSouth", 558 /// groups = "Gamepad" 559 /// }; 560 /// 561 /// // Returns "fire: &lt;Gamepad&gt;/buttonSouth [Gamepad]". 562 /// binding.ToString(); 563 /// </code> 564 /// </example> 565 public override string ToString() 566 { 567 var builder = new StringBuilder(); 568 569 // Add action. 570 if (!string.IsNullOrEmpty(action)) 571 { 572 builder.Append(action); 573 builder.Append(':'); 574 } 575 576 // Add path. 577 var path = effectivePath; 578 if (!string.IsNullOrEmpty(path)) 579 builder.Append(path); 580 581 // Add groups. 582 if (!string.IsNullOrEmpty(groups)) 583 { 584 builder.Append('['); 585 builder.Append(groups); 586 builder.Append(']'); 587 } 588 589 return builder.ToString(); 590 } 591 592 /// <summary> 593 /// A set of flags to turn individual default behaviors of <see cref="InputBinding.ToDisplayString(DisplayStringOptions,InputControl)"/> off. 594 /// </summary> 595 [Flags] 596 public enum DisplayStringOptions 597 { 598 /// <summary> 599 /// Do not use short names of controls as set up by <see cref="InputControlAttribute.shortDisplayName"/> 600 /// and the <c>"shortDisplayName"</c> property in JSON. This will, for example, not use LMB instead of "left Button" 601 /// on <see cref="Mouse.leftButton"/>. 602 /// </summary> 603 DontUseShortDisplayNames = 1 << 0, 604 605 /// <summary> 606 /// By default device names are omitted from display strings. With this option, they are included instead. 607 /// For example, <c>"A"</c> will be <c>"A [Gamepad]"</c> instead. 608 /// </summary> 609 DontOmitDevice = 1 << 1, 610 611 /// <summary> 612 /// By default, interactions on bindings are included in the resulting display string. For example, a binding to 613 /// the gamepad's A button that has a "Hold" interaction on it, would come out as "Hold A". This can be suppressed 614 /// with this option in which case the same setup would come out as just "A". 615 /// </summary> 616 DontIncludeInteractions = 1 << 2, 617 618 /// <summary> 619 /// By default, <see cref="effectivePath"/> is used for generating a display name. Using this option, the display 620 /// string can be forced to <see cref="path"/> instead. 621 /// </summary> 622 IgnoreBindingOverrides = 1 << 3, 623 } 624 625 /// <summary> 626 /// Turn the binding into a string suitable for display in a UI. 627 /// </summary> 628 /// <param name="options">Optional set of formatting options.</param> 629 /// <param name="control">Optional control to which the binding has been resolved. If this is supplied, 630 /// the resulting string can reflect things such as the current keyboard layout or hardware/platform-specific 631 /// naming of controls (e.g. Xbox vs PS4 controllers as opposed to naming things generically based on the 632 /// <see cref="Gamepad"/> layout).</param> 633 /// <returns>A string representation of the binding suitable for display in a UI.</returns> 634 /// <remarks> 635 /// This method works only for bindings that are not composites. If the method is called on a binding 636 /// that is a composite (<see cref="isComposite"/> is true), an empty string will be returned. To automatically 637 /// handle composites, use <see cref="InputActionRebindingExtensions.GetBindingDisplayString(InputAction,DisplayStringOptions,string)"/> 638 /// instead. 639 /// 640 /// <example> 641 /// <code> 642 /// var gamepadBinding = new InputBinding("&lt;Gamepad&gt;/buttonSouth"); 643 /// var mouseBinding = new InputBinding("&lt;Mouse&gt;/leftButton"); 644 /// var keyboardBinding = new InputBinding("&lt;Keyboard&gt;/a"); 645 /// 646 /// // Prints "A" except on PS4 where it prints "Cross". 647 /// Debug.Log(gamepadBinding.ToDisplayString()); 648 /// 649 /// // Prints "LMB". 650 /// Debug.Log(mouseBinding.ToDisplayString()); 651 /// 652 /// // Print "Left Button". 653 /// Debug.Log(mouseBinding.ToDisplayString(DisplayStringOptions.DontUseShortDisplayNames)); 654 /// 655 /// // Prints the character associated with the "A" key on the current keyboard layout. 656 /// Debug.Log(keyboardBinding, control: Keyboard.current); 657 /// </code> 658 /// </example> 659 /// </remarks> 660 /// <seealso cref="InputControlPath.ToHumanReadableString(string,InputControlPath.HumanReadableStringOptions,InputControl)"/> 661 /// <seealso cref="InputActionRebindingExtensions.GetBindingDisplayString(InputAction,int,InputBinding.DisplayStringOptions)"/> 662 public string ToDisplayString(DisplayStringOptions options = default, InputControl control = default) 663 { 664 return ToDisplayString(out var _, out var _, options, control); 665 } 666 667 /// <summary> 668 /// Turn the binding into a string suitable for display in a UI. 669 /// </summary> 670 /// <param name="options">Optional set of formatting options.</param> 671 /// <param name="control">Optional control to which the binding has been resolved. If this is supplied, 672 /// the resulting string can reflect things such as the current keyboard layout or hardware/platform-specific 673 /// naming of controls (e.g. Xbox vs PS4 controllers as opposed to naming things generically based on the 674 /// <see cref="Gamepad"/> layout).</param> 675 /// <returns>A string representation of the binding suitable for display in a UI.</returns> 676 /// <remarks> 677 /// This method is the same as <see cref="ToDisplayString(DisplayStringOptions,InputControl)"/> except that it 678 /// will also return the name of the device layout and path of the control, if applicable to the binding. This is 679 /// useful when needing more context on the resulting display string, for example to decide on an icon to display 680 /// instead of the textual display string. 681 /// 682 /// <example> 683 /// <code> 684 /// var displayString = new InputBinding("&lt;Gamepad&gt;/dpad/up") 685 /// .ToDisplayString(out deviceLayout, out controlPath); 686 /// 687 /// // Will print "D-Pad Up". 688 /// Debug.Log(displayString); 689 /// 690 /// // Will print "Gamepad". 691 /// Debug.Log(deviceLayout); 692 /// 693 /// // Will print "dpad/up". 694 /// Debug.Log(controlPath); 695 /// </code> 696 /// </example> 697 /// </remarks> 698 /// <seealso cref="InputControlPath.ToHumanReadableString(string,out string,out string,InputControlPath.HumanReadableStringOptions,InputControl)"/> 699 /// <seealso cref="InputActionRebindingExtensions.GetBindingDisplayString(InputAction,int,out string,out string,InputBinding.DisplayStringOptions)"/> 700 public string ToDisplayString(out string deviceLayoutName, out string controlPath, DisplayStringOptions options = default, 701 InputControl control = default) 702 { 703 if (isComposite) 704 { 705 deviceLayoutName = null; 706 controlPath = null; 707 return string.Empty; 708 } 709 710 var readableStringOptions = default(InputControlPath.HumanReadableStringOptions); 711 if ((options & DisplayStringOptions.DontOmitDevice) == 0) 712 readableStringOptions |= InputControlPath.HumanReadableStringOptions.OmitDevice; 713 if ((options & DisplayStringOptions.DontUseShortDisplayNames) == 0) 714 readableStringOptions |= InputControlPath.HumanReadableStringOptions.UseShortNames; 715 716 var pathToUse = (options & DisplayStringOptions.IgnoreBindingOverrides) != 0 717 ? path 718 : effectivePath; 719 720 var result = InputControlPath.ToHumanReadableString(pathToUse, out deviceLayoutName, out controlPath, readableStringOptions, control); 721 722 if (!string.IsNullOrEmpty(effectiveInteractions) && (options & DisplayStringOptions.DontIncludeInteractions) == 0) 723 { 724 var interactionString = string.Empty; 725 var parsedInteractions = NameAndParameters.ParseMultiple(effectiveInteractions); 726 727 foreach (var element in parsedInteractions) 728 { 729 var interaction = element.name; 730 var interactionDisplayName = InputInteraction.GetDisplayName(interaction); 731 732 // An interaction can avoid being mentioned explicitly by just setting its display 733 // name to an empty string. 734 if (string.IsNullOrEmpty(interactionDisplayName)) 735 continue; 736 737 ////TODO: this will need support for localization 738 if (!string.IsNullOrEmpty(interactionString)) 739 interactionString = $"{interactionString} or {interactionDisplayName}"; 740 else 741 interactionString = interactionDisplayName; 742 } 743 744 if (!string.IsNullOrEmpty(interactionString)) 745 result = $"{interactionString} {result}"; 746 } 747 748 return result; 749 } 750 751 internal bool TriggersAction(InputAction action) 752 { 753 // Match both name and ID on binding. 754 return string.Compare(action.name, this.action, StringComparison.InvariantCultureIgnoreCase) == 0 755 || this.action == action.m_Id; 756 } 757 758 ////TODO: also support matching by name (taking the binding tree into account so that components 759 //// of composites can be referenced through their parent) 760 761 /// <summary> 762 /// Check whether <paramref name="binding"/> matches the mask 763 /// represented by the current binding. 764 /// </summary> 765 /// <param name="binding">An input binding.</param> 766 /// <returns>True if <paramref name="binding"/> is matched by the mask represented 767 /// by <c>this</c>.</returns> 768 /// <remarks> 769 /// In this method, the current binding acts as a "mask". When used this way, only 770 /// three properties of the binding are taken into account: <see cref="path"/>, 771 /// <see cref="groups"/>, and <see cref="action"/>. 772 /// 773 /// For each of these properties, the method checks whether they are set on the current 774 /// binding and, if so, matches them against the respective property in <paramref name="binding"/>. 775 /// 776 /// The way this matching works is that the value of the property in the current binding is 777 /// allowed to be a semicolon-separated list where each element specifies one possible value 778 /// that will produce a match. 779 /// 780 /// Note that all comparisons are case-insensitive. 781 /// 782 /// <example> 783 /// <code> 784 /// // Create a couple bindings which we can test against. 785 /// var keyboardBinding = new InputBinding 786 /// { 787 /// path = "&lt;Keyboard&gt;/space", 788 /// groups = "Keyboard", 789 /// action = "Fire" 790 /// }; 791 /// var gamepadBinding = new InputBinding 792 /// { 793 /// path = "&lt;Gamepad&gt;/buttonSouth", 794 /// groups = "Gamepad", 795 /// action = "Jump" 796 /// }; 797 /// var touchBinding = new InputBinding 798 /// { 799 /// path = "&lt;Touchscreen&gt;/*/tap", 800 /// groups = "Touch", 801 /// action = "Jump" 802 /// }; 803 /// 804 /// // Example 1: Match any binding in the "Keyboard" or "Gamepad" group. 805 /// var mask1 = new InputBinding 806 /// { 807 /// // We put two elements in the list here and separate them with a semicolon. 808 /// groups = "Keyboard;Gamepad" 809 /// }; 810 /// 811 /// mask1.Matches(keyboardBinding); // True 812 /// mask1.Matches(gamepadBinding); // True 813 /// mask1.Matches(touchBinding); // False 814 /// 815 /// // Example 2: Match any binding to the "Jump" or the "Roll" action 816 /// // (the latter we don't actually have a binding for) 817 /// var mask2 = new InputBinding 818 /// { 819 /// action = "Jump;Roll" 820 /// }; 821 /// 822 /// mask2.Matches(keyboardBinding); // False 823 /// mask2.Matches(gamepadBinding); // True 824 /// mask2.Matches(touchBinding); // True 825 /// 826 /// // Example: Match any binding to the space or enter key in the 827 /// // "Keyboard" group. 828 /// var mask3 = new InputBinding 829 /// { 830 /// path = "&lt;Keyboard&gt;/space;&lt;Keyboard&gt;/enter", 831 /// groups = "Keyboard" 832 /// }; 833 /// 834 /// mask3.Matches(keyboardBinding); // True 835 /// mask3.Matches(gamepadBinding); // False 836 /// mask3.Matches(touchBinding); // False 837 /// </code> 838 /// </example> 839 /// </remarks> 840 public bool Matches(InputBinding binding) 841 { 842 return Matches(ref binding); 843 } 844 845 // Internally we pass by reference to not unnecessarily copy the struct. 846 internal bool Matches(ref InputBinding binding, MatchOptions options = default) 847 { 848 // Match name. 849 if (name != null) 850 { 851 if (binding.name == null 852 || !StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(name, binding.name, Separator)) 853 return false; 854 } 855 856 // Match path. 857 if (path != null) 858 { 859 ////REVIEW: should this use binding.effectivePath? 860 ////REVIEW: should this support matching by prefix only? should this use control path matching instead of just string comparisons? 861 ////TODO: handle things like ignoring leading '/' 862 if (binding.path == null 863 || !StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(path, binding.path, Separator)) 864 return false; 865 } 866 867 // Match action. 868 if (action != null) 869 { 870 ////TODO: handle "map/action" format 871 ////TODO: handle "map/*" format 872 ////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 873 if (binding.action == null 874 || !StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(action, binding.action, Separator)) 875 return false; 876 } 877 878 // Match groups. 879 if (groups != null) 880 { 881 var haveGroupsOnBinding = !string.IsNullOrEmpty(binding.groups); 882 if (!haveGroupsOnBinding && (options & MatchOptions.EmptyGroupMatchesAny) == 0) 883 return false; 884 885 if (haveGroupsOnBinding 886 && !StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(groups, binding.groups, Separator)) 887 return false; 888 } 889 890 // Match ID. 891 if (!string.IsNullOrEmpty(m_Id)) 892 { 893 if (binding.id != id) 894 return false; 895 } 896 897 return true; 898 } 899 900 [Flags] 901 internal enum MatchOptions 902 { 903 EmptyGroupMatchesAny = 1 << 0, 904 } 905 906 [Flags] 907 internal enum Flags 908 { 909 None = 0, 910 Composite = 1 << 2, 911 PartOfComposite = 1 << 3, 912 } 913 } 914}