@@ -15,6 +15,29 @@ namespace UnityEngine.InputSystem
1515 /// </summary>
1616 public static class InputControlExtensions
1717 {
18+ /// <summary>
19+ /// Find a control of the given type in control hierarchy of <paramref name="control"/>.
20+ /// </summary>
21+ /// <param name="control">Control whose parents to inspect.</param>
22+ /// <typeparam name="TControl">Type of control to look for. Actual control type can be
23+ /// subtype of this.</typeparam>
24+ /// <remarks>The found control of type <typeparamref name="TControl"/> which may be either
25+ /// <paramref name="control"/> itself or one of its parents. If no such control was found,
26+ /// returns <c>null</c>.</remarks>
27+ /// <exception cref="ArgumentNullException"><paramref name="control"/> is <c>null</c>.</exception>
28+ public static TControl FindInParentChain < TControl > ( this InputControl control )
29+ where TControl : InputControl
30+ {
31+ if ( control == null )
32+ throw new ArgumentNullException ( nameof ( control ) ) ;
33+
34+ for ( var parent = control ; parent != null ; parent = parent . parent )
35+ if ( parent is TControl parentOfType )
36+ return parentOfType ;
37+
38+ return null ;
39+ }
40+
1841 /// <summary>
1942 /// Return true if the given control is actuated.
2043 /// </summary>
@@ -113,16 +136,38 @@ public static unsafe object ReadDefaultValueAsObject(this InputControl control)
113136 }
114137
115138 /// <summary>
116- /// Read the value of the given control from an event.
139+ /// Read the value for the given control from the given event.
117140 /// </summary>
118- /// <param name="control"></param>
119- /// <param name="inputEvent">Input event. This must be a <see cref="StateEvent"/> or <seealso cref="DeltaStateEvent"/>.
141+ /// <param name="control">Control to read value for.</param>
142+ /// <param name="inputEvent">Event to read value from. Must be a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.</param>
143+ /// <typeparam name="TValue">Type of value to read.</typeparam>
144+ /// <exception cref="ArgumentNullException"><paramref name="control"/> is <c>null</c>.</exception>
145+ /// <exception cref="ArgumentException"><paramref name="inputEvent"/> is not a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.</exception>
146+ /// <returns>The value for the given control as read out from the given event or <c>default(TValue)</c> if the given
147+ /// event does not contain a value for the control (e.g. if it is a <see cref="DeltaStateEvent"/> not containing the relevant
148+ /// portion of the device's state memory).</returns>
149+ public static TValue ReadValueFromEvent < TValue > ( this InputControl < TValue > control , InputEventPtr inputEvent )
150+ where TValue : struct
151+ {
152+ if ( control == null )
153+ throw new ArgumentNullException ( nameof ( control ) ) ;
154+ if ( ! ReadValueFromEvent ( control , inputEvent , out var value ) )
155+ return default ;
156+ return value ;
157+ }
158+
159+ /// <summary>
160+ /// Check if the given event contains a value for the given control and if so, read the value.
161+ /// </summary>
162+ /// <param name="control">Control to read value for.</param>
163+ /// <param name="inputEvent">Input event. This must be a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.
120164 /// Note that in the case of a <see cref="DeltaStateEvent"/>, the control may not actually be part of the event. In this
121165 /// case, the method returns false and stores <c>default(TValue)</c> in <paramref name="value"/>.</param>
122166 /// <param name="value">Variable that receives the control value.</param>
123- /// <typeparam name="TValue"></typeparam>
167+ /// <typeparam name="TValue">Type of value to read. </typeparam>
124168 /// <returns>True if the value has been successfully read from the event, false otherwise.</returns>
125- /// <exception cref="ArgumentNullException"><paramref name="control"/> is null.</exception>
169+ /// <exception cref="ArgumentNullException"><paramref name="control"/> is <c>null</c>.</exception>
170+ /// <exception cref="ArgumentException"><paramref name="inputEvent"/> is not a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.</exception>
126171 /// <seealso cref="ReadUnprocessedValueFromEvent{TValue}(InputControl{TValue},InputEventPtr)"/>
127172 [ System . Diagnostics . CodeAnalysis . SuppressMessage ( "Microsoft.Design" , "CA1021:AvoidOutParameters" , MessageId = "2#" ) ]
128173 public static unsafe bool ReadValueFromEvent < TValue > ( this InputControl < TValue > control , InputEventPtr inputEvent , out TValue value )
@@ -457,6 +502,26 @@ public static unsafe bool HasValueChangeInEvent(this InputControl control, Input
457502 return control . CompareValue ( control . currentStatePtr , control . GetStatePtrFromStateEvent ( eventPtr ) ) ;
458503 }
459504
505+ /// <summary>
506+ /// Given a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>, return the raw memory pointer that can
507+ /// be used, for example, with <see cref="InputControl{T}.ReadValueFromState"/> to read out the value of <paramref name="control"/>
508+ /// contained in the event.
509+ /// </summary>
510+ /// <param name="control">Control to access state for in the given state event.</param>
511+ /// <param name="eventPtr">A <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/> containing input state.</param>
512+ /// <returns>A pointer that can be used with methods such as <see cref="InputControl{TValue}.ReadValueFromState"/> or <c>null</c>
513+ /// if <paramref name="eventPtr"/> does not contain state for the given <paramref name="control"/>.</returns>
514+ /// <exception cref="ArgumentNullException"><paramref name="control"/> is <c>null</c> -or- <paramref name="eventPtr"/> is invalid.</exception>
515+ /// <exception cref="ArgumentException"><paramref name="eventPtr"/> is not a <see cref="StateEvent"/> or <see cref="DeltaStateEvent"/>.</exception>
516+ /// <remarks>
517+ /// Note that the given state event must have the same state format (see <see cref="InputStateBlock.format"/>) as the device
518+ /// to which <paramref name="control"/> belongs. If this is not the case, the method will invariably return <c>null</c>.
519+ ///
520+ /// In practice, this means that the method cannot be used with touch events or, in general, with events sent to devices
521+ /// that implement <see cref="IInputStateCallbackReceiver"/> (which <see cref="Touchscreen"/> does) except if the event
522+ /// is in the same state format as the device. Touch events will generally be sent as state events containing only the
523+ /// state of a single <see cref="Controls.TouchControl"/>, not the state of the entire <see cref="Touchscreen"/>.
524+ /// </remarks>
460525 public static unsafe void * GetStatePtrFromStateEvent ( this InputControl control , InputEventPtr eventPtr )
461526 {
462527 if ( control == null )
@@ -489,7 +554,7 @@ public static unsafe bool HasValueChangeInEvent(this InputControl control, Input
489554 }
490555 else
491556 {
492- throw new ArgumentException ( "Event must be a state or delta state event" , " eventPtr" ) ;
557+ throw new ArgumentException ( "Event must be a state or delta state event" , nameof ( eventPtr ) ) ;
493558 }
494559
495560 // Make sure we have a state event compatible with our device. The event doesn't
@@ -498,11 +563,18 @@ public static unsafe bool HasValueChangeInEvent(this InputControl control, Input
498563 // to read.
499564 var device = control . device ;
500565 if ( stateFormat != device . m_StateBlock . format )
501- throw new InvalidOperationException (
502- $ "Cannot read control '{ control . path } ' from { eventPtr . type } with format { stateFormat } ; device '{ device } ' expects format { device . m_StateBlock . format } ") ;
566+ {
567+ // If the device is an IInputStateCallbackReceiver, there's a chance it actually recognizes
568+ // the state format in the event and can correlate it to the state as found on the device.
569+ if ( ! device . hasStateCallbacks ||
570+ ! ( ( IInputStateCallbackReceiver ) device ) . GetStateOffsetForEvent ( control , eventPtr , ref stateOffset ) )
571+ return null ;
572+ }
503573
504574 // Once a device has been added, global state buffer offsets are baked into control hierarchies.
505575 // We need to unsubtract those offsets here.
576+ // NOTE: If the given device has not actually been added to the system, the offset is simply 0
577+ // and this is a harmless NOP.
506578 stateOffset += device . m_StateBlock . byteOffset ;
507579
508580 // Return null if state is out of range.
0 commit comments