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