1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 @file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
18 
19 package androidx.compose.ui.input.pointer
20 
21 import androidx.compose.runtime.Immutable
22 import androidx.compose.ui.ExperimentalComposeUiApi
23 import androidx.compose.ui.Modifier
24 import androidx.compose.ui.geometry.Offset
25 import androidx.compose.ui.geometry.Size
26 import androidx.compose.ui.input.pointer.PointerEventPass.Final
27 import androidx.compose.ui.input.pointer.PointerEventPass.Initial
28 import androidx.compose.ui.input.pointer.PointerEventPass.Main
29 import androidx.compose.ui.internal.JvmDefaultWithCompatibility
30 import androidx.compose.ui.layout.LayoutCoordinates
31 import androidx.compose.ui.unit.IntSize
32 
33 /** A [Modifier.Element] that can interact with pointer input. */
34 @JvmDefaultWithCompatibility
35 interface PointerInputModifier : Modifier.Element {
36     val pointerInputFilter: PointerInputFilter
37 }
38 
39 /**
40  * A PointerInputFilter represents a single entity that receives [PointerInputChange]s), interprets
41  * them, and consumes the aspects of the changes that it is react to such that other
42  * PointerInputFilters don't also react to them.
43  */
44 abstract class PointerInputFilter {
45 
46     /**
47      * Invoked when pointers that previously hit this [PointerInputFilter] have changed. It is
48      * expected that any [PointerInputChange]s that are used during this event and should not be
49      * considered valid to be used in other filters should be marked as consumed by calling
50      * [PointerInputChange.consume].
51      *
52      * @param pointerEvent The list of [PointerInputChange]s with positions relative to this
53      *   [PointerInputFilter].
54      * @param pass The [PointerEventPass] in which this function is being called.
55      * @param bounds The width and height associated with this [PointerInputFilter].
56      * @see PointerInputChange
57      * @see PointerEventPass
58      */
onPointerEventnull59     abstract fun onPointerEvent(pointerEvent: PointerEvent, pass: PointerEventPass, bounds: IntSize)
60 
61     /**
62      * Invoked to notify the handler that no more calls to [PointerInputFilter] will be made, until
63      * at least new pointers exist. This can occur for a few reasons:
64      * 1. Android dispatches ACTION_CANCEL to Compose.
65      * 2. This [PointerInputFilter] is no longer associated with a LayoutNode.
66      * 3. This [PointerInputFilter]'s associated LayoutNode is no longer in the composition tree.
67      */
68     abstract fun onCancel()
69 
70     internal var layoutCoordinates: LayoutCoordinates? = null
71 
72     /** The layout size assigned to this [PointerInputFilter]. */
73     val size: IntSize
74         get() = layoutCoordinates?.size ?: IntSize.Zero
75 
76     internal var isAttached: Boolean = false
77 
78     /**
79      * Intercept pointer input that children receive even if the pointer is out of bounds.
80      *
81      * If `true`, and a child has been moved out of this layout and receives an event, this will
82      * receive that event. If `false`, a child receiving pointer input outside of the bounds of this
83      * layout will not trigger any events in this.
84      */
85     open val interceptOutOfBoundsChildEvents: Boolean
86         get() = false
87 
88     /**
89      * If `false`, then this [PointerInputFilter] will not allow siblings under it to respond to
90      * events. If `true`, this will have the first chance to respond and the next sibling under will
91      * then get a chance to respond as well. This trigger acts at the Layout level, so if any
92      * [PointerInputFilter]s on a Layout has [shareWithSiblings] set to `true` then the Layout will
93      * share with siblings.
94      */
95     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
96     open val shareWithSiblings: Boolean
97         get() = false
98 }
99 
100 /** Describes a pointer input change event that has occurred at a particular point in time. */
101 expect class PointerEvent
102 internal constructor(
103     changes: List<PointerInputChange>,
104     internalPointerEvent: InternalPointerEvent?
105 ) {
106     /** @param changes The changes. */
107     constructor(changes: List<PointerInputChange>)
108 
109     /** The changes. */
110     val changes: List<PointerInputChange>
111 
112     /** The state of buttons (e.g. mouse or stylus buttons) during this event. */
113     val buttons: PointerButtons
114 
115     /** The state of modifier keys during this event. */
116     val keyboardModifiers: PointerKeyboardModifiers
117 
118     /** The primary reason the [PointerEvent] was sent. */
119     var type: PointerEventType
120         internal set
121 }
122 
123 // TODO mark internal once https://youtrack.jetbrains.com/issue/KT-36695 is fixed
124 /* internal */ expect class NativePointerButtons
125 
126 /** Contains the state of pointer buttons (e.g. mouse and stylus buttons). */
127 @kotlin.jvm.JvmInline value class PointerButtons(internal val packedValue: NativePointerButtons)
128 
129 /**
130  * `true` when the primary button (left mouse button) is pressed or `false` when it isn't pressed.
131  */
132 expect val PointerButtons.isPrimaryPressed: Boolean
133 
134 /**
135  * `true` when the secondary button (right mouse button) is pressed or `false` when it isn't
136  * pressed.
137  */
138 expect val PointerButtons.isSecondaryPressed: Boolean
139 
140 /**
141  * `true` when the tertiary button (middle mouse button) is pressed or `false` when it isn't
142  * pressed.
143  */
144 expect val PointerButtons.isTertiaryPressed: Boolean
145 
146 /**
147  * `true` when the back button (mouse back button) is pressed or `false` when it isn't pressed or
148  * there is no mouse button assigned to "back."
149  */
150 expect val PointerButtons.isBackPressed: Boolean
151 
152 /**
153  * `true` when the forward button (mouse forward button) is pressed or `false` when it isn't pressed
154  * or there is no button assigned to "forward."
155  */
156 expect val PointerButtons.isForwardPressed: Boolean
157 
158 /**
159  * Returns `true` when the button at [buttonIndex] is pressed and `false` when it isn't pressed.
160  * This method can handle buttons that haven't been assigned a designated purpose like
161  * [isPrimaryPressed] and [isSecondaryPressed].
162  */
isPressednull163 expect fun PointerButtons.isPressed(buttonIndex: Int): Boolean
164 
165 /** Returns `true` if any button is pressed or `false` if all buttons are released. */
166 expect val PointerButtons.areAnyPressed: Boolean
167 
168 /**
169  * Returns the index of first button pressed as used in [isPressed] or `-1` if no button is pressed.
170  */
171 expect fun PointerButtons.indexOfFirstPressed(): Int
172 
173 /**
174  * Returns the index of last button pressed as used in [isPressed] or `-1` if no button is pressed.
175  */
176 expect fun PointerButtons.indexOfLastPressed(): Int
177 
178 // TODO mark internal once https://youtrack.jetbrains.com/issue/KT-36695 is fixed
179 /* internal */ expect class NativePointerKeyboardModifiers
180 
181 /**
182  * Contains the state of modifier keys, such as Shift, Control, and Alt, as well as the state of the
183  * lock keys, such as Caps Lock and Num Lock.
184  */
185 @kotlin.jvm.JvmInline
186 value class PointerKeyboardModifiers(internal val packedValue: NativePointerKeyboardModifiers)
187 
188 // helps initialize `WindowInfo.keyboardModifiers` with a non-null value
189 internal expect fun EmptyPointerKeyboardModifiers(): PointerKeyboardModifiers
190 
191 /** `true` when the Control key is pressed. */
192 expect val PointerKeyboardModifiers.isCtrlPressed: Boolean
193 
194 /**
195  * `true` when the Meta key is pressed. This is commonly associated with the Windows or Command key
196  * on some keyboards.
197  */
198 expect val PointerKeyboardModifiers.isMetaPressed: Boolean
199 
200 /**
201  * `true` when the Alt key is pressed. This is commonly associated with the Option key on some
202  * keyboards.
203  */
204 expect val PointerKeyboardModifiers.isAltPressed: Boolean
205 
206 /** `true` when the AltGraph key is pressed. */
207 expect val PointerKeyboardModifiers.isAltGraphPressed: Boolean
208 
209 /** `true` when the Sym key is pressed. */
210 expect val PointerKeyboardModifiers.isSymPressed: Boolean
211 
212 /** `true` when the Shift key is pressed. */
213 expect val PointerKeyboardModifiers.isShiftPressed: Boolean
214 
215 /** `true` when the Function key is pressed. */
216 expect val PointerKeyboardModifiers.isFunctionPressed: Boolean
217 
218 /** `true` when the keyboard's Caps Lock is on. */
219 expect val PointerKeyboardModifiers.isCapsLockOn: Boolean
220 
221 /** `true` when the keyboard's Scroll Lock is on. */
222 expect val PointerKeyboardModifiers.isScrollLockOn: Boolean
223 
224 /** `true` when the keyboard's Num Lock is on. */
225 expect val PointerKeyboardModifiers.isNumLockOn: Boolean
226 
227 /** The device type that produces a [PointerInputChange], such as a mouse or stylus. */
228 @kotlin.jvm.JvmInline
229 value class PointerType private constructor(private val value: Int) {
230 
231     override fun toString(): String =
232         when (value) {
233             1 -> "Touch"
234             2 -> "Mouse"
235             3 -> "Stylus"
236             4 -> "Eraser"
237             else -> "Unknown"
238         }
239 
240     companion object {
241         /** An unknown device type or the device type isn't relevant. */
242         val Unknown = PointerType(0)
243 
244         /** Touch (finger) input. */
245         val Touch = PointerType(1)
246 
247         /** A mouse pointer. */
248         val Mouse = PointerType(2)
249 
250         /** A stylus. */
251         val Stylus = PointerType(3)
252 
253         /** An eraser or an inverted stylus. */
254         val Eraser = PointerType(4)
255     }
256 }
257 
258 /** Indicates the primary reason that the [PointerEvent] was sent. */
259 @kotlin.jvm.JvmInline
260 value class PointerEventType private constructor(internal val value: Int) {
261     companion object {
262         /** An unknown reason for the event. */
263         val Unknown = PointerEventType(0)
264 
265         /** A button on the device was pressed or a new pointer was detected. */
266         val Press = PointerEventType(1)
267 
268         /** A button on the device was released or a pointer was raised. */
269         val Release = PointerEventType(2)
270 
271         /** The cursor or one or more touch pointers was moved. */
272         val Move = PointerEventType(3)
273 
274         /**
275          * The cursor has entered the input region. This will only be sent after the cursor is
276          * hovering when in the input region.
277          *
278          * For example, the user's cursor is outside the input region and presses the button prior
279          * to entering the input region. The [Enter] event will be sent when the button is released
280          * inside the input region.
281          */
282         val Enter = PointerEventType(4)
283 
284         /**
285          * A cursor device or elevated stylus exited the input region. This will only follow an
286          * [Enter] event, so if a cursor with the button pressed enters and exits region, neither
287          * [Enter] nor [Exit] will be sent for the input region. However, if a cursor enters the
288          * input region, then a button is pressed, then the cursor exits and reenters, [Enter],
289          * [Exit], and [Enter] will be received.
290          */
291         val Exit = PointerEventType(5)
292 
293         /**
294          * A scroll event was sent. This can happen, for example, due to a mouse scroll wheel. This
295          * event indicates that the [PointerInputChange.scrollDelta]'s [Offset] is non-zero.
296          */
297         val Scroll = PointerEventType(6)
298     }
299 
toStringnull300     override fun toString(): String =
301         when (this) {
302             Press -> "Press"
303             Release -> "Release"
304             Move -> "Move"
305             Enter -> "Enter"
306             Exit -> "Exit"
307             Scroll -> "Scroll"
308             else -> "Unknown"
309         }
310 }
311 
312 /**
313  * Describes a change that has occurred for a particular pointer, as well as how much of the change
314  * has been consumed (meaning, used by a node in the UI).
315  *
316  * The [position] represents the position of the pointer relative to the element that this
317  * [PointerInputChange] is being dispatched to.
318  *
319  * Note 1: A [PointerEvent]'s [PointerEventType] is the cause of an event and the associated
320  * [PointerInputChange]'s properties reflecting that. Most of those are exclusive, in other words, a
321  * Press/Release [PointerEventType] will not cause a scrollDelta change and a Scroll
322  * [PointerEventType] will not cause a pressed or previousPressed change. However, either a a Scroll
323  * or a Press/Release may contain a position change in its [PointerInputChange]s. (You can imagine
324  * one finger moving while another is lifted up.)
325  *
326  * Examples of [PointerEventType] and the associated [PointerInputChange] property changes:
327  * - Press -> press will change (position may change) but scroll delta will not.
328  * - Release -> press will change (position may change) but scroll delta will not.
329  * - Move -> position will change but press and scroll delta will not.
330  * - Scroll -> scroll delta will change (position may change) but press will not.
331  *
332  * Note 2: The [position] values can be outside the actual bounds of the element itself meaning the
333  * numbers can be negative or larger than the element bounds.
334  *
335  * The [previousPosition] represents the position of the pointer offset to the current position of
336  * the pointer relative to the screen.
337  *
338  * This means that [position] and [previousPosition] can always be used to understand how much a
339  * pointer has moved relative to an element, even if that element is moving along with the changes
340  * to the pointer. For example, if a pointer touches a 1x1 pixel box in the middle, [position] will
341  * report a position of (0, 0) when dispatched to it. If the next event moves x position 5 pixels,
342  * [position] will report (5, 0) and [previousPosition] will report (0, 0). If the box moves all 5
343  * pixels, and the next event represents the pointer moving along the x axis for 5 more pixels,
344  * [position] will again report (5, 0) and [previousPosition] will report (0, 0).
345  *
346  * @param id The unique id of the pointer associated with this [PointerInputChange].
347  * @param uptimeMillis The time of the current pointer event, in milliseconds. The start (`0`) time
348  *   is platform-dependent
349  * @param position The [Offset] of the current pointer event, relative to the containing element
350  *   (values can be negative or larger than the element bounds).
351  * @param pressed `true` if the pointer event is considered "pressed." For example, finger touching
352  *   the screen or a mouse button is pressed [pressed] would be `true`.
353  * @param pressure The pressure of the of the pointer event
354  * @param previousUptimeMillis The [uptimeMillis] of the previous pointer event
355  * @param previousPosition The [Offset] of the previous pointer event, offset to the [position] and
356  *   relative to the containing element.
357  * @param previousPressed `true` if the pointer event was considered "pressed." For example , if a
358  *   finger was touching the screen or a mouse button was pressed, [previousPressed] would be
359  *   `true`.
360  * @param isInitiallyConsumed whether the change was consumed from the start or not. This value can
361  *   change over time as change is propagated through the pointer handlers. To query the actual
362  *   status of the change use [isConsumed]
363  * @param type The device type that produced the event, such as [mouse][PointerType.Mouse], or
364  *   [touch][PointerType.Touch].git
365  * @param scrollDelta The amount of scroll wheel movement in the horizontal and vertical directions.
366  */
367 @Immutable
368 class PointerInputChange(
369     val id: PointerId,
370     val uptimeMillis: Long,
371     val position: Offset,
372     val pressed: Boolean,
373     val pressure: Float,
374     val previousUptimeMillis: Long,
375     val previousPosition: Offset,
376     val previousPressed: Boolean,
377     isInitiallyConsumed: Boolean,
378     val type: PointerType = PointerType.Touch,
379     val scrollDelta: Offset = Offset.Zero
380 ) {
381     constructor(
382         id: PointerId,
383         uptimeMillis: Long,
384         position: Offset,
385         pressed: Boolean,
386         previousUptimeMillis: Long,
387         previousPosition: Offset,
388         previousPressed: Boolean,
389         isInitiallyConsumed: Boolean,
390         type: PointerType = PointerType.Touch,
391         scrollDelta: Offset = Offset.Zero
392     ) : this(
393         id,
394         uptimeMillis,
395         position,
396         pressed,
397         pressure = 1.0f,
398         previousUptimeMillis,
399         previousPosition,
400         previousPressed,
401         isInitiallyConsumed,
402         type,
403         scrollDelta
404     )
405 
406     @Deprecated(
407         level = DeprecationLevel.HIDDEN,
408         replaceWith =
409             ReplaceWith(
410                 "this(id, uptimeMillis, position, pressed, previousUptimeMillis," +
411                     " previousPosition, previousPressed," +
412                     " consumed.downChange || consumed.positionChange, type, Offset.Zero)"
413             ),
414         message = "Use another constructor with `scrollDelta` and without `ConsumedData` instead"
415     )
416     @Suppress("DEPRECATION")
417     constructor(
418         id: PointerId,
419         uptimeMillis: Long,
420         position: Offset,
421         pressed: Boolean,
422         previousUptimeMillis: Long,
423         previousPosition: Offset,
424         previousPressed: Boolean,
425         consumed: ConsumedData,
426         type: PointerType = PointerType.Touch
427     ) : this(
428         id,
429         uptimeMillis,
430         position,
431         pressed,
432         pressure = 1.0f,
433         previousUptimeMillis,
434         previousPosition,
435         previousPressed,
436         consumed.downChange || consumed.positionChange,
437         type,
438         Offset.Zero
439     )
440 
441     internal constructor(
442         id: PointerId,
443         uptimeMillis: Long,
444         position: Offset,
445         pressed: Boolean,
446         pressure: Float,
447         previousUptimeMillis: Long,
448         previousPosition: Offset,
449         previousPressed: Boolean,
450         isInitiallyConsumed: Boolean,
451         type: PointerType,
452         historical: List<HistoricalChange>,
453         scrollDelta: Offset,
454         originalEventPosition: Offset,
455     ) : this(
456         id,
457         uptimeMillis,
458         position,
459         pressed,
460         pressure,
461         previousUptimeMillis,
462         previousPosition,
463         previousPressed,
464         isInitiallyConsumed,
465         type,
466         scrollDelta
467     ) {
468         _historical = historical
469         this.originalEventPosition = originalEventPosition
470     }
471 
472     /**
473      * Optional high-frequency pointer moves in between the last two dispatched events. Can be used
474      * for extra accuracy when touchscreen rate exceeds framerate.
475      */
476     // With these experimental annotations, the API can be either cleanly removed or
477     // stabilized. It doesn't appear in current.txt; and in experimental_current.txt,
478     // it has the same effect as a primary constructor val.
479     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
480     val historical: List<HistoricalChange>
481         get() = _historical ?: listOf()
482 
483     private var _historical: List<HistoricalChange>? = null
484 
485     internal var originalEventPosition: Offset = Offset.Zero
486 
487     /**
488      * Indicates whether the change was consumed or not. Note that the change must be consumed in
489      * full as there's no partial consumption system provided.
490      */
491     val isConsumed: Boolean
492         get() = consumedDelegate?.isConsumed ?: (downChange || positionChange)
493 
494     internal var downChange = isInitiallyConsumed
495     internal var positionChange = isInitiallyConsumed
496     // Used by shallow copies (see [copy]) to share the consumed state across pointer input changes
497     internal var consumedDelegate: PointerInputChange? = null
498 
499     /**
500      * Consume change event, claiming all the corresponding change info to the caller. This is
501      * usually needed when, button, when being clicked, consumed the "up" event so no other parents
502      * of this button could consume this "up" again.
503      *
504      * "Consumption" is just an indication of the claim and each pointer input handler
505      * implementation must manually check this flag to respect it.
506      */
consumenull507     fun consume() {
508         if (consumedDelegate == null) {
509             downChange = true
510             positionChange = true
511         } else {
512             consumedDelegate?.consume()
513         }
514     }
515 
516     @Suppress("DEPRECATION") private var _consumed: ConsumedData? = null
517 
518     @Deprecated("use isConsumed and consume() pair of methods instead")
519     @Suppress("DEPRECATION")
520     val consumed: ConsumedData
521         get() {
522             if (_consumed == null) {
523                 _consumed = ConsumedData(this)
524             }
525             return _consumed!!
526         }
527 
528     @Deprecated(
529         level = DeprecationLevel.HIDDEN,
530         replaceWith =
531             ReplaceWith(
532                 "copy(id,currentTime, currentPosition, currentPressed, previousTime," +
533                     "previousPosition, previousPressed, consumed, type, this.scrollDelta)"
534             ),
535         message = "Use another copy() method with scrollDelta parameter instead"
536     )
537     @Suppress("DEPRECATION")
copynull538     fun copy(
539         id: PointerId = this.id,
540         currentTime: Long = this.uptimeMillis,
541         currentPosition: Offset = this.position,
542         currentPressed: Boolean = this.pressed,
543         previousTime: Long = this.previousUptimeMillis,
544         previousPosition: Offset = this.previousPosition,
545         previousPressed: Boolean = this.previousPressed,
546         consumed: ConsumedData = this.consumed,
547         type: PointerType = this.type,
548     ): PointerInputChange =
549         PointerInputChange(
550                 id,
551                 currentTime,
552                 currentPosition,
553                 currentPressed,
554                 this.pressure,
555                 previousTime,
556                 previousPosition,
557                 previousPressed,
558                 consumed.downChange || consumed.positionChange,
559                 type,
560                 this.historical,
561                 this.scrollDelta,
562                 this.originalEventPosition,
563             )
564             .also {
565                 // This method makes a deep copy, copy the consumed state directly without setting
566                 // the consumed delegate, which is used for shallow copies.
567                 it.positionChange = this.positionChange
568                 it.downChange = this.downChange
569             }
570 
571     /**
572      * Make a shallow copy of the [PointerInputChange]
573      *
574      * **NOTE:** Due to the need of the inner contract of the [PointerInputChange], this method
575      * performs a shallow copy of the [PointerInputChange]. Any [consume] call between any of the
576      * copies will consume any other copy automatically. Therefore, copy with the new [isConsumed]
577      * is not possible. Consider creating a new [PointerInputChange]
578      */
copynull579     fun copy(
580         id: PointerId = this.id,
581         currentTime: Long = this.uptimeMillis,
582         currentPosition: Offset = this.position,
583         currentPressed: Boolean = this.pressed,
584         previousTime: Long = this.previousUptimeMillis,
585         previousPosition: Offset = this.previousPosition,
586         previousPressed: Boolean = this.previousPressed,
587         type: PointerType = this.type,
588         scrollDelta: Offset = this.scrollDelta
589     ): PointerInputChange =
590         copy(
591                 id = id,
592                 currentTime = currentTime,
593                 currentPosition = currentPosition,
594                 currentPressed = currentPressed,
595                 pressure = this.pressure,
596                 previousTime = previousTime,
597                 previousPosition = previousPosition,
598                 previousPressed = previousPressed,
599                 type = type,
600                 historical = this.historical,
601                 scrollDelta = scrollDelta
602             )
603             .also {
604                 // This method makes a shallow copy, copy the delegate to share the consumed state
605                 // across instances. The local consumed state is irrelevant since we won't look at
606                 // it, meaning there's no need to copy positionChange and downChange.
607                 it.consumedDelegate = this.consumedDelegate ?: this
608             }
609 
610     @Suppress("DEPRECATION")
611     @Deprecated(
612         "Partial consumption has been deprecated. Use copy() instead without `consumed` " +
613             "parameter to create a shallow copy or a constructor to create a new " +
614             "PointerInputChange",
615         replaceWith =
616             ReplaceWith(
617                 "copy(id, currentTime, currentPosition, currentPressed, previousTime, " +
618                     "previousPosition, previousPressed, type, scrollDelta)"
619             )
620     )
copynull621     fun copy(
622         id: PointerId = this.id,
623         currentTime: Long = this.uptimeMillis,
624         currentPosition: Offset = this.position,
625         currentPressed: Boolean = this.pressed,
626         previousTime: Long = this.previousUptimeMillis,
627         previousPosition: Offset = this.previousPosition,
628         previousPressed: Boolean = this.previousPressed,
629         consumed: ConsumedData,
630         type: PointerType = this.type,
631         scrollDelta: Offset = this.scrollDelta
632     ): PointerInputChange =
633         PointerInputChange(
634                 id,
635                 currentTime,
636                 currentPosition,
637                 currentPressed,
638                 this.pressure,
639                 previousTime,
640                 previousPosition,
641                 previousPressed,
642                 consumed.downChange || consumed.positionChange,
643                 type,
644                 this.historical,
645                 scrollDelta,
646                 this.originalEventPosition,
647             )
648             .also {
649                 // This method makes a deep copy, copy the consumed state directly without setting
650                 // the consumed delegate, which is used for shallow copies.
651                 it.positionChange = this.positionChange
652                 it.downChange = this.downChange
653             }
654 
655     /**
656      * Make a shallow copy of the [PointerInputChange]
657      *
658      * **NOTE:** Due to the need of the inner contract of the [PointerInputChange], this method
659      * performs a shallow copy of the [PointerInputChange]. Any [consume] call between any of the
660      * copies will consume any other copy automatically. Therefore, copy with the new [isConsumed]
661      * is not possible. Consider creating a new [PointerInputChange].
662      */
copynull663     fun copy(
664         id: PointerId = this.id,
665         currentTime: Long = this.uptimeMillis,
666         currentPosition: Offset = this.position,
667         currentPressed: Boolean = this.pressed,
668         pressure: Float = this.pressure,
669         previousTime: Long = this.previousUptimeMillis,
670         previousPosition: Offset = this.previousPosition,
671         previousPressed: Boolean = this.previousPressed,
672         type: PointerType = this.type,
673         scrollDelta: Offset = this.scrollDelta
674     ): PointerInputChange =
675         PointerInputChange(
676                 id,
677                 currentTime,
678                 currentPosition,
679                 currentPressed,
680                 pressure,
681                 previousTime,
682                 previousPosition,
683                 previousPressed,
684                 isInitiallyConsumed = false, // doesn't matter, we will copy the consumed booleans
685                 type,
686                 historical = this.historical,
687                 scrollDelta,
688                 this.originalEventPosition,
689             )
690             .also {
691                 // This method makes a shallow copy, copy the delegate to share the consumed state
692                 // across instances. The local consumed state is irrelevant since we won't look at
693                 // it, meaning there's no need to copy positionChange and downChange.
694                 it.consumedDelegate = this.consumedDelegate ?: this
695             }
696 
697     /**
698      * Make a shallow copy of the [PointerInputChange]
699      *
700      * **NOTE:** Due to the need of the inner contract of the [PointerInputChange], this method
701      * performs a shallow copy of the [PointerInputChange]. Any [consume] call between any of the
702      * copies will consume any other copy automatically. Therefore, copy with the new [isConsumed]
703      * is not possible. Consider creating a new [PointerInputChange].
704      */
705     @ExperimentalComposeUiApi
copynull706     fun copy(
707         id: PointerId = this.id,
708         currentTime: Long = this.uptimeMillis,
709         currentPosition: Offset = this.position,
710         currentPressed: Boolean = this.pressed,
711         previousTime: Long = this.previousUptimeMillis,
712         previousPosition: Offset = this.previousPosition,
713         previousPressed: Boolean = this.previousPressed,
714         type: PointerType = this.type,
715         historical: List<HistoricalChange>,
716         scrollDelta: Offset = this.scrollDelta
717     ): PointerInputChange =
718         copy(
719                 id = id,
720                 currentTime = currentTime,
721                 currentPosition = currentPosition,
722                 currentPressed = currentPressed,
723                 pressure = this.pressure,
724                 previousTime = previousTime,
725                 previousPosition = previousPosition,
726                 previousPressed = previousPressed,
727                 type = type,
728                 historical = historical,
729                 scrollDelta = scrollDelta
730             )
731             .also {
732                 // This method makes a shallow copy, copy the delegate to share the consumed state
733                 // across instances. The local consumed state is irrelevant since we won't look at
734                 // it, meaning there's no need to copy positionChange and downChange.
735                 it.consumedDelegate = this.consumedDelegate ?: this
736             }
737 
738     /**
739      * Make a shallow copy of the [PointerInputChange]
740      *
741      * **NOTE:** Due to the need of the inner contract of the [PointerInputChange], this method
742      * performs a shallow copy of the [PointerInputChange]. Any [consume] call between any of the
743      * copies will consume any other copy automatically. Therefore, copy with the new [isConsumed]
744      * is not possible. Consider creating a new [PointerInputChange].
745      */
copynull746     fun copy(
747         id: PointerId = this.id,
748         currentTime: Long = this.uptimeMillis,
749         currentPosition: Offset = this.position,
750         currentPressed: Boolean = this.pressed,
751         pressure: Float = this.pressure,
752         previousTime: Long = this.previousUptimeMillis,
753         previousPosition: Offset = this.previousPosition,
754         previousPressed: Boolean = this.previousPressed,
755         type: PointerType = this.type,
756         historical: List<HistoricalChange> = this.historical,
757         scrollDelta: Offset = this.scrollDelta
758     ): PointerInputChange =
759         PointerInputChange(
760                 id,
761                 currentTime,
762                 currentPosition,
763                 currentPressed,
764                 pressure,
765                 previousTime,
766                 previousPosition,
767                 previousPressed,
768                 isInitiallyConsumed = false, // doesn't matter, we will copy the consumed booleans
769                 type,
770                 historical,
771                 scrollDelta,
772                 originalEventPosition = this.originalEventPosition,
773             )
774             .also {
775                 // This method makes a shallow copy, copy the delegate to share the consumed state
776                 // across instances. The local consumed state is irrelevant since we won't look at
777                 // it, meaning there's no need to copy positionChange and downChange.
778                 it.consumedDelegate = this.consumedDelegate ?: this
779             }
780 
toStringnull781     override fun toString(): String {
782         return "PointerInputChange(id=$id, " +
783             "uptimeMillis=$uptimeMillis, " +
784             "position=$position, " +
785             "pressed=$pressed, " +
786             "pressure=$pressure, " +
787             "previousUptimeMillis=$previousUptimeMillis, " +
788             "previousPosition=$previousPosition, " +
789             "previousPressed=$previousPressed, " +
790             "isConsumed=$isConsumed, " +
791             "type=$type, " +
792             "historical=$historical," +
793             "scrollDelta=$scrollDelta)"
794     }
795 }
796 
797 /**
798  * Data structure for "historical" pointer moves.
799  *
800  * Optional high-frequency pointer moves in between the last two dispatched events: can be used for
801  * extra accuracy when touchscreen rate exceeds framerate.
802  *
803  * @param uptimeMillis The time of the historical pointer event, in milliseconds. In between the
804  *   current and previous pointer event times.
805  * @param position The [Offset] of the historical pointer event, relative to the containing element.
806  */
807 @Immutable
808 class HistoricalChange(val uptimeMillis: Long, val position: Offset) {
809     internal var originalEventPosition: Offset = Offset.Zero
810         private set
811 
812     internal constructor(
813         uptimeMillis: Long,
814         position: Offset,
815         originalEventPosition: Offset
816     ) : this(uptimeMillis, position) {
817         this.originalEventPosition = originalEventPosition
818     }
819 
toStringnull820     override fun toString(): String {
821         return "HistoricalChange(uptimeMillis=$uptimeMillis, position=$position)"
822     }
823 }
824 
825 /**
826  * An ID for a given pointer.
827  *
828  * @param value The actual value of the id.
829  */
830 @kotlin.jvm.JvmInline value class PointerId(val value: Long)
831 
832 /**
833  * Describes what aspects of a change has been consumed.
834  *
835  * @param positionChange True if a position change in this event has been consumed.
836  * @param downChange True if a change to down or up has been consumed.
837  */
838 @Deprecated("Use PointerInputChange.isConsumed and PointerInputChange.consume() instead")
839 class ConsumedData(positionChange: Boolean = false, downChange: Boolean = false) {
840     private var change: PointerInputChange? = null
841 
842     internal constructor(
843         change: PointerInputChange
844     ) : this(change.positionChange, change.downChange) {
845         this.change = change
846     }
847 
848     @Suppress("GetterSetterNames")
849     @get:Suppress("GetterSetterNames")
850     @Deprecated(
851         "Partial consumption was deprecated. Use PointerEvent.isConsumed " +
852             "and PointerEvent.consume() instead."
853     )
854     var positionChange: Boolean = positionChange
855         get() = change?.consumedDelegate?.positionChange ?: (change?.positionChange ?: field)
856         set(value) {
857             change?.consumedDelegate?.positionChange = value
858             change?.positionChange = value
859             field = value
860         }
861 
862     @Suppress("GetterSetterNames")
863     @get:Suppress("GetterSetterNames")
864     @Deprecated(
865         "Partial consumption was deprecated. Use PointerEvent.isConsumed " +
866             "and PointerEvent.consume() instead."
867     )
868     var downChange: Boolean = downChange
869         get() = change?.consumedDelegate?.downChange ?: (change?.downChange ?: field)
870         set(value) {
871             change?.consumedDelegate?.downChange = value
872             change?.downChange = value
873             field = value
874         }
875 }
876 
877 /**
878  * The enumeration of passes where [PointerInputChange] traverses up and down the UI tree.
879  *
880  * PointerInputChanges traverse throw the hierarchy in the following passes:
881  * 1. [Initial]: Down the tree from ancestor to descendant.
882  * 2. [Main]: Up the tree from descendant to ancestor.
883  * 3. [Final]: Down the tree from ancestor to descendant.
884  *
885  * These passes serve the following purposes:
886  * 1. Initial: Allows ancestors to consume aspects of [PointerInputChange] before descendants. This
887  *    is where, for example, a scroller may block buttons from getting tapped by other fingers once
888  *    scrolling has started.
889  * 2. Main: The primary pass where gesture filters should react to and consume aspects of
890  *    [PointerInputChange]s. This is the primary path where descendants will interact with
891  *    [PointerInputChange]s before parents. This allows for buttons to respond to a tap before a
892  *    container of the bottom to respond to a tap.
893  * 3. Final: This pass is where children can learn what aspects of [PointerInputChange]s were
894  *    consumed by parents during the [Main] pass. For example, this is how a button determines that
895  *    it should no longer respond to fingers lifting off of it because a parent scroller has
896  *    consumed movement in a [PointerInputChange].
897  */
898 enum class PointerEventPass {
899     Initial,
900     Main,
901     Final
902 }
903 
904 /**
905  * True if this [PointerInputChange] represents a pointer coming in contact with the screen and that
906  * change has not been consumed.
907  */
PointerInputChangenull908 fun PointerInputChange.changedToDown() = !isConsumed && !previousPressed && pressed
909 
910 /**
911  * True if this [PointerInputChange] represents a pointer coming in contact with the screen, whether
912  * or not that change has been consumed.
913  */
914 fun PointerInputChange.changedToDownIgnoreConsumed() = !previousPressed && pressed
915 
916 /**
917  * True if this [PointerInputChange] represents a pointer breaking contact with the screen and that
918  * change has not been consumed.
919  */
920 fun PointerInputChange.changedToUp() = !isConsumed && previousPressed && !pressed
921 
922 /**
923  * True if this [PointerInputChange] represents a pointer breaking contact with the screen, whether
924  * or not that change has been consumed.
925  */
926 fun PointerInputChange.changedToUpIgnoreConsumed() = previousPressed && !pressed
927 
928 /**
929  * True if this [PointerInputChange] represents a pointer moving on the screen and some of that
930  * movement has not been consumed.
931  */
932 fun PointerInputChange.positionChanged() =
933     this.positionChangeInternal(false) != Offset.Companion.Zero
934 
935 /**
936  * True if this [PointerInputChange] represents a pointer moving on the screen ignoring how much of
937  * that movement may have been consumed.
938  */
939 fun PointerInputChange.positionChangedIgnoreConsumed() =
940     this.positionChangeInternal(true) != Offset.Companion.Zero
941 
942 /**
943  * The distance that the pointer has moved on the screen minus any distance that has been consumed.
944  */
945 fun PointerInputChange.positionChange() = this.positionChangeInternal(false)
946 
947 /**
948  * The distance that the pointer has moved on the screen, ignoring the fact that it might have been
949  * consumed.
950  */
951 fun PointerInputChange.positionChangeIgnoreConsumed() = this.positionChangeInternal(true)
952 
953 private fun PointerInputChange.positionChangeInternal(ignoreConsumed: Boolean = false): Offset {
954     val previousPosition = previousPosition
955     val currentPosition = position
956 
957     val offset = currentPosition - previousPosition
958 
959     return if (!ignoreConsumed && isConsumed) Offset.Zero else offset
960 }
961 
962 /** True if this [PointerInputChange]'s movement has been consumed. */
963 @Deprecated(
964     "Partial consumption has been deprecated. Use isConsumed instead",
965     replaceWith = ReplaceWith("isConsumed")
966 )
positionChangeConsumednull967 fun PointerInputChange.positionChangeConsumed() = isConsumed
968 
969 /** True if any aspect of this [PointerInputChange] has been consumed. */
970 @Deprecated(
971     "Partial consumption has been deprecated. Use isConsumed instead",
972     replaceWith = ReplaceWith("isConsumed")
973 )
974 fun PointerInputChange.anyChangeConsumed() = isConsumed
975 
976 /**
977  * Consume the up or down change of this [PointerInputChange] if there is an up or down change to
978  * consume.
979  */
980 @Deprecated(
981     "Partial consumption has been deprecated. Use consume() instead.",
982     replaceWith = ReplaceWith("if (pressed != previousPressed) consume()")
983 )
984 fun PointerInputChange.consumeDownChange() {
985     if (pressed != previousPressed) {
986         consume()
987     }
988 }
989 
990 /** Consume position change if there is any */
991 @Deprecated(
992     "Partial consumption has been deprecated. Use consume() instead.",
993     replaceWith = ReplaceWith("if (positionChange() != Offset.Zero) consume()")
994 )
PointerInputChangenull995 fun PointerInputChange.consumePositionChange() {
996     if (positionChange() != Offset.Zero) {
997         consume()
998     }
999 }
1000 
1001 /** Consumes all changes associated with the [PointerInputChange] */
1002 @Deprecated("Use consume() instead", replaceWith = ReplaceWith("consume()"))
PointerInputChangenull1003 fun PointerInputChange.consumeAllChanges() {
1004     consume()
1005 }
1006 
1007 /**
1008  * Returns `true` if the pointer has moved outside of the region of `(0, 0, size.width,
1009  * size.height)` or `false` if the current pointer is up or it is inside the given bounds.
1010  */
1011 @Deprecated(
1012     message = "Use isOutOfBounds() that supports minimum touch target",
1013     replaceWith = ReplaceWith("this.isOutOfBounds(size, extendedTouchPadding)")
1014 )
PointerInputChangenull1015 fun PointerInputChange.isOutOfBounds(size: IntSize): Boolean {
1016     val position = position
1017     val x = position.x
1018     val y = position.y
1019     val width = size.width
1020     val height = size.height
1021     // Branch-less
1022     return (x < 0f) or (x > width) or (y < 0f) or (y > height)
1023 }
1024 
1025 /**
1026  * Returns `true` if the pointer has moved outside of the pointer region. For Touch events, this is
1027  * (-extendedTouchPadding.width, -extendedTouchPadding.height, size.width +
1028  * extendedTouchPadding.width, size.height + extendedTouchPadding.height) and for other events, this
1029  * is `(0, 0, size.width, size.height)`. Returns`false` if the current pointer is up or it is inside
1030  * the pointer region.
1031  */
PointerInputChangenull1032 fun PointerInputChange.isOutOfBounds(size: IntSize, extendedTouchPadding: Size): Boolean {
1033     // Set to 1 when the pointer type is touch, 0 otherwise
1034     // No-op at the CPU level
1035     val isTouch = (type == PointerType.Touch).toInt()
1036 
1037     val position = position
1038     val x = position.x
1039     val y = position.y
1040 
1041     // Set extentX to 0 when the pointer type is *not* touch
1042     val extentX = extendedTouchPadding.width * isTouch
1043     val maxX = size.width + extentX
1044 
1045     // Set extentY to 0 when the pointer type is *not* touch
1046     val extentY = extendedTouchPadding.height * isTouch
1047     val maxY = size.height + extentY
1048 
1049     // Don't branch
1050     return (x < -extentX) or (x > maxX) or (y < -extentY) or (y > maxY)
1051 }
1052 
toIntnull1053 private inline fun Boolean.toInt() = if (this) 1 else 0
1054