[go: nahoru, domu]

blob: 53ea90aef922d53b176b154045fff8cc859bcb01 [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.ui.core.gesture
import androidx.ui.core.PointerEventPass
import androidx.ui.core.PointerInputChange
import androidx.ui.core.PxPosition
import androidx.ui.core.anyPositionChangeConsumed
import androidx.ui.core.changedToDown
import androidx.ui.core.consumeDownChange
import androidx.ui.engine.geometry.Offset
import androidx.compose.Composable
import androidx.compose.memo
import androidx.compose.unaryPlus
import androidx.ui.core.IntPxSize
import androidx.ui.core.PointerInputWrapper
import androidx.ui.core.changedToUpIgnoreConsumed
import androidx.ui.core.gesture.util.anyPointersInBounds
/**
* This gesture detector has callbacks for when a press gesture starts and ends for the purposes of
* displaying visual feedback for those two states.
*
* More specifically:
* - It will call [onStart] if the first pointer down it receives during the
* [PointerEventPass.PostUp] pass is not consumed.
* - It will call [onStop] if [onStart] has been called and the last [PointerInputChange] it
* receives during the [PointerEventPass.PostUp] pass has an up change, consumed or not, indicating
* the press gesture indication should end.
* - It will call [onCancel] if movement has been consumed by the time of the
* [PointerEventPass.PostDown] pass, indicating that the press gesture indication should end because
* something moved.
*
* This gesture detector always consumes the down change during the [PointerEventPass.PostUp] pass.
*/
// TODO(b/139020678): Probably has shared functionality with other press based detectors.
@Composable
fun PressIndicatorGestureDetector(
onStart: ((PxPosition) -> Unit)? = null,
onStop: (() -> Unit)? = null,
onCancel: (() -> Unit)? = null,
children: @Composable() () -> Unit
) {
val recognizer = +memo { PressIndicatorGestureRecognizer() }
recognizer.onStart = onStart
recognizer.onStop = onStop
recognizer.onCancel = onCancel
PointerInputWrapper(pointerInputHandler = recognizer.pointerInputHandler, children = children)
}
internal class PressIndicatorGestureRecognizer {
/**
* Called if the first pointer's down change was not consumed by the time this gesture
* recognizer receives it in the [PointerEventPass.PostUp] pass.
*
* This callback should be used to indicate that the press state should be shown. An [Offset]
* is provided to indicate where the first pointer made contact with this gesrure detector.
*/
var onStart: ((PxPosition) -> Unit)? = null
/**
* Called if onStart was attempted to be called (it may have been null), no pointer movement
* was consumed, and the last pointer went up (consumed or not).
*
* This should be used for removing visual feedback that indicates that the press has ended with
* a completed press released gesture.
*/
var onStop: (() -> Unit)? = null
/**
* Called if onStart was attempted to be called (it may have been null), and pointer movement
* was consumed by the time of the [PointerEventPass.PostDown] reaches this gesture detector.
*
* This should be used for removing visual feedback that indicates that the press gesture was
* cancelled.
*/
var onCancel: (() -> Unit)? = null
private var started = false
val pointerInputHandler =
{ changes: List<PointerInputChange>, pass: PointerEventPass, bounds: IntPxSize ->
var internalChanges = changes
if (pass == PointerEventPass.InitialDown && started) {
internalChanges = internalChanges.map {
if (it.changedToDown()) {
it.consumeDownChange()
} else {
it
}
}
}
if (pass == PointerEventPass.PostUp) {
if (!started && internalChanges.all { it.changedToDown() }) {
// If we have not yet started and all of the changes changed to down, we are
// starting.
started = true
onStart?.invoke(internalChanges.first().current.position!!)
internalChanges = internalChanges.map { it.consumeDownChange() }
} else if (started) {
if (internalChanges.all { it.changedToUpIgnoreConsumed() }) {
// If we have started and all of the changes changed to up, we are stopping.
started = false
onStop?.invoke()
} else if (!internalChanges.anyPointersInBounds(bounds)) {
// If all of the down pointers are currently out of bounds, we should cancel
// as this indicates that the user does not which to trigger a press based
// event.
started = false
onCancel?.invoke()
}
}
if (started) {
internalChanges = internalChanges.map { it.consumeDownChange() }
}
}
if (pass == PointerEventPass.PostDown &&
started &&
internalChanges
.any { it.anyPositionChangeConsumed() }
) {
// On the final pass, if we have started and any of the changes had consumed
// position changes, we cancel.
started = false
onCancel?.invoke()
}
internalChanges
}
}