@@ -15,6 +15,29 @@ namespace UnityEngine.InputSystem
15
15
/// </summary>
16
16
public static class InputControlExtensions
17
17
{
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
+
18
41
/// <summary>
19
42
/// Return true if the given control is actuated.
20
43
/// </summary>
@@ -113,16 +136,38 @@ public static unsafe object ReadDefaultValueAsObject(this InputControl control)
113
136
}
114
137
115
138
/// <summary>
116
- /// Read the value of the given control from an event.
139
+ /// Read the value for the given control from the given event.
117
140
/// </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"/>.
120
164
/// Note that in the case of a <see cref="DeltaStateEvent"/>, the control may not actually be part of the event. In this
121
165
/// case, the method returns false and stores <c>default(TValue)</c> in <paramref name="value"/>.</param>
122
166
/// <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>
124
168
/// <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>
126
171
/// <seealso cref="ReadUnprocessedValueFromEvent{TValue}(InputControl{TValue},InputEventPtr)"/>
127
172
[ System . Diagnostics . CodeAnalysis . SuppressMessage ( "Microsoft.Design" , "CA1021:AvoidOutParameters" , MessageId = "2#" ) ]
128
173
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
457
502
return control . CompareValue ( control . currentStatePtr , control . GetStatePtrFromStateEvent ( eventPtr ) ) ;
458
503
}
459
504
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>
460
525
public static unsafe void * GetStatePtrFromStateEvent ( this InputControl control , InputEventPtr eventPtr )
461
526
{
462
527
if ( control == null )
@@ -489,7 +554,7 @@ public static unsafe bool HasValueChangeInEvent(this InputControl control, Input
489
554
}
490
555
else
491
556
{
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 ) ) ;
493
558
}
494
559
495
560
// 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
498
563
// to read.
499
564
var device = control . device ;
500
565
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
+ }
503
573
504
574
// Once a device has been added, global state buffer offsets are baked into control hierarchies.
505
575
// 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.
506
578
stateOffset += device . m_StateBlock . byteOffset ;
507
579
508
580
// Return null if state is out of range.
0 commit comments