[go: nahoru, domu]

blob: c5cf1add58fd6c5db16119cfc3c1645e2e407982 [file] [log] [blame]
shepshapard5c0d0382019-01-23 09:39:17 -08001/*
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
George Mount842c8c12020-01-08 16:03:42 -080019import androidx.compose.remember
Shep Shaparda6a588e2020-04-29 14:58:35 -070020import androidx.ui.core.CustomEvent
Shep Shapard89316462020-03-03 17:00:52 -080021import androidx.ui.core.Modifier
shepshapard64443d72019-03-07 14:18:26 -080022import androidx.ui.core.PointerEventPass
Shep Shaparda6a588e2020-04-29 14:58:35 -070023import androidx.ui.core.PointerId
shepshapard64443d72019-03-07 14:18:26 -080024import androidx.ui.core.PointerInputChange
25import androidx.ui.core.anyPositionChangeConsumed
26import androidx.ui.core.changedToDown
shepshapard64443d72019-03-07 14:18:26 -080027import androidx.ui.core.changedToUp
Shep Shaparda6a588e2020-04-29 14:58:35 -070028import androidx.ui.core.changedToUpIgnoreConsumed
Adam Powella1f55b92020-04-20 16:32:38 -070029import androidx.ui.core.composed
shepshapard64443d72019-03-07 14:18:26 -080030import androidx.ui.core.consumeDownChange
Shep Shaparda6a588e2020-04-29 14:58:35 -070031import androidx.ui.core.gesture.customevents.DelayUpEvent
32import androidx.ui.core.gesture.customevents.DelayUpMessage
Shep Shapard89316462020-03-03 17:00:52 -080033import androidx.ui.core.pointerinput.PointerInputFilter
Louis Pullen-Freilichf434a132020-07-22 14:19:24 +010034import androidx.compose.ui.geometry.Offset
Louis Pullen-Freilicha7eeb102020-07-22 17:54:24 +010035import androidx.compose.ui.unit.IntSize
George Mount39cbf922020-03-25 17:32:47 -070036import androidx.ui.util.fastAny
shepshapard5c0d0382019-01-23 09:39:17 -080037
shepshapard5c0d0382019-01-23 09:39:17 -080038/**
shepshapard0cc02c42019-06-28 15:51:14 -070039 * This gesture detector fires a callback when a traditional press is being released. This is
40 * generally the same thing as "onTap" or "onClick".
shepshapard5c0d0382019-01-23 09:39:17 -080041 *
Shep Shaparde7d051f2020-05-13 15:19:16 -070042 * [onTap] is called with the position of the last pointer to go "up".
43 *
Shep Shapard89316462020-03-03 17:00:52 -080044 * More specifically, it will call [onTap] if:
shepshapard0cc02c42019-06-28 15:51:14 -070045 * - All of the first [PointerInputChange]s it receives during the [PointerEventPass.PostUp] pass
46 * have unconsumed down changes, thus representing new set of pointers, none of which have had
47 * their down events consumed.
48 * - The last [PointerInputChange] it receives during the [PointerEventPass.PostUp] pass has
shepshapard5c0d0382019-01-23 09:39:17 -080049 * an unconsumed up change.
shepshapard0cc02c42019-06-28 15:51:14 -070050 * - While it has at least one pointer touching it, no [PointerInputChange] has had any
shepshapard5c0d0382019-01-23 09:39:17 -080051 * movement consumed (as that would indicate that something in the heirarchy moved and this a
52 * press should be cancelled.
Shep Shaparda6a588e2020-04-29 14:58:35 -070053 * - It also fully cooperates with [DelayUpEvent] [CustomEvent]s it receives such that it will delay
54 * calling [onTap] if all of it's up events are being blocked. If it was being blocked and later
55 * is allowed to fire it's up event (which is [onTap]) it will do so and consume the delayed up
56 * custom event such that no other gesture filters will also respond to the delayed up.
Shep Shaparda29e20f2020-03-20 17:03:59 -070057 *
58 * @param onTap Called when a tap has occurred.
shepshapard5c0d0382019-01-23 09:39:17 -080059 */
shepshapard0cc02c42019-06-28 15:51:14 -070060// TODO(b/139020678): Probably has shared functionality with other press based detectors.
Shep Shaparda6a588e2020-04-29 14:58:35 -070061
Shep Shapard70a8cfe2020-03-30 16:17:26 -070062fun Modifier.tapGestureFilter(
Nader Jawad6df06122020-06-03 15:27:08 -070063 onTap: (Offset) -> Unit
Adam Powella1f55b92020-04-20 16:32:38 -070064): Modifier = composed {
Shep Shapardad4e8152020-04-03 13:22:08 -070065 val filter = remember { TapGestureFilter() }
66 filter.onTap = onTap
Adam Powella1f55b92020-04-20 16:32:38 -070067 PointerInputModifierImpl(filter)
shepshapard5c0d0382019-01-23 09:39:17 -080068}
69
Shep Shapardad4e8152020-04-03 13:22:08 -070070internal class TapGestureFilter : PointerInputFilter() {
shepshapard5c0d0382019-01-23 09:39:17 -080071 /**
72 * Called to indicate that a press gesture has successfully completed.
73 *
74 * This should be used to fire a state changing event as if a button was pressed.
75 */
Nader Jawad6df06122020-06-03 15:27:08 -070076 lateinit var onTap: (Offset) -> Unit
Shep Shaparda29e20f2020-03-20 17:03:59 -070077
shepshapard5c0d0382019-01-23 09:39:17 -080078 /**
Shep Shapard89316462020-03-03 17:00:52 -080079 * True when we are primed to call [onTap] and may be consuming all down changes.
shepshapard0cc02c42019-06-28 15:51:14 -070080 */
Shep Shaparda6a588e2020-04-29 14:58:35 -070081 private var primed = false
82
83 private var downPointers: MutableSet<PointerId> = mutableSetOf()
84 private var upBlockedPointers: MutableSet<PointerId> = mutableSetOf()
Nader Jawad6df06122020-06-03 15:27:08 -070085 private var lastPxPosition: Offset? = null
shepshapard5c0d0382019-01-23 09:39:17 -080086
Shep Shapard6c9f1da2020-03-24 14:12:35 -070087 override fun onPointerInput(
88 changes: List<PointerInputChange>,
89 pass: PointerEventPass,
George Mount8f237572020-04-30 12:08:30 -070090 bounds: IntSize
Shep Shapard6c9f1da2020-03-24 14:12:35 -070091 ): List<PointerInputChange> {
shepshapard5c0d0382019-01-23 09:39:17 -080092
Shep Shapard6c9f1da2020-03-24 14:12:35 -070093 if (pass == PointerEventPass.PostUp) {
shepshapard5c0d0382019-01-23 09:39:17 -080094
Shep Shaparda6a588e2020-04-29 14:58:35 -070095 if (primed &&
Shep Shaparde7d051f2020-05-13 15:19:16 -070096 changes.all { it.changedToUp() }
Shep Shaparda6a588e2020-04-29 14:58:35 -070097 ) {
Nader Jawad6df06122020-06-03 15:27:08 -070098 val pointerPxPosition: Offset = changes[0].previous.position!!
Shep Shaparde7d051f2020-05-13 15:19:16 -070099 if (changes.fastAny { !upBlockedPointers.contains(it.id) }) {
100 // If we are primed, all pointers went up, and at least one of the pointers is
101 // not blocked, we can fire, reset, and consume all of the up events.
102 reset()
103 onTap.invoke(pointerPxPosition)
104 return changes.map { it.consumeDownChange() }
105 } else {
106 lastPxPosition = pointerPxPosition
107 }
Shep Shaparda6a588e2020-04-29 14:58:35 -0700108 }
109
110 if (changes.all { it.changedToDown() }) {
Shep Shaparde7d051f2020-05-13 15:19:16 -0700111 // Reset in case we were incorrectly left waiting on a delayUp message.
112 reset()
Shep Shaparda6a588e2020-04-29 14:58:35 -0700113 // If all of the changes are down, can become primed.
114 primed = true
115 }
116
117 if (primed) {
118 changes.forEach {
119 if (it.changedToDown()) {
120 downPointers.add(it.id)
121 }
122 if (it.changedToUpIgnoreConsumed()) {
123 downPointers.remove(it.id)
124 }
125 }
126 }
127 }
128
129 if (pass == PointerEventPass.PostDown && primed) {
130
131 val anyPositionChangeConsumed = changes.fastAny { it.anyPositionChangeConsumed() }
132
133 val noPointersInBounds =
134 upBlockedPointers.isEmpty() && !changes.anyPointersInBounds(bounds)
135
136 if (anyPositionChangeConsumed || noPointersInBounds) {
137 // If we are on the final pass, we are primed, and either we aren't blocked and
138 // all pointers are out of bounds.
Shep Shapard6c9f1da2020-03-24 14:12:35 -0700139 reset()
shepshapard5c0d0382019-01-23 09:39:17 -0800140 }
shepshapard5c0d0382019-01-23 09:39:17 -0800141 }
Shep Shapardbf513c52019-12-16 17:06:51 -0800142
Shep Shaparda6a588e2020-04-29 14:58:35 -0700143 return changes
Shep Shapard6c9f1da2020-03-24 14:12:35 -0700144 }
145
146 override fun onCancel() {
147 reset()
148 }
149
Shep Shaparda6a588e2020-04-29 14:58:35 -0700150 override fun onCustomEvent(customEvent: CustomEvent, pass: PointerEventPass) {
151 if (!primed || pass != PointerEventPass.PostUp || customEvent !is DelayUpEvent) {
152 return
153 }
154
155 if (customEvent.message == DelayUpMessage.DelayUp) {
156 // If the message is to DelayUp, track all currently down pointers that are also ones
157 // we are supposed to block the up event for.
158 customEvent.pointers.forEach {
159 if (downPointers.contains(it)) {
160 upBlockedPointers.add(it)
161 }
162 }
163 return
164 }
165
166 upBlockedPointers.removeAll(customEvent.pointers)
167 if (upBlockedPointers.isEmpty() && downPointers.isEmpty()) {
Shep Shaparde7d051f2020-05-13 15:19:16 -0700168 if (customEvent.message == DelayUpMessage.DelayedUpNotConsumed) {
169 // If the up was not consumed, then we can fire our callback and consume it.
170 onTap.invoke(lastPxPosition!!)
171 customEvent.message = DelayUpMessage.DelayedUpConsumed
172 }
Shep Shaparda6a588e2020-04-29 14:58:35 -0700173 // At this point, we were primed, no pointers were down, and we are unblocked, so we
174 // are at least resetting.
175 reset()
Shep Shaparda6a588e2020-04-29 14:58:35 -0700176 }
177 }
178
Shep Shapard6c9f1da2020-03-24 14:12:35 -0700179 private fun reset() {
Shep Shaparda6a588e2020-04-29 14:58:35 -0700180 primed = false
181 upBlockedPointers.clear()
182 downPointers.clear()
Shep Shaparde7d051f2020-05-13 15:19:16 -0700183 lastPxPosition = null
Shep Shapardbf513c52019-12-16 17:06:51 -0800184 }
shepshapard5c0d0382019-01-23 09:39:17 -0800185}