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

Commit df6c3ed2 authored by Andreas Miko's avatar Andreas Miko
Browse files

Introduce element transformations across NestedSTLs

A STL containing NestedSTLs can now reference all elements of any
nestingDepth to apply transformations.

There are a few limitations:
- There is currently no mechanism to select priority over which STL
transition is chosen when they run in parallel. Instead the priority is
currently always the STL with the lowest nestingDepth. This means that
if a child runs a transition and an ancestor starts a transition that
has a transformation for the same element defined (only then) the
element will adhere to the transformation of the ancestor.
- Interruptions may not always work as expected. For example when the
child completes a transition which ends in a scene where the element is
not present anymore, then the ancestor transition will still end but
then the element might suddenly disappear (because it's not composed in
the child STL anymore)

Bug: 376659778
Test: new unit tests and manual test app
Flag: com.android.systemui.scene_container
Change-Id: I30a025bd749ed0e1b0b400b51532cba748456bc7
parent 38ecd007
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -434,7 +434,8 @@ private class AnimatedStateImpl<T, Delta>(
            if (element != null) {
                layoutImpl.elements[element]?.let { element ->
                    elementState(
                        layoutImpl.state.transitionStates,
                        listOf(layoutImpl.state.transitionStates),
                        elementKey = element.key,
                        isInContent = { it in element.stateByContent },
                    )
                        as? TransitionState.Transition
+175 −58

File changed.

Preview size limit exceeded, changes collapsed.

+9 −5
Original line number Diff line number Diff line
@@ -163,7 +163,7 @@ private class MovableElementScopeImpl(
            // Important: Like in Modifier.element(), we read the transition states during
            // composition then pass them to Layout to make sure that composition sees new states
            // before layout and drawing.
            val transitionStates = layoutImpl.state.transitionStates
            val transitionStates = getAllNestedTransitionStates(layoutImpl)
            Layout { _, _ ->
                // No need to measure or place anything.
                val size =
@@ -186,7 +186,7 @@ private fun shouldComposeMovableElement(
    element: MovableElementKey,
): Boolean {
    return when (
        val elementState = movableElementState(element, layoutImpl.state.transitionStates)
        val elementState = movableElementState(element, getAllNestedTransitionStates(layoutImpl))
    ) {
        null ->
            movableElementContentWhenIdle(layoutImpl, element, layoutImpl.state.transitionState) ==
@@ -221,10 +221,14 @@ private fun shouldComposeMoveableElement(

private fun movableElementState(
    element: MovableElementKey,
    transitionStates: List<TransitionState>,
    transitionStates: List<List<TransitionState>>,
): TransitionState? {
    val contents = element.contentPicker.contents
    return elementState(transitionStates, isInContent = { contents.contains(it) })
    return elementState(
        transitionStates,
        elementKey = element,
        isInContent = { contents.contains(it) },
    )
}

private fun movableElementContentWhenIdle(
@@ -245,7 +249,7 @@ private fun placeholderContentSize(
    content: ContentKey,
    element: Element,
    elementKey: MovableElementKey,
    transitionStates: List<TransitionState>,
    transitionStates: List<List<TransitionState>>,
): IntSize {
    // If the content of the movable element was already composed in this scene before, use that
    // target size.
+4 −4
Original line number Diff line number Diff line
@@ -698,7 +698,7 @@ internal fun SceneTransitionLayoutForTesting(
    transitionInterceptionThreshold: Float = 0f,
    onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
    sharedElementMap: MutableMap<ElementKey, Element> = remember { mutableMapOf() },
    ancestorContentKeys: List<ContentKey> = emptyList(),
    ancestors: List<Ancestor> = remember { emptyList() },
    lookaheadScope: LookaheadScope? = null,
    builder: SceneTransitionLayoutScope.() -> Unit,
) {
@@ -715,7 +715,7 @@ internal fun SceneTransitionLayoutForTesting(
                builder = builder,
                animationScope = animationScope,
                elements = sharedElementMap,
                ancestorContentKeys = ancestorContentKeys,
                ancestors = ancestors,
                lookaheadScope = lookaheadScope,
            )
            .also { onLayoutImpl?.invoke(it) }
@@ -738,9 +738,9 @@ internal fun SceneTransitionLayoutForTesting(
                    "when creating it, which is not supported"
            )
        }
        if (layoutImpl.ancestorContentKeys != ancestorContentKeys) {
        if (layoutImpl.ancestors != ancestors) {
            error(
                "This SceneTransitionLayout was bound to a different ancestorContents that was " +
                "This SceneTransitionLayout was bound to a different ancestors that was " +
                    "used when creating it, which is not supported"
            )
        }
+17 −4
Original line number Diff line number Diff line
@@ -57,6 +57,18 @@ import kotlinx.coroutines.launch
/** The type for the content of movable elements. */
internal typealias MovableElementContent = @Composable (@Composable () -> Unit) -> Unit

internal data class Ancestor(
    val layoutImpl: SceneTransitionLayoutImpl,

    /**
     * This is the content in which the corresponding descendant of this ancestor appears in.
     *
     * Example: When A is the root and has two scenes SA and SB and SB contains a NestedSTL called
     * B. Then A is the ancestor of B and inContent is SB.
     */
    val inContent: ContentKey,
)

@Stable
internal class SceneTransitionLayoutImpl(
    internal val state: MutableSceneTransitionLayoutStateImpl,
@@ -83,16 +95,17 @@ internal class SceneTransitionLayoutImpl(
    internal val elements: MutableMap<ElementKey, Element> = mutableMapOf(),

    /**
     * When this STL is a [NestedSceneTransitionLayout], this is a list of [ContentKey]s of where
     * this STL is composed in within its ancestors.
     * When this STL is a [NestedSceneTransitionLayout], this is a list of [Ancestor]s which
     * provides a reference to the ancestor STLs and indicates where this STL is composed in within
     * its ancestors.
     *
     * The root STL holds an emptyList. With each nesting level the parent is supposed to add
     * exactly one scene to the list, therefore the size of this list is equal to the nesting depth
     * of this STL.
     *
     * This is used to know in which content of the ancestors a sharedElement appears in.
     * This is used to enable transformations and shared elements across NestedSTLs.
     */
    internal val ancestorContentKeys: List<ContentKey> = emptyList(),
    internal val ancestors: List<Ancestor> = emptyList(),
    lookaheadScope: LookaheadScope? = null,
) {

Loading