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
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 3873878..8552aaf 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -64,8 +64,8 @@
@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 @@
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 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 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 @@
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 @@
) = delegate.onPointerEvent(pointerEvent, pass, bounds)
private suspend fun PointerInputScope.pointerInput() {
- if (!enabled) {
+ if (!enabled()) {
return
}
@@ -163,8 +163,7 @@
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 @@
detectDragGestures(
orientation = orientation,
- startDragImmediately = { startDragImmediately },
+ startDragImmediately = startDragImmediately,
onDragStart = onDragStart,
onDragEnd = onDragEnd,
onDragCancel = onDragCancel,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 0d3bc7d..b9c4ac0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -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) {
+private data class SwipeToSceneElement(
+ val gestureHandler: SceneGestureHandler,
+) : ModifierNodeElement<SwipeToSceneNode>() {
+ override fun create(): SwipeToSceneNode = SwipeToSceneNode(gestureHandler)
+
+ override fun update(node: SwipeToSceneNode) {
+ node.gestureHandler = gestureHandler
+ }
+}
+
+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 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,
- onDragStarted = gestureHandler.draggable::onDragStarted,
- onDragDelta = gestureHandler.draggable::onDelta,
- onDragStopped = gestureHandler.draggable::onDragStopped,
- )
+ return currentScene().shouldEnableSwipes(oppositeOrientation)
+ }
}