shepshapard | 5c0d038 | 2019-01-23 09:39:17 -0800 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package androidx.ui.core.gesture |
| 18 | |
George Mount | 842c8c1 | 2020-01-08 16:03:42 -0800 | [diff] [blame] | 19 | import androidx.compose.remember |
Shep Shapard | a6a588e | 2020-04-29 14:58:35 -0700 | [diff] [blame] | 20 | import androidx.ui.core.CustomEvent |
Shep Shapard | 8931646 | 2020-03-03 17:00:52 -0800 | [diff] [blame] | 21 | import androidx.ui.core.Modifier |
shepshapard | 64443d7 | 2019-03-07 14:18:26 -0800 | [diff] [blame] | 22 | import androidx.ui.core.PointerEventPass |
Shep Shapard | a6a588e | 2020-04-29 14:58:35 -0700 | [diff] [blame] | 23 | import androidx.ui.core.PointerId |
shepshapard | 64443d7 | 2019-03-07 14:18:26 -0800 | [diff] [blame] | 24 | import androidx.ui.core.PointerInputChange |
| 25 | import androidx.ui.core.anyPositionChangeConsumed |
| 26 | import androidx.ui.core.changedToDown |
shepshapard | 64443d7 | 2019-03-07 14:18:26 -0800 | [diff] [blame] | 27 | import androidx.ui.core.changedToUp |
Shep Shapard | a6a588e | 2020-04-29 14:58:35 -0700 | [diff] [blame] | 28 | import androidx.ui.core.changedToUpIgnoreConsumed |
Adam Powell | a1f55b9 | 2020-04-20 16:32:38 -0700 | [diff] [blame] | 29 | import androidx.ui.core.composed |
shepshapard | 64443d7 | 2019-03-07 14:18:26 -0800 | [diff] [blame] | 30 | import androidx.ui.core.consumeDownChange |
Shep Shapard | a6a588e | 2020-04-29 14:58:35 -0700 | [diff] [blame] | 31 | import androidx.ui.core.gesture.customevents.DelayUpEvent |
| 32 | import androidx.ui.core.gesture.customevents.DelayUpMessage |
Shep Shapard | 8931646 | 2020-03-03 17:00:52 -0800 | [diff] [blame] | 33 | import androidx.ui.core.pointerinput.PointerInputFilter |
Louis Pullen-Freilich | f434a13 | 2020-07-22 14:19:24 +0100 | [diff] [blame] | 34 | import androidx.compose.ui.geometry.Offset |
Louis Pullen-Freilich | a7eeb10 | 2020-07-22 17:54:24 +0100 | [diff] [blame^] | 35 | import androidx.compose.ui.unit.IntSize |
George Mount | 39cbf92 | 2020-03-25 17:32:47 -0700 | [diff] [blame] | 36 | import androidx.ui.util.fastAny |
shepshapard | 5c0d038 | 2019-01-23 09:39:17 -0800 | [diff] [blame] | 37 | |
shepshapard | 5c0d038 | 2019-01-23 09:39:17 -0800 | [diff] [blame] | 38 | /** |
shepshapard | 0cc02c4 | 2019-06-28 15:51:14 -0700 | [diff] [blame] | 39 | * This gesture detector fires a callback when a traditional press is being released. This is |
| 40 | * generally the same thing as "onTap" or "onClick". |
shepshapard | 5c0d038 | 2019-01-23 09:39:17 -0800 | [diff] [blame] | 41 | * |
Shep Shapard | e7d051f | 2020-05-13 15:19:16 -0700 | [diff] [blame] | 42 | * [onTap] is called with the position of the last pointer to go "up". |
| 43 | * |
Shep Shapard | 8931646 | 2020-03-03 17:00:52 -0800 | [diff] [blame] | 44 | * More specifically, it will call [onTap] if: |
shepshapard | 0cc02c4 | 2019-06-28 15:51:14 -0700 | [diff] [blame] | 45 | * - 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 |
shepshapard | 5c0d038 | 2019-01-23 09:39:17 -0800 | [diff] [blame] | 49 | * an unconsumed up change. |
shepshapard | 0cc02c4 | 2019-06-28 15:51:14 -0700 | [diff] [blame] | 50 | * - While it has at least one pointer touching it, no [PointerInputChange] has had any |
shepshapard | 5c0d038 | 2019-01-23 09:39:17 -0800 | [diff] [blame] | 51 | * movement consumed (as that would indicate that something in the heirarchy moved and this a |
| 52 | * press should be cancelled. |
Shep Shapard | a6a588e | 2020-04-29 14:58:35 -0700 | [diff] [blame] | 53 | * - 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 Shapard | a29e20f | 2020-03-20 17:03:59 -0700 | [diff] [blame] | 57 | * |
| 58 | * @param onTap Called when a tap has occurred. |
shepshapard | 5c0d038 | 2019-01-23 09:39:17 -0800 | [diff] [blame] | 59 | */ |
shepshapard | 0cc02c4 | 2019-06-28 15:51:14 -0700 | [diff] [blame] | 60 | // TODO(b/139020678): Probably has shared functionality with other press based detectors. |
Shep Shapard | a6a588e | 2020-04-29 14:58:35 -0700 | [diff] [blame] | 61 | |
Shep Shapard | 70a8cfe | 2020-03-30 16:17:26 -0700 | [diff] [blame] | 62 | fun Modifier.tapGestureFilter( |
Nader Jawad | 6df0612 | 2020-06-03 15:27:08 -0700 | [diff] [blame] | 63 | onTap: (Offset) -> Unit |
Adam Powell | a1f55b9 | 2020-04-20 16:32:38 -0700 | [diff] [blame] | 64 | ): Modifier = composed { |
Shep Shapard | ad4e815 | 2020-04-03 13:22:08 -0700 | [diff] [blame] | 65 | val filter = remember { TapGestureFilter() } |
| 66 | filter.onTap = onTap |
Adam Powell | a1f55b9 | 2020-04-20 16:32:38 -0700 | [diff] [blame] | 67 | PointerInputModifierImpl(filter) |
shepshapard | 5c0d038 | 2019-01-23 09:39:17 -0800 | [diff] [blame] | 68 | } |
| 69 | |
Shep Shapard | ad4e815 | 2020-04-03 13:22:08 -0700 | [diff] [blame] | 70 | internal class TapGestureFilter : PointerInputFilter() { |
shepshapard | 5c0d038 | 2019-01-23 09:39:17 -0800 | [diff] [blame] | 71 | /** |
| 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 Jawad | 6df0612 | 2020-06-03 15:27:08 -0700 | [diff] [blame] | 76 | lateinit var onTap: (Offset) -> Unit |
Shep Shapard | a29e20f | 2020-03-20 17:03:59 -0700 | [diff] [blame] | 77 | |
shepshapard | 5c0d038 | 2019-01-23 09:39:17 -0800 | [diff] [blame] | 78 | /** |
Shep Shapard | 8931646 | 2020-03-03 17:00:52 -0800 | [diff] [blame] | 79 | * True when we are primed to call [onTap] and may be consuming all down changes. |
shepshapard | 0cc02c4 | 2019-06-28 15:51:14 -0700 | [diff] [blame] | 80 | */ |
Shep Shapard | a6a588e | 2020-04-29 14:58:35 -0700 | [diff] [blame] | 81 | private var primed = false |
| 82 | |
| 83 | private var downPointers: MutableSet<PointerId> = mutableSetOf() |
| 84 | private var upBlockedPointers: MutableSet<PointerId> = mutableSetOf() |
Nader Jawad | 6df0612 | 2020-06-03 15:27:08 -0700 | [diff] [blame] | 85 | private var lastPxPosition: Offset? = null |
shepshapard | 5c0d038 | 2019-01-23 09:39:17 -0800 | [diff] [blame] | 86 | |
Shep Shapard | 6c9f1da | 2020-03-24 14:12:35 -0700 | [diff] [blame] | 87 | override fun onPointerInput( |
| 88 | changes: List<PointerInputChange>, |
| 89 | pass: PointerEventPass, |
George Mount | 8f23757 | 2020-04-30 12:08:30 -0700 | [diff] [blame] | 90 | bounds: IntSize |
Shep Shapard | 6c9f1da | 2020-03-24 14:12:35 -0700 | [diff] [blame] | 91 | ): List<PointerInputChange> { |
shepshapard | 5c0d038 | 2019-01-23 09:39:17 -0800 | [diff] [blame] | 92 | |
Shep Shapard | 6c9f1da | 2020-03-24 14:12:35 -0700 | [diff] [blame] | 93 | if (pass == PointerEventPass.PostUp) { |
shepshapard | 5c0d038 | 2019-01-23 09:39:17 -0800 | [diff] [blame] | 94 | |
Shep Shapard | a6a588e | 2020-04-29 14:58:35 -0700 | [diff] [blame] | 95 | if (primed && |
Shep Shapard | e7d051f | 2020-05-13 15:19:16 -0700 | [diff] [blame] | 96 | changes.all { it.changedToUp() } |
Shep Shapard | a6a588e | 2020-04-29 14:58:35 -0700 | [diff] [blame] | 97 | ) { |
Nader Jawad | 6df0612 | 2020-06-03 15:27:08 -0700 | [diff] [blame] | 98 | val pointerPxPosition: Offset = changes[0].previous.position!! |
Shep Shapard | e7d051f | 2020-05-13 15:19:16 -0700 | [diff] [blame] | 99 | 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 Shapard | a6a588e | 2020-04-29 14:58:35 -0700 | [diff] [blame] | 108 | } |
| 109 | |
| 110 | if (changes.all { it.changedToDown() }) { |
Shep Shapard | e7d051f | 2020-05-13 15:19:16 -0700 | [diff] [blame] | 111 | // Reset in case we were incorrectly left waiting on a delayUp message. |
| 112 | reset() |
Shep Shapard | a6a588e | 2020-04-29 14:58:35 -0700 | [diff] [blame] | 113 | // 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 Shapard | 6c9f1da | 2020-03-24 14:12:35 -0700 | [diff] [blame] | 139 | reset() |
shepshapard | 5c0d038 | 2019-01-23 09:39:17 -0800 | [diff] [blame] | 140 | } |
shepshapard | 5c0d038 | 2019-01-23 09:39:17 -0800 | [diff] [blame] | 141 | } |
Shep Shapard | bf513c5 | 2019-12-16 17:06:51 -0800 | [diff] [blame] | 142 | |
Shep Shapard | a6a588e | 2020-04-29 14:58:35 -0700 | [diff] [blame] | 143 | return changes |
Shep Shapard | 6c9f1da | 2020-03-24 14:12:35 -0700 | [diff] [blame] | 144 | } |
| 145 | |
| 146 | override fun onCancel() { |
| 147 | reset() |
| 148 | } |
| 149 | |
Shep Shapard | a6a588e | 2020-04-29 14:58:35 -0700 | [diff] [blame] | 150 | 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 Shapard | e7d051f | 2020-05-13 15:19:16 -0700 | [diff] [blame] | 168 | 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 Shapard | a6a588e | 2020-04-29 14:58:35 -0700 | [diff] [blame] | 173 | // At this point, we were primed, no pointers were down, and we are unblocked, so we |
| 174 | // are at least resetting. |
| 175 | reset() |
Shep Shapard | a6a588e | 2020-04-29 14:58:35 -0700 | [diff] [blame] | 176 | } |
| 177 | } |
| 178 | |
Shep Shapard | 6c9f1da | 2020-03-24 14:12:35 -0700 | [diff] [blame] | 179 | private fun reset() { |
Shep Shapard | a6a588e | 2020-04-29 14:58:35 -0700 | [diff] [blame] | 180 | primed = false |
| 181 | upBlockedPointers.clear() |
| 182 | downPointers.clear() |
Shep Shapard | e7d051f | 2020-05-13 15:19:16 -0700 | [diff] [blame] | 183 | lastPxPosition = null |
Shep Shapard | bf513c5 | 2019-12-16 17:06:51 -0800 | [diff] [blame] | 184 | } |
shepshapard | 5c0d038 | 2019-01-23 09:39:17 -0800 | [diff] [blame] | 185 | } |