2023-03-28 13:24:16 -04:00
using System ;
using UnityEngine.InputSystem.Layouts ;
using UnityEngine.InputSystem.Utilities ;
////TODO: Rename all the xxxSyntax structs to xxxAccessor
////TODO: Replace all 'WithXXX' in the accessors with just 'SetXXX'; the 'WithXXX' reads too awkwardly
namespace UnityEngine.InputSystem
{
/// <summary>
/// Methods to change the setup of <see cref="InputAction"/>, <see cref="InputActionMap"/>,
/// and <see cref="InputActionAsset"/> objects.
/// </summary>
/// <remarks>
/// Unlike the methods in <see cref="InputActionRebindingExtensions"/>, the methods here are
/// generally destructive, i.e. they will rearrange the data for actions.
/// </remarks>
public static class InputActionSetupExtensions
{
/// <summary>
/// Create an action map with the given name and add it to the asset.
/// </summary>
/// <param name="asset">Asset to add the action map to</param>
/// <param name="name">Name to assign to the </param>
/// <returns>The newly added action map.</returns>
/// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> or
/// <exception cref="InvalidOperationException">An action map with the given <paramref name="name"/>
/// already exists in <paramref name="asset"/>.</exception>
/// <paramref name="name"/> is <c>null</c> or empty.</exception>
public static InputActionMap AddActionMap ( this InputActionAsset asset , string name )
{
if ( asset = = null )
throw new ArgumentNullException ( nameof ( asset ) ) ;
if ( string . IsNullOrEmpty ( name ) )
throw new ArgumentNullException ( nameof ( name ) ) ;
if ( asset . FindActionMap ( name ) ! = null )
throw new InvalidOperationException (
$"An action map called '{name}' already exists in the asset" ) ;
var map = new InputActionMap ( name ) ;
map . GenerateId ( ) ;
asset . AddActionMap ( map ) ;
return map ;
}
/// <summary>
/// Add an action map to the asset.
/// </summary>
/// <param name="asset">Asset to add the map to.</param>
/// <param name="map">A named action map.</param>
/// <exception cref="ArgumentNullException"><paramref name="map"/> or <paramref name="asset"/> is <c>null</c>.</exception>
/// <exception cref="InvalidOperationException"><paramref name="map"/> has no name or asset already contains a
/// map with the same name -or- <paramref name="map"/> is currently enabled -or- <paramref name="map"/> is part of
/// an <see cref="InputActionAsset"/> that has <see cref="InputActionMap"/>s that are enabled.</exception>
/// <seealso cref="InputActionAsset.actionMaps"/>
public static void AddActionMap ( this InputActionAsset asset , InputActionMap map )
{
if ( asset = = null )
throw new ArgumentNullException ( nameof ( asset ) ) ;
if ( map = = null )
throw new ArgumentNullException ( nameof ( map ) ) ;
if ( string . IsNullOrEmpty ( map . name ) )
throw new InvalidOperationException ( "Maps added to an input action asset must be named" ) ;
if ( map . asset ! = null )
throw new InvalidOperationException (
$"Cannot add map '{map}' to asset '{asset}' as it has already been added to asset '{map.asset}'" ) ;
////REVIEW: some of the rules here seem stupid; just replace?
if ( asset . FindActionMap ( map . name ) ! = null )
throw new InvalidOperationException (
$"An action map called '{map.name}' already exists in the asset" ) ;
map . OnWantToChangeSetup ( ) ;
asset . OnWantToChangeSetup ( ) ;
ArrayHelpers . Append ( ref asset . m_ActionMaps , map ) ;
map . m_Asset = asset ;
asset . OnSetupChanged ( ) ;
}
/// <summary>
/// Remove the given action map from the asset.
/// </summary>
/// <param name="asset">Asset to add the action map to.</param>
/// <param name="map">An action map. If the given map is not part of the asset, the method
/// does nothing.</param>
/// <exception cref="ArgumentNullException"><paramref name="asset"/> or <paramref name="map"/> is <c>null</c>.</exception>
/// <exception cref="InvalidOperationException"><paramref name="map"/> is currently enabled (see <see
/// cref="InputActionMap.enabled"/>) or is part of an <see cref="InputActionAsset"/> that has <see cref="InputActionMap"/>s
/// that are currently enabled.</exception>
/// <seealso cref="RemoveActionMap(InputActionAsset,string)"/>
/// <seealso cref="InputActionAsset.actionMaps"/>
public static void RemoveActionMap ( this InputActionAsset asset , InputActionMap map )
{
if ( asset = = null )
throw new ArgumentNullException ( nameof ( asset ) ) ;
if ( map = = null )
throw new ArgumentNullException ( nameof ( map ) ) ;
map . OnWantToChangeSetup ( ) ;
asset . OnWantToChangeSetup ( ) ;
// Ignore if not part of this asset.
if ( map . m_Asset ! = asset )
return ;
ArrayHelpers . Erase ( ref asset . m_ActionMaps , map ) ;
map . m_Asset = null ;
asset . OnSetupChanged ( ) ;
}
/// <summary>
/// Remove the action map with the given name or ID from the asset.
/// </summary>
/// <param name="asset">Asset to remove the action map from.</param>
/// <param name="nameOrId">The name or ID (see <see cref="InputActionMap.id"/>) of a map in the
/// asset. Note that lookup is case-insensitive. If no map with the given name or ID is found,
/// the method does nothing.</param>
/// <exception cref="ArgumentNullException"><paramref name="asset"/> or <paramref name="nameOrId"/> is <c>null</c>.</exception>
/// <exception cref="InvalidOperationException">The map referenced by <paramref name="nameOrId"/> is currently enabled
/// (see <see cref="InputActionMap.enabled"/>).</exception>
/// <seealso cref="RemoveActionMap(InputActionAsset,string)"/>
/// <seealso cref="InputActionAsset.actionMaps"/>
public static void RemoveActionMap ( this InputActionAsset asset , string nameOrId )
{
if ( asset = = null )
throw new ArgumentNullException ( nameof ( asset ) ) ;
if ( nameOrId = = null )
throw new ArgumentNullException ( nameof ( nameOrId ) ) ;
var map = asset . FindActionMap ( nameOrId ) ;
if ( map ! = null )
asset . RemoveActionMap ( map ) ;
}
////TODO: add method to add an existing InputAction to a map
/// <summary>
/// Add a new <see cref="InputAction"/> to the given <paramref name="map"/>.
/// </summary>
/// <param name="map">Action map to add the action to. The action will be appended to
/// <see cref="InputActionMap.actions"/> of the map. The map must be disabled (see
/// <see cref="InputActionMap.enabled"/>).</param>
/// <param name="name">Name to give to the action. Must not be <c>null</c> or empty. Also,
/// no other action that already exists in <paramref name="map"/> must have this name already.</param>
/// <param name="type">Action type. See <see cref="InputAction.type"/>.</param>
/// <param name="binding">If not <c>null</c>, a binding is automatically added to the newly created action
/// with the value of this parameter being used as the binding's <see cref="InputBinding.path"/>.</param>
/// <param name="interactions">If <paramref name="binding"/> is not <c>null</c>, this string is used for
/// <see cref="InputBinding.interactions"/> of the binding that is automatically added for the action.</param>
/// <param name="processors">If <paramref name="binding"/> is not <c>null</c>, this string is used for
/// <see cref="InputBinding.processors"/> of the binding that is automatically added for the action.</param>
/// <param name="groups">If <paramref name="binding"/> is not <c>null</c>, this string is used for
/// <see cref="InputBinding.groups"/> of the binding that is automatically added for the action.</param>
/// <param name="expectedControlLayout">Value for <see cref="InputAction.expectedControlType"/>; <c>null</c>
/// by default.</param>
/// <returns>The newly added input action.</returns>
/// <exception cref="ArgumentNullException"><paramref name="map"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException"><paramref name="name"/> is <c>null</c> or empty.</exception>
/// <exception cref="InvalidOperationException"><paramref name="map"/> is enabled (see <see cref="InputActionMap.enabled"/>)
/// or is part of an <see cref="InputActionAsset"/> that has <see cref="InputActionMap"/>s that are <see cref="InputActionMap.enabled"/>
/// -or- <paramref name="map"/> already contains an action called <paramref name="name"/> (case-insensitive).</exception>
public static InputAction AddAction ( this InputActionMap map , string name , InputActionType type = default , string binding = null ,
string interactions = null , string processors = null , string groups = null , string expectedControlLayout = null )
{
if ( map = = null )
throw new ArgumentNullException ( nameof ( map ) ) ;
if ( string . IsNullOrEmpty ( name ) )
throw new ArgumentException ( "Action must have name" , nameof ( name ) ) ;
map . OnWantToChangeSetup ( ) ;
if ( map . FindAction ( name ) ! = null )
throw new InvalidOperationException (
$"Cannot add action with duplicate name '{name}' to set '{map.name}'" ) ;
// Append action to array.
var action = new InputAction ( name , type )
{
expectedControlType = expectedControlLayout
} ;
action . GenerateId ( ) ;
ArrayHelpers . Append ( ref map . m_Actions , action ) ;
action . m_ActionMap = map ;
// Add binding, if supplied.
if ( ! string . IsNullOrEmpty ( binding ) )
{
// Will trigger OnSetupChanged.
action . AddBinding ( binding , interactions : interactions , processors : processors , groups : groups ) ;
}
else
{
if ( ! string . IsNullOrEmpty ( groups ) )
throw new ArgumentException (
$"No binding path was specified for action '{action}' but groups was specified ('{groups}'); cannot apply groups without binding" ,
nameof ( groups ) ) ;
// If no binding has been supplied but there are interactions and processors, they go on the action itself.
action . m_Interactions = interactions ;
action . m_Processors = processors ;
map . OnSetupChanged ( ) ;
}
return action ;
}
/// <summary>
/// Remove the given action from its <see cref="InputActionMap"/>.
/// </summary>
/// <param name="action">An input action that is part of an <see cref="InputActionMap"/>.</param>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException"><paramref name="action"/> is a standalone action
/// that is not part of an <see cref="InputActionMap"/> and thus cannot be removed from anything.</exception>
/// <exception cref="InvalidOperationException"><paramref name="action"/> is part of an <see cref="InputActionMap"/>
/// or <see cref="InputActionAsset"/> that has at least one enabled action.</exception>
/// <remarks>
/// After removal, the action's <see cref="InputAction.actionMap"/> will be set to <c>null</c>
/// and the action will effectively become a standalone action that is not associated with
/// any action map. Bindings on the action will be preserved. On the action map, the bindings
/// for the action will be removed.
/// </remarks>
/// <seealso cref="AddAction"/>
public static void RemoveAction ( this InputAction action )
{
if ( action = = null )
throw new ArgumentNullException ( nameof ( action ) ) ;
var actionMap = action . actionMap ;
if ( actionMap = = null )
throw new ArgumentException (
$"Action '{action}' does not belong to an action map; nowhere to remove from" , nameof ( action ) ) ;
actionMap . OnWantToChangeSetup ( ) ;
var bindingsForAction = action . bindings . ToArray ( ) ;
var index = actionMap . m_Actions . IndexOfReference ( action ) ;
Debug . Assert ( index ! = - 1 , "Could not find action in map" ) ;
ArrayHelpers . EraseAt ( ref actionMap . m_Actions , index ) ;
action . m_ActionMap = null ;
action . m_SingletonActionBindings = bindingsForAction ;
// Remove bindings to action from map.
var newActionMapBindingCount = actionMap . m_Bindings . Length - bindingsForAction . Length ;
if ( newActionMapBindingCount = = 0 )
{
actionMap . m_Bindings = null ;
}
else
{
var newActionMapBindings = new InputBinding [ newActionMapBindingCount ] ;
var oldActionMapBindings = actionMap . m_Bindings ;
var bindingIndex = 0 ;
for ( var i = 0 ; i < oldActionMapBindings . Length ; + + i )
{
var binding = oldActionMapBindings [ i ] ;
if ( bindingsForAction . IndexOf ( b = > b = = binding ) = = - 1 )
newActionMapBindings [ bindingIndex + + ] = binding ;
}
actionMap . m_Bindings = newActionMapBindings ;
}
actionMap . OnSetupChanged ( ) ;
}
/// <summary>
/// Remove the action with the given name from the asset.
/// </summary>
/// <param name="asset">Asset to remove the action from.</param>
/// <param name="nameOrId">Name or ID of the action. See <see cref="InputActionAsset.FindAction(string,bool)"/> for
/// details.</param>
/// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> -or- <paramref name="nameOrId"/>
/// is <c>null</c> or empty.</exception>
/// <seealso cref="RemoveAction(InputAction)"/>
public static void RemoveAction ( this InputActionAsset asset , string nameOrId )
{
if ( asset = = null )
throw new ArgumentNullException ( nameof ( asset ) ) ;
if ( nameOrId = = null )
throw new ArgumentNullException ( nameof ( nameOrId ) ) ;
var action = asset . FindAction ( nameOrId ) ;
action ? . RemoveAction ( ) ;
}
/// <summary>
/// Add a new binding to the given action.
/// </summary>
/// <param name="action">Action to add the binding to. If the action is part of an <see cref="InputActionMap"/>,
/// the newly added binding will be visible on <see cref="InputActionMap.bindings"/>.</param>
/// <param name="path">Binding path string. See <see cref="InputBinding.path"/> for details.</param>
/// <param name="interactions">Optional list of interactions to apply to the binding. See <see
/// cref="InputBinding.interactions"/> for details.</param>
/// <param name="processors">Optional list of processors to apply to the binding. See <see
/// cref="InputBinding.processors"/> for details.</param>
/// <param name="groups">Optional list of binding groups that should be assigned to the binding. See
/// <see cref="InputBinding.groups"/> for details.</param>
/// <returns>Fluent-style syntax to further configure the binding.</returns>
public static BindingSyntax AddBinding ( this InputAction action , string path , string interactions = null ,
string processors = null , string groups = null )
{
return AddBinding ( action , new InputBinding
{
path = path ,
interactions = interactions ,
processors = processors ,
groups = groups
} ) ;
}
/// <summary>
/// Add a binding that references the given <paramref name="control"/> and triggers
/// the given <paramref cref="action"/>.
/// </summary>
/// <param name="action">Action to trigger.</param>
/// <param name="control">Control to bind to. The full <see cref="InputControl.path"/> of the control will
/// be used in the resulting <see cref="InputBinding">binding</see>.</param>
/// <returns>Syntax to configure the binding further.</returns>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is null or <paramref name="control"/> is null.</exception>
/// <seealso cref="InputAction.bindings"/>
public static BindingSyntax AddBinding ( this InputAction action , InputControl control )
{
if ( control = = null )
throw new ArgumentNullException ( nameof ( control ) ) ;
return AddBinding ( action , control . path ) ;
}
/// <summary>
/// Add a new binding to the action.
/// </summary>
/// <param name="action">An action to add the binding to.</param>
/// <param name="binding">Binding to add to the action or default. Binding can be further configured via
/// the struct returned by the method.</param>
/// <returns>
/// Returns a fluent-style syntax structure that allows performing additional modifications
/// based on the new binding.
/// </returns>
/// <remarks>
/// This works both with actions that are part of an action set as well as with actions that aren't.
///
/// Note that actions must be disabled while altering their binding sets. Also, if the action belongs
/// to a set, all actions in the set must be disabled.
///
/// <example>
/// <code>
/// fireAction.AddBinding()
/// .WithPath("<Gamepad>/buttonSouth")
/// .WithGroup("Gamepad");
/// </code>
/// </example>
/// </remarks>
public static BindingSyntax AddBinding ( this InputAction action , InputBinding binding = default )
{
if ( action = = null )
throw new ArgumentNullException ( nameof ( action ) ) ;
////REVIEW: should this reference actions by ID?
Debug . Assert ( action . m_Name ! = null | | action . isSingletonAction ) ;
binding . action = action . name ;
var actionMap = action . GetOrCreateActionMap ( ) ;
var bindingIndex = AddBindingInternal ( actionMap , binding ) ;
return new BindingSyntax ( actionMap , bindingIndex ) ;
}
/// <summary>
/// Add a new binding to the given action map.
/// </summary>
/// <param name="actionMap">Action map to add the binding to.</param>
/// <param name="path">Path of the control(s) to bind to. See <see cref="InputControlPath"/> and
/// <see cref="InputBinding.path"/>.</param>
/// <param name="interactions">Names and parameters for interactions to apply to the
/// binding. See <see cref="InputBinding.interactions"/>.</param>
/// <param name="groups">Optional list of groups to apply to the binding. See <see cref="InputBinding.groups"/>.</param>
/// <param name="action">Action to trigger from the binding. See <see cref="InputBinding.action"/>.</param>
/// <param name="processors">Optional list of processors to apply to the binding. See <see cref="InputBinding.processors"/>.</param>
/// <returns>A write-accessor to the newly added binding.</returns>
/// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c>.</exception>
/// <remarks>
/// <example>
/// <code>
/// // Add a binding for the A button the gamepad and make it trigger
/// // the "fire" action.
/// var gameplayActions = playerInput.actions.FindActionMap("gameplay");
/// gameplayActions.AddBinding("<Gamepad>/buttonSouth", action: "fire");
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="InputBinding"/>
/// <seealso cref="InputActionMap.bindings"/>
public static BindingSyntax AddBinding ( this InputActionMap actionMap , string path ,
string interactions = null , string groups = null , string action = null , string processors = null )
{
if ( path = = null )
throw new ArgumentNullException ( nameof ( path ) , "Binding path cannot be null" ) ;
return AddBinding ( actionMap ,
new InputBinding
{
path = path ,
interactions = interactions ,
groups = groups ,
action = action ,
processors = processors ,
} ) ;
}
/// <summary>
/// Add a new binding that triggers the given action to the given action map.
/// </summary>
/// <param name="actionMap">Action map to add the binding to.</param>
/// <param name="action">Action to trigger from the binding. See <see cref="InputBinding.action"/>.
/// Must be part of <paramref name="actionMap"/>.</param>
/// <param name="path">Path of the control(s) to bind to. See <see cref="InputControlPath"/> and
/// <see cref="InputBinding.path"/>.</param>
/// <param name="interactions">Names and parameters for interactions to apply to the
/// binding. See <see cref="InputBinding.interactions"/>.</param>
/// <param name="groups">Binding groups to apply to the binding. See <see cref="InputBinding.groups"/>.</param>
/// <returns>A write-accessor to the newly added binding.</returns>
/// <exception cref="ArgumentException"><paramref name="action"/> is not part of <paramref name="actionMap"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c>.</exception>
/// <seealso cref="InputBinding"/>
/// <seealso cref="InputActionMap.bindings"/>
public static BindingSyntax AddBinding ( this InputActionMap actionMap , string path , InputAction action ,
string interactions = null , string groups = null )
{
if ( action ! = null & & action . actionMap ! = actionMap )
throw new ArgumentException (
$"Action '{action}' is not part of action map '{actionMap}'" , nameof ( action ) ) ;
if ( action = = null )
return AddBinding ( actionMap , path : path , interactions : interactions , groups : groups ) ;
return AddBinding ( actionMap , path : path , interactions : interactions , groups : groups ,
action : action . id ) ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Add a new binding that triggers the action given by GUID <paramref name="action"/>.
/// </summary>
/// <param name="actionMap">Action map to add the binding to.</param>
/// <param name="path">Path of the control(s) to bind to. See <see cref="InputControlPath"/> and <see cref="InputBinding.path"/>.</param>
/// <param name="action">ID of the action as per <see cref="InputAction.id"/>.</param>
/// <param name="interactions">Optional list of names and parameters for interactions to apply to the
/// binding. See <see cref="InputBinding.interactions"/>.</param>
/// <param name="groups">Optional list of groups to apply to the binding. See <see cref="InputBinding.groups"/>.</param>
/// <returns>A write-accessor to the newly added binding.</returns>
/// <exception cref="ArgumentException"><paramref name="action"/> is not part of <paramref name="actionMap"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c>.</exception>
/// <seealso cref="InputBinding"/>
/// <seealso cref="InputActionMap.bindings"/>
/// <remarks>
/// Example of adding a binding to an action map that binds to a Gamepad device "leftStick" control and associates it with an action:
/// <example>
/// <code>
/// var map = new InputActionMap();
/// var action = map.AddAction("action");
/// map.AddBinding("<Gamepad>/leftStick", action: action.id);
/// </code>
/// </example>
/// </remarks>
2023-03-28 13:24:16 -04:00
public static BindingSyntax AddBinding ( this InputActionMap actionMap , string path , Guid action ,
string interactions = null , string groups = null )
{
if ( action = = Guid . Empty )
return AddBinding ( actionMap , path : path , interactions : interactions , groups : groups ) ;
return AddBinding ( actionMap , path : path , interactions : interactions , groups : groups ,
action : action . ToString ( ) ) ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Add a binding to the given action map.
/// </summary>
/// <param name="actionMap">Action map to add the binding to.</param>
/// <param name="binding">Binding to add to the action map.</param>
/// <returns>A write-accessor to the newly added binding.</returns>
/// <exception cref="ArgumentException"><paramref name="action"/> is not part of <paramref name="actionMap"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c>.</exception>
/// <seealso cref="InputBinding"/>
/// <seealso cref="InputActionMap.bindings"/>
2023-03-28 13:24:16 -04:00
public static BindingSyntax AddBinding ( this InputActionMap actionMap , InputBinding binding )
{
if ( actionMap = = null )
throw new ArgumentNullException ( nameof ( actionMap ) ) ;
if ( binding . path = = null )
throw new ArgumentException ( "Binding path cannot be null" , nameof ( binding ) ) ;
var bindingIndex = AddBindingInternal ( actionMap , binding ) ;
return new BindingSyntax ( actionMap , bindingIndex ) ;
}
/// <summary>
/// Add a composite binding to the <see cref="InputAction.bindings"/> of <paramref name="action"/>.
/// </summary>
/// <param name="action">Action to add the binding to.</param>
/// <param name="composite">Type of composite to add. This needs to be the name the composite
/// has been registered under using <see cref="InputSystem.RegisterBindingComposite{T}"/>. Case-insensitive.</param>
/// <param name="interactions">Interactions to add to the binding. See <see cref="InputBinding.interactions"/>.</param>
/// <param name="processors">Processors to add to the binding. See <see cref="InputBinding.processors"/>.</param>
/// <returns>A write accessor to the newly added composite binding.</returns>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException"><paramref name="composite"/> is <c>null</c> or empty.</exception>
public static CompositeSyntax AddCompositeBinding ( this InputAction action , string composite ,
string interactions = null , string processors = null )
{
if ( action = = null )
throw new ArgumentNullException ( nameof ( action ) ) ;
if ( string . IsNullOrEmpty ( composite ) )
throw new ArgumentException ( "Composite name cannot be null or empty" , nameof ( composite ) ) ;
var actionMap = action . GetOrCreateActionMap ( ) ;
var binding = new InputBinding
{
name = NameAndParameters . ParseName ( composite ) ,
path = composite ,
interactions = interactions ,
processors = processors ,
isComposite = true ,
action = action . name
} ;
var bindingIndex = AddBindingInternal ( actionMap , binding ) ;
return new CompositeSyntax ( actionMap , action , bindingIndex ) ;
}
////TODO: AddCompositeBinding<T>
private static int AddBindingInternal ( InputActionMap map , InputBinding binding , int bindingIndex = - 1 )
{
Debug . Assert ( map ! = null ) ;
// Make sure the binding has an ID.
if ( string . IsNullOrEmpty ( binding . m_Id ) )
binding . GenerateId ( ) ;
// Append to bindings in set.
if ( bindingIndex < 0 )
bindingIndex = ArrayHelpers . Append ( ref map . m_Bindings , binding ) ;
else
ArrayHelpers . InsertAt ( ref map . m_Bindings , bindingIndex , binding ) ;
// Make sure this asset is reloaded from disk when exiting play mode so it isn't inadvertently
// changed between play sessions. Only applies when running in the editor.
if ( map . asset ! = null )
map . asset . MarkAsDirty ( ) ;
// If we're looking at a singleton action, make sure m_Bindings is up to date just
// in case the action gets serialized.
if ( map . m_SingletonAction ! = null )
map . m_SingletonAction . m_SingletonActionBindings = map . m_Bindings ;
// NOTE: We treat this as a mere binding modification, even though we have added something.
// InputAction.RestoreActionStatesAfterReResolvingBindings() can deal with bindings
// having been removed or added.
map . OnBindingModified ( ) ;
return bindingIndex ;
}
/// <summary>
/// Get write access to the binding in <see cref="InputAction.bindings"/> of <paramref name="action"/>
/// at the given <paramref name="index"/>.
/// </summary>
/// <param name="action">Action whose bindings to change.</param>
/// <param name="index">Index in <paramref name="action"/>'s <see cref="InputAction.bindings"/> of the binding to be changed.</param>
/// <returns>A write accessor to the given binding.</returns>
/// <remarks>
/// <example>
/// <code>
/// // Grab "fire" action from PlayerInput.
/// var fireAction = playerInput.actions["fire"];
///
/// // Change its second binding to go to the left mouse button.
/// fireAction.ChangeBinding(1)
/// .WithPath("<Mouse>/leftButton");
/// </code>
/// </example>
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range (as per <see cref="InputAction.bindings"/>
/// of <paramref name="action"/>).</exception>
public static BindingSyntax ChangeBinding ( this InputAction action , int index )
{
if ( action = = null )
throw new ArgumentNullException ( nameof ( action ) ) ;
var indexOnMap = action . BindingIndexOnActionToBindingIndexOnMap ( index ) ;
return new BindingSyntax ( action . GetOrCreateActionMap ( ) , indexOnMap , action ) ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Get write access to the binding in <see cref="InputAction.bindings"/> of <paramref name="action"/>
/// with the given <paramref name="name"/>.
/// </summary>
/// <param name="action">Action whose bindings to change.</param>
/// <param name="name">Name of the binding to be changed <see cref="InputAction.bindings"/>.</param>
/// <returns>A write accessor to the given binding.</returns>
/// <remarks>
/// <example>
/// <code>
/// // Grab "fire" action from PlayerInput.
/// var fireAction = playerInput.actions["fire"];
///
/// // Change its second binding to go to the left mouse button.
/// fireAction.ChangeBinding("fire")
/// .WithPath("<Mouse>/leftButton");
/// </code>
/// </example>
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range (as per <see cref="InputAction.bindings"/>
/// of <paramref name="action"/>).</exception>
2023-03-28 13:24:16 -04:00
public static BindingSyntax ChangeBinding ( this InputAction action , string name )
{
return action . ChangeBinding ( new InputBinding { name = name } ) ;
}
/// <summary>
/// Get write access to the binding in <see cref="InputActionMap.bindings"/> of <paramref name="actionMap"/>
/// at the given <paramref name="index"/>.
/// </summary>
/// <param name="actionMap">Action map whose bindings to change.</param>
/// <param name="index">Index in <paramref name="actionMap"/>'s <see cref="InputActionMap.bindings"/> of the binding to be changed.</param>
/// <returns>A write accessor to the given binding.</returns>
/// <remarks>
/// <example>
/// <code>
/// // Grab "gameplay" actions from PlayerInput.
/// var gameplayActions = playerInput.actions.FindActionMap("gameplay");
///
/// // Change its second binding to go to the left mouse button.
/// gameplayActions.ChangeBinding(1)
/// .WithPath("<Mouse>/leftButton");
/// </code>
/// </example>
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="actionMap"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range (as per <see cref="InputActionMap.bindings"/>
/// of <paramref name="actionMap"/>).</exception>
public static BindingSyntax ChangeBinding ( this InputActionMap actionMap , int index )
{
if ( actionMap = = null )
throw new ArgumentNullException ( nameof ( actionMap ) ) ;
if ( index < 0 | | index > = actionMap . m_Bindings . LengthSafe ( ) )
throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
return new BindingSyntax ( actionMap , index ) ;
}
/// <summary>
/// Get write access to the binding in <see cref="InputAction.bindings"/> of <paramref name="action"/>
/// that has the given <paramref name="id"/>.
/// </summary>
/// <param name="action">Action whose bindings to change.</param>
/// <param name="id">ID of the binding as per <see cref="InputBinding.id"/>.</param>
/// <returns>A write accessor to the binding with the given ID.</returns>
/// <remarks>
/// <example>
/// <code>
/// // Grab "fire" action from PlayerInput.
/// var fireAction = playerInput.actions["fire"];
///
/// // Change the binding with the given ID to go to the left mouse button.
/// fireAction.ChangeBindingWithId("c3de9215-31c3-4654-8562-854bf2f7864f")
/// .WithPath("<Mouse>/leftButton");
/// </code>
/// </example>
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">No binding with the given <paramref name="id"/> exists
/// on <paramref name="action"/>.</exception>
public static BindingSyntax ChangeBindingWithId ( this InputAction action , string id )
{
if ( action = = null )
throw new ArgumentNullException ( nameof ( action ) ) ;
return action . ChangeBinding ( new InputBinding { m_Id = id } ) ;
}
/// <summary>
/// Get write access to the binding in <see cref="InputAction.bindings"/> of <paramref name="action"/>
/// that has the given <paramref name="id"/>.
/// </summary>
/// <param name="action">Action whose bindings to change.</param>
/// <param name="id">ID of the binding as per <see cref="InputBinding.id"/>.</param>
/// <returns>A write accessor to the binding with the given ID.</returns>
/// <remarks>
/// <example>
/// <code>
/// // Grab "fire" action from PlayerInput.
/// var fireAction = playerInput.actions["fire"];
///
/// // Change the binding with the given ID to go to the left mouse button.
/// fireAction.ChangeBindingWithId(new Guid("c3de9215-31c3-4654-8562-854bf2f7864f"))
/// .WithPath("<Mouse>/leftButton");
/// </code>
/// </example>
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">No binding with the given <paramref name="id"/> exists
/// on <paramref name="action"/>.</exception>
public static BindingSyntax ChangeBindingWithId ( this InputAction action , Guid id )
{
if ( action = = null )
throw new ArgumentNullException ( nameof ( action ) ) ;
return action . ChangeBinding ( new InputBinding { id = id } ) ;
}
/// <summary>
/// Get write access to the first binding in <see cref="InputAction.bindings"/> of <paramref name="action"/>
/// that is assigned to the given binding <paramref name="group"/>.
/// </summary>
/// <param name="action">Action whose bindings to change.</param>
/// <param name="group">Name of the binding group as per <see cref="InputBinding.groups"/>.</param>
/// <returns>A write accessor to the first binding on <paramref name="action"/> that is assigned to the
/// given binding <paramref name="group"/>.</returns>
/// <remarks>
/// <example>
/// <code>
/// // Grab "fire" action from PlayerInput.
/// var fireAction = playerInput.actions["fire"];
///
/// // Change the binding in the "Keyboard&Mouse" group to go to the left mouse button.
/// fireAction.ChangeBindingWithGroup("Keyboard&Mouse")
/// .WithPath("<Mouse>/leftButton");
/// </code>
/// </example>
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">No binding on the <paramref name="action"/> is assigned
/// to the given binding <paramref name="group"/>.</exception>
public static BindingSyntax ChangeBindingWithGroup ( this InputAction action , string group )
{
if ( action = = null )
throw new ArgumentNullException ( nameof ( action ) ) ;
return action . ChangeBinding ( new InputBinding { groups = group } ) ;
}
/// <summary>
/// Get write access to the binding in <see cref="InputAction.bindings"/> of <paramref name="action"/>
/// that is bound to the given <paramref name="path"/>.
/// </summary>
/// <param name="action">Action whose bindings to change.</param>
/// <param name="path">Path of the binding as per <see cref="InputBinding.path"/>.</param>
/// <returns>A write accessor to the binding on <paramref name="action"/> that is assigned the
/// given <paramref name="path"/>.</returns>
/// <remarks>
/// <example>
/// <code>
/// // Grab "fire" action from PlayerInput.
/// var fireAction = playerInput.actions["fire"];
///
/// // Change the binding to the right mouse button to go to the left mouse button instead.
/// fireAction.ChangeBindingWithPath("<Mouse>/rightButton")
/// .WithPath("<Mouse>/leftButton");
/// </code>
/// </example>
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">No binding on the <paramref name="action"/> is assigned
/// the given <paramref name="path"/>.</exception>
public static BindingSyntax ChangeBindingWithPath ( this InputAction action , string path )
{
if ( action = = null )
throw new ArgumentNullException ( nameof ( action ) ) ;
return action . ChangeBinding ( new InputBinding { path = path } ) ;
}
/// <summary>
/// Get write access to the binding on <paramref name="action"/> that matches the given
/// <paramref name="match"/>.
/// </summary>
/// <param name="action">Action whose bindings to match against.</param>
/// <param name="match">A binding mask. See <see cref="InputBinding.Matches"/> for
/// details.</param>
/// <returns>A write-accessor to the first binding matching <paramref name="match"/> or
/// an invalid accessor (see <see cref="BindingSyntax.valid"/>) if no binding was found to
/// match the mask.</returns>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
public static BindingSyntax ChangeBinding ( this InputAction action , InputBinding match )
{
if ( action = = null )
throw new ArgumentNullException ( nameof ( action ) ) ;
var actionMap = action . GetOrCreateActionMap ( ) ;
int bindingIndexInMap = - 1 ;
var id = action . idDontGenerate ;
if ( id ! = null )
{
// Prio1: Attempt to match action id (stronger)
match . action = action . id . ToString ( ) ;
bindingIndexInMap = actionMap . FindBindingRelativeToMap ( match ) ;
}
if ( bindingIndexInMap = = - 1 )
{
// Prio2: Attempt to match action name (weaker)
match . action = action . name ;
bindingIndexInMap = actionMap . FindBindingRelativeToMap ( match ) ;
}
if ( bindingIndexInMap = = - 1 )
return default ;
2023-05-07 18:43:11 -04:00
return new BindingSyntax ( actionMap , bindingIndexInMap , action ) ;
2023-03-28 13:24:16 -04:00
}
/// <summary>
/// Get a write accessor to the binding of <paramref name="action"/> that is both a composite
/// (see <see cref="InputBinding.isComposite"/>) and has the given binding name or composite
/// type.
/// </summary>
/// <param name="action">Action to look up the binding on. All bindings in the action's
/// <see cref="InputAction.bindings"/> property will be considered.</param>
/// <param name="compositeName">Either the name of the composite binding (see <see cref="InputBinding.name"/>)
/// to look for or the name of the composite type used in the binding (such as "1DAxis"). Case-insensitive.</param>
/// <returns>A write accessor to the given composite binding or an invalid accessor if no composite
/// matching <paramref name="compositeName"/> could be found on <paramref name="action"/>.</returns>
/// <remarks>
/// <example>
/// <code>
/// // Add arrow keys as alternatives to the WASD Vector2 composite.
/// playerInput.actions["move"]
/// .ChangeCompositeBinding("WASD")
/// .InsertPartBinding("Up", "<Keyboard>/upArrow")
/// .InsertPartBinding("Down", "<Keyboard>/downArrow")
/// .InsertPartBinding("Left", "<Keyboard>/leftArrow")
/// .InsertPartBinding("Right", "<Keyboard>/rightArrow");
/// </code>
/// </example>
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="compositeName"/>
/// is <c>null</c> or empty.</exception>
/// <seealso cref="InputBinding.isComposite"/>
/// <seealso cref="InputBindingComposite"/>
public static BindingSyntax ChangeCompositeBinding ( this InputAction action , string compositeName )
{
if ( action = = null )
throw new ArgumentNullException ( nameof ( action ) ) ;
if ( string . IsNullOrEmpty ( compositeName ) )
throw new ArgumentNullException ( nameof ( compositeName ) ) ;
var actionMap = action . GetOrCreateActionMap ( ) ;
var bindings = actionMap . m_Bindings ;
var numBindings = bindings . LengthSafe ( ) ;
for ( var i = 0 ; i < numBindings ; + + i )
{
ref var binding = ref bindings [ i ] ;
if ( ! binding . isComposite | | ! binding . TriggersAction ( action ) )
continue ;
////REVIEW: should this do a registration lookup to deal with aliases?
if ( compositeName . Equals ( binding . name , StringComparison . InvariantCultureIgnoreCase )
| | compositeName . Equals ( NameAndParameters . ParseName ( binding . path ) ,
StringComparison . InvariantCultureIgnoreCase ) )
return new BindingSyntax ( actionMap , i , action ) ;
}
return default ;
}
////TODO: update binding mask if necessary
/// <summary>
/// Rename an existing action.
/// </summary>
/// <param name="action">Action to assign a new name to. Can be singleton action or action that
/// is part of a map.</param>
/// <param name="newName">New name to assign to action. Cannot be empty.</param>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is null or <paramref name="newName"/> is
/// null or empty.</exception>
/// <exception cref="InvalidOperationException"><see cref="InputAction.actionMap"/> of <paramref name="action"/>
/// already contains an action called <paramref name="newName"/>.</exception>
/// <remarks>
/// Renaming an action will also update the bindings that refer to the action.
/// </remarks>
public static void Rename ( this InputAction action , string newName )
{
if ( action = = null )
throw new ArgumentNullException ( nameof ( action ) ) ;
if ( string . IsNullOrEmpty ( newName ) )
throw new ArgumentNullException ( nameof ( newName ) ) ;
if ( action . name = = newName )
return ;
// Make sure name isn't already taken in map.
var actionMap = action . actionMap ;
if ( actionMap ? . FindAction ( newName ) ! = null )
throw new InvalidOperationException (
$"Cannot rename '{action}' to '{newName}' in map '{actionMap}' as the map already contains an action with that name" ) ;
var oldName = action . m_Name ;
action . m_Name = newName ;
actionMap ? . ClearActionLookupTable ( ) ;
if ( actionMap ? . asset ! = null )
actionMap ? . asset . MarkAsDirty ( ) ;
// Update bindings.
var bindings = action . GetOrCreateActionMap ( ) . m_Bindings ;
var bindingCount = bindings . LengthSafe ( ) ;
for ( var i = 0 ; i < bindingCount ; + + i )
if ( string . Compare ( bindings [ i ] . action , oldName , StringComparison . InvariantCultureIgnoreCase ) = = 0 )
bindings [ i ] . action = newName ;
}
/// <summary>
/// Add a new control scheme to the asset.
/// </summary>
/// <param name="asset">Asset to add the control scheme to.</param>
/// <param name="controlScheme">Control scheme to add.</param>
/// <exception cref="ArgumentException"><paramref name="controlScheme"/> has no name.</exception>
/// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c>.</exception>
/// <exception cref="InvalidOperationException">A control scheme with the same name as <paramref name="controlScheme"/>
/// already exists in the asset.</exception>
/// <remarks>
/// </remarks>
public static void AddControlScheme ( this InputActionAsset asset , InputControlScheme controlScheme )
{
if ( asset = = null )
throw new ArgumentNullException ( nameof ( asset ) ) ;
if ( string . IsNullOrEmpty ( controlScheme . name ) )
throw new ArgumentException ( "Cannot add control scheme without name to asset " + asset . name , nameof ( controlScheme ) ) ;
if ( asset . FindControlScheme ( controlScheme . name ) ! = null )
throw new InvalidOperationException (
$"Asset '{asset.name}' already contains a control scheme called '{controlScheme.name}'" ) ;
ArrayHelpers . Append ( ref asset . m_ControlSchemes , controlScheme ) ;
asset . MarkAsDirty ( ) ;
}
/// <summary>
/// Add a new control scheme to the given <paramref name="asset"/>.
/// </summary>
/// <param name="asset">Asset to add the control scheme to.</param>
/// <param name="name">Name to give to the control scheme. Must be unique within the control schemes of the
/// asset. Also used as default name of <see cref="InputControlScheme.bindingGroup">binding group</see> associated
/// with the control scheme.</param>
/// <returns>Syntax to allow providing additional configuration for the newly added control scheme.</returns>
/// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> -or- <paramref name="name"/>
/// is <c>null</c> or empty.</exception>
/// <remarks>
/// <example>
/// <code>
/// // Create an .inputactions asset.
/// var asset = ScriptableObject.CreateInstance<InputActionAsset>();
///
/// // Add an action map to it.
/// var actionMap = asset.AddActionMap("actions");
///
/// // Add an action to it and bind it to the A button on the gamepad.
/// // Also, associate that binding with the "Gamepad" control scheme.
/// var action = actionMap.AddAction("action");
/// action.AddBinding("<Gamepad>/buttonSouth", groups: "Gamepad");
///
/// // Add a control scheme called "Gamepad" that requires a Gamepad device.
/// asset.AddControlScheme("Gamepad")
/// .WithRequiredDevice<Gamepad>();
/// </code>
/// </example>
/// </remarks>
public static ControlSchemeSyntax AddControlScheme ( this InputActionAsset asset , string name )
{
if ( asset = = null )
throw new ArgumentNullException ( nameof ( asset ) ) ;
if ( string . IsNullOrEmpty ( name ) )
throw new ArgumentNullException ( nameof ( name ) ) ;
var index = asset . controlSchemes . Count ;
asset . AddControlScheme ( new InputControlScheme ( name ) ) ;
return new ControlSchemeSyntax ( asset , index ) ;
}
/// <summary>
/// Remove the control scheme with the given name from the asset.
/// </summary>
/// <param name="asset">Asset to remove the control scheme from.</param>
/// <param name="name">Name of the control scheme. Matching is case-insensitive.</param>
/// <exception cref="ArgumentNullException"><paramref name="asset"/> is null -or- <paramref name="name"/>
/// is <c>null</c> or empty.</exception>
/// <remarks>
/// If no control scheme with the given name can be found, the method does nothing.
/// </remarks>
public static void RemoveControlScheme ( this InputActionAsset asset , string name )
{
if ( asset = = null )
throw new ArgumentNullException ( nameof ( asset ) ) ;
if ( string . IsNullOrEmpty ( name ) )
throw new ArgumentNullException ( nameof ( name ) ) ;
var index = asset . FindControlSchemeIndex ( name ) ;
if ( index ! = - 1 )
ArrayHelpers . EraseAt ( ref asset . m_ControlSchemes , index ) ;
asset . MarkAsDirty ( ) ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Associates the control scheme given by <paramref name="scheme"/> with the binding group given by <paramref name="bindingGroup"/>.
/// </summary>
/// <param name="scheme">The control scheme to modify.</param>
/// <param name="bindingGroup">The binding group to be associated with the control scheme.</param>
/// <returns><paramref name="scheme"/></returns>
2023-03-28 13:24:16 -04:00
public static InputControlScheme WithBindingGroup ( this InputControlScheme scheme , string bindingGroup )
{
return new ControlSchemeSyntax ( scheme ) . WithBindingGroup ( bindingGroup ) . Done ( ) ;
}
public static InputControlScheme WithDevice ( this InputControlScheme scheme , string controlPath , bool required )
{
if ( required )
return new ControlSchemeSyntax ( scheme ) . WithRequiredDevice ( controlPath ) . Done ( ) ;
return new ControlSchemeSyntax ( scheme ) . WithOptionalDevice ( controlPath ) . Done ( ) ;
}
public static InputControlScheme WithRequiredDevice ( this InputControlScheme scheme , string controlPath )
{
return new ControlSchemeSyntax ( scheme ) . WithRequiredDevice ( controlPath ) . Done ( ) ;
}
public static InputControlScheme WithOptionalDevice ( this InputControlScheme scheme , string controlPath )
{
return new ControlSchemeSyntax ( scheme ) . WithOptionalDevice ( controlPath ) . Done ( ) ;
}
public static InputControlScheme OrWithRequiredDevice ( this InputControlScheme scheme , string controlPath )
{
return new ControlSchemeSyntax ( scheme ) . OrWithRequiredDevice ( controlPath ) . Done ( ) ;
}
public static InputControlScheme OrWithOptionalDevice ( this InputControlScheme scheme , string controlPath )
{
return new ControlSchemeSyntax ( scheme ) . OrWithOptionalDevice ( controlPath ) . Done ( ) ;
}
/// <summary>
/// Write accessor to a binding on either an <see cref="InputAction"/> or an
/// <see cref="InputActionMap"/>.
/// </summary>
/// <remarks>
/// Both <see cref="InputAction.bindings"/> and <see cref="InputActionMap.bindings"/> are
/// read-only. To modify bindings (other than setting overrides which you can do
/// through <see cref="InputActionRebindingExtensions.ApplyBindingOverride(InputAction,int,InputBinding)"/>),
/// it is necessary to gain indirect write access through this structure.
///
/// <example>
/// <code>
/// playerInput.actions["fire"]
/// .ChangeBinding(0)
/// .WithPath("<Keyboard>/space");
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="AddBinding(InputAction,InputBinding)"/>
/// <seealso cref="ChangeBinding(InputAction,int)"/>
public struct BindingSyntax
{
private readonly InputActionMap m_ActionMap ;
private readonly InputAction m_Action ;
internal readonly int m_BindingIndexInMap ;
/// <summary>
/// True if the if binding accessor is valid.
/// </summary>
public bool valid = > m_ActionMap ! = null & & m_BindingIndexInMap > = 0 & & m_BindingIndexInMap < m_ActionMap . m_Bindings . LengthSafe ( ) ;
/// <summary>
/// Index of the binding that the accessor refers to.
/// </summary>
/// <remarks>
/// When accessing bindings on an <see cref="InputAction"/>, this is the index in
/// <see cref="InputAction.bindings"/> of the action. When accessing bindings on an
/// <see cref="InputActionMap"/>, it is the index <see cref="InputActionMap.bindings"/>
/// of the map.
/// </remarks>
public int bindingIndex
{
get
{
if ( ! valid )
return - 1 ;
if ( m_Action ! = null )
return m_Action . BindingIndexOnMapToBindingIndexOnAction ( m_BindingIndexInMap ) ;
return m_BindingIndexInMap ;
}
}
/// <summary>
/// The current binding in entirety.
/// </summary>
/// <exception cref="InvalidOperationException">The accessor is not <see cref="valid"/>.</exception>
public InputBinding binding
{
get
{
if ( ! valid )
throw new InvalidOperationException ( "BindingSyntax accessor is not valid" ) ;
return m_ActionMap . m_Bindings [ m_BindingIndexInMap ] ;
}
}
internal BindingSyntax ( InputActionMap map , int bindingIndexInMap , InputAction action = null )
{
m_ActionMap = map ;
m_BindingIndexInMap = bindingIndexInMap ;
m_Action = action ;
}
/// <summary>
/// Set the <see cref="InputBinding.name"/> of the binding.
/// </summary>
/// <param name="name">Name for the binding.</param>
/// <returns>The same binding syntax for further configuration.</returns>
/// <exception cref="InvalidOperationException">The binding accessor is not <see cref="valid"/>.</exception>
/// <seealso cref="InputBinding.name"/>
public BindingSyntax WithName ( string name )
{
if ( ! valid )
throw new InvalidOperationException ( "Accessor is not valid" ) ;
m_ActionMap . m_Bindings [ m_BindingIndexInMap ] . name = name ;
m_ActionMap . OnBindingModified ( ) ;
return this ;
}
/// <summary>
/// Set the <see cref="InputBinding.path"/> of the binding.
/// </summary>
/// <param name="path">Path for the binding.</param>
/// <returns>The same binding syntax for further configuration.</returns>
/// <exception cref="InvalidOperationException">The binding accessor is not <see cref="valid"/>.</exception>
/// <seealso cref="InputBinding.path"/>
public BindingSyntax WithPath ( string path )
{
if ( ! valid )
throw new InvalidOperationException ( "Accessor is not valid" ) ;
m_ActionMap . m_Bindings [ m_BindingIndexInMap ] . path = path ;
m_ActionMap . OnBindingModified ( ) ;
return this ;
}
/// <summary>
/// Add <paramref name="group"/> to the list of <see cref="InputBinding.groups"/> of the binding.
/// </summary>
/// <param name="group">Name of the binding group (such as "Gamepad").</param>
/// <returns>The same binding syntax for further configuration.</returns>
/// <exception cref="ArgumentException"><paramref name="group"/> is <c>null</c> or empty -or- it contains
/// a <see cref="InputBinding.Separator"/> character.</exception>
public BindingSyntax WithGroup ( string group )
{
if ( ! valid )
throw new InvalidOperationException ( "Accessor is not valid" ) ;
if ( string . IsNullOrEmpty ( group ) )
throw new ArgumentException ( "Group name cannot be null or empty" , nameof ( group ) ) ;
if ( group . IndexOf ( InputBinding . Separator ) ! = - 1 )
throw new ArgumentException (
$"Group name cannot contain separator character '{InputBinding.Separator}'" , nameof ( group ) ) ;
return WithGroups ( group ) ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Add all the groups specified in <paramref name="groups"/> to the list of <see cref="InputBinding.groups"/> of the binding.
/// </summary>
/// <param name="groups">A semi-colon separated list of group names.</param>
/// <returns>The same binding syntax for further configuration.</returns>
/// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception>
2023-03-28 13:24:16 -04:00
public BindingSyntax WithGroups ( string groups )
{
if ( ! valid )
throw new InvalidOperationException ( "Accessor is not valid" ) ;
if ( string . IsNullOrEmpty ( groups ) )
return this ;
// Join with existing group, if any.
var currentGroups = m_ActionMap . m_Bindings [ m_BindingIndexInMap ] . groups ;
if ( ! string . IsNullOrEmpty ( currentGroups ) )
groups = string . Join ( InputBinding . kSeparatorString , currentGroups , groups ) ;
// Set groups on binding.
m_ActionMap . m_Bindings [ m_BindingIndexInMap ] . groups = groups ;
m_ActionMap . OnBindingModified ( ) ;
return this ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Add an interaction via specified name in <paramref name="interaction"/> to the list of interactions.
/// </summary>
/// <param name="interaction">Interaction class name</param>
/// <returns>The same binding syntax for further configuration.</returns>
/// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception>
/// <exception cref="ArgumentException">If interaction name is null or empty.</exception>
2023-03-28 13:24:16 -04:00
public BindingSyntax WithInteraction ( string interaction )
{
if ( ! valid )
throw new InvalidOperationException ( "Accessor is not valid" ) ;
if ( string . IsNullOrEmpty ( interaction ) )
throw new ArgumentException ( "Interaction cannot be null or empty" , nameof ( interaction ) ) ;
if ( interaction . IndexOf ( InputBinding . Separator ) ! = - 1 )
throw new ArgumentException (
$"Interaction string cannot contain separator character '{InputBinding.Separator}'" , nameof ( interaction ) ) ;
return WithInteractions ( interaction ) ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Add the set of interactions specified in <paramref name="interactions"/> to the list of interactions.
/// </summary>
/// <param name="interactions">A semi-colon separated list of interaction names.</param>
/// <returns>The same binding syntax for further configuration.</returns>
/// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception>
2023-03-28 13:24:16 -04:00
public BindingSyntax WithInteractions ( string interactions )
{
if ( ! valid )
throw new InvalidOperationException ( "Accessor is not valid" ) ;
if ( string . IsNullOrEmpty ( interactions ) )
return this ;
// Join with existing interaction string, if any.
var currentInteractions = m_ActionMap . m_Bindings [ m_BindingIndexInMap ] . interactions ;
if ( ! string . IsNullOrEmpty ( currentInteractions ) )
interactions = string . Join ( InputBinding . kSeparatorString , currentInteractions , interactions ) ;
// Set interactions on binding.
m_ActionMap . m_Bindings [ m_BindingIndexInMap ] . interactions = interactions ;
m_ActionMap . OnBindingModified ( ) ;
return this ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Add an interaction of type specified in <typeparamref name="TInteraction"/> to the list of interactions.
/// </summary>
/// <typeparam name="TInteraction"></typeparam>
/// <returns>The same binding syntax for further configuration.</returns>
/// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception>
/// <exception cref="NotSupportedException">Interaction type has not been registered.</exception>
2023-03-28 13:24:16 -04:00
public BindingSyntax WithInteraction < TInteraction > ( )
where TInteraction : IInputInteraction
{
if ( ! valid )
throw new InvalidOperationException ( "Accessor is not valid" ) ;
var interactionName = InputProcessor . s_Processors . FindNameForType ( typeof ( TInteraction ) ) ;
if ( interactionName . IsEmpty ( ) )
throw new NotSupportedException ( $"Type '{typeof(TInteraction)}' has not been registered as a processor" ) ;
return WithInteraction ( interactionName ) ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Add a processor to the list of <see cref="InputBinding.processors"/> of the binding.
/// </summary>
/// <param name="processor">Name of the processor, such as "Scale".</param>
/// <returns>The same binding syntax for further configuration.</returns>
/// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception>
/// <exception cref="ArgumentException">The processor name is null or empty.</exception>
/// <exception cref="ArgumentException">The processor name contains a semi-colon.</exception>
2023-03-28 13:24:16 -04:00
public BindingSyntax WithProcessor ( string processor )
{
if ( ! valid )
throw new InvalidOperationException ( "Accessor is not valid" ) ;
if ( string . IsNullOrEmpty ( processor ) )
throw new ArgumentException ( "Processor cannot be null or empty" , nameof ( processor ) ) ;
if ( processor . IndexOf ( InputBinding . Separator ) ! = - 1 )
throw new ArgumentException (
2023-05-07 18:43:11 -04:00
$"Processor string cannot contain separator character '{InputBinding.Separator}'" , nameof ( processor ) ) ;
2023-03-28 13:24:16 -04:00
return WithProcessors ( processor ) ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Add processors to the list of <see cref="InputBinding.processors"/> of the binding.
/// </summary>
/// <param name="processors">A semi-colon separated list of processor names.</param>
/// <returns>The same binding syntax for further configuration.</returns>
/// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception>
2023-03-28 13:24:16 -04:00
public BindingSyntax WithProcessors ( string processors )
{
if ( ! valid )
throw new InvalidOperationException ( "Accessor is not valid" ) ;
if ( string . IsNullOrEmpty ( processors ) )
return this ;
// Join with existing processor string, if any.
var currentProcessors = m_ActionMap . m_Bindings [ m_BindingIndexInMap ] . processors ;
if ( ! string . IsNullOrEmpty ( currentProcessors ) )
processors = string . Join ( InputBinding . kSeparatorString , currentProcessors , processors ) ;
// Set processors on binding.
m_ActionMap . m_Bindings [ m_BindingIndexInMap ] . processors = processors ;
m_ActionMap . OnBindingModified ( ) ;
return this ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Add a processor to the list of <see cref="InputBinding.processors"/> of the binding.
/// </summary>
/// <typeparam name="TProcessor">Type of processor.</typeparam>
/// <returns>The same binding syntax for further configuration.</returns>
/// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception>
/// <exception cref="NotSupportedException">Processor type has not been registered.</exception>
2023-03-28 13:24:16 -04:00
public BindingSyntax WithProcessor < TProcessor > ( )
{
if ( ! valid )
throw new InvalidOperationException ( "Accessor is not valid" ) ;
var processorName = InputProcessor . s_Processors . FindNameForType ( typeof ( TProcessor ) ) ;
if ( processorName . IsEmpty ( ) )
throw new NotSupportedException ( $"Type '{typeof(TProcessor)}' has not been registered as a processor" ) ;
return WithProcessor ( processorName ) ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Specify which action to trigger.
/// </summary>
/// <param name="action">Action to trigger.</param>
/// <returns>The same binding syntax for further configuration.</returns>
/// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception>
/// <exception cref="ArgumentNullException">Provided action is null.</exception>
/// <exception cref="ArgumentException">Provided action is a singleton action (not part of any action maps).</exception>
2023-03-28 13:24:16 -04:00
public BindingSyntax Triggering ( InputAction action )
{
if ( ! valid )
throw new InvalidOperationException ( "Accessor is not valid" ) ;
if ( action = = null )
throw new ArgumentNullException ( nameof ( action ) ) ;
if ( action . isSingletonAction )
throw new ArgumentException (
$"Cannot change the action a binding triggers on singleton action '{action}'" , nameof ( action ) ) ;
m_ActionMap . m_Bindings [ m_BindingIndexInMap ] . action = action . name ;
m_ActionMap . OnBindingModified ( ) ;
return this ;
}
/// <summary>
/// Replace the current binding with the given one.
/// </summary>
/// <param name="binding">An input binding.</param>
/// <returns>The same binding syntax for further configuration.</returns>
/// <remarks>
/// This method replaces the current binding wholesale, i.e. it will overwrite all fields.
/// Be aware that this has the potential of corrupting the binding data in case the given
/// binding is a composite.
/// </remarks>
public BindingSyntax To ( InputBinding binding )
{
if ( ! valid )
throw new InvalidOperationException ( "Accessor is not valid" ) ;
m_ActionMap . m_Bindings [ m_BindingIndexInMap ] = binding ;
// If it's a singleton action, we force the binding to stay with the action.
if ( m_ActionMap . m_SingletonAction ! = null )
m_ActionMap . m_Bindings [ m_BindingIndexInMap ] . action = m_ActionMap . m_SingletonAction . name ;
m_ActionMap . OnBindingModified ( ) ;
return this ;
}
/// <summary>
/// Switch to configuring the next binding.
/// </summary>
/// <returns>An instance configured to edit the next binding or an invalid (see <see cref="valid"/>) instance if
/// there is no next binding.</returns>
/// <remarks>If the BindingSyntax is restricted to a single action, the result will be invalid (see <see cref="valid"/>)
/// if there is no next binding on the action. If the BindingSyntax is restricted to an <see cref="InputActionMap"/>, the result will
/// be be invalid if there is no next binding in the map.</remarks>
public BindingSyntax NextBinding ( )
{
return Iterate ( true ) ;
}
/// <summary>
/// Switch to configuring the previous binding.
/// </summary>
/// <returns>An instance configured to edit the previous binding or an invalid (see <see cref="valid"/>) instance if
/// there is no previous binding.</returns>
/// <remarks>If the BindingSyntax is restricted to a single action, the result will be invalid (see <see cref="valid"/>)
/// if there is no previous binding on the action. If the BindingSyntax is restricted to an <see cref="InputActionMap"/>, the result will
/// be be invalid if there is no previous binding in the map.</remarks>
public BindingSyntax PreviousBinding ( )
{
return Iterate ( false ) ;
}
/// <summary>
/// Iterate to the next part binding of the current composite with the given part name.
/// </summary>
/// <param name="partName">Name of the part of the binding, such as <c>"Positive"</c>.</param>
/// <returns>An accessor to the next part binding with the given name or an invalid (see <see cref="valid"/>)
/// accessor if there is no such binding.</returns>
/// <exception cref="ArgumentNullException"><paramref name="partName"/> is <c>null</c> or empty.</exception>
/// <remarks>
/// Each binding that is part of a composite is marked with <see cref="InputBinding.isPartOfComposite"/>
/// set to true. The name of the part is determined by <see cref="InputBinding.name"/> (comparison is
/// case-insensitive). Which parts are relevant to a specific composite is determined by the type of
/// composite. An <see cref="Composites.AxisComposite"/>, for example, has <c>"Negative"</c> and a
/// <c>"Positive"</c> part.
///
/// <example>
/// <code>
/// // Delete first "Positive" part of "Axis" composite.
/// action.ChangeCompositeBinding("Axis")
/// .NextPartBinding("Positive").Erase();
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="InputBinding.isPartOfComposite"/>
/// <seealso cref="InputBinding.isComposite"/>
/// <seealso cref="InputBindingComposite"/>
public BindingSyntax NextPartBinding ( string partName )
{
if ( string . IsNullOrEmpty ( partName ) )
throw new ArgumentNullException ( nameof ( partName ) ) ;
return IteratePartBinding ( true , partName ) ;
}
/// <summary>
/// Iterate to the previous part binding of the current composite with the given part name.
/// </summary>
/// <param name="partName">Name of the part of the binding, such as <c>"Positive"</c>.</param>
/// <returns>An accessor to the previous part binding with the given name or an invalid (see <see cref="valid"/>)
/// accessor if there is no such binding.</returns>
/// <exception cref="ArgumentNullException"><paramref name="partName"/> is <c>null</c> or empty.</exception>
/// <remarks>
/// Each binding that is part of a composite is marked with <see cref="InputBinding.isPartOfComposite"/>
/// set to true. The name of the part is determined by <see cref="InputBinding.name"/> (comparison is
/// case-insensitive). Which parts are relevant to a specific composite is determined by the type of
/// composite. An <see cref="Composites.AxisComposite"/>, for example, has <c>"Negative"</c> and a
/// <c>"Positive"</c> part.
/// </remarks>
/// <seealso cref="InputBinding.isPartOfComposite"/>
/// <seealso cref="InputBinding.isComposite"/>
/// <seealso cref="InputBindingComposite"/>
public BindingSyntax PreviousPartBinding ( string partName )
{
if ( string . IsNullOrEmpty ( partName ) )
throw new ArgumentNullException ( nameof ( partName ) ) ;
return IteratePartBinding ( false , partName ) ;
}
/// <summary>
/// Iterate to the next composite binding.
/// </summary>
/// <param name="compositeName">If <c>null</c> (default), an accessor to the next composite binding,
/// regardless of name or type, is returned. If it is not <c>null</c>, can be either the name of
/// the binding (see <see cref="InputBinding.name"/>) or the name of the composite used in the
/// binding (see <see cref="InputSystem.RegisterBindingComposite"/></param>).
/// <returns>A write accessor to the next composite binding or an invalid accessor (see
/// <see cref="valid"/>) if no such binding was found.</returns>
public BindingSyntax NextCompositeBinding ( string compositeName = null )
{
return IterateCompositeBinding ( true , compositeName ) ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Iterate to the previous composite binding.
/// </summary>
/// <param name="compositeName">If <c>null</c> (default), an accessor to the previous composite binding,
/// regardless of name or type, is returned. If it is not <c>null</c>, can be either the name of
/// the binding (see <see cref="InputBinding.name"/>) or the name of the composite used in the
/// binding (see <see cref="InputSystem.RegisterBindingComposite"/>).</param>
/// <returns>A write accessor to the previous composite binding or an invalid accessor (see
/// <see cref="valid"/>) if no such binding was found.</returns>
2023-03-28 13:24:16 -04:00
public BindingSyntax PreviousCompositeBinding ( string compositeName = null )
{
return IterateCompositeBinding ( false , compositeName ) ;
}
private BindingSyntax Iterate ( bool next )
{
if ( m_ActionMap = = null )
return default ;
var bindings = m_ActionMap . m_Bindings ;
if ( bindings = = null )
return default ;
// To find the next binding for a specific action, we may have to jump
// over unrelated bindings in-between.
var index = m_BindingIndexInMap ;
while ( true )
{
index + = next ? 1 : - 1 ;
if ( index < 0 | | index > = bindings . Length )
return default ;
if ( m_Action = = null | | bindings [ index ] . TriggersAction ( m_Action ) )
break ;
}
return new BindingSyntax ( m_ActionMap , index , m_Action ) ;
}
private BindingSyntax IterateCompositeBinding ( bool next , string compositeName )
{
for ( var accessor = Iterate ( next ) ; accessor . valid ; accessor = accessor . Iterate ( next ) )
{
if ( ! accessor . binding . isComposite )
continue ;
if ( compositeName = = null )
return accessor ;
// Try name of binding.
if ( compositeName . Equals ( accessor . binding . name , StringComparison . InvariantCultureIgnoreCase ) )
return accessor ;
// Try composite type name.
var name = NameAndParameters . ParseName ( accessor . binding . path ) ;
if ( compositeName . Equals ( name , StringComparison . InvariantCultureIgnoreCase ) )
return accessor ;
}
return default ;
}
private BindingSyntax IteratePartBinding ( bool next , string partName )
{
if ( ! valid )
return default ;
if ( binding . isComposite )
{
// If we're at the composite, only proceed if we're iterating down
// instead of up.
if ( ! next )
return default ;
}
else if ( ! binding . isPartOfComposite )
return default ;
for ( var accessor = Iterate ( next ) ; accessor . valid ; accessor = accessor . Iterate ( next ) )
{
if ( ! accessor . binding . isPartOfComposite )
return default ;
if ( partName . Equals ( accessor . binding . name , StringComparison . InvariantCultureIgnoreCase ) )
return accessor ;
}
return default ;
}
////TODO: allow setting overrides through this accessor
/// <summary>
/// Remove the binding.
/// </summary>
/// <remarks>
/// If the binding is a composite (see <see cref="InputBinding.isComposite"/>), part bindings of the
/// composite will be removed as well.
///
/// Note that the accessor will not necessarily be invalidated. Instead, it will point to what used
/// to be the next binding in the array (though that means the accessor will be invalid if the binding
/// that got erased was the last one in the array).
/// </remarks>
/// <exception cref="InvalidOperationException">The instance is not <see cref="valid"/>.</exception>
public void Erase ( )
{
if ( ! valid )
throw new InvalidOperationException ( "Instance not valid" ) ;
var isComposite = m_ActionMap . m_Bindings [ m_BindingIndexInMap ] . isComposite ;
ArrayHelpers . EraseAt ( ref m_ActionMap . m_Bindings , m_BindingIndexInMap ) ;
// If it's a composite, also erase part bindings.
if ( isComposite )
{
while ( m_BindingIndexInMap < m_ActionMap . m_Bindings . LengthSafe ( ) & & m_ActionMap . m_Bindings [ m_BindingIndexInMap ] . isPartOfComposite )
2023-05-07 18:43:11 -04:00
{
2023-03-28 13:24:16 -04:00
ArrayHelpers . EraseAt ( ref m_ActionMap . m_Bindings , m_BindingIndexInMap ) ;
2023-05-07 18:43:11 -04:00
}
2023-03-28 13:24:16 -04:00
}
2023-05-07 18:43:11 -04:00
m_Action . m_BindingsCount = m_ActionMap . m_Bindings . LengthSafe ( ) ;
2023-03-28 13:24:16 -04:00
m_ActionMap . OnBindingModified ( ) ;
// We have switched to a different binding array. For singleton actions, we need to
// sync up the reference that the action itself has.
if ( m_ActionMap . m_SingletonAction ! = null )
m_ActionMap . m_SingletonAction . m_SingletonActionBindings = m_ActionMap . m_Bindings ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Insert a composite part into a composite binding.
/// </summary>
/// <param name="partName">Name of the part in composite binding.</param>
/// <param name="path">Control path to bind to.</param>
/// <returns>The same binding syntax for further configuration.</returns>
/// <exception cref="ArgumentNullException">Part name is null or empty.</exception>
/// <exception cref="InvalidOperationException">The binding accessor is invalid or the binding accessor is not pointing to composite or part binding.</exception>
2023-03-28 13:24:16 -04:00
public BindingSyntax InsertPartBinding ( string partName , string path )
{
if ( string . IsNullOrEmpty ( partName ) )
throw new ArgumentNullException ( nameof ( partName ) ) ;
if ( ! valid )
throw new InvalidOperationException ( "Binding accessor is not valid" ) ;
var binding = this . binding ;
if ( ! binding . isPartOfComposite & & ! binding . isComposite )
throw new InvalidOperationException ( "Binding accessor must point to composite or part binding" ) ;
AddBindingInternal ( m_ActionMap ,
new InputBinding { path = path , isPartOfComposite = true , name = partName } ,
m_BindingIndexInMap + 1 ) ;
return new BindingSyntax ( m_ActionMap , m_BindingIndexInMap + 1 , m_Action ) ;
}
}
////TODO: remove this and merge it into BindingSyntax
2023-05-07 18:43:11 -04:00
/// <summary>
/// Write accessor to a composite binding.
/// </summary>
/// <remarks>
/// To add a composite binding to an action, you must first call <see cref="InputActionSetupExtensions.AddCompositeBinding"/>
/// and then use the CompositeSyntax struct to add composite parts.
/// <example>
/// <code>
/// playerInput.actions["fire"]
/// .ChangeBinding(0)
/// .AddCompositeBinding("2DVector")
/// .With("Up", "<Keyboard>/w")
/// .With("Down", "<Keyboard>/s")
/// .With("Left", "<Keyboard>/a")
/// .With("Right", "<Keyboard>/d");
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="AddBinding(InputAction,InputBinding)"/>
/// <seealso cref="ChangeBinding(InputAction,int)"/>
2023-03-28 13:24:16 -04:00
public struct CompositeSyntax
{
private readonly InputAction m_Action ;
private readonly InputActionMap m_ActionMap ;
private int m_BindingIndexInMap ;
/// <summary>
/// Index of the binding that the accessor refers to.
/// </summary>
/// <remarks>
/// When accessing bindings on an <see cref="InputAction"/>, this is the index in
/// <see cref="InputAction.bindings"/> of the action. When accessing bindings on an
/// <see cref="InputActionMap"/>, it is the index <see cref="InputActionMap.bindings"/>
/// of the map.
/// </remarks>
public int bindingIndex
{
get
{
if ( m_ActionMap = = null )
return - 1 ;
if ( m_Action ! = null )
return m_Action . BindingIndexOnMapToBindingIndexOnAction ( m_BindingIndexInMap ) ;
return m_BindingIndexInMap ;
}
}
internal CompositeSyntax ( InputActionMap map , InputAction action , int compositeIndex )
{
m_Action = action ;
m_ActionMap = map ;
m_BindingIndexInMap = compositeIndex ;
}
/// <summary>
/// Add a part binding to the composite.
/// </summary>
/// <param name="name">Name of the part. This is dependent on the type of composite. For
/// <see cref="Composites.Vector2Composite"/>, for example, the valid parts are <c>"Up"</c>, <c>"Down"</c>,
/// <c>"Left"</c>, and <c>"Right"</c>.</param>
/// <param name="binding">Control path to binding to. See <see cref="InputBinding.path"/>.</param>
/// <param name="groups">Binding groups to assign to the part binding. See <see cref="InputBinding.groups"/>.</param>
/// <param name="processors">Optional list of processors to apply to the binding. See <see cref="InputBinding.processors"/>.</param>
/// <returns>The same composite syntax for further configuration.</returns>
public CompositeSyntax With ( string name , string binding , string groups = null , string processors = null )
{
////TODO: check whether non-composite bindings have been added in-between
using ( InputActionRebindingExtensions . DeferBindingResolution ( ) )
{
int bindingIndex ;
if ( m_Action ! = null )
bindingIndex = m_Action . AddBinding ( path : binding , groups : groups , processors : processors )
. m_BindingIndexInMap ;
else
bindingIndex = m_ActionMap . AddBinding ( path : binding , groups : groups , processors : processors )
. m_BindingIndexInMap ;
m_ActionMap . m_Bindings [ bindingIndex ] . name = name ;
m_ActionMap . m_Bindings [ bindingIndex ] . isPartOfComposite = true ;
}
return this ;
}
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Write accessor to a control scheme.
/// </summary>
2023-03-28 13:24:16 -04:00
public struct ControlSchemeSyntax
{
2023-05-07 18:43:11 -04:00
// REVIEW: Consider removing this for major revision, e.g. v2 or let functionality used by it be internal to reduce API surface.
2023-03-28 13:24:16 -04:00
private readonly InputActionAsset m_Asset ;
private readonly int m_ControlSchemeIndex ;
private InputControlScheme m_ControlScheme ;
internal ControlSchemeSyntax ( InputActionAsset asset , int index )
{
m_Asset = asset ;
m_ControlSchemeIndex = index ;
m_ControlScheme = new InputControlScheme ( ) ;
}
internal ControlSchemeSyntax ( InputControlScheme controlScheme )
{
m_Asset = null ;
m_ControlSchemeIndex = - 1 ;
m_ControlScheme = controlScheme ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Sets or overwrite the binding group for control scheme.
/// </summary>
/// <param name="bindingGroup">A binding group. See <see cref="InputBinding.groups"/>.</param>
/// <returns>The same control scheme syntax for further configuration.</returns>
/// <exception cref="ArgumentNullException">If provided name is null or empty.</exception>
2023-03-28 13:24:16 -04:00
public ControlSchemeSyntax WithBindingGroup ( string bindingGroup )
{
if ( string . IsNullOrEmpty ( bindingGroup ) )
throw new ArgumentNullException ( nameof ( bindingGroup ) ) ;
if ( m_Asset = = null )
m_ControlScheme . m_BindingGroup = bindingGroup ;
else
m_Asset . m_ControlSchemes [ m_ControlSchemeIndex ] . bindingGroup = bindingGroup ;
return this ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Adds a required device to control scheme.
/// </summary>
/// <typeparam name="TDevice">Type of device.</typeparam>
/// <returns>The same control scheme syntax for further configuration.</returns>
2023-03-28 13:24:16 -04:00
public ControlSchemeSyntax WithRequiredDevice < TDevice > ( )
where TDevice : InputDevice
{
return WithRequiredDevice ( DeviceTypeToControlPath < TDevice > ( ) ) ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Adds an optional device to control scheme.
/// </summary>
/// <typeparam name="TDevice">Type of device.</typeparam>
/// <returns>The same control scheme syntax for further configuration.</returns>
/// <see cref="InputControlScheme.DeviceRequirement.isOptional"/>
2023-03-28 13:24:16 -04:00
public ControlSchemeSyntax WithOptionalDevice < TDevice > ( )
where TDevice : InputDevice
{
return WithOptionalDevice ( DeviceTypeToControlPath < TDevice > ( ) ) ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Combines another required device with the previous device using boolean OR
/// logic such that either the previous device or this device are required to be present.
/// </summary>
/// <typeparam name="TDevice">Type of device.</typeparam>
/// <returns>The same control scheme syntax for further configuration.</returns>
/// <remarks>
/// <example>
/// <code>
/// // Create an .inputactions asset.
/// var asset = ScriptableObject.CreateInstance<InputActionAsset>();
///
/// asset.AddControlScheme("KeyboardAndMouseOrPen")
/// .WithRequiredDevice("<Keyboard>")
/// .WithRequiredDevice("<Mouse>")
/// .OrWithRequiredDevice("<Pen>")
/// </code>
/// </example>
/// </remarks>
/// <see cref="InputControlScheme.DeviceRequirement.isOR"/>
2023-03-28 13:24:16 -04:00
public ControlSchemeSyntax OrWithRequiredDevice < TDevice > ( )
where TDevice : InputDevice
{
2023-05-07 18:43:11 -04:00
return OrWithRequiredDevice ( DeviceTypeToControlPath < TDevice > ( ) ) ;
2023-03-28 13:24:16 -04:00
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Combines another optional device with the previous device using boolean OR
/// logic such that either the previous device or this device are required to be present.
/// If this is the last device in a chain of OR'd devices, the entire chain of
/// devices becomes optional.
/// </summary>
/// <typeparam name="TDevice">Type of device.</typeparam>
/// <returns>The same control scheme syntax for further configuration.</returns>
/// <see cref="InputControlScheme.DeviceRequirement.isOR"/>
2023-03-28 13:24:16 -04:00
public ControlSchemeSyntax OrWithOptionalDevice < TDevice > ( )
where TDevice : InputDevice
{
2023-05-07 18:43:11 -04:00
return OrWithOptionalDevice ( DeviceTypeToControlPath < TDevice > ( ) ) ;
2023-03-28 13:24:16 -04:00
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Adds a required device to control scheme.
/// </summary>
/// <param name="controlPath">Device path, like <Gamepad>.</param>
/// <returns>The same control scheme syntax for further configuration.</returns>
2023-03-28 13:24:16 -04:00
public ControlSchemeSyntax WithRequiredDevice ( string controlPath )
{
AddDeviceEntry ( controlPath , InputControlScheme . DeviceRequirement . Flags . None ) ;
return this ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Add an optional device to the control scheme.
/// </summary>
/// <param name="controlPath">The device path, like <Gamepad>.</param>
/// <returns>The same control scheme syntax for further configuration.</returns>
2023-03-28 13:24:16 -04:00
public ControlSchemeSyntax WithOptionalDevice ( string controlPath )
{
AddDeviceEntry ( controlPath , InputControlScheme . DeviceRequirement . Flags . Optional ) ;
return this ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Adds another possible required device to the control scheme.
/// </summary>
/// <param name="controlPath">Device path, like <Gamepad>.</param>
/// <returns>The same control scheme syntax for further configuration.</returns>
2023-03-28 13:24:16 -04:00
public ControlSchemeSyntax OrWithRequiredDevice ( string controlPath )
{
AddDeviceEntry ( controlPath , InputControlScheme . DeviceRequirement . Flags . Or ) ;
return this ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Adds another possible optional device to control scheme.
/// </summary>
/// <param name="controlPath">Device path, like <Gamepad>.</param>
/// <returns>The same control scheme syntax for further configuration.</returns>
2023-03-28 13:24:16 -04:00
public ControlSchemeSyntax OrWithOptionalDevice ( string controlPath )
{
AddDeviceEntry ( controlPath ,
InputControlScheme . DeviceRequirement . Flags . Optional |
InputControlScheme . DeviceRequirement . Flags . Or ) ;
return this ;
}
private string DeviceTypeToControlPath < TDevice > ( )
where TDevice : InputDevice
{
var layoutName = InputControlLayout . s_Layouts . TryFindLayoutForType ( typeof ( TDevice ) ) . ToString ( ) ;
if ( string . IsNullOrEmpty ( layoutName ) )
layoutName = typeof ( TDevice ) . Name ;
return $"<{layoutName}>" ;
}
2023-05-07 18:43:11 -04:00
/// <summary>
/// Call this method when done building out the control scheme to return the associated instance.
/// </summary>
/// <returns>The associated control scheme</returns>
2023-03-28 13:24:16 -04:00
public InputControlScheme Done ( )
{
if ( m_Asset ! = null )
return m_Asset . m_ControlSchemes [ m_ControlSchemeIndex ] ;
return m_ControlScheme ;
}
private void AddDeviceEntry ( string controlPath , InputControlScheme . DeviceRequirement . Flags flags )
{
if ( string . IsNullOrEmpty ( controlPath ) )
throw new ArgumentNullException ( nameof ( controlPath ) ) ;
var scheme = m_Asset ! = null ? m_Asset . m_ControlSchemes [ m_ControlSchemeIndex ] : m_ControlScheme ;
ArrayHelpers . Append ( ref scheme . m_DeviceRequirements ,
new InputControlScheme . DeviceRequirement
{
m_ControlPath = controlPath ,
m_Flags = flags ,
} ) ;
if ( m_Asset = = null )
m_ControlScheme = scheme ;
else
m_Asset . m_ControlSchemes [ m_ControlSchemeIndex ] = scheme ;
}
}
}
}