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

Commit 0b21899e authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Migrate Modifier.swipeToScene to the Modifier.Node API

Bug: 291071158
Test: atest PlatformComposeSceneTransitionLayoutTests
Test: Manual in the gallery app
Flag: N/A
Change-Id: Ic0d0cc2935378d166b9883cb058f72f5c13b2d99
parent dab71a3a
Loading
Loading
Loading
Loading
+12 −13
Original line number Diff line number Diff line
@@ -64,8 +64,8 @@ import androidx.compose.ui.util.fastForEach
@Stable
internal fun Modifier.multiPointerDraggable(
    orientation: Orientation,
    enabled: Boolean,
    startDragImmediately: Boolean,
    enabled: () -> Boolean,
    startDragImmediately: () -> Boolean,
    onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
    onDragDelta: (delta: Float) -> Unit,
    onDragStopped: (velocity: Float) -> Unit,
@@ -83,8 +83,8 @@ internal fun Modifier.multiPointerDraggable(

private data class MultiPointerDraggableElement(
    private val orientation: Orientation,
    private val enabled: Boolean,
    private val startDragImmediately: Boolean,
    private val enabled: () -> Boolean,
    private val startDragImmediately: () -> Boolean,
    private val onDragStarted:
        (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
    private val onDragDelta: (Float) -> Unit,
@@ -110,10 +110,10 @@ private data class MultiPointerDraggableElement(
    }
}

private class MultiPointerDraggableNode(
internal class MultiPointerDraggableNode(
    orientation: Orientation,
    enabled: Boolean,
    var startDragImmediately: Boolean,
    enabled: () -> Boolean,
    var startDragImmediately: () -> Boolean,
    var onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
    var onDragDelta: (Float) -> Unit,
    var onDragStopped: (velocity: Float) -> Unit,
@@ -122,7 +122,7 @@ private class MultiPointerDraggableNode(
    private val delegate = delegate(SuspendingPointerInputModifierNode(pointerInputHandler))
    private val velocityTracker = VelocityTracker()

    var enabled: Boolean = enabled
    var enabled: () -> Boolean = enabled
        set(value) {
            // Reset the pointer input whenever enabled changed.
            if (value != field) {
@@ -133,7 +133,7 @@ private class MultiPointerDraggableNode(

    var orientation: Orientation = orientation
        set(value) {
            // Reset the pointer input whenever enabled orientation.
            // Reset the pointer input whenever orientation changed.
            if (value != field) {
                field = value
                delegate.resetPointerInputHandler()
@@ -149,7 +149,7 @@ private class MultiPointerDraggableNode(
    ) = delegate.onPointerEvent(pointerEvent, pass, bounds)

    private suspend fun PointerInputScope.pointerInput() {
        if (!enabled) {
        if (!enabled()) {
            return
        }

@@ -163,8 +163,7 @@ private class MultiPointerDraggableNode(
        val onDragEnd: () -> Unit = {
            val maxFlingVelocity =
                currentValueOf(LocalViewConfiguration).maximumFlingVelocity.let { max ->
                    val maxF = max.toFloat()
                    Velocity(maxF, maxF)
                    Velocity(max, max)
                }

            val velocity = velocityTracker.calculateVelocity(maxFlingVelocity)
@@ -183,7 +182,7 @@ private class MultiPointerDraggableNode(

        detectDragGestures(
            orientation = orientation,
            startDragImmediately = { startDragImmediately },
            startDragImmediately = startDragImmediately,
            onDragStart = onDragStart,
            onDragEnd = onDragEnd,
            onDragCancel = onDragCancel,
+84 −26
Original line number Diff line number Diff line
@@ -17,40 +17,98 @@
package com.android.compose.animation.scene

import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.PointerInputModifierNode
import androidx.compose.ui.unit.IntSize

/**
 * Configures the swipeable behavior of a [SceneTransitionLayout] depending on the current state.
 */
@Stable
internal fun Modifier.swipeToScene(gestureHandler: SceneGestureHandler): Modifier {
    /** Whether swipe should be enabled in the given [orientation]. */
    fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean =
        userActions.keys.any { it is Swipe && it.direction.orientation == orientation }
    return this.then(SwipeToSceneElement(gestureHandler))
}

    val layoutImpl = gestureHandler.layoutImpl
    val currentScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
    val orientation = gestureHandler.orientation
    val canSwipe = currentScene.shouldEnableSwipes(orientation)
    val canOppositeSwipe =
        currentScene.shouldEnableSwipes(
            when (orientation) {
                Orientation.Vertical -> Orientation.Horizontal
                Orientation.Horizontal -> Orientation.Vertical
private data class SwipeToSceneElement(
    val gestureHandler: SceneGestureHandler,
) : ModifierNodeElement<SwipeToSceneNode>() {
    override fun create(): SwipeToSceneNode = SwipeToSceneNode(gestureHandler)

    override fun update(node: SwipeToSceneNode) {
        node.gestureHandler = gestureHandler
    }
}
        )

    return multiPointerDraggable(
        orientation = orientation,
        enabled = gestureHandler.isDrivingTransition || canSwipe,
        // Immediately start the drag if this our [transition] is currently animating to a scene
        // (i.e. the user released their input pointer after swiping in this orientation) and the
        // user can't swipe in the other direction.
        startDragImmediately =
            gestureHandler.isDrivingTransition &&
                gestureHandler.swipeTransition.isAnimatingOffset &&
                !canOppositeSwipe,
private class SwipeToSceneNode(
    gestureHandler: SceneGestureHandler,
) : DelegatingNode(), PointerInputModifierNode {
    private val delegate =
        delegate(
            MultiPointerDraggableNode(
                orientation = gestureHandler.orientation,
                enabled = ::enabled,
                startDragImmediately = ::startDragImmediately,
                onDragStarted = gestureHandler.draggable::onDragStarted,
                onDragDelta = gestureHandler.draggable::onDelta,
                onDragStopped = gestureHandler.draggable::onDragStopped,
            )
        )

    var gestureHandler: SceneGestureHandler = gestureHandler
        set(value) {
            if (value != field) {
                field = value

                // Make sure to update the delegate orientation. Note that this will automatically
                // reset the underlying pointer input handler, so previous gestures will be
                // cancelled.
                delegate.orientation = value.orientation
            }
        }

    override fun onPointerEvent(
        pointerEvent: PointerEvent,
        pass: PointerEventPass,
        bounds: IntSize,
    ) = delegate.onPointerEvent(pointerEvent, pass, bounds)

    override fun onCancelPointerInput() = delegate.onCancelPointerInput()

    private fun enabled(): Boolean {
        return gestureHandler.isDrivingTransition ||
            currentScene().shouldEnableSwipes(gestureHandler.orientation)
    }

    private fun currentScene(): Scene {
        val layoutImpl = gestureHandler.layoutImpl
        return layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
    }

    /** Whether swipe should be enabled in the given [orientation]. */
    private fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean {
        return userActions.keys.any { it is Swipe && it.direction.orientation == orientation }
    }

    private fun startDragImmediately(): Boolean {
        // Immediately start the drag if this our transition is currently animating to a scene
        // (i.e. the user released their input pointer after swiping in this orientation) and the
        // user can't swipe in the other direction.
        return gestureHandler.isDrivingTransition &&
            gestureHandler.swipeTransition.isAnimatingOffset &&
            !canOppositeSwipe()
    }

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