diff options
10 files changed, 74 insertions, 102 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt index fadbdce80cbf..22dc0aee45b4 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt @@ -10,7 +10,6 @@ import com.android.systemui.shade.ui.composable.Shade fun TransitionBuilder.lockscreenToShadeTransition() { spec = tween(durationMillis = 500) - punchHole(Shade.Elements.QuickSettings, bounds = Shade.Elements.Scrim, Shade.Shapes.Scrim) translate(Shade.Elements.Scrim, Edge.Top, startsOutsideLayoutBounds = false) fractionRange(end = 0.5f) { fade(Shade.Elements.ScrimBackground) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index 3b999e304491..cf4aa625628a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -161,7 +161,6 @@ internal fun Modifier.element( } } } - .modifierTransformations(layoutImpl, scene, element, sceneValues) .intermediateLayout { measurable, constraints -> val placeable = measure(layoutImpl, scene, element, sceneValues, measurable, constraints) @@ -332,39 +331,6 @@ internal fun sharedElementTransformation( } /** - * Chain the [com.android.compose.animation.scene.transformation.ModifierTransformation] applied - * throughout the current transition, if any. - */ -private fun Modifier.modifierTransformations( - layoutImpl: SceneTransitionLayoutImpl, - scene: Scene, - element: Element, - sceneValues: Element.TargetValues, -): Modifier { - when (val state = layoutImpl.state.transitionState) { - is TransitionState.Idle -> return this - is TransitionState.Transition -> { - val fromScene = state.fromScene - val toScene = state.toScene - if (fromScene == toScene) { - // Same as idle. - return this - } - - return layoutImpl.transitions - .transitionSpec(fromScene, state.toScene) - .transformations(element.key, scene.key) - .modifier - .fold(this) { modifier, transformation -> - with(transformation) { - modifier.transform(layoutImpl, scene, element, sceneValues) - } - } - } - } -} - -/** * Whether the element is opaque or not. * * Important: The logic here should closely match the logic in [elementAlpha]. Note that we don't diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt index a8d364060e88..560e92becba5 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package com.android.compose.animation.scene import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.geometry.toRect @@ -28,48 +27,68 @@ import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.ContentDrawScope import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.withSaveLayer +import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.toSize -import com.android.compose.animation.scene.transformation.ModifierTransformation -/** Punch a hole in an element using the bounds of another element and a given [shape]. */ -internal class PunchHole( - override val matcher: ElementMatcher, +internal fun Modifier.punchHole( + layoutImpl: SceneTransitionLayoutImpl, + element: ElementKey, + bounds: ElementKey, + shape: Shape, +): Modifier = this.then(PunchHoleElement(layoutImpl, element, bounds, shape)) + +private data class PunchHoleElement( + private val layoutImpl: SceneTransitionLayoutImpl, + private val element: ElementKey, private val bounds: ElementKey, private val shape: Shape, -) : ModifierTransformation { +) : ModifierNodeElement<PunchHoleNode>() { + override fun create(): PunchHoleNode = PunchHoleNode(layoutImpl, element, bounds, shape) + override fun update(node: PunchHoleNode) { + node.layoutImpl = layoutImpl + node.element = element + node.bounds = bounds + node.shape = shape + } +} + +private class PunchHoleNode( + var layoutImpl: SceneTransitionLayoutImpl, + var element: ElementKey, + var bounds: ElementKey, + var shape: Shape, +) : Modifier.Node(), DrawModifierNode { private var lastSize: Size = Size.Unspecified private var lastLayoutDirection: LayoutDirection = LayoutDirection.Ltr private var lastOutline: Outline? = null - override fun Modifier.transform( - layoutImpl: SceneTransitionLayoutImpl, - scene: Scene, - element: Element, - sceneValues: Element.TargetValues, - ): Modifier { - return drawWithContent { - val bounds = layoutImpl.elements[bounds] - if ( - bounds == null || - bounds.lastSharedValues.size == Element.SizeUnspecified || - bounds.lastSharedValues.offset == Offset.Unspecified - ) { + override fun ContentDrawScope.draw() { + val bounds = layoutImpl.elements[bounds] + + if ( + bounds == null || + bounds.lastSharedValues.size == Element.SizeUnspecified || + bounds.lastSharedValues.offset == Offset.Unspecified + ) { + drawContent() + return + } + + val element = layoutImpl.elements.getValue(element) + drawIntoCanvas { canvas -> + canvas.withSaveLayer(size.toRect(), Paint()) { drawContent() - return@drawWithContent - } - drawIntoCanvas { canvas -> - canvas.withSaveLayer(size.toRect(), Paint()) { - drawContent() - val offset = bounds.lastSharedValues.offset - element.lastSharedValues.offset - translate(offset.x, offset.y) { drawHole(bounds) } - } + val offset = bounds.lastSharedValues.offset - element.lastSharedValues.offset + translate(offset.x, offset.y) { drawHole(bounds) } } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt index 857a596a1404..f5b271019748 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt @@ -27,6 +27,7 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.layout.intermediateLayout import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.IntSize @@ -130,4 +131,10 @@ private class SceneScopeImpl( ) { MovableElement(layoutImpl, scene, key, modifier, content) } + + override fun Modifier.punchHole( + element: ElementKey, + bounds: ElementKey, + shape: Shape + ): Modifier = punchHole(layoutImpl, element, bounds, shape) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 30d13dfa3f70..1f698ff00141 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.State import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.platform.LocalDensity @@ -177,6 +178,18 @@ interface SceneScope { lerp: (start: T, stop: T, fraction: Float) -> T, canOverflow: Boolean, ): State<T> + + /** + * Punch a hole in this [element] using the bounds of [bounds] in [scene] and the given [shape]. + * + * Punching a hole in an element will "remove" any pixel drawn by that element in the hole area. + * This can be used to make content drawn below an opaque element visible. For example, if we + * have [this lockscreen scene](http://shortn/_VYySFnJDhN) drawn below + * [this shade scene](http://shortn/_fpxGUk0Rg7) and punch a hole in the latter using the big + * clock time bounds and a RoundedCornerShape(10dp), [this](http://shortn/_qt80IvORFj) would be + * the result. + */ + fun Modifier.punchHole(element: ElementKey, bounds: ElementKey, shape: Shape): Modifier } // TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index 64c9775a386e..6a87662135de 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -46,6 +46,12 @@ class SceneTransitionLayoutState(initialScene: SceneKey) { return (from == null || transition.fromScene == from) && (to == null || transition.toScene == to) } + + /** Whether we are transitioning from [scene] to [other], or from [other] to [scene]. */ + fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean { + return isTransitioning(from = scene, to = other) || + isTransitioning(from = other, to = scene) + } } sealed interface TransitionState { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt index 2172ed300a33..828dff27637b 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt @@ -27,7 +27,6 @@ import com.android.compose.animation.scene.transformation.AnchoredTranslate import com.android.compose.animation.scene.transformation.DrawScale import com.android.compose.animation.scene.transformation.EdgeTranslate import com.android.compose.animation.scene.transformation.Fade -import com.android.compose.animation.scene.transformation.ModifierTransformation import com.android.compose.animation.scene.transformation.PropertyTransformation import com.android.compose.animation.scene.transformation.RangedPropertyTransformation import com.android.compose.animation.scene.transformation.ScaleSize @@ -122,7 +121,6 @@ data class TransitionSpec( scene: SceneKey, ): ElementTransformations { var shared: SharedElementTransformation? = null - val modifier = mutableListOf<ModifierTransformation>() var offset: PropertyTransformation<Offset>? = null var size: PropertyTransformation<IntSize>? = null var drawScale: PropertyTransformation<Scale>? = null @@ -166,12 +164,11 @@ data class TransitionSpec( throwIfNotNull(shared, element, name = "shared") shared = transformation } - is ModifierTransformation -> modifier.add(transformation) is PropertyTransformation<*> -> onPropertyTransformation(transformation) } } - return ElementTransformations(shared, modifier, offset, size, drawScale, alpha) + return ElementTransformations(shared, offset, size, drawScale, alpha) } private fun throwIfNotNull( @@ -188,7 +185,6 @@ data class TransitionSpec( /** The transformations of an element during a transition. */ internal class ElementTransformations( val shared: SharedElementTransformation?, - val modifier: List<ModifierTransformation>, val offset: PropertyTransformation<Offset>?, val size: PropertyTransformation<IntSize>?, val drawScale: PropertyTransformation<Scale>?, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt index ca66dff5e231..f820074ec3d1 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt @@ -18,8 +18,6 @@ package com.android.compose.animation.scene import androidx.compose.animation.core.AnimationSpec import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -131,19 +129,6 @@ interface TransitionBuilder : PropertyTransformationBuilder { ) /** - * Punch a hole in the element(s) matching [matcher] that has the same bounds as [bounds] and - * using the given [shape]. - * - * Punching a hole in an element will "remove" any pixel drawn by that element in the hole area. - * This can be used to make content drawn below an opaque element visible. For example, if we - * have [this lockscreen scene](http://shortn/_VYySFnJDhN) drawn below - * [this shade scene](http://shortn/_fpxGUk0Rg7) and punch a hole in the latter using the big - * clock time bounds and a RoundedCornerShape(10dp), [this](http://shortn/_qt80IvORFj) would be - * the result. - */ - fun punchHole(matcher: ElementMatcher, bounds: ElementKey, shape: Shape = RectangleShape) - - /** * Adds the transformations in [builder] but in reversed order. This allows you to partially * reuse the definition of the transition from scene `Foo` to scene `Bar` inside the definition * of the transition from scene `Bar` to scene `Foo`. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt index bcb20913e554..8c0a5a394331 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt @@ -22,7 +22,6 @@ import androidx.compose.animation.core.Spring import androidx.compose.animation.core.VectorConverter import androidx.compose.animation.core.spring import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.Dp import com.android.compose.animation.scene.transformation.AnchoredSize import com.android.compose.animation.scene.transformation.AnchoredTranslate @@ -92,10 +91,6 @@ internal class TransitionBuilderImpl : TransitionBuilder { spec.vectorize(Float.VectorConverter).durationMillis } - override fun punchHole(matcher: ElementMatcher, bounds: ElementKey, shape: Shape) { - transformations.add(PunchHole(matcher, bounds, shape)) - } - override fun reversed(builder: TransitionBuilder.() -> Unit) { reversed = true builder() diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt index 0db8469466ef..206935558179 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt @@ -16,7 +16,6 @@ package com.android.compose.animation.scene.transformation -import androidx.compose.ui.Modifier import com.android.compose.animation.scene.Element import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.Scene @@ -52,19 +51,6 @@ internal class SharedElementTransformation( internal val scenePicker: SharedElementScenePicker, ) : Transformation -/** A transformation that is applied on the element during the whole transition. */ -internal interface ModifierTransformation : Transformation { - /** Apply the transformation to [element]. */ - // TODO(b/290184746): Figure out a public API for custom transformations that don't have access - // to these internal classes. - fun Modifier.transform( - layoutImpl: SceneTransitionLayoutImpl, - scene: Scene, - element: Element, - sceneValues: Element.TargetValues, - ): Modifier -} - /** A transformation that changes the value of an element property, like its size or offset. */ internal sealed interface PropertyTransformation<T> : Transformation { /** |