Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +1 −0 Original line number Diff line number Diff line Loading @@ -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 Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt +64 −5 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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, Loading @@ -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( Loading @@ -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) Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +48 −5 Original line number Diff line number Diff line Loading @@ -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 /** Loading Loading @@ -53,7 +56,7 @@ private class SwipeToSceneNode( draggableHandler: DraggableHandlerImpl, swipeDetector: SwipeDetector, ) : DelegatingNode(), PointerInputModifierNode { private val delegate = private val multiPointerDraggableNode = delegate( MultiPointerDraggableNode( orientation = draggableHandler.orientation, Loading @@ -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 { Loading Loading @@ -118,3 +125,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. } } Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +1 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt +64 −5 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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, Loading @@ -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( Loading @@ -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) Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +48 −5 Original line number Diff line number Diff line Loading @@ -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 /** Loading Loading @@ -53,7 +56,7 @@ private class SwipeToSceneNode( draggableHandler: DraggableHandlerImpl, swipeDetector: SwipeDetector, ) : DelegatingNode(), PointerInputModifierNode { private val delegate = private val multiPointerDraggableNode = delegate( MultiPointerDraggableNode( orientation = draggableHandler.orientation, Loading @@ -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 { Loading Loading @@ -118,3 +125,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. } }