commonMain.androidx.compose.material3.DragGestureDetectorCopy.kt Maven / Gradle / Ivy
/*
* Copyright 2022 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.compose.material3
import androidx.compose.foundation.gestures.awaitTouchSlopOrCancellation
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.AwaitPointerEventScope
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerId
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastFirstOrNull
import kotlin.math.abs
import kotlin.math.sign
// Copy-paste version of DragGestureDetector.kt. Please don't change this file without changing
// DragGestureDetector.kt
internal suspend fun AwaitPointerEventScope.awaitHorizontalPointerSlopOrCancellation(
pointerId: PointerId,
pointerType: PointerType,
onPointerSlopReached: (change: PointerInputChange, overSlop: Float) -> Unit
) = awaitPointerSlopOrCancellation(
pointerId = pointerId,
pointerType = pointerType,
onPointerSlopReached = onPointerSlopReached,
getDragDirectionValue = { it.x }
)
/**
* Waits for drag motion along one axis based on [getDragDirectionValue] to pass pointer slop,
* using [pointerId] as the pointer to examine. If [pointerId] is raised, another pointer
* from those that are down will be chosen to lead the gesture, and if none are down,
* `null` is returned. If [pointerId] is not down when [awaitPointerSlopOrCancellation] is called,
* then `null` is returned.
*
* When pointer slop is detected, [onPointerSlopReached] is called with the change and the distance
* beyond the pointer slop. [getDragDirectionValue] should return the position change in the
* direction of the drag axis. If [onPointerSlopReached] does not consume the position change,
* pointer slop will not have been considered detected and the detection will continue or,
* if it is consumed, the [PointerInputChange] that was consumed will be returned.
*
* This works with [awaitTouchSlopOrCancellation] for the other axis to ensure that only horizontal
* or vertical dragging is done, but not both.
*
* @return The [PointerInputChange] of the event that was consumed in [onPointerSlopReached] or
* `null` if all pointers are raised or the position change was consumed by another gesture
* detector.
*/
private suspend inline fun AwaitPointerEventScope.awaitPointerSlopOrCancellation(
pointerId: PointerId,
pointerType: PointerType,
onPointerSlopReached: (PointerInputChange, Float) -> Unit,
getDragDirectionValue: (Offset) -> Float
): PointerInputChange? {
if (currentEvent.isPointerUp(pointerId)) {
return null // The pointer has already been lifted, so the gesture is canceled
}
val touchSlop = viewConfiguration.pointerSlop(pointerType)
var pointer: PointerId = pointerId
var totalPositionChange = 0f
while (true) {
val event = awaitPointerEvent()
val dragEvent = event.changes.fastFirstOrNull { it.id == pointer }!!
if (dragEvent.isConsumed) {
return null
} else if (dragEvent.changedToUpIgnoreConsumed()) {
val otherDown = event.changes.fastFirstOrNull { it.pressed }
if (otherDown == null) {
// This is the last "up"
return null
} else {
pointer = otherDown.id
}
} else {
val currentPosition = dragEvent.position
val previousPosition = dragEvent.previousPosition
val positionChange = getDragDirectionValue(currentPosition) -
getDragDirectionValue(previousPosition)
totalPositionChange += positionChange
val inDirection = abs(totalPositionChange)
if (inDirection < touchSlop) {
// verify that nothing else consumed the drag event
awaitPointerEvent(PointerEventPass.Final)
if (dragEvent.isConsumed) {
return null
}
} else {
onPointerSlopReached(
dragEvent,
totalPositionChange - (sign(totalPositionChange) * touchSlop)
)
if (dragEvent.isConsumed) {
return dragEvent
} else {
totalPositionChange = 0f
}
}
}
}
}
private fun PointerEvent.isPointerUp(pointerId: PointerId): Boolean =
changes.fastFirstOrNull { it.id == pointerId }?.pressed != true
private val mouseSlop = 0.125.dp
private val defaultTouchSlop = 18.dp // The default touch slop on Android devices
private val mouseToTouchSlopRatio = mouseSlop / defaultTouchSlop
internal fun ViewConfiguration.pointerSlop(pointerType: PointerType): Float {
return when (pointerType) {
PointerType.Mouse -> touchSlop * mouseToTouchSlopRatio
else -> touchSlop
}
}