summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt120
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt7
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt13
3 files changed, 86 insertions, 54 deletions
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 454c0ecf8ac5..0acc76f8d4ef 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
@@ -16,6 +16,7 @@
package com.android.compose.animation.scene
+import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
@@ -32,80 +33,95 @@ 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.layout.LayoutCoordinates
+import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.GlobalPositionAwareModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.toSize
-internal fun Modifier.punchHole(
- layoutImpl: SceneTransitionLayoutImpl,
- element: ElementKey,
- bounds: ElementKey,
- shape: Shape,
-): Modifier = this.then(PunchHoleElement(layoutImpl, element, bounds, shape))
+/**
+ * Punch a hole in this node with the given [size], [offset] and [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.
+ */
+@Stable
+fun Modifier.punchHole(
+ size: () -> Size,
+ offset: () -> Offset,
+ shape: Shape = RectangleShape,
+): Modifier = this.then(PunchHoleElement(size, offset, shape))
+
+/**
+ * Punch a hole in this node using the bounds of [coords] and the given [shape].
+ *
+ * You can use [androidx.compose.ui.layout.onGloballyPositioned] to get the last coordinates of a
+ * node.
+ */
+@Stable
+fun Modifier.punchHole(
+ coords: () -> LayoutCoordinates?,
+ shape: Shape = RectangleShape,
+): Modifier = this.then(PunchHoleWithBoundsElement(coords, shape))
private data class PunchHoleElement(
- private val layoutImpl: SceneTransitionLayoutImpl,
- private val element: ElementKey,
- private val bounds: ElementKey,
+ private val size: () -> Size,
+ private val offset: () -> Offset,
private val shape: Shape,
) : ModifierNodeElement<PunchHoleNode>() {
- override fun create(): PunchHoleNode = PunchHoleNode(layoutImpl, element, bounds, shape)
+ override fun create(): PunchHoleNode = PunchHoleNode(size, offset, { shape })
override fun update(node: PunchHoleNode) {
- node.layoutImpl = layoutImpl
- node.element = element
- node.bounds = bounds
- node.shape = shape
+ node.size = size
+ node.offset = offset
+ node.shape = { shape }
}
}
private class PunchHoleNode(
- var layoutImpl: SceneTransitionLayoutImpl,
- var element: ElementKey,
- var bounds: ElementKey,
- var shape: Shape,
+ var size: () -> Size,
+ var offset: () -> Offset,
+ 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 ContentDrawScope.draw() {
- val bounds = layoutImpl.elements[bounds]
-
- if (
- bounds == null ||
- bounds.lastSharedState.size == Element.SizeUnspecified ||
- bounds.lastSharedState.offset == Offset.Unspecified
- ) {
+ val holeSize = size()
+ if (holeSize == Size.Zero) {
drawContent()
return
}
- val element = layoutImpl.elements.getValue(element)
drawIntoCanvas { canvas ->
canvas.withSaveLayer(size.toRect(), Paint()) {
drawContent()
- val offset = bounds.lastSharedState.offset - element.lastSharedState.offset
- translate(offset.x, offset.y) { drawHole(bounds) }
+ val offset = offset()
+ translate(offset.x, offset.y) { drawHole(holeSize) }
}
}
}
- private fun DrawScope.drawHole(bounds: Element) {
- val boundsSize = bounds.lastSharedState.size.toSize()
+ private fun DrawScope.drawHole(size: Size) {
if (shape == RectangleShape) {
- drawRect(Color.Black, size = boundsSize, blendMode = BlendMode.DstOut)
+ drawRect(Color.Black, size = size, blendMode = BlendMode.DstOut)
return
}
val outline =
- if (boundsSize == lastSize && layoutDirection == lastLayoutDirection) {
+ if (size == lastSize && layoutDirection == lastLayoutDirection) {
lastOutline!!
} else {
- val newOutline = shape.createOutline(boundsSize, layoutDirection, this)
- lastSize = boundsSize
+ val newOutline = shape().createOutline(size, layoutDirection, this)
+ lastSize = size
lastLayoutDirection = layoutDirection
lastOutline = newOutline
newOutline
@@ -118,3 +134,39 @@ private class PunchHoleNode(
)
}
}
+
+private data class PunchHoleWithBoundsElement(
+ private val coords: () -> LayoutCoordinates?,
+ private val shape: Shape,
+) : ModifierNodeElement<PunchHoleWithBoundsNode>() {
+ override fun create(): PunchHoleWithBoundsNode = PunchHoleWithBoundsNode(coords, shape)
+
+ override fun update(node: PunchHoleWithBoundsNode) {
+ node.holeCoords = coords
+ node.shape = shape
+ }
+}
+
+private class PunchHoleWithBoundsNode(
+ var holeCoords: () -> LayoutCoordinates?,
+ var shape: Shape,
+) : DelegatingNode(), DrawModifierNode, GlobalPositionAwareModifierNode {
+ private val delegate = delegate(PunchHoleNode(::holeSize, ::holeOffset, ::shape))
+ private var lastCoords: LayoutCoordinates? = null
+
+ override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
+ this.lastCoords = coordinates
+ }
+
+ override fun ContentDrawScope.draw() = with(delegate) { draw() }
+
+ private fun holeSize(): Size {
+ return holeCoords()?.size?.toSize() ?: Size.Zero
+ }
+
+ private fun holeOffset(): Offset {
+ val holeCoords = holeCoords() ?: return Offset.Zero
+ val lastCoords = lastCoords ?: error("draw() was called before onGloballyPositioned()")
+ return lastCoords.localPositionOf(holeCoords, relativeToSource = Offset.Zero)
+ }
+}
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 3537b7989ed5..f67df54b088c 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
@@ -26,7 +26,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
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
@@ -139,12 +138,6 @@ internal class SceneScopeImpl(
bottomOrRightBehavior = bottomBehavior,
)
- override fun Modifier.punchHole(
- element: ElementKey,
- bounds: ElementKey,
- shape: Shape
- ): Modifier = punchHole(layoutImpl, element, bounds, shape)
-
override fun Modifier.noResizeDuringTransitions(): Modifier {
return noResizeDuringTransitions(layoutState = layoutImpl.state)
}
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 0ada23a76afa..80f8c1c9e987 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
@@ -25,7 +25,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.platform.LocalDensity
@@ -230,18 +229,6 @@ interface BaseSceneScope {
): Modifier
/**
- * 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
-
- /**
* Don't resize during transitions. This can for instance be used to make sure that scrollable
* lists keep a constant size during transitions even if its elements are growing/shrinking.
*/