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

Commit 7201c560 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Remove Modifier transformations (1/2)

This CL removes the ModifierTransformation interface. This interface was
only implement by PunchHole, which is now replaced by Modifier.punchHole
that can be called directly from user code.

There were 2 main reasons for this removal, which are both performance
reasons:

 - This removes the modifierTransformations() chain applied in
   http://shortn/_9vgJ2a7Yx6. With this removed and once b/311132415 is
   fixed, we will be able to make Modifier.element() be a single
   ModifierNodeElement.

 - modifierTransformations() was reading the current transition state in
   http://shortn/_EAjP93YKeQ, which means that *all elements* always
   recompose whenever the transition state has changed, just because of
   that PunchHole transformation.

Test: PunchHoleTest
Test: Manual, in the gallery app by going from Lockscreen <=> Shade
Bug: 291071158
Flag: NA
Change-Id: I45e71dce8acb98318ac61127bd5294f0a7053dda
parent d9c1b450
Loading
Loading
Loading
Loading
+0 −1
Original line number Original line Diff line number Diff line
@@ -10,7 +10,6 @@ import com.android.systemui.shade.ui.composable.Shade
fun TransitionBuilder.lockscreenToShadeTransition() {
fun TransitionBuilder.lockscreenToShadeTransition() {
    spec = tween(durationMillis = 500)
    spec = tween(durationMillis = 500)


    punchHole(Shade.Elements.QuickSettings, bounds = Shade.Elements.Scrim, Shade.Shapes.Scrim)
    translate(Shade.Elements.Scrim, Edge.Top, startsOutsideLayoutBounds = false)
    translate(Shade.Elements.Scrim, Edge.Top, startsOutsideLayoutBounds = false)
    fractionRange(end = 0.5f) {
    fractionRange(end = 0.5f) {
        fade(Shade.Elements.ScrimBackground)
        fade(Shade.Elements.ScrimBackground)
+0 −34
Original line number Original line Diff line number Diff line
@@ -161,7 +161,6 @@ internal fun Modifier.element(
                }
                }
            }
            }
        }
        }
        .modifierTransformations(layoutImpl, scene, element, sceneValues)
        .intermediateLayout { measurable, constraints ->
        .intermediateLayout { measurable, constraints ->
            val placeable =
            val placeable =
                measure(layoutImpl, scene, element, sceneValues, measurable, constraints)
                measure(layoutImpl, scene, element, sceneValues, measurable, constraints)
@@ -331,39 +330,6 @@ internal fun sharedElementTransformation(
    return sharedInFromScene
    return sharedInFromScene
}
}


/**
 * 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.
 * Whether the element is opaque or not.
 *
 *
+47 −28
Original line number Original line Diff line number Diff line
/*
/*
 * 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");
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package com.android.compose.animation.scene
package com.android.compose.animation.scene


import androidx.compose.ui.Modifier
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.geometry.toRect
@@ -28,41 +27,62 @@ import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.drawOutline
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.DrawScope
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.withSaveLayer
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.LayoutDirection
import androidx.compose.ui.unit.toSize
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 fun Modifier.punchHole(
internal class PunchHole(
    layoutImpl: SceneTransitionLayoutImpl,
    override val matcher: ElementMatcher,
    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 bounds: ElementKey,
    private val shape: Shape,
    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 lastSize: Size = Size.Unspecified
    private var lastLayoutDirection: LayoutDirection = LayoutDirection.Ltr
    private var lastLayoutDirection: LayoutDirection = LayoutDirection.Ltr
    private var lastOutline: Outline? = null
    private var lastOutline: Outline? = null


    override fun Modifier.transform(
    override fun ContentDrawScope.draw() {
        layoutImpl: SceneTransitionLayoutImpl,
        scene: Scene,
        element: Element,
        sceneValues: Element.TargetValues,
    ): Modifier {
        return drawWithContent {
        val bounds = layoutImpl.elements[bounds]
        val bounds = layoutImpl.elements[bounds]

        if (
        if (
            bounds == null ||
            bounds == null ||
                bounds.lastSharedValues.size == Element.SizeUnspecified ||
                bounds.lastSharedValues.size == Element.SizeUnspecified ||
                bounds.lastSharedValues.offset == Offset.Unspecified
                bounds.lastSharedValues.offset == Offset.Unspecified
        ) {
        ) {
            drawContent()
            drawContent()
                return@drawWithContent
            return
        }
        }

        val element = layoutImpl.elements.getValue(element)
        drawIntoCanvas { canvas ->
        drawIntoCanvas { canvas ->
            canvas.withSaveLayer(size.toRect(), Paint()) {
            canvas.withSaveLayer(size.toRect(), Paint()) {
                drawContent()
                drawContent()
@@ -72,7 +92,6 @@ internal class PunchHole(
            }
            }
        }
        }
    }
    }
    }


    private fun DrawScope.drawHole(bounds: Element) {
    private fun DrawScope.drawHole(bounds: Element) {
        val boundsSize = bounds.lastSharedValues.size.toSize()
        val boundsSize = bounds.lastSharedValues.size.toSize()
+7 −0
Original line number Original line Diff line number Diff line
@@ -27,6 +27,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.layout.intermediateLayout
import androidx.compose.ui.layout.intermediateLayout
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.IntSize
@@ -130,4 +131,10 @@ private class SceneScopeImpl(
    ) {
    ) {
        MovableElement(layoutImpl, scene, key, modifier, content)
        MovableElement(layoutImpl, scene, key, modifier, content)
    }
    }

    override fun Modifier.punchHole(
        element: ElementKey,
        bounds: ElementKey,
        shape: Shape
    ): Modifier = punchHole(layoutImpl, element, bounds, shape)
}
}
+13 −0
Original line number Original line Diff line number Diff line
@@ -23,6 +23,7 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalDensity


@@ -177,6 +178,18 @@ interface SceneScope {
        lerp: (start: T, stop: T, fraction: Float) -> T,
        lerp: (start: T, stop: T, fraction: Float) -> T,
        canOverflow: Boolean,
        canOverflow: Boolean,
    ): State<T>
    ): 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
// TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey
Loading