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

Commit ff5962db authored by Jordan Demeulenaere's avatar Jordan Demeulenaere Committed by Android (Google) Code Review
Browse files

Merge changes Iccac180e,Iae714148 into main

* changes:
  Merge Content modifiers into Content{Element,Node}
  Implement Modifier.container() using the Modifier Node API
parents 48e4c70d 9006c1a9
Loading
Loading
Loading
Loading
+25 −7
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
@@ -32,7 +31,6 @@ import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.layer.GraphicsLayer
import androidx.compose.ui.graphics.layer.drawLayer
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.modifier.ModifierLocalModifierNode
import androidx.compose.ui.node.DrawModifierNode
@@ -50,11 +48,7 @@ import androidx.compose.ui.util.fastForEach
 * The elements redirected to this container will be drawn above the content of this composable.
 */
fun Modifier.container(state: ContainerState): Modifier {
    return onPlaced { state.lastOffsetInWindow = it.positionInWindow() }
        .drawWithContent {
            drawContent()
            state.drawInOverlay(this)
        }
    return this then ContainerElement(state)
}

/**
@@ -105,6 +99,30 @@ internal interface LayerRenderer {
    fun drawInOverlay(drawScope: DrawScope)
}

private data class ContainerElement(private val state: ContainerState) :
    ModifierNodeElement<ContainerNode>() {
    override fun create(): ContainerNode {
        return ContainerNode(state)
    }

    override fun update(node: ContainerNode) {
        node.state = state
    }
}

/** A node implementing [container] that can be delegated to. */
class ContainerNode(var state: ContainerState) :
    Modifier.Node(), LayoutAwareModifierNode, DrawModifierNode {
    override fun onPlaced(coordinates: LayoutCoordinates) {
        state.lastOffsetInWindow = coordinates.positionInWindow()
    }

    override fun ContentDrawScope.draw() {
        drawContent()
        state.drawInOverlay(this)
    }
}

private data class DrawInContainerElement(
    var state: ContainerState,
    var enabled: () -> Boolean,
+82 −26
Original line number Diff line number Diff line
@@ -32,12 +32,17 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ApproachLayoutModifierNode
import androidx.compose.ui.layout.ApproachMeasureScope
import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.zIndex
import com.android.compose.animation.scene.Ancestor
import com.android.compose.animation.scene.AnimatedState
import com.android.compose.animation.scene.ContentKey
@@ -68,8 +73,8 @@ import com.android.compose.gesture.NestedScrollControlState
import com.android.compose.gesture.NestedScrollableBound
import com.android.compose.gesture.nestedScrollController
import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.ContainerNode
import com.android.compose.ui.graphics.ContainerState
import com.android.compose.ui.graphics.container
import kotlin.math.pow

/** A content defined in a [SceneTransitionLayout], i.e. a scene or an overlay. */
@@ -158,24 +163,14 @@ internal sealed class Content(
    fun Content(modifier: Modifier = Modifier, isInvisible: Boolean = false) {
        // If this content has a custom factory, provide it to the content so that the factory is
        // automatically used when calling rememberOverscrollEffect().
        val isElevationPossible =
            layoutImpl.state.isElevationPossible(content = key, element = null)
        Box(
            modifier
                .thenIf(isInvisible) { InvisibleModifier }
                .zIndex(zIndex)
                .approachLayout(
                    isMeasurementApproachInProgress = { layoutImpl.state.isTransitioning() }
                ) { measurable, constraints ->
                    // TODO(b/353679003): Use the ModifierNode API to set this *before* the
                    // approach
                    // pass is started.
                    targetSize = lookaheadSize
                    val placeable = measurable.measure(constraints)
                    layout(placeable.width, placeable.height) { placeable.place(0, 0) }
                }
                .thenIf(layoutImpl.state.isElevationPossible(content = key, element = null)) {
                    Modifier.container(containerState)
                }
                .thenIf(layoutImpl.implicitTestTags) { Modifier.testTag(key.testTag) }
            modifier.then(ContentElement(this, isElevationPossible, isInvisible)).thenIf(
                layoutImpl.implicitTestTags
            ) {
                Modifier.testTag(key.testTag)
            }
        ) {
            CompositionLocalProvider(LocalOverscrollFactory provides lastFactory) {
                scope.content()
@@ -194,6 +189,72 @@ internal sealed class Content(
    }
}

private data class ContentElement(
    private val content: Content,
    private val isElevationPossible: Boolean,
    private val isInvisible: Boolean,
) : ModifierNodeElement<ContentNode>() {
    override fun create(): ContentNode = ContentNode(content, isElevationPossible, isInvisible)

    override fun update(node: ContentNode) {
        node.update(content, isElevationPossible, isInvisible)
    }
}

private class ContentNode(
    private var content: Content,
    private var isElevationPossible: Boolean,
    private var isInvisible: Boolean,
) : DelegatingNode(), ApproachLayoutModifierNode {
    private var containerDelegate = containerDelegate(isElevationPossible)

    private fun containerDelegate(isElevationPossible: Boolean): ContainerNode? {
        return if (isElevationPossible) delegate(ContainerNode(content.containerState)) else null
    }

    fun update(content: Content, isElevationPossible: Boolean, isInvisible: Boolean) {
        if (content != this.content || isElevationPossible != this.isElevationPossible) {
            this.content = content
            this.isElevationPossible = isElevationPossible

            containerDelegate?.let { undelegate(it) }
            containerDelegate = containerDelegate(isElevationPossible)
        }

        this.isInvisible = isInvisible
    }

    override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean = false

    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints,
    ): MeasureResult {
        check(isLookingAhead)
        return measurable.measure(constraints).run {
            content.targetSize = IntSize(width, height)
            layout(width, height) {
                if (!isInvisible) {
                    place(0, 0, zIndex = content.zIndex)
                }
            }
        }
    }

    override fun ApproachMeasureScope.approachMeasure(
        measurable: Measurable,
        constraints: Constraints,
    ): MeasureResult {
        return measurable.measure(constraints).run {
            layout(width, height) {
                if (!isInvisible) {
                    place(0, 0, zIndex = content.zIndex)
                }
            }
        }
    }
}

internal class ContentEffects(factory: OverscrollFactory) {
    val overscrollEffect = factory.createOverscrollEffect()
    val gestureEffect = GestureEffect(overscrollEffect)
@@ -307,8 +368,3 @@ internal class ContentScopeImpl(
        )
    }
}

private val InvisibleModifier =
    Modifier.layout { measurable, constraints ->
        measurable.measure(constraints).run { layout(width, height) {} }
    }