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

Commit b29945a6 authored by omarmt's avatar omarmt
Browse files

NestedScrollToScene communicate desired behavior while scrolling

This CL introduces a new node but no new behavior.
The NestedScrollConnection will be moved to SwipeToScene, in a following
CL.

Test: Just a refactor
Bug: 336710600
Flag: com.android.systemui.scene_container
Change-Id: Ib3485505bb639d9bc84d52f0796ee65253624aba
parent d441a8bc
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -64,6 +64,7 @@ internal class DraggableHandlerImpl(
    internal val orientation: Orientation,
    internal val coroutineScope: CoroutineScope,
) : DraggableHandler {
    internal val nestedScrollKey = Any()
    /** The [DraggableHandler] can only have one active [DragController] at a time. */
    private var dragController: DragControllerImpl? = null

+64 −5
Original line number Diff line number Diff line
@@ -18,6 +18,9 @@ package com.android.compose.animation.scene

import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode
import androidx.compose.ui.node.DelegatableNode
import androidx.compose.ui.node.DelegatingNode
@@ -122,13 +125,55 @@ private data class NestedScrollToSceneElement(
}

private class NestedScrollToSceneNode(
    layoutImpl: SceneTransitionLayoutImpl,
    orientation: Orientation,
    topOrLeftBehavior: NestedScrollBehavior,
    bottomOrRightBehavior: NestedScrollBehavior,
    isExternalOverscrollGesture: () -> Boolean,
    private var layoutImpl: SceneTransitionLayoutImpl,
    private var orientation: Orientation,
    private var topOrLeftBehavior: NestedScrollBehavior,
    private var bottomOrRightBehavior: NestedScrollBehavior,
    private var isExternalOverscrollGesture: () -> Boolean,
) : DelegatingNode() {
    lateinit var pointersInfoOwner: PointersInfoOwner
    private var scrollBehaviorOwner: ScrollBehaviorOwner? = null

    private fun requireScrollBehaviorOwner(): ScrollBehaviorOwner {
        var behaviorOwner = scrollBehaviorOwner
        if (behaviorOwner == null) {
            behaviorOwner = requireScrollBehaviorOwner(layoutImpl.draggableHandler(orientation))
            scrollBehaviorOwner = behaviorOwner
        }
        return behaviorOwner
    }

    val updateScrollBehaviorsConnection =
        object : NestedScrollConnection {
            /**
             * When using [NestedScrollConnection.onPostScroll], we can specify the desired behavior
             * before our parent components. This gives them the option to override our behavior if
             * they choose.
             *
             * The behavior can be communicated at every scroll gesture to ensure that the hierarchy
             * is respected, even if one of our descendant nodes changes behavior after we set it.
             */
            override fun onPostScroll(
                consumed: Offset,
                available: Offset,
                source: NestedScrollSource,
            ): Offset {
                // If we have some remaining scroll, that scroll can be used to initiate a
                // transition between scenes. We can assume that the behavior is only needed if
                // there is some remaining amount.
                if (available != Offset.Zero) {
                    requireScrollBehaviorOwner()
                        .updateScrollBehaviors(
                            topOrLeftBehavior = topOrLeftBehavior,
                            bottomOrRightBehavior = bottomOrRightBehavior,
                            isExternalOverscrollGesture = isExternalOverscrollGesture,
                        )
                }

                return Offset.Zero
            }
        }

    private var priorityNestedScrollConnection: PriorityNestedScrollConnection =
        scenePriorityNestedScrollConnection(
            layoutImpl = layoutImpl,
@@ -145,14 +190,22 @@ private class NestedScrollToSceneNode(
            dispatcher = null,
        )

    private var updateScrollBehaviorsNestedScrollNode: DelegatableNode =
        nestedScrollModifierNode(
            connection = updateScrollBehaviorsConnection,
            dispatcher = null,
        )

    override fun onAttach() {
        pointersInfoOwner = requireAncestorPointersInfoOwner()
        delegate(nestedScrollNode)
        delegate(updateScrollBehaviorsNestedScrollNode)
    }

    override fun onDetach() {
        // Make sure we reset the scroll connection when this modifier is removed from composition
        priorityNestedScrollConnection.reset()
        scrollBehaviorOwner = null
    }

    fun update(
@@ -162,6 +215,12 @@ private class NestedScrollToSceneNode(
        bottomOrRightBehavior: NestedScrollBehavior,
        isExternalOverscrollGesture: () -> Boolean,
    ) {
        this.layoutImpl = layoutImpl
        this.orientation = orientation
        this.topOrLeftBehavior = topOrLeftBehavior
        this.bottomOrRightBehavior = bottomOrRightBehavior
        this.isExternalOverscrollGesture = isExternalOverscrollGesture

        // Clean up the old nested scroll connection
        priorityNestedScrollConnection.reset()
        undelegate(nestedScrollNode)
+48 −5
Original line number Diff line number Diff line
@@ -22,9 +22,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.node.DelegatableNode
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.PointerInputModifierNode
import androidx.compose.ui.node.TraversableNode
import androidx.compose.ui.node.findNearestAncestor
import androidx.compose.ui.unit.IntSize

/**
@@ -53,7 +56,7 @@ private class SwipeToSceneNode(
    draggableHandler: DraggableHandlerImpl,
    swipeDetector: SwipeDetector,
) : DelegatingNode(), PointerInputModifierNode {
    private val delegate =
    private val multiPointerDraggableNode =
        delegate(
            MultiPointerDraggableNode(
                orientation = draggableHandler.orientation,
@@ -74,21 +77,25 @@ private class SwipeToSceneNode(
                // 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
                multiPointerDraggableNode.orientation = value.orientation
            }
        }

    override fun onAttach() {
        delegate(ScrollBehaviorOwnerNode(draggableHandler.nestedScrollKey))
    }

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

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

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

    private fun currentScene(): Scene {
@@ -116,3 +123,39 @@ private class SwipeToSceneNode(
        return currentScene().shouldEnableSwipes(oppositeOrientation)
    }
}

/** Find the [ScrollBehaviorOwner] for the current orientation. */
internal fun DelegatableNode.requireScrollBehaviorOwner(
    draggableHandler: DraggableHandlerImpl
): ScrollBehaviorOwner {
    val ancestorNode =
        checkNotNull(findNearestAncestor(draggableHandler.nestedScrollKey)) {
            "This should never happen! Couldn't find a ScrollBehaviorOwner. " +
                "Are we inside an SceneTransitionLayout?"
        }
    return ancestorNode as ScrollBehaviorOwner
}

internal fun interface ScrollBehaviorOwner {
    fun updateScrollBehaviors(
        topOrLeftBehavior: NestedScrollBehavior,
        bottomOrRightBehavior: NestedScrollBehavior,
        isExternalOverscrollGesture: () -> Boolean,
    )
}

/**
 * We need a node that receives the desired behavior.
 *
 * TODO(b/353234530) move this logic into [SwipeToSceneNode]
 */
private class ScrollBehaviorOwnerNode(override val traverseKey: Any) :
    Modifier.Node(), TraversableNode, ScrollBehaviorOwner {
    override fun updateScrollBehaviors(
        topOrLeftBehavior: NestedScrollBehavior,
        bottomOrRightBehavior: NestedScrollBehavior,
        isExternalOverscrollGesture: () -> Boolean
    ) {
        // This method will be used to update the desired behavior in a following CL.
    }
}