1 /*
<lambda>null2  * Copyright 2021 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 package androidx.compose.foundation.text
18 
19 import androidx.compose.foundation.gestures.awaitEachGesture
20 import androidx.compose.foundation.gestures.awaitFirstDown
21 import androidx.compose.foundation.gestures.detectDragGestures
22 import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
23 import androidx.compose.ui.geometry.Offset
24 import androidx.compose.ui.input.pointer.PointerInputScope
25 import androidx.compose.ui.util.fastAny
26 import kotlinx.coroutines.CoroutineStart
27 import kotlinx.coroutines.coroutineScope
28 import kotlinx.coroutines.launch
29 
30 internal interface TextDragObserver {
31     /**
32      * Called as soon as a down event is received. If the pointer eventually moves while remaining
33      * down, a drag gesture may be started. After this method:
34      * - [onUp] will always be called eventually, once the pointer is released.
35      * - [onStart] _may_ be called, if there is a drag that exceeds touch slop.
36      *
37      * This method will not be called before [onStart] in the case when a down event happens that
38      * may not result in a drag, e.g. on the down before a long-press that starts a selection.
39      */
40     fun onDown(point: Offset)
41 
42     /** Called after [onDown] if an up event is received without dragging. */
43     fun onUp()
44 
45     /**
46      * Called once a drag gesture has started, which means touch slop has been exceeded. [onDown]
47      * _may_ be called before this method if the down event could not have started a different
48      * gesture.
49      */
50     fun onStart(startPoint: Offset)
51 
52     fun onDrag(delta: Offset)
53 
54     fun onStop()
55 
56     fun onCancel()
57 }
58 
detectDragGesturesAfterLongPressWithObservernull59 internal suspend fun PointerInputScope.detectDragGesturesAfterLongPressWithObserver(
60     observer: TextDragObserver
61 ) =
62     detectDragGesturesAfterLongPress(
63         onDragEnd = { observer.onStop() },
offsetnull64         onDrag = { _, offset -> observer.onDrag(offset) },
<lambda>null65         onDragStart = { observer.onStart(it) },
<lambda>null66         onDragCancel = { observer.onCancel() }
67     )
68 
69 /**
70  * Detects gesture events for a [TextDragObserver], including both initial down events and drag
71  * events.
72  */
detectDownAndDragGesturesWithObservernull73 internal suspend fun PointerInputScope.detectDownAndDragGesturesWithObserver(
74     observer: TextDragObserver
75 ) {
76     coroutineScope {
77         launch(start = CoroutineStart.UNDISPATCHED) { detectPreDragGesturesWithObserver(observer) }
78         launch(start = CoroutineStart.UNDISPATCHED) { detectDragGesturesWithObserver(observer) }
79     }
80 }
81 
82 /** Detects initial down events and calls [TextDragObserver.onDown] and [TextDragObserver.onUp]. */
detectPreDragGesturesWithObservernull83 private suspend fun PointerInputScope.detectPreDragGesturesWithObserver(
84     observer: TextDragObserver
85 ) {
86     awaitEachGesture {
87         val down = awaitFirstDown(requireUnconsumed = false)
88         observer.onDown(down.position)
89         // Wait for that pointer to come up.
90         do {
91             val event = awaitPointerEvent()
92         } while (event.changes.fastAny { it.id == down.id && it.pressed })
93         observer.onUp()
94     }
95 }
96 
97 /** Detects drag gestures for a [TextDragObserver]. */
detectDragGesturesWithObservernull98 private suspend fun PointerInputScope.detectDragGesturesWithObserver(observer: TextDragObserver) {
99     detectDragGestures(
100         onDragEnd = { observer.onStop() },
101         onDrag = { _, offset -> observer.onDrag(offset) },
102         onDragStart = { observer.onStart(it) },
103         onDragCancel = { observer.onCancel() }
104     )
105 }
106