InputActionSetupExtensions.cs 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938
  1. using System;
  2. using UnityEngine.InputSystem.Layouts;
  3. using UnityEngine.InputSystem.Utilities;
  4. namespace UnityEngine.InputSystem
  5. {
  6. /// <summary>
  7. /// Methods to change the setup of <see cref="InputAction"/>, <see cref="InputActionMap"/>,
  8. /// and <see cref="InputActionAsset"/> objects.
  9. /// </summary>
  10. /// <remarks>
  11. /// Unlike the methods in <see cref="InputActionRebindingExtensions"/>, the methods here are
  12. /// generally destructive, i.e. they will rearrange the data for actions.
  13. /// </remarks>
  14. public static class InputActionSetupExtensions
  15. {
  16. /// <summary>
  17. /// Create an action map with the given name and add it to the asset.
  18. /// </summary>
  19. /// <param name="asset">Asset to add the action map to</param>
  20. /// <param name="name">Name to assign to the </param>
  21. /// <returns>The newly added action map.</returns>
  22. /// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> or
  23. /// <exception cref="InvalidOperationException">An action map with the given <paramref name="name"/>
  24. /// already exists in <paramref name="asset"/>.</exception>
  25. /// <paramref name="name"/> is <c>null</c> or empty.</exception>
  26. public static InputActionMap AddActionMap(this InputActionAsset asset, string name)
  27. {
  28. if (asset == null)
  29. throw new ArgumentNullException(nameof(asset));
  30. if (string.IsNullOrEmpty(name))
  31. throw new ArgumentNullException(nameof(name));
  32. if (asset.FindActionMap(name) != null)
  33. throw new InvalidOperationException(
  34. $"An action map called '{name}' already exists in the asset");
  35. var map = new InputActionMap(name);
  36. map.GenerateId();
  37. asset.AddActionMap(map);
  38. return map;
  39. }
  40. /// <summary>
  41. /// Add an action map to the asset.
  42. /// </summary>
  43. /// <param name="asset">Asset to add the map to.</param>
  44. /// <param name="map">A named action map.</param>
  45. /// <exception cref="ArgumentNullException"><paramref name="map"/> or <paramref name="asset"/> is <c>null</c>.</exception>
  46. /// <exception cref="InvalidOperationException"><paramref name="map"/> has no name or asset already contains a
  47. /// map with the same name.</exception>
  48. /// <seealso cref="InputActionAsset.actionMaps"/>
  49. public static void AddActionMap(this InputActionAsset asset, InputActionMap map)
  50. {
  51. if (asset == null)
  52. throw new ArgumentNullException(nameof(asset));
  53. if (map == null)
  54. throw new ArgumentNullException(nameof(map));
  55. if (string.IsNullOrEmpty(map.name))
  56. throw new InvalidOperationException("Maps added to an input action asset must be named");
  57. if (map.asset != null)
  58. throw new InvalidOperationException(
  59. $"Cannot add map '{map}' to asset '{asset}' as it has already been added to asset '{map.asset}'");
  60. ////REVIEW: some of the rules here seem stupid; just replace?
  61. if (asset.FindActionMap(map.name) != null)
  62. throw new InvalidOperationException(
  63. $"An action map called '{map.name}' already exists in the asset");
  64. ArrayHelpers.Append(ref asset.m_ActionMaps, map);
  65. map.m_Asset = asset;
  66. }
  67. /// <summary>
  68. /// Remove the given action map from the asset.
  69. /// </summary>
  70. /// <param name="asset">Asset to add the action map to.</param>
  71. /// <param name="map">An action map. If the given map is not part of the asset, the method
  72. /// does nothing.</param>
  73. /// <exception cref="ArgumentNullException"><paramref name="asset"/> or <paramref name="map"/> is <c>null</c>.</exception>
  74. /// <exception cref="InvalidOperationException"><paramref name="map"/> is currently enabled (see <see
  75. /// cref="InputActionMap.enabled"/>).</exception>
  76. /// <seealso cref="RemoveActionMap(InputActionAsset,string)"/>
  77. /// <seealso cref="InputActionAsset.actionMaps"/>
  78. public static void RemoveActionMap(this InputActionAsset asset, InputActionMap map)
  79. {
  80. if (asset == null)
  81. throw new ArgumentNullException(nameof(asset));
  82. if (map == null)
  83. throw new ArgumentNullException(nameof(map));
  84. if (map.enabled)
  85. throw new InvalidOperationException("Cannot remove an action map from the asset while it is enabled");
  86. // Ignore if not part of this asset.
  87. if (map.m_Asset != asset)
  88. return;
  89. ArrayHelpers.Erase(ref asset.m_ActionMaps, map);
  90. map.m_Asset = null;
  91. }
  92. /// <summary>
  93. /// Remove the action map with the given name or ID from the asset.
  94. /// </summary>
  95. /// <param name="asset">Asset to remove the action map from.</param>
  96. /// <param name="nameOrId">The name or ID (see <see cref="InputActionMap.id"/>) of a map in the
  97. /// asset. Note that lookup is case-insensitive. If no map with the given name or ID is found,
  98. /// the method does nothing.</param>
  99. /// <exception cref="ArgumentNullException"><paramref name="asset"/> or <paramref name="nameOrId"/> is <c>null</c>.</exception>
  100. /// <exception cref="InvalidOperationException">The map referenced by <paramref name="nameOrId"/> is currently enabled
  101. /// (see <see cref="InputActionMap.enabled"/>).</exception>
  102. /// <seealso cref="RemoveActionMap(InputActionAsset,string)"/>
  103. /// <seealso cref="InputActionAsset.actionMaps"/>
  104. public static void RemoveActionMap(this InputActionAsset asset, string nameOrId)
  105. {
  106. if (asset == null)
  107. throw new ArgumentNullException(nameof(asset));
  108. if (nameOrId == null)
  109. throw new ArgumentNullException(nameof(nameOrId));
  110. var map = asset.FindActionMap(nameOrId);
  111. if (map != null)
  112. asset.RemoveActionMap(map);
  113. }
  114. ////TODO: add method to add an existing InputAction to a map
  115. public static InputAction AddAction(this InputActionMap map, string name, InputActionType type = default, string binding = null,
  116. string interactions = null, string processors = null, string groups = null, string expectedControlLayout = null)
  117. {
  118. if (map == null)
  119. throw new ArgumentNullException(nameof(map));
  120. if (string.IsNullOrEmpty(name))
  121. throw new ArgumentException("Action must have name", nameof(name));
  122. if (map.enabled)
  123. throw new InvalidOperationException(
  124. $"Cannot add action '{name}' to map '{map}' while it the map is enabled");
  125. if (map.FindAction(name) != null)
  126. throw new InvalidOperationException(
  127. $"Cannot add action with duplicate name '{name}' to set '{map.name}'");
  128. // Append action to array.
  129. var action = new InputAction(name, type)
  130. {
  131. expectedControlType = expectedControlLayout
  132. };
  133. action.GenerateId();
  134. ArrayHelpers.Append(ref map.m_Actions, action);
  135. action.m_ActionMap = map;
  136. ////TODO: make sure we blast out existing action map state
  137. // Add binding, if supplied.
  138. if (!string.IsNullOrEmpty(binding))
  139. {
  140. action.AddBinding(binding, interactions: interactions, processors: processors, groups: groups);
  141. }
  142. else
  143. {
  144. if (!string.IsNullOrEmpty(groups))
  145. throw new ArgumentException(
  146. $"No binding path was specified for action '{action}' but groups was specified ('{groups}'); cannot apply groups without binding",
  147. nameof(groups));
  148. // If no binding has been supplied but there are interactions and processors, they go on the action itself.
  149. action.m_Interactions = interactions;
  150. action.m_Processors = processors;
  151. }
  152. return action;
  153. }
  154. /// <summary>
  155. /// Remove the given action from its <see cref="InputActionMap"/>.
  156. /// </summary>
  157. /// <param name="action">An input action that is part of an <see cref="InputActionMap"/>.</param>
  158. /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
  159. /// <exception cref="ArgumentException"><paramref name="action"/> is part of an <see cref="InputActionMap"/>
  160. /// that has at least one enabled action -or- <paramref name="action"/> is a standalone action
  161. /// that is not part of an <see cref="InputActionMap"/> and thus cannot be removed from anything.</exception>
  162. /// <remarks>
  163. /// After removal, the action's <see cref="InputAction.actionMap"/> will be set to <c>null</c>
  164. /// and the action will effectively become a standalone action that is not associated with
  165. /// any action map. Bindings on the action will be preserved. On the action map, the bindings
  166. /// for the action will be removed.
  167. /// </remarks>
  168. /// <seealso cref="AddAction"/>
  169. public static void RemoveAction(this InputAction action)
  170. {
  171. if (action == null)
  172. throw new ArgumentNullException(nameof(action));
  173. var actionMap = action.actionMap;
  174. if (actionMap == null)
  175. throw new ArgumentException(
  176. $"Action '{action}' does not belong to an action map; nowhere to remove from", nameof(action));
  177. if (actionMap.enabled)
  178. throw new ArgumentException($"Cannot remove action '{action}' while its action map is enabled");
  179. var bindingsForAction = action.bindings.ToArray();
  180. var index = ArrayHelpers.IndexOfReference(actionMap.m_Actions, action);
  181. Debug.Assert(index != -1, "Could not find action in map");
  182. ArrayHelpers.EraseAt(ref actionMap.m_Actions, index);
  183. action.m_ActionMap = null;
  184. action.m_SingletonActionBindings = bindingsForAction;
  185. actionMap.ClearPerActionCachedBindingData();
  186. // Remove bindings to action from map.
  187. var newActionMapBindingCount = actionMap.m_Bindings.Length - bindingsForAction.Length;
  188. if (newActionMapBindingCount == 0)
  189. actionMap.m_Bindings = null;
  190. else
  191. {
  192. var newActionMapBindings = new InputBinding[newActionMapBindingCount];
  193. var oldActionMapBindings = actionMap.m_Bindings;
  194. var bindingIndex = 0;
  195. for (var i = 0; i < oldActionMapBindings.Length; ++i)
  196. {
  197. var binding = oldActionMapBindings[i];
  198. if (bindingsForAction.IndexOf(b => b == binding) == -1)
  199. newActionMapBindings[bindingIndex++] = binding;
  200. }
  201. actionMap.m_Bindings = newActionMapBindings;
  202. }
  203. }
  204. /// <summary>
  205. /// Remove the action with the given name from the asset.
  206. /// </summary>
  207. /// <param name="asset">Asset to remove the action from.</param>
  208. /// <param name="nameOrId">Name or ID of the action. See <see cref="InputActionAsset.FindAction(string,bool)"/> for
  209. /// details.</param>
  210. /// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> -or- <paramref name="nameOrId"/>
  211. /// is <c>null</c> or empty.</exception>
  212. /// <seealso cref="RemoveAction(InputAction)"/>
  213. public static void RemoveAction(this InputActionAsset asset, string nameOrId)
  214. {
  215. if (asset == null)
  216. throw new ArgumentNullException(nameof(asset));
  217. if (nameOrId == null)
  218. throw new ArgumentNullException(nameof(nameOrId));
  219. var action = asset.FindAction(nameOrId);
  220. action?.RemoveAction();
  221. }
  222. /// <summary>
  223. /// Add a new binding to the given action.
  224. /// </summary>
  225. /// <param name="action">Action to add the binding to. If the action is part of an <see cref="InputActionMap"/>,
  226. /// the newly added binding will be visible on <see cref="InputActionMap.bindings"/>.</param>
  227. /// <param name="path">Binding path string. See <see cref="InputBinding.path"/> for details.</param>
  228. /// <param name="interactions">Optional list of interactions to apply to the binding. See <see
  229. /// cref="InputBinding.interactions"/> for details.</param>
  230. /// <param name="processors">Optional list of processors to apply to the binding. See <see
  231. /// cref="InputBinding.processors"/> for details.</param>
  232. /// <param name="groups">Optional list of binding groups that should be assigned to the binding. See
  233. /// <see cref="InputBinding.groups"/> for details.</param>
  234. /// <returns>Fluent-style syntax to further configure the binding.</returns>
  235. public static BindingSyntax AddBinding(this InputAction action, string path, string interactions = null,
  236. string processors = null, string groups = null)
  237. {
  238. return AddBinding(action, new InputBinding
  239. {
  240. path = path,
  241. interactions = interactions,
  242. processors = processors,
  243. groups = groups
  244. });
  245. }
  246. /// <summary>
  247. /// Add a binding that references the given <paramref name="control"/> and triggers
  248. /// the given <seealso cref="action"/>.
  249. /// </summary>
  250. /// <param name="action">Action to trigger.</param>
  251. /// <param name="control">Control to bind to. The full <see cref="InputControl.path"/> of the control will
  252. /// be used in the resulting <see cref="InputBinding">binding</see>.</param>
  253. /// <returns>Syntax to configure the binding further.</returns>
  254. /// <exception cref="ArgumentNullException"><paramref name="action"/> is null or <paramref name="control"/> is null.</exception>
  255. /// <seealso cref="InputAction.bindings"/>
  256. public static BindingSyntax AddBinding(this InputAction action, InputControl control)
  257. {
  258. if (control == null)
  259. throw new ArgumentNullException(nameof(control));
  260. return AddBinding(action, control.path);
  261. }
  262. /// <summary>
  263. /// Add a new binding to the action.
  264. /// </summary>
  265. /// <param name="action">An action to add the binding to.</param>
  266. /// <param name="binding">Binding to add to the action or default. Binding can be further configured via
  267. /// the struct returned by the method.</param>
  268. /// <returns>
  269. /// Returns a fluent-style syntax structure that allows performing additional modifications
  270. /// based on the new binding.
  271. /// </returns>
  272. /// <remarks>
  273. /// This works both with actions that are part of an action set as well as with actions that aren't.
  274. ///
  275. /// Note that actions must be disabled while altering their binding sets. Also, if the action belongs
  276. /// to a set, all actions in the set must be disabled.
  277. ///
  278. /// <example>
  279. /// <code>
  280. /// fireAction.AddBinding()
  281. /// .WithPath("&lt;Gamepad&gt;/buttonSouth")
  282. /// .WithGroup("Gamepad");
  283. /// </code>
  284. /// </example>
  285. /// </remarks>
  286. public static BindingSyntax AddBinding(this InputAction action, InputBinding binding = default)
  287. {
  288. if (action == null)
  289. throw new ArgumentNullException(nameof(action));
  290. ////REVIEW: should this reference actions by ID?
  291. Debug.Assert(action.m_Name != null || action.isSingletonAction);
  292. binding.action = action.name;
  293. var actionMap = action.GetOrCreateActionMap();
  294. var bindingIndex = AddBindingInternal(actionMap, binding);
  295. return new BindingSyntax(actionMap, action, bindingIndex);
  296. }
  297. public static BindingSyntax AddBinding(this InputActionMap actionMap, string path,
  298. string interactions = null, string groups = null, string action = null)
  299. {
  300. if (path == null)
  301. throw new ArgumentException("Binding path cannot be null", nameof(path));
  302. return AddBinding(actionMap, new InputBinding
  303. {
  304. path = path,
  305. interactions = interactions,
  306. groups = groups,
  307. action = action
  308. });
  309. }
  310. public static BindingSyntax AddBinding(this InputActionMap actionMap, string path, InputAction action,
  311. string interactions = null, string groups = null)
  312. {
  313. if (action != null && action.actionMap != actionMap)
  314. throw new ArgumentException(
  315. $"Action '{action}' is not part of action map '{actionMap}'", nameof(action));
  316. if (action == null)
  317. return AddBinding(actionMap, path: path, interactions: interactions, groups: groups);
  318. return AddBinding(actionMap, path: path, interactions: interactions, groups: groups,
  319. action: action.id);
  320. }
  321. public static BindingSyntax AddBinding(this InputActionMap actionMap, string path, Guid action,
  322. string interactions = null, string groups = null)
  323. {
  324. if (action == Guid.Empty)
  325. return AddBinding(actionMap, path: path, interactions: interactions, groups: groups);
  326. return AddBinding(actionMap, path: path, interactions: interactions, groups: groups,
  327. action: action.ToString());
  328. }
  329. public static BindingSyntax AddBinding(this InputActionMap actionMap, InputBinding binding)
  330. {
  331. if (actionMap == null)
  332. throw new ArgumentNullException(nameof(actionMap));
  333. if (binding.path == null)
  334. throw new ArgumentException("Binding path cannot be null", nameof(binding));
  335. var bindingIndex = AddBindingInternal(actionMap, binding);
  336. return new BindingSyntax(actionMap, null, bindingIndex);
  337. }
  338. public static CompositeSyntax AddCompositeBinding(this InputAction action, string composite, string interactions = null, string processors = null)
  339. {
  340. if (action == null)
  341. throw new ArgumentNullException(nameof(action));
  342. if (string.IsNullOrEmpty(composite))
  343. throw new ArgumentException("Composite name cannot be null or empty", nameof(composite));
  344. var actionMap = action.GetOrCreateActionMap();
  345. ////REVIEW: use 'name' instead of 'path' field here?
  346. var binding = new InputBinding {path = composite, interactions = interactions, processors = processors, isComposite = true, action = action.name};
  347. var bindingIndex = AddBindingInternal(actionMap, binding);
  348. return new CompositeSyntax(actionMap, action, bindingIndex);
  349. }
  350. private static int AddBindingInternal(InputActionMap map, InputBinding binding)
  351. {
  352. Debug.Assert(map != null);
  353. // Make sure the binding has an ID.
  354. if (string.IsNullOrEmpty(binding.m_Id))
  355. binding.GenerateId();
  356. // Append to bindings in set.
  357. var bindingIndex = ArrayHelpers.Append(ref map.m_Bindings, binding);
  358. // Invalidate per-action binding sets so that this gets refreshed if
  359. // anyone queries it.
  360. map.ClearPerActionCachedBindingData();
  361. // Make sure bindings get re-resolved.
  362. map.LazyResolveBindings();
  363. // If we're looking at a singleton action, make sure m_Bindings is up to date just
  364. // in case the action gets serialized.
  365. if (map.m_SingletonAction != null)
  366. map.m_SingletonAction.m_SingletonActionBindings = map.m_Bindings;
  367. return bindingIndex;
  368. }
  369. public static BindingSyntax ChangeBinding(this InputAction action, int index)
  370. {
  371. if (action == null)
  372. throw new ArgumentNullException(nameof(action));
  373. var indexOnMap = action.BindingIndexOnActionToBindingIndexOnMap(index);
  374. return new BindingSyntax(action.GetOrCreateActionMap(), action, indexOnMap);
  375. }
  376. public static BindingSyntax ChangeBindingWithId(this InputAction action, string id)
  377. {
  378. return action.ChangeBinding(new InputBinding {m_Id = id});
  379. }
  380. public static BindingSyntax ChangeBindingWithId(this InputAction action, Guid id)
  381. {
  382. return action.ChangeBinding(new InputBinding {id = id});
  383. }
  384. public static BindingSyntax ChangeBindingWithGroup(this InputAction action, string group)
  385. {
  386. return action.ChangeBinding(new InputBinding {groups = group});
  387. }
  388. public static BindingSyntax ChangeBindingWithPath(this InputAction action, string path)
  389. {
  390. return action.ChangeBinding(new InputBinding {path = path});
  391. }
  392. public static BindingSyntax ChangeBinding(this InputAction action, InputBinding match)
  393. {
  394. if (action == null)
  395. throw new ArgumentNullException(nameof(action));
  396. var actionMap = action.GetOrCreateActionMap();
  397. var bindingIndex = actionMap.FindBinding(match);
  398. if (bindingIndex == -1)
  399. throw new ArgumentException($"Cannot find binding matching '{match}' in '{action}'", nameof(match));
  400. return new BindingSyntax(actionMap, action, bindingIndex);
  401. }
  402. ////TODO: update binding mask if necessary
  403. /// <summary>
  404. /// Rename an existing action.
  405. /// </summary>
  406. /// <param name="action">Action to assign a new name to. Can be singleton action or action that
  407. /// is part of a map.</param>
  408. /// <param name="newName">New name to assign to action. Cannot be empty.</param>
  409. /// <exception cref="ArgumentNullException"><paramref name="action"/> is null or <paramref name="newName"/> is
  410. /// null or empty.</exception>
  411. /// <exception cref="InvalidOperationException"><see cref="InputAction.actionMap"/> of <paramref name="action"/>
  412. /// already contains an action called <paramref name="newName"/>.</exception>
  413. /// <remarks>
  414. /// Renaming an action will also update the bindings that refer to the action.
  415. /// </remarks>
  416. public static void Rename(this InputAction action, string newName)
  417. {
  418. if (action == null)
  419. throw new ArgumentNullException(nameof(action));
  420. if (string.IsNullOrEmpty(newName))
  421. throw new ArgumentNullException(nameof(newName));
  422. if (action.name == newName)
  423. return;
  424. // Make sure name isn't already taken in map.
  425. var actionMap = action.actionMap;
  426. if (actionMap?.FindAction(newName) != null)
  427. throw new InvalidOperationException(
  428. $"Cannot rename '{action}' to '{newName}' in map '{actionMap}' as the map already contains an action with that name");
  429. var oldName = action.m_Name;
  430. action.m_Name = newName;
  431. // Update bindings.
  432. var bindings = action.GetOrCreateActionMap().m_Bindings;
  433. var bindingCount = bindings.LengthSafe();
  434. for (var i = 0; i < bindingCount; ++i)
  435. if (string.Compare(bindings[i].action, oldName, StringComparison.InvariantCultureIgnoreCase) == 0)
  436. bindings[i].action = newName;
  437. }
  438. /// <summary>
  439. /// Add a new control scheme to the asset.
  440. /// </summary>
  441. /// <param name="asset">Asset to add the control scheme to.</param>
  442. /// <param name="controlScheme">Control scheme to add.</param>
  443. /// <exception cref="ArgumentException"><paramref name="controlScheme"/> has no name.</exception>
  444. /// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c>.</exception>
  445. /// <exception cref="InvalidOperationException">A control scheme with the same name as <paramref name="controlScheme"/>
  446. /// already exists in the asset.</exception>
  447. /// <remarks>
  448. /// </remarks>
  449. public static void AddControlScheme(this InputActionAsset asset, InputControlScheme controlScheme)
  450. {
  451. if (asset == null)
  452. throw new ArgumentNullException(nameof(asset));
  453. if (string.IsNullOrEmpty(controlScheme.name))
  454. throw new ArgumentException("Cannot add control scheme without name to asset " + asset.name, nameof(controlScheme));
  455. if (asset.FindControlScheme(controlScheme.name) != null)
  456. throw new InvalidOperationException(
  457. $"Asset '{asset.name}' already contains a control scheme called '{controlScheme.name}'");
  458. ArrayHelpers.Append(ref asset.m_ControlSchemes, controlScheme);
  459. }
  460. /// <summary>
  461. /// Add a new control scheme to the given <paramref name="asset"/>.
  462. /// </summary>
  463. /// <param name="asset">Asset to add the control scheme to.</param>
  464. /// <param name="name">Name to give to the control scheme. Must be unique within the control schemes of the
  465. /// asset. Also used as default name of <see cref="InputControlScheme.bindingGroup">binding group</see> associated
  466. /// with the control scheme.</param>
  467. /// <returns>Syntax to allow providing additional configuration for the newly added control scheme.</returns>
  468. /// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> -or- <paramref name="name"/>
  469. /// is <c>null</c> or empty.</exception>
  470. public static ControlSchemeSyntax AddControlScheme(this InputActionAsset asset, string name)
  471. {
  472. if (asset == null)
  473. throw new ArgumentNullException(nameof(asset));
  474. if (string.IsNullOrEmpty(name))
  475. throw new ArgumentNullException(nameof(name));
  476. var index = asset.controlSchemes.Count;
  477. asset.AddControlScheme(new InputControlScheme(name));
  478. return new ControlSchemeSyntax(asset, index);
  479. }
  480. /// <summary>
  481. /// Remove the control scheme with the given name from the asset.
  482. /// </summary>
  483. /// <param name="asset">Asset to remove the control scheme from.</param>
  484. /// <param name="name">Name of the control scheme. Matching is case-insensitive.</param>
  485. /// <exception cref="ArgumentNullException"><paramref name="asset"/> is null -or- <paramref name="name"/>
  486. /// is <c>null</c> or empty.</exception>
  487. /// <remarks>
  488. /// If no control scheme with the given name can be found, the method does nothing.
  489. /// </remarks>
  490. public static void RemoveControlScheme(this InputActionAsset asset, string name)
  491. {
  492. if (asset == null)
  493. throw new ArgumentNullException(nameof(asset));
  494. if (string.IsNullOrEmpty(name))
  495. throw new ArgumentNullException(nameof(name));
  496. var index = asset.FindControlSchemeIndex(name);
  497. if (index != -1)
  498. ArrayHelpers.EraseAt(ref asset.m_ControlSchemes, index);
  499. }
  500. public static InputControlScheme WithBindingGroup(this InputControlScheme scheme, string bindingGroup)
  501. {
  502. return new ControlSchemeSyntax(scheme).WithBindingGroup(bindingGroup).Done();
  503. }
  504. public static InputControlScheme WithRequiredDevice(this InputControlScheme scheme, string controlPath)
  505. {
  506. return new ControlSchemeSyntax(scheme).WithRequiredDevice(controlPath).Done();
  507. }
  508. public static InputControlScheme WithOptionalDevice(this InputControlScheme scheme, string controlPath)
  509. {
  510. return new ControlSchemeSyntax(scheme).WithOptionalDevice(controlPath).Done();
  511. }
  512. public static InputControlScheme OrWithRequiredDevice(this InputControlScheme scheme, string controlPath)
  513. {
  514. return new ControlSchemeSyntax(scheme).OrWithRequiredDevice(controlPath).Done();
  515. }
  516. public static InputControlScheme OrWithOptionalDevice(this InputControlScheme scheme, string controlPath)
  517. {
  518. return new ControlSchemeSyntax(scheme).OrWithOptionalDevice(controlPath).Done();
  519. }
  520. /// <summary>
  521. /// Syntax to configure a binding added to an <see cref="InputAction"/> or an
  522. /// <see cref="InputActionMap"/>.
  523. /// </summary>
  524. /// <seealso cref="AddBinding(InputAction,InputBinding)"/>
  525. public struct BindingSyntax
  526. {
  527. private readonly InputActionMap m_ActionMap;
  528. private readonly InputAction m_Action;
  529. internal readonly int m_BindingIndex;
  530. internal BindingSyntax(InputActionMap map, InputAction action, int bindingIndex)
  531. {
  532. m_ActionMap = map;
  533. m_Action = action;
  534. m_BindingIndex = bindingIndex;
  535. }
  536. /// <summary>
  537. /// Set the <see cref="InputBinding.name"/> of the binding.
  538. /// </summary>
  539. /// <param name="name">Name for the binding.</param>
  540. /// <returns>The same binding syntax for further configuration.</returns>
  541. /// <seealso cref="InputBinding.name"/>
  542. /// <seealso cref="AddBinding"/>
  543. public BindingSyntax WithName(string name)
  544. {
  545. m_ActionMap.m_Bindings[m_BindingIndex].name = name;
  546. m_ActionMap.ClearPerActionCachedBindingData();
  547. m_ActionMap.LazyResolveBindings();
  548. return this;
  549. }
  550. /// <summary>
  551. /// Set the <see cref="InputBinding.path"/> of the binding.
  552. /// </summary>
  553. /// <param name="path">Path for the binding.</param>
  554. /// <returns>The same binding syntax for further configuration.</returns>
  555. /// <seealso cref="InputBinding.path"/>
  556. public BindingSyntax WithPath(string path)
  557. {
  558. m_ActionMap.m_Bindings[m_BindingIndex].path = path;
  559. m_ActionMap.ClearPerActionCachedBindingData();
  560. m_ActionMap.LazyResolveBindings();
  561. return this;
  562. }
  563. public BindingSyntax WithGroup(string group)
  564. {
  565. if (string.IsNullOrEmpty(group))
  566. throw new ArgumentException("Group name cannot be null or empty", nameof(group));
  567. if (group.IndexOf(InputBinding.Separator) != -1)
  568. throw new ArgumentException(
  569. $"Group name cannot contain separator character '{InputBinding.Separator}'", nameof(group));
  570. return WithGroups(group);
  571. }
  572. public BindingSyntax WithGroups(string groups)
  573. {
  574. if (string.IsNullOrEmpty(groups))
  575. return this;
  576. // Join with existing group, if any.
  577. var currentGroups = m_ActionMap.m_Bindings[m_BindingIndex].groups;
  578. if (!string.IsNullOrEmpty(currentGroups))
  579. groups = string.Join(InputBinding.kSeparatorString, currentGroups, groups);
  580. // Set groups on binding.
  581. m_ActionMap.m_Bindings[m_BindingIndex].groups = groups;
  582. m_ActionMap.ClearPerActionCachedBindingData();
  583. m_ActionMap.LazyResolveBindings();
  584. return this;
  585. }
  586. public BindingSyntax WithInteraction(string interaction)
  587. {
  588. if (string.IsNullOrEmpty(interaction))
  589. throw new ArgumentException("Interaction cannot be null or empty", nameof(interaction));
  590. if (interaction.IndexOf(InputBinding.Separator) != -1)
  591. throw new ArgumentException(
  592. $"Interaction string cannot contain separator character '{InputBinding.Separator}'", nameof(interaction));
  593. return WithInteractions(interaction);
  594. }
  595. public BindingSyntax WithInteractions(string interactions)
  596. {
  597. if (string.IsNullOrEmpty(interactions))
  598. return this;
  599. // Join with existing interaction string, if any.
  600. var currentInteractions = m_ActionMap.m_Bindings[m_BindingIndex].interactions;
  601. if (!string.IsNullOrEmpty(currentInteractions))
  602. interactions = string.Join(InputBinding.kSeparatorString, currentInteractions, interactions);
  603. // Set interactions on binding.
  604. m_ActionMap.m_Bindings[m_BindingIndex].interactions = interactions;
  605. m_ActionMap.ClearPerActionCachedBindingData();
  606. m_ActionMap.LazyResolveBindings();
  607. return this;
  608. }
  609. public BindingSyntax WithInteraction<TInteraction>()
  610. where TInteraction : IInputInteraction
  611. {
  612. var interactionName = InputProcessor.s_Processors.FindNameForType(typeof(TInteraction));
  613. if (interactionName.IsEmpty())
  614. throw new NotSupportedException($"Type '{typeof(TInteraction)}' has not been registered as a processor");
  615. return WithInteraction(interactionName);
  616. }
  617. public BindingSyntax WithProcessor(string processor)
  618. {
  619. if (string.IsNullOrEmpty(processor))
  620. throw new ArgumentException("Processor cannot be null or empty", nameof(processor));
  621. if (processor.IndexOf(InputBinding.Separator) != -1)
  622. throw new ArgumentException(
  623. $"Interaction string cannot contain separator character '{InputBinding.Separator}'", nameof(processor));
  624. return WithProcessors(processor);
  625. }
  626. public BindingSyntax WithProcessors(string processors)
  627. {
  628. if (string.IsNullOrEmpty(processors))
  629. return this;
  630. // Join with existing processor string, if any.
  631. var currentProcessors = m_ActionMap.m_Bindings[m_BindingIndex].processors;
  632. if (!string.IsNullOrEmpty(currentProcessors))
  633. processors = string.Join(InputBinding.kSeparatorString, currentProcessors, processors);
  634. // Set processors on binding.
  635. m_ActionMap.m_Bindings[m_BindingIndex].processors = processors;
  636. m_ActionMap.ClearPerActionCachedBindingData();
  637. m_ActionMap.LazyResolveBindings();
  638. return this;
  639. }
  640. public BindingSyntax WithProcessor<TProcessor>()
  641. {
  642. var processorName = InputProcessor.s_Processors.FindNameForType(typeof(TProcessor));
  643. if (processorName.IsEmpty())
  644. throw new NotSupportedException($"Type '{typeof(TProcessor)}' has not been registered as a processor");
  645. return WithProcessor(processorName);
  646. }
  647. public BindingSyntax Triggering(InputAction action)
  648. {
  649. if (action == null)
  650. throw new ArgumentNullException(nameof(action));
  651. if (action.isSingletonAction)
  652. throw new ArgumentException(
  653. $"Cannot change the action a binding triggers on singleton action '{action}'", nameof(action));
  654. m_ActionMap.m_Bindings[m_BindingIndex].action = action.name;
  655. m_ActionMap.ClearPerActionCachedBindingData();
  656. m_ActionMap.LazyResolveBindings();
  657. return this;
  658. }
  659. public BindingSyntax To(InputBinding binding)
  660. {
  661. m_ActionMap.m_Bindings[m_BindingIndex] = binding;
  662. m_ActionMap.ClearPerActionCachedBindingData();
  663. m_ActionMap.LazyResolveBindings();
  664. // If it's a singleton action, we force the binding to stay with the action.
  665. if (m_ActionMap.m_SingletonAction != null)
  666. m_ActionMap.m_Bindings[m_BindingIndex].action = m_Action.name;
  667. return this;
  668. }
  669. public void Erase()
  670. {
  671. ArrayHelpers.EraseAt(ref m_ActionMap.m_Bindings, m_BindingIndex);
  672. m_ActionMap.ClearPerActionCachedBindingData();
  673. m_ActionMap.LazyResolveBindings();
  674. // We have switched to a different binding array. For singleton actions, we need to
  675. // sync up the reference that the action itself has.
  676. if (m_ActionMap.m_SingletonAction != null)
  677. m_ActionMap.m_SingletonAction.m_SingletonActionBindings = m_ActionMap.m_Bindings;
  678. }
  679. internal BindingSyntax And => throw new NotImplementedException();
  680. }
  681. public struct CompositeSyntax
  682. {
  683. private readonly InputAction m_Action;
  684. private readonly InputActionMap m_ActionMap;
  685. private int m_CompositeIndex;
  686. internal CompositeSyntax(InputActionMap map, InputAction action, int compositeIndex)
  687. {
  688. m_Action = action;
  689. m_ActionMap = map;
  690. m_CompositeIndex = compositeIndex;
  691. }
  692. public CompositeSyntax With(string name, string binding, string groups = null)
  693. {
  694. ////TODO: check whether non-composite bindings have been added in-between
  695. int bindingIndex;
  696. if (m_Action != null)
  697. bindingIndex = m_Action.AddBinding(path: binding, groups: groups)
  698. .m_BindingIndex;
  699. else
  700. bindingIndex = m_ActionMap.AddBinding(path: binding, groups: groups)
  701. .m_BindingIndex;
  702. m_ActionMap.m_Bindings[bindingIndex].name = name;
  703. m_ActionMap.m_Bindings[bindingIndex].isPartOfComposite = true;
  704. return this;
  705. }
  706. }
  707. public struct ControlSchemeSyntax
  708. {
  709. private readonly InputActionAsset m_Asset;
  710. private readonly int m_ControlSchemeIndex;
  711. private InputControlScheme m_ControlScheme;
  712. internal ControlSchemeSyntax(InputActionAsset asset, int index)
  713. {
  714. m_Asset = asset;
  715. m_ControlSchemeIndex = index;
  716. m_ControlScheme = new InputControlScheme();
  717. }
  718. internal ControlSchemeSyntax(InputControlScheme controlScheme)
  719. {
  720. m_Asset = null;
  721. m_ControlSchemeIndex = -1;
  722. m_ControlScheme = controlScheme;
  723. }
  724. public ControlSchemeSyntax WithBindingGroup(string bindingGroup)
  725. {
  726. if (string.IsNullOrEmpty(bindingGroup))
  727. throw new ArgumentNullException(nameof(bindingGroup));
  728. if (m_Asset == null)
  729. m_ControlScheme.m_BindingGroup = bindingGroup;
  730. else
  731. m_Asset.m_ControlSchemes[m_ControlSchemeIndex].bindingGroup = bindingGroup;
  732. return this;
  733. }
  734. public ControlSchemeSyntax WithRequiredDevice<TDevice>()
  735. where TDevice : InputDevice
  736. {
  737. return WithRequiredDevice(DeviceTypeToControlPath<TDevice>());
  738. }
  739. public ControlSchemeSyntax WithOptionalDevice<TDevice>()
  740. where TDevice : InputDevice
  741. {
  742. return WithOptionalDevice(DeviceTypeToControlPath<TDevice>());
  743. }
  744. public ControlSchemeSyntax OrWithRequiredDevice<TDevice>()
  745. where TDevice : InputDevice
  746. {
  747. return WithRequiredDevice(DeviceTypeToControlPath<TDevice>());
  748. }
  749. public ControlSchemeSyntax OrWithOptionalDevice<TDevice>()
  750. where TDevice : InputDevice
  751. {
  752. return WithOptionalDevice(DeviceTypeToControlPath<TDevice>());
  753. }
  754. public ControlSchemeSyntax WithRequiredDevice(string controlPath)
  755. {
  756. AddDeviceEntry(controlPath, InputControlScheme.DeviceRequirement.Flags.None);
  757. return this;
  758. }
  759. public ControlSchemeSyntax WithOptionalDevice(string controlPath)
  760. {
  761. AddDeviceEntry(controlPath, InputControlScheme.DeviceRequirement.Flags.Optional);
  762. return this;
  763. }
  764. public ControlSchemeSyntax OrWithRequiredDevice(string controlPath)
  765. {
  766. AddDeviceEntry(controlPath, InputControlScheme.DeviceRequirement.Flags.Or);
  767. return this;
  768. }
  769. public ControlSchemeSyntax OrWithOptionalDevice(string controlPath)
  770. {
  771. AddDeviceEntry(controlPath,
  772. InputControlScheme.DeviceRequirement.Flags.Optional |
  773. InputControlScheme.DeviceRequirement.Flags.Or);
  774. return this;
  775. }
  776. private string DeviceTypeToControlPath<TDevice>()
  777. where TDevice : InputDevice
  778. {
  779. var layoutName = InputControlLayout.s_Layouts.TryFindLayoutForType(typeof(TDevice)).ToString();
  780. if (string.IsNullOrEmpty(layoutName))
  781. layoutName = typeof(TDevice).Name;
  782. return $"<{layoutName}>";
  783. }
  784. public InputControlScheme Done()
  785. {
  786. if (m_Asset != null)
  787. return m_Asset.m_ControlSchemes[m_ControlSchemeIndex];
  788. return m_ControlScheme;
  789. }
  790. private void AddDeviceEntry(string controlPath, InputControlScheme.DeviceRequirement.Flags flags)
  791. {
  792. if (string.IsNullOrEmpty(controlPath))
  793. throw new ArgumentNullException(nameof(controlPath));
  794. var scheme = m_Asset != null ? m_Asset.m_ControlSchemes[m_ControlSchemeIndex] : m_ControlScheme;
  795. ArrayHelpers.Append(ref scheme.m_DeviceRequirements,
  796. new InputControlScheme.DeviceRequirement
  797. {
  798. m_ControlPath = controlPath,
  799. m_Flags = flags,
  800. });
  801. if (m_Asset == null)
  802. m_ControlScheme = scheme;
  803. else
  804. m_Asset.m_ControlSchemes[m_ControlSchemeIndex] = scheme;
  805. }
  806. }
  807. }
  808. }