Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c3337e29 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere Committed by Android (Google) Code Review
Browse files

Merge "Remove interception capabilities in STL drag handler" into main

parents 342b9599 32af9bf7
Loading
Loading
Loading
Loading
+36 −191
Original line number Original line Diff line number Diff line
@@ -14,8 +14,6 @@
 * limitations under the License.
 * limitations under the License.
 */
 */


@file:Suppress("NOTHING_TO_INLINE")

package com.android.compose.animation.scene
package com.android.compose.animation.scene


import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.Orientation
@@ -97,68 +95,11 @@ internal class DraggableHandlerImpl(
    internal val positionalThreshold
    internal val positionalThreshold
        get() = with(layoutImpl.density) { 56.dp.toPx() }
        get() = with(layoutImpl.density) { 56.dp.toPx() }


    /**
     * Whether we should immediately intercept a gesture.
     *
     * Note: if this returns true, then [onDragStarted] will be called with overSlop equal to 0f,
     * indicating that the transition should be intercepted.
     */
    internal fun shouldImmediatelyIntercept(pointersDown: PointersInfo.PointersDown?): Boolean {
        // We don't intercept the touch if we are not currently driving the transition.
        val dragController = dragController
        if (dragController?.isDrivingTransition != true) {
            return false
        }

        val swipeAnimation = dragController.swipeAnimation

        // Only intercept the current transition if one of the 2 swipes results is also a transition
        // between the same pair of contents.
        val swipes = computeSwipes(pointersDown)
        val fromContent = layoutImpl.content(swipeAnimation.currentContent)
        val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromContent)
        val currentScene = layoutImpl.state.currentScene
        val contentTransition = swipeAnimation.contentTransition
        return (upOrLeft != null &&
            contentTransition.isTransitioningBetween(
                fromContent.key,
                upOrLeft.toContent(currentScene),
            )) ||
            (downOrRight != null &&
                contentTransition.isTransitioningBetween(
                    fromContent.key,
                    downOrRight.toContent(currentScene),
                ))
    }

    override fun onDragStarted(
    override fun onDragStarted(
        pointersDown: PointersInfo.PointersDown?,
        pointersDown: PointersInfo.PointersDown?,
        overSlop: Float,
        overSlop: Float,
    ): DragController {
    ): DragController {
        if (overSlop == 0f) {
        check(overSlop != 0f)
            val oldDragController = dragController
            check(oldDragController != null && oldDragController.isDrivingTransition) {
                val isActive = oldDragController?.isDrivingTransition
                "onDragStarted(overSlop=0f) requires an active dragController, but was $isActive"
            }

            // This [transition] was already driving the animation: simply take over it.
            // Stop animating and start from the current offset.
            val oldSwipeAnimation = oldDragController.swipeAnimation

            // We need to recompute the swipe results since this is a new gesture, and the
            // fromScene.userActions may have changed.
            val swipes = oldDragController.swipes
            swipes.updateSwipesResults(
                fromContent = layoutImpl.content(oldSwipeAnimation.fromContent)
            )

            // A new gesture should always create a new SwipeAnimation. This way there cannot be
            // different gestures controlling the same transition.
            val swipeAnimation = createSwipeAnimation(oldSwipeAnimation)
            return updateDragController(swipes, swipeAnimation)
        }

        val swipes = computeSwipes(pointersDown)
        val swipes = computeSwipes(pointersDown)
        val fromContent = layoutImpl.contentForUserActions()
        val fromContent = layoutImpl.contentForUserActions()


@@ -196,7 +137,7 @@ internal class DraggableHandlerImpl(
        return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation)
        return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation)
    }
    }


    internal fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? {
    private fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? {
        return layoutImpl.swipeSourceDetector.source(
        return layoutImpl.swipeSourceDetector.source(
            layoutSize = layoutImpl.lastSize,
            layoutSize = layoutImpl.lastSize,
            position = startedPosition.round(),
            position = startedPosition.round(),
@@ -291,71 +232,25 @@ private class DragControllerImpl(
            return 0f
            return 0f
        }
        }


        val toContent = swipeAnimation.toContent
        val distance = swipeAnimation.distance()
        val distance = swipeAnimation.distance()
        val previousOffset = swipeAnimation.dragOffset
        val previousOffset = swipeAnimation.dragOffset
        val desiredOffset = previousOffset + delta
        val desiredOffset = previousOffset + delta

        fun hasReachedToSceneUpOrLeft() =
            distance < 0 &&
                desiredOffset <= distance &&
                swipes.upOrLeftResult?.toContent(layoutState.currentScene) == toContent

        fun hasReachedToSceneDownOrRight() =
            distance > 0 &&
                desiredOffset >= distance &&
                swipes.downOrRightResult?.toContent(layoutState.currentScene) == toContent

        // Considering accelerated swipe: Change fromContent in the case where the user quickly
        // swiped multiple times in the same direction to accelerate the transition from A => B then
        // B => C.
        //
        // TODO(b/290184746): the second drag needs to pass B to work. Add support for flinging
        //  twice before B has been reached
        val hasReachedToContent =
            swipeAnimation.currentContent == toContent &&
                (hasReachedToSceneUpOrLeft() || hasReachedToSceneDownOrRight())

        val fromContent: ContentKey
        val currentTransitionOffset: Float
        val newOffset: Float
        val consumedDelta: Float
        if (hasReachedToContent) {
            // The new transition will start from the current toContent.
            fromContent = toContent

            // The current transition is completed (we have reached the full swipe distance).
            currentTransitionOffset = distance

            // The next transition will start with the remaining offset.
            newOffset = desiredOffset - distance
            consumedDelta = delta
        } else {
            fromContent = swipeAnimation.fromContent
        val desiredProgress = swipeAnimation.computeProgress(desiredOffset)
        val desiredProgress = swipeAnimation.computeProgress(desiredOffset)


        // Note: the distance could be negative if fromContent is above or to the left of
        // Note: the distance could be negative if fromContent is above or to the left of
        // toContent.
        // toContent.
            currentTransitionOffset =
        val newOffset =
            when {
            when {
                distance == DistanceUnspecified ||
                distance == DistanceUnspecified ||
                    swipeAnimation.contentTransition.isWithinProgressRange(desiredProgress) ->
                    swipeAnimation.contentTransition.isWithinProgressRange(desiredProgress) ->
                    desiredOffset
                    desiredOffset

                distance > 0f -> desiredOffset.fastCoerceIn(0f, distance)
                distance > 0f -> desiredOffset.fastCoerceIn(0f, distance)
                else -> desiredOffset.fastCoerceIn(distance, 0f)
                else -> desiredOffset.fastCoerceIn(distance, 0f)
            }
            }


            // If there is a new transition, we will use the same offset
        val consumedDelta = newOffset - previousOffset
            newOffset = currentTransitionOffset
            consumedDelta = newOffset - previousOffset
        }

        swipeAnimation.dragOffset = currentTransitionOffset


        if (hasReachedToContent) {
        swipeAnimation.dragOffset = newOffset
            swipes.updateSwipesResults(draggableHandler.layoutImpl.content(fromContent))
        }
        val result = swipes.findUserActionResult(directionOffset = newOffset)
        val result = swipes.findUserActionResult(directionOffset = newOffset)


        if (result == null) {
        if (result == null) {
@@ -372,13 +267,12 @@ private class DragControllerImpl(


        val needNewTransition =
        val needNewTransition =
            !currentTransitionIrreversible &&
            !currentTransitionIrreversible &&
                (hasReachedToContent ||
                (result.toContent(layoutState.currentScene) != swipeAnimation.toContent ||
                    result.toContent(layoutState.currentScene) != swipeAnimation.toContent ||
                    result.transitionKey != swipeAnimation.contentTransition.key)
                    result.transitionKey != swipeAnimation.contentTransition.key)


        if (needNewTransition) {
        if (needNewTransition) {
            // Make sure the current transition will finish to the right current scene.
            // Make sure the current transition will finish to the right current scene.
            swipeAnimation.currentContent = fromContent
            swipeAnimation.currentContent = swipeAnimation.fromContent


            val newSwipeAnimation = draggableHandler.createSwipeAnimation(swipes, result)
            val newSwipeAnimation = draggableHandler.createSwipeAnimation(swipes, result)
            newSwipeAnimation.dragOffset = newOffset
            newSwipeAnimation.dragOffset = newOffset
@@ -499,7 +393,9 @@ internal class Swipes(val upOrLeft: Swipe.Resolved, val downOrRight: Swipe.Resol
    var upOrLeftResult: UserActionResult? = null
    var upOrLeftResult: UserActionResult? = null
    var downOrRightResult: UserActionResult? = null
    var downOrRightResult: UserActionResult? = null


    fun computeSwipesResults(fromContent: Content): Pair<UserActionResult?, UserActionResult?> {
    private fun computeSwipesResults(
        fromContent: Content
    ): Pair<UserActionResult?, UserActionResult?> {
        val upOrLeftResult = fromContent.findActionResultBestMatch(swipe = upOrLeft)
        val upOrLeftResult = fromContent.findActionResultBestMatch(swipe = upOrLeft)
        val downOrRightResult = fromContent.findActionResultBestMatch(swipe = downOrRight)
        val downOrRightResult = fromContent.findActionResultBestMatch(swipe = downOrRight)
        return upOrLeftResult to downOrRightResult
        return upOrLeftResult to downOrRightResult
@@ -564,48 +460,9 @@ internal class NestedScrollHandlerImpl(
                .shouldEnableSwipes(draggableHandler.orientation)
                .shouldEnableSwipes(draggableHandler.orientation)
        }
        }


        var isIntercepting = false

        return PriorityNestedScrollConnection(
        return PriorityNestedScrollConnection(
            orientation = draggableHandler.orientation,
            orientation = draggableHandler.orientation,
            canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ ->
            canStartPreScroll = { _, _, _ -> false },
                val pointersDown: PointersInfo.PointersDown? =
                    when (val info = pointersInfoOwner.pointersInfo()) {
                        PointersInfo.MouseWheel -> {
                            // Do not support mouse wheel interactions
                            return@PriorityNestedScrollConnection false
                        }

                        is PointersInfo.PointersDown -> info
                        null -> null
                    }

                canChangeScene =
                    if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f

                val canInterceptSwipeTransition =
                    canChangeScene &&
                        offsetAvailable != 0f &&
                        draggableHandler.shouldImmediatelyIntercept(pointersDown)
                if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false

                val layoutImpl = draggableHandler.layoutImpl
                val threshold = layoutImpl.transitionInterceptionThreshold
                val hasSnappedToIdle = layoutImpl.state.snapToIdleIfClose(threshold)
                if (hasSnappedToIdle) {
                    // If the current swipe transition is closed to 0f or 1f, then we want to
                    // interrupt the transition (snapping it to Idle) and scroll the list.
                    return@PriorityNestedScrollConnection false
                }

                lastPointersDown = pointersDown

                // If the current swipe transition is *not* closed to 0f or 1f, then we want the
                // scroll events to intercept the current transition to continue the scene
                // transition.
                isIntercepting = true
                true
            },
            canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ ->
            canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ ->
                val behavior: NestedScrollBehavior =
                val behavior: NestedScrollBehavior =
                    when {
                    when {
@@ -629,7 +486,6 @@ internal class NestedScrollHandlerImpl(
                    }
                    }
                lastPointersDown = pointersDown
                lastPointersDown = pointersDown


                val canStart =
                when (behavior) {
                when (behavior) {
                    NestedScrollBehavior.EdgeNoPreview -> {
                    NestedScrollBehavior.EdgeNoPreview -> {
                        canChangeScene = isZeroOffset
                        canChangeScene = isZeroOffset
@@ -646,12 +502,6 @@ internal class NestedScrollHandlerImpl(
                        shouldEnableSwipes()
                        shouldEnableSwipes()
                    }
                    }
                }
                }

                if (canStart) {
                    isIntercepting = false
                }

                canStart
            },
            },
            canStartPostFling = { velocityAvailable ->
            canStartPostFling = { velocityAvailable ->
                val behavior: NestedScrollBehavior =
                val behavior: NestedScrollBehavior =
@@ -676,19 +526,14 @@ internal class NestedScrollHandlerImpl(
                    }
                    }
                lastPointersDown = pointersDown
                lastPointersDown = pointersDown


                val canStart = behavior.canStartOnPostFling && shouldEnableSwipes()
                behavior.canStartOnPostFling && shouldEnableSwipes()
                if (canStart) {
                    isIntercepting = false
                }

                canStart
            },
            },
            onStart = { firstScroll ->
            onStart = { firstScroll ->
                scrollController(
                scrollController(
                    dragController =
                    dragController =
                        draggableHandler.onDragStarted(
                        draggableHandler.onDragStarted(
                            pointersDown = lastPointersDown,
                            pointersDown = lastPointersDown,
                            overSlop = if (isIntercepting) 0f else firstScroll,
                            overSlop = firstScroll,
                        ),
                        ),
                    canChangeScene = canChangeScene,
                    canChangeScene = canChangeScene,
                    pointersInfoOwner = pointersInfoOwner,
                    pointersInfoOwner = pointersInfoOwner,
+37 −68
Original line number Original line Diff line number Diff line
@@ -80,7 +80,6 @@ import kotlinx.coroutines.launch
@Stable
@Stable
internal fun Modifier.multiPointerDraggable(
internal fun Modifier.multiPointerDraggable(
    orientation: Orientation,
    orientation: Orientation,
    startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
    onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
    onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
    onFirstPointerDown: () -> Unit = {},
    onFirstPointerDown: () -> Unit = {},
    swipeDetector: SwipeDetector = DefaultSwipeDetector,
    swipeDetector: SwipeDetector = DefaultSwipeDetector,
@@ -89,7 +88,6 @@ internal fun Modifier.multiPointerDraggable(
    this.then(
    this.then(
        MultiPointerDraggableElement(
        MultiPointerDraggableElement(
            orientation,
            orientation,
            startDragImmediately,
            onDragStarted,
            onDragStarted,
            onFirstPointerDown,
            onFirstPointerDown,
            swipeDetector,
            swipeDetector,
@@ -99,7 +97,6 @@ internal fun Modifier.multiPointerDraggable(


private data class MultiPointerDraggableElement(
private data class MultiPointerDraggableElement(
    private val orientation: Orientation,
    private val orientation: Orientation,
    private val startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
    private val onDragStarted:
    private val onDragStarted:
        (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
        (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
    private val onFirstPointerDown: () -> Unit,
    private val onFirstPointerDown: () -> Unit,
@@ -109,7 +106,6 @@ private data class MultiPointerDraggableElement(
    override fun create(): MultiPointerDraggableNode =
    override fun create(): MultiPointerDraggableNode =
        MultiPointerDraggableNode(
        MultiPointerDraggableNode(
            orientation = orientation,
            orientation = orientation,
            startDragImmediately = startDragImmediately,
            onDragStarted = onDragStarted,
            onDragStarted = onDragStarted,
            onFirstPointerDown = onFirstPointerDown,
            onFirstPointerDown = onFirstPointerDown,
            swipeDetector = swipeDetector,
            swipeDetector = swipeDetector,
@@ -118,7 +114,6 @@ private data class MultiPointerDraggableElement(


    override fun update(node: MultiPointerDraggableNode) {
    override fun update(node: MultiPointerDraggableNode) {
        node.orientation = orientation
        node.orientation = orientation
        node.startDragImmediately = startDragImmediately
        node.onDragStarted = onDragStarted
        node.onDragStarted = onDragStarted
        node.onFirstPointerDown = onFirstPointerDown
        node.onFirstPointerDown = onFirstPointerDown
        node.swipeDetector = swipeDetector
        node.swipeDetector = swipeDetector
@@ -127,7 +122,6 @@ private data class MultiPointerDraggableElement(


internal class MultiPointerDraggableNode(
internal class MultiPointerDraggableNode(
    orientation: Orientation,
    orientation: Orientation,
    var startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
    var onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
    var onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
    var onFirstPointerDown: () -> Unit,
    var onFirstPointerDown: () -> Unit,
    swipeDetector: SwipeDetector = DefaultSwipeDetector,
    swipeDetector: SwipeDetector = DefaultSwipeDetector,
@@ -293,7 +287,6 @@ internal class MultiPointerDraggableNode(
                try {
                try {
                    detectDragGestures(
                    detectDragGestures(
                        orientation = orientation,
                        orientation = orientation,
                        startDragImmediately = startDragImmediately,
                        onDragStart = { pointersDown, overSlop ->
                        onDragStart = { pointersDown, overSlop ->
                            onDragStarted(pointersDown, overSlop)
                            onDragStarted(pointersDown, overSlop)
                        },
                        },
@@ -434,13 +427,11 @@ internal class MultiPointerDraggableNode(
     * Detect drag gestures in the given [orientation].
     * Detect drag gestures in the given [orientation].
     *
     *
     * This function is a mix of [androidx.compose.foundation.gestures.awaitDownAndSlop] and
     * This function is a mix of [androidx.compose.foundation.gestures.awaitDownAndSlop] and
     * [androidx.compose.foundation.gestures.detectVerticalDragGestures] to add support for:
     * [androidx.compose.foundation.gestures.detectVerticalDragGestures] to add support for passing
     * 1) starting the gesture immediately without requiring a drag >= touch slope;
     * the number of pointers down to [onDragStart].
     * 2) passing the number of pointers down to [onDragStart].
     */
     */
    private suspend fun AwaitPointerEventScope.detectDragGestures(
    private suspend fun AwaitPointerEventScope.detectDragGestures(
        orientation: Orientation,
        orientation: Orientation,
        startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
        onDragStart: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
        onDragStart: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
        onDrag: (controller: DragController, dragAmount: Float) -> Unit,
        onDrag: (controller: DragController, dragAmount: Float) -> Unit,
        onDragEnd: (controller: DragController) -> Unit,
        onDragEnd: (controller: DragController) -> Unit,
@@ -466,17 +457,6 @@ internal class MultiPointerDraggableNode(
                .first()
                .first()


        var overSlop = 0f
        var overSlop = 0f
        var lastPointersDown: PointersInfo.PointersDown =
            checkNotNull(pointersInfo()) {
                "We should have pointers down, last event: $currentEvent"
            }
                as PointersInfo.PointersDown

        val drag =
            if (startDragImmediately(lastPointersDown)) {
                consumablePointer.consume()
                consumablePointer
            } else {
        val onSlopReached = { change: PointerInputChange, over: Float ->
        val onSlopReached = { change: PointerInputChange, over: Float ->
            if (swipeDetector.detectSwipe(change)) {
            if (swipeDetector.detectSwipe(change)) {
                change.consume()
                change.consume()
@@ -489,27 +469,19 @@ internal class MultiPointerDraggableNode(
        val drag =
        val drag =
            when (orientation) {
            when (orientation) {
                Orientation.Horizontal ->
                Orientation.Horizontal ->
                            awaitHorizontalTouchSlopOrCancellation(
                    awaitHorizontalTouchSlopOrCancellation(consumablePointer.id, onSlopReached)
                                consumablePointer.id,
                                onSlopReached,
                            )
                Orientation.Vertical ->
                Orientation.Vertical ->
                            awaitVerticalTouchSlopOrCancellation(
                    awaitVerticalTouchSlopOrCancellation(consumablePointer.id, onSlopReached)
                                consumablePointer.id,
                                onSlopReached,
                            )
            } ?: return
            } ?: return


                lastPointersDown =
        val lastPointersDown =
            checkNotNull(pointersInfo()) {
            checkNotNull(pointersInfo()) {
                "We should have pointers down, last event: $currentEvent"
                "We should have pointers down, last event: $currentEvent"
            }
            }
                as PointersInfo.PointersDown
                as PointersInfo.PointersDown
        // Make sure that overSlop is not 0f. This can happen when the user drags by exactly
        // Make sure that overSlop is not 0f. This can happen when the user drags by exactly
        // the touch slop. However, the overSlop we pass to onDragStarted() is used to
        // the touch slop. However, the overSlop we pass to onDragStarted() is used to
                // compute the direction we are dragging in, so overSlop should never be 0f unless
        // compute the direction we are dragging in, so overSlop should never be 0f.
                // we intercept an ongoing swipe transition (i.e. startDragImmediately() returned
                // true).
        if (overSlop == 0f) {
        if (overSlop == 0f) {
            // If the user drags in the opposite direction, the delta becomes zero because
            // If the user drags in the opposite direction, the delta becomes zero because
            // we return to the original point. Therefore, we should use the previous event
            // we return to the original point. Therefore, we should use the previous event
@@ -526,11 +498,8 @@ internal class MultiPointerDraggableNode(
            }
            }
            overSlop = delta.sign
            overSlop = delta.sign
        }
        }
                drag
            }


        val controller = onDragStart(lastPointersDown, overSlop)
        val controller = onDragStart(lastPointersDown, overSlop)

        val successful: Boolean
        val successful: Boolean
        try {
        try {
            onDrag(controller, overSlop)
            onDrag(controller, overSlop)
+0 −16
Original line number Original line Diff line number Diff line
@@ -149,7 +149,6 @@ private class SwipeToSceneNode(
        delegate(
        delegate(
            MultiPointerDraggableNode(
            MultiPointerDraggableNode(
                orientation = draggableHandler.orientation,
                orientation = draggableHandler.orientation,
                startDragImmediately = ::startDragImmediately,
                onDragStarted = draggableHandler::onDragStarted,
                onDragStarted = draggableHandler::onDragStarted,
                onFirstPointerDown = ::onFirstPointerDown,
                onFirstPointerDown = ::onFirstPointerDown,
                swipeDetector = swipeDetector,
                swipeDetector = swipeDetector,
@@ -198,21 +197,6 @@ private class SwipeToSceneNode(
    ) = multiPointerDraggableNode.onPointerEvent(pointerEvent, pass, bounds)
    ) = multiPointerDraggableNode.onPointerEvent(pointerEvent, pass, bounds)


    override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput()
    override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput()

    private fun startDragImmediately(pointersDown: PointersInfo.PointersDown): Boolean {
        // Immediately start the drag if the user can't swipe in the other direction and the gesture
        // handler can intercept it.
        return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(pointersDown)
    }

    private fun canOppositeSwipe(): Boolean {
        val oppositeOrientation =
            when (draggableHandler.orientation) {
                Orientation.Vertical -> Orientation.Horizontal
                Orientation.Horizontal -> Orientation.Vertical
            }
        return draggableHandler.contentForSwipes().shouldEnableSwipes(oppositeOrientation)
    }
}
}


/** Find the [ScrollBehaviorOwner] for the current orientation. */
/** Find the [ScrollBehaviorOwner] for the current orientation. */
+6 −242

File changed.

Preview size limit exceeded, changes collapsed.

+0 −11
Original line number Original line Diff line number Diff line
@@ -101,7 +101,6 @@ class MultiPointerDraggableTest {
                    .thenIf(enabled) {
                    .thenIf(enabled) {
                        Modifier.multiPointerDraggable(
                        Modifier.multiPointerDraggable(
                            orientation = Orientation.Vertical,
                            orientation = Orientation.Vertical,
                            startDragImmediately = { false },
                            onDragStarted = { _, _ ->
                            onDragStarted = { _, _ ->
                                started = true
                                started = true
                                SimpleDragController(
                                SimpleDragController(
@@ -169,8 +168,6 @@ class MultiPointerDraggableTest {
                    .nestedScrollDispatcher()
                    .nestedScrollDispatcher()
                    .multiPointerDraggable(
                    .multiPointerDraggable(
                        orientation = Orientation.Vertical,
                        orientation = Orientation.Vertical,
                        // We want to start a drag gesture immediately
                        startDragImmediately = { true },
                        onDragStarted = { _, _ ->
                        onDragStarted = { _, _ ->
                            started = true
                            started = true
                            SimpleDragController(
                            SimpleDragController(
@@ -242,7 +239,6 @@ class MultiPointerDraggableTest {
                    .nestedScrollDispatcher()
                    .nestedScrollDispatcher()
                    .multiPointerDraggable(
                    .multiPointerDraggable(
                        orientation = Orientation.Vertical,
                        orientation = Orientation.Vertical,
                        startDragImmediately = { false },
                        onDragStarted = { _, _ ->
                        onDragStarted = { _, _ ->
                            started = true
                            started = true
                            SimpleDragController(
                            SimpleDragController(
@@ -361,7 +357,6 @@ class MultiPointerDraggableTest {
                    .nestedScrollDispatcher()
                    .nestedScrollDispatcher()
                    .multiPointerDraggable(
                    .multiPointerDraggable(
                        orientation = Orientation.Vertical,
                        orientation = Orientation.Vertical,
                        startDragImmediately = { false },
                        onDragStarted = { _, _ ->
                        onDragStarted = { _, _ ->
                            started = true
                            started = true
                            SimpleDragController(
                            SimpleDragController(
@@ -466,7 +461,6 @@ class MultiPointerDraggableTest {
                    .nestedScrollDispatcher()
                    .nestedScrollDispatcher()
                    .multiPointerDraggable(
                    .multiPointerDraggable(
                        orientation = Orientation.Vertical,
                        orientation = Orientation.Vertical,
                        startDragImmediately = { false },
                        onDragStarted = { _, _ ->
                        onDragStarted = { _, _ ->
                            verticalStarted = true
                            verticalStarted = true
                            SimpleDragController(
                            SimpleDragController(
@@ -478,7 +472,6 @@ class MultiPointerDraggableTest {
                    )
                    )
                    .multiPointerDraggable(
                    .multiPointerDraggable(
                        orientation = Orientation.Horizontal,
                        orientation = Orientation.Horizontal,
                        startDragImmediately = { false },
                        onDragStarted = { _, _ ->
                        onDragStarted = { _, _ ->
                            horizontalStarted = true
                            horizontalStarted = true
                            SimpleDragController(
                            SimpleDragController(
@@ -570,7 +563,6 @@ class MultiPointerDraggableTest {
                    .nestedScrollDispatcher()
                    .nestedScrollDispatcher()
                    .multiPointerDraggable(
                    .multiPointerDraggable(
                        orientation = Orientation.Vertical,
                        orientation = Orientation.Vertical,
                        startDragImmediately = { false },
                        swipeDetector =
                        swipeDetector =
                            object : SwipeDetector {
                            object : SwipeDetector {
                                override fun detectSwipe(change: PointerInputChange): Boolean {
                                override fun detectSwipe(change: PointerInputChange): Boolean {
@@ -636,7 +628,6 @@ class MultiPointerDraggableTest {
                    .nestedScrollDispatcher()
                    .nestedScrollDispatcher()
                    .multiPointerDraggable(
                    .multiPointerDraggable(
                        orientation = Orientation.Vertical,
                        orientation = Orientation.Vertical,
                        startDragImmediately = { false },
                        swipeDetector =
                        swipeDetector =
                            object : SwipeDetector {
                            object : SwipeDetector {
                                override fun detectSwipe(change: PointerInputChange): Boolean {
                                override fun detectSwipe(change: PointerInputChange): Boolean {
@@ -738,7 +729,6 @@ class MultiPointerDraggableTest {
                    .nestedScrollDispatcher()
                    .nestedScrollDispatcher()
                    .multiPointerDraggable(
                    .multiPointerDraggable(
                        orientation = Orientation.Vertical,
                        orientation = Orientation.Vertical,
                        startDragImmediately = { false },
                        onDragStarted = { _, _ ->
                        onDragStarted = { _, _ ->
                            SimpleDragController(
                            SimpleDragController(
                                onDrag = { consumedOnDrag = it },
                                onDrag = { consumedOnDrag = it },
@@ -809,7 +799,6 @@ class MultiPointerDraggableTest {
                    .nestedScrollDispatcher()
                    .nestedScrollDispatcher()
                    .multiPointerDraggable(
                    .multiPointerDraggable(
                        orientation = Orientation.Vertical,
                        orientation = Orientation.Vertical,
                        startDragImmediately = { false },
                        onDragStarted = { _, _ ->
                        onDragStarted = { _, _ ->
                            SimpleDragController(
                            SimpleDragController(
                                onDrag = { /* do nothing */ },
                                onDrag = { /* do nothing */ },