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

Commit 22ed296e authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Rename fadingBackground to animatedBackground with () -> Color param

This CL renames fadingBackground() to animatedBackground() and makes the
color parameter be a () -> Color, so that the color can also be
animated. This will be used by Expandable so that its color value is
read during the drawing phase.

The implementation is also now based on the Modifier Node API, and is
mostly a fork of the background() modifier implementation.

Bug: 402059261
Test: FooterActionsScreenshotTest
Flag: EXEMPT simple refactoring
Change-Id: Id4751ddb690b4c94d2cb669141cd27a2eb349b56
parent 2d07926f
Loading
Loading
Loading
Loading
+159 −0
Original line number Diff line number Diff line
@@ -17,32 +17,37 @@
package com.android.compose.modifiers

import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.DrawModifier
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.ObserverModifierNode
import androidx.compose.ui.node.invalidateDraw
import androidx.compose.ui.node.observeReads
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.platform.InspectorValueInfo
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.unit.LayoutDirection

/**
 * Draws a fading [shape] with a solid [color] and [alpha] behind the content.
 * Draws a background in a given [shape] and with a [color] or [alpha] that can be animated.
 *
 * @param color color to paint background with
 * @param alpha alpha of the background
 * @param shape desired shape of the background
 */
fun Modifier.fadingBackground(color: Color, alpha: () -> Float, shape: Shape = RectangleShape) =
fun Modifier.animatedBackground(
    color: () -> Color,
    alpha: () -> Float = DefaultAlpha,
    shape: Shape = RectangleShape,
) =
    this.then(
        FadingBackground(
            brush = SolidColor(color),
        BackgroundElement(
            color = color,
            alpha = alpha,
            shape = shape,
            inspectorInfo =
@@ -56,17 +61,53 @@ fun Modifier.fadingBackground(color: Color, alpha: () -> Float, shape: Shape = R
        )
    )

private class FadingBackground
constructor(
    private val brush: Brush,
    private val shape: Shape,
private val DefaultAlpha = { 1f }

private class BackgroundElement(
    private val color: () -> Color,
    private val alpha: () -> Float,
    inspectorInfo: InspectorInfo.() -> Unit,
) : DrawModifier, InspectorValueInfo(inspectorInfo) {
    // naive cache outline calculation if size is the same
    private var lastSize: Size? = null
    private val shape: Shape,
    private val inspectorInfo: InspectorInfo.() -> Unit,
) : ModifierNodeElement<BackgroundNode>() {
    override fun create(): BackgroundNode {
        return BackgroundNode(color, alpha, shape)
    }

    override fun update(node: BackgroundNode) {
        node.color = color
        node.alpha = alpha
        node.shape = shape
    }

    override fun InspectorInfo.inspectableProperties() {
        inspectorInfo()
    }

    override fun hashCode(): Int {
        var result = color.hashCode()
        result = 31 * result + alpha.hashCode()
        result = 31 * result + shape.hashCode()
        return result
    }

    override fun equals(other: Any?): Boolean {
        val otherModifier = other as? BackgroundElement ?: return false
        return color == otherModifier.color &&
            alpha == otherModifier.alpha &&
            shape == otherModifier.shape
    }
}

private class BackgroundNode(var color: () -> Color, var alpha: () -> Float, var shape: Shape) :
    DrawModifierNode, Modifier.Node(), ObserverModifierNode {

    // Naively cache outline calculation if input parameters are the same, we manually observe
    // reads inside shape#createOutline separately
    private var lastSize: Size = Size.Unspecified
    private var lastLayoutDirection: LayoutDirection? = null
    private var lastOutline: Outline? = null
    private var lastShape: Shape? = null
    private var tmpOutline: Outline? = null

    override fun ContentDrawScope.draw() {
        if (shape === RectangleShape) {
@@ -78,36 +119,41 @@ constructor(
        drawContent()
    }

    override fun onObservedReadsChanged() {
        // Reset cached properties
        lastSize = Size.Unspecified
        lastLayoutDirection = null
        lastOutline = null
        lastShape = null
        // Invalidate draw so we build the cache again - this is needed because observeReads within
        // the draw scope obscures the state reads from the draw scope's observer
        invalidateDraw()
    }

    private fun ContentDrawScope.drawRect() {
        drawRect(brush, alpha = alpha())
        drawRect(color = color(), alpha = alpha())
    }

    private fun ContentDrawScope.drawOutline() {
        val outline =
            if (size == lastSize && layoutDirection == lastLayoutDirection) {
                lastOutline!!
        val outline = getOutline()
        drawOutline(outline, color = color(), alpha = alpha())
    }

    private fun ContentDrawScope.getOutline(): Outline {
        val outline: Outline?
        if (size == lastSize && layoutDirection == lastLayoutDirection && lastShape == shape) {
            outline = lastOutline!!
        } else {
                shape.createOutline(size, layoutDirection, this)
            // Manually observe reads so we can directly invalidate the outline when it changes
            // Use tmpOutline to avoid creating an object reference to local var outline
            observeReads { tmpOutline = shape.createOutline(size, layoutDirection, this) }
            outline = tmpOutline
            tmpOutline = null
        }
        drawOutline(outline, brush = brush, alpha = alpha())
        lastOutline = outline
        lastSize = size
        lastLayoutDirection = layoutDirection
        lastShape = shape
        return outline!!
    }

    override fun hashCode(): Int {
        var result = brush.hashCode()
        result = 31 * result + alpha.hashCode()
        result = 31 * result + shape.hashCode()
        return result
    }

    override fun equals(other: Any?): Boolean {
        val otherModifier = other as? FadingBackground ?: return false
        return brush == otherModifier.brush &&
            alpha == otherModifier.alpha &&
            shape == otherModifier.shape
    }

    override fun toString(): String = "FadingBackground(brush=$brush, alpha = $alpha, shape=$shape)"
}
+3 −3
Original line number Diff line number Diff line
@@ -73,7 +73,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.animation.Expandable
import com.android.compose.animation.scene.ContentScope
import com.android.compose.modifiers.fadingBackground
import com.android.compose.modifiers.animatedBackground
import com.android.compose.theme.colorAttr
import com.android.systemui.Flags.notificationShadeBlur
import com.android.systemui.animation.Expandable
@@ -172,8 +172,8 @@ fun FooterActions(
    val backgroundTopRadius = dimensionResource(R.dimen.qs_corner_radius)
    val backgroundModifier =
        remember(backgroundColor, backgroundAlphaValue, backgroundTopRadius) {
            Modifier.fadingBackground(
                backgroundColor,
            Modifier.animatedBackground(
                { backgroundColor },
                backgroundAlphaValue,
                RoundedCornerShape(topStart = backgroundTopRadius, topEnd = backgroundTopRadius),
            )