[go: nahoru, domu]

blob: e0c33b3059305fe8cbeaf154cd80a23dc4c959b4 [file] [log] [blame]
shepshapardc56cc4e2019-07-03 14:31:55 -07001/*
2 * Copyright 2019 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
17package androidx.ui.core.gesture
18
shepshapardc56cc4e2019-07-03 14:31:55 -070019import androidx.compose.Composable
Leland Richardson7f848ab2019-12-12 13:43:41 -080020import androidx.compose.remember
George Mount842c8c12020-01-08 16:03:42 -080021import androidx.ui.core.CoroutineContextAmbient
Shep Shapard2cdd21a2020-01-24 13:44:54 -080022import androidx.ui.core.CustomEvent
23import androidx.ui.core.CustomEventDispatcher
Shep Shapard89316462020-03-03 17:00:52 -080024import androidx.ui.core.Modifier
George Mount842c8c12020-01-08 16:03:42 -080025import androidx.ui.core.PointerEventPass
Shep Shapard89316462020-03-03 17:00:52 -080026import androidx.ui.core.PointerId
George Mount842c8c12020-01-08 16:03:42 -080027import androidx.ui.core.PointerInputChange
George Mount842c8c12020-01-08 16:03:42 -080028import androidx.ui.core.anyPositionChangeConsumed
29import androidx.ui.core.changedToDown
shepshapardc56cc4e2019-07-03 14:31:55 -070030import androidx.ui.core.changedToUp
31import androidx.ui.core.changedToUpIgnoreConsumed
32import androidx.ui.core.consumeDownChange
Shep Shapard89316462020-03-03 17:00:52 -080033import androidx.ui.core.pointerinput.PointerInputFilter
shepshapardc56cc4e2019-07-03 14:31:55 -070034import androidx.ui.temputils.delay
George Mount842c8c12020-01-08 16:03:42 -080035import androidx.ui.unit.IntPxSize
36import androidx.ui.unit.PxPosition
George Mount39cbf922020-03-25 17:32:47 -070037import androidx.ui.util.fastAny
shepshapardc56cc4e2019-07-03 14:31:55 -070038import kotlinx.coroutines.Job
39import kotlin.coroutines.CoroutineContext
40
shepshapard0cc02c42019-06-28 15:51:14 -070041// TODO(b/137569202): This bug tracks the note below regarding the need to eventually
42// improve LongPressGestureDetector.
43// TODO(b/139020678): Probably has shared functionality with other press based detectors.
shepshapardc56cc4e2019-07-03 14:31:55 -070044/**
45 * Responds to a pointer being "down" for an extended amount of time.
46 *
shepshapard0cc02c42019-06-28 15:51:14 -070047 * Note: this is likely a temporary, naive, and flawed approach. It is not necessarily guaranteed
48 * to interoperate well with forthcoming behavior related to disambiguation between multi-tap
49 * (double tap, triple tap) and tap.
shepshapardc56cc4e2019-07-03 14:31:55 -070050 */
51@Composable
52fun LongPressGestureDetector(
Shep Shapard89316462020-03-03 17:00:52 -080053 onLongPress: (PxPosition) -> Unit
54): Modifier {
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -080055 val coroutineContext = CoroutineContextAmbient.current
Shep Shapard2cdd21a2020-01-24 13:44:54 -080056 val recognizer = remember { LongPressGestureRecognizer(coroutineContext) }
shepshapard485257c2019-08-06 17:43:31 -070057 recognizer.onLongPress = onLongPress
Shep Shapard02391f82020-03-18 11:58:21 -070058 return PointerInputModifierImpl(recognizer)
shepshapardc56cc4e2019-07-03 14:31:55 -070059}
60
61internal class LongPressGestureRecognizer(
Shep Shapard2cdd21a2020-01-24 13:44:54 -080062 private val coroutineContext: CoroutineContext
Shep Shapard89316462020-03-03 17:00:52 -080063) : PointerInputFilter() {
shepshapard485257c2019-08-06 17:43:31 -070064 lateinit var onLongPress: (PxPosition) -> Unit
shepshapardc56cc4e2019-07-03 14:31:55 -070065
Shep Shapard2cdd21a2020-01-24 13:44:54 -080066 var longPressTimeout = LongPressTimeout
67
shepshapardc56cc4e2019-07-03 14:31:55 -070068 private enum class State {
69 Idle, Primed, Fired
70 }
71
72 private var state = State.Idle
Shep Shapard9cfb33382020-01-16 10:51:53 -080073 private val pointerPositions = linkedMapOf<PointerId, PxPosition>()
Shep Shapard2cdd21a2020-01-24 13:44:54 -080074 private var job: Job? = null
75 private lateinit var customEventDispatcher: CustomEventDispatcher
76
Shep Shapard89316462020-03-03 17:00:52 -080077 override val initHandler = { customEventDispatcher: CustomEventDispatcher ->
Shep Shapard2cdd21a2020-01-24 13:44:54 -080078 this.customEventDispatcher = customEventDispatcher
79 }
shepshapardc56cc4e2019-07-03 14:31:55 -070080
Shep Shapard89316462020-03-03 17:00:52 -080081 override val pointerInputHandler =
shepshapard2f3e1162019-10-18 17:25:22 -070082 { changes: List<PointerInputChange>, pass: PointerEventPass, bounds: IntPxSize ->
shepshapardc56cc4e2019-07-03 14:31:55 -070083
84 var changesToReturn = changes
85
86 if (pass == PointerEventPass.InitialDown && state == State.Fired) {
Shep Shapard2cdd21a2020-01-24 13:44:54 -080087 // If we fired and have not reset, we should prevent other pointer input nodes from
88 // responding to up, so consume it early on.
shepshapardc56cc4e2019-07-03 14:31:55 -070089 changesToReturn = changesToReturn.map {
90 if (it.changedToUp()) {
91 it.consumeDownChange()
92 } else {
93 it
94 }
95 }
96 }
97
98 if (pass == PointerEventPass.PostUp) {
99 if (state == State.Idle && changes.all { it.changedToDown() }) {
Shep Shapard2cdd21a2020-01-24 13:44:54 -0800100 // If we are idle and all of the changes changed to down, we are prime to fire
101 // the event.
102 primeToFire()
shepshapardc56cc4e2019-07-03 14:31:55 -0700103 } else if (state != State.Idle && changes.all { it.changedToUpIgnoreConsumed() }) {
Shep Shapard2cdd21a2020-01-24 13:44:54 -0800104 // If we have started and all of the changes changed to up, reset to idle.
105 resetToIdle()
shepshapard2f3e1162019-10-18 17:25:22 -0700106 } else if (!changesToReturn.anyPointersInBounds(bounds)) {
Shep Shapard2cdd21a2020-01-24 13:44:54 -0800107 // If all pointers have gone out of bounds, reset to idle.
108 resetToIdle()
shepshapardc56cc4e2019-07-03 14:31:55 -0700109 }
110
111 if (state == State.Primed) {
Shep Shapard2cdd21a2020-01-24 13:44:54 -0800112 // If we are primed, keep track of all down pointer positions so we can pass
113 // pointer position information to the event we will fire.
shepshapardc56cc4e2019-07-03 14:31:55 -0700114 changes.forEach {
115 if (it.current.down) {
116 pointerPositions[it.id] = it.current.position!!
117 } else {
118 pointerPositions.remove(it.id)
119 }
120 }
121 }
122 }
123
Shep Shapard2cdd21a2020-01-24 13:44:54 -0800124 if (
125 pass == PointerEventPass.PostDown &&
shepshapardd9f05232019-08-08 10:21:47 -0700126 state != State.Idle &&
George Mount39cbf922020-03-25 17:32:47 -0700127 changes.fastAny { it.anyPositionChangeConsumed() }
shepshapardc56cc4e2019-07-03 14:31:55 -0700128 ) {
Shep Shapard2cdd21a2020-01-24 13:44:54 -0800129 // If we are anything but Idle and something consumed movement, reset.
130 resetToIdle()
shepshapardc56cc4e2019-07-03 14:31:55 -0700131 }
132
133 changesToReturn
134 }
135
Shep Shapard89316462020-03-03 17:00:52 -0800136 override val customEventHandler: (CustomEvent, PointerEventPass) -> Unit =
Shep Shapard2cdd21a2020-01-24 13:44:54 -0800137 { customEvent, pointerInputPass ->
138 if (
139 state == State.Primed &&
140 customEvent is LongPressFiredEvent &&
141 pointerInputPass == PointerEventPass.InitialDown
142 ) {
143 // If we are primed but something else fired long press, we should reset.
144 // Doesn't matter what pass we are on, just choosing one so we only reset once.
145 resetToIdle()
146 }
147 }
148
Shep Shapard89316462020-03-03 17:00:52 -0800149 override val cancelHandler = {
Shep Shapard2cdd21a2020-01-24 13:44:54 -0800150 resetToIdle()
shepshapardc56cc4e2019-07-03 14:31:55 -0700151 }
Shep Shapard2cdd21a2020-01-24 13:44:54 -0800152
153 private fun fireLongPress() {
154 state = State.Fired
155 onLongPress.invoke(pointerPositions.asIterable().first().value)
156 customEventDispatcher.dispatchCustomEvent(LongPressFiredEvent)
157 }
158
159 private fun primeToFire() {
160 state = State.Primed
161 job = delay(longPressTimeout, coroutineContext) {
162 fireLongPress()
163 }
164 }
165
166 private fun resetToIdle() {
167 state = State.Idle
168 job?.cancel()
169 pointerPositions.clear()
170 }
171}
172
173object LongPressFiredEvent : CustomEvent