Experiement with Modifier.animateBounds(modifier: Modifier)

Add Modifier.animateBounds to demos. Will perfect it more
based on user feedback, and move to animation lib when
LookaheadLayout is stable.

Test: Ran demos
Change-Id: Ia79c5f53179af7949c04730a5a19d5d5b3a5874d
diff --git a/compose/animation/animation/integration-tests/animation-demos/build.gradle b/compose/animation/animation/integration-tests/animation-demos/build.gradle
index a44d97e..5a12844 100644
--- a/compose/animation/animation/integration-tests/animation-demos/build.gradle
+++ b/compose/animation/animation/integration-tests/animation-demos/build.gradle
@@ -1,3 +1,5 @@
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
 plugins {
     id("AndroidXPlugin")
     id("com.android.library")
@@ -23,6 +25,10 @@
     debugImplementation(project(":compose:ui:ui-tooling"))
 }
 
+tasks.withType(KotlinCompile).configureEach {
+    kotlinOptions.freeCompilerArgs += "-Xcontext-receivers"
+}
+
 android {
     namespace "androidx.compose.animation.demos"
 }
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
index ad98bd2..9243b74d 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
@@ -35,9 +35,10 @@
 import androidx.compose.animation.demos.layoutanimation.ScaleEnterExitDemo
 import androidx.compose.animation.demos.layoutanimation.ScreenTransitionDemo
 import androidx.compose.animation.demos.layoutanimation.ShrineCartDemo
+import androidx.compose.animation.demos.lookahead.AnimateBoundsModifierDemo
 import androidx.compose.animation.demos.lookahead.CraneDemo
 import androidx.compose.animation.demos.lookahead.LookaheadLayoutWithAlignmentLinesDemo
-import androidx.compose.animation.demos.lookahead.LookaheadMeasurePlaceDemo
+import androidx.compose.animation.demos.lookahead.LookaheadWithFlowRowDemo
 import androidx.compose.animation.demos.lookahead.LookaheadWithMovableContentDemo
 import androidx.compose.animation.demos.lookahead.ScreenSizeChangeDemo
 import androidx.compose.animation.demos.singlevalue.SingleValueAnimationDemo
@@ -98,6 +99,9 @@
         DemoCategory(
             "\uD83E\uDD7C\uD83E\uDDD1\u200D\uD83D\uDD2C Lookahead Animation Demos",
             listOf(
+                ComposableDemo("AnimateBoundsModifier") {
+                    AnimateBoundsModifierDemo()
+                },
                 ComposableDemo("Crane Nested Shared Element") { CraneDemo() },
                 ComposableDemo("Screen Size Change Demo") { ScreenSizeChangeDemo() },
                 ComposableDemo("Lookahead With Movable Content") {
@@ -106,7 +110,7 @@
                 ComposableDemo("Lookahead With Alignment Lines") {
                     LookaheadLayoutWithAlignmentLinesDemo()
                 },
-                ComposableDemo("Flow Row Lookahead") { LookaheadMeasurePlaceDemo() },
+                ComposableDemo("Flow Row Lookahead") { LookaheadWithFlowRowDemo() },
             )
         ),
         DemoCategory(
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsModifier.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsModifier.kt
new file mode 100644
index 0000000..801a1ff
--- /dev/null
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsModifier.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalComposeUiApi::class)
+
+package androidx.compose.animation.demos.lookahead
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector
+import androidx.compose.animation.core.FiniteAnimationSpec
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.TwoWayConverter
+import androidx.compose.animation.core.VectorConverter
+import androidx.compose.animation.core.spring
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.layout.LookaheadLayoutScope
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.round
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+context(LookaheadLayoutScope)
+fun Modifier.animateBounds(
+    modifier: Modifier,
+    sizeAnimationSpec: FiniteAnimationSpec<IntSize> = spring(
+        Spring.DampingRatioNoBouncy,
+        Spring.StiffnessMediumLow
+    ),
+    positionAnimationSpec: FiniteAnimationSpec<IntOffset> = spring(
+        Spring.DampingRatioNoBouncy,
+        Spring.StiffnessMediumLow
+    ),
+) = composed {
+    val coroutineScope = rememberCoroutineScope()
+    var placementOffset: IntOffset by remember { mutableStateOf(IntOffset.Zero) }
+
+    val offsetAnimation = remember {
+        DeferredAnimation(coroutineScope, IntOffset.VectorConverter)
+    }
+    val sizeAnimation = remember {
+        DeferredAnimation(coroutineScope, IntSize.VectorConverter)
+    }
+    val outerSizeAnimation = remember {
+        DeferredAnimation(coroutineScope, IntSize.VectorConverter)
+    }
+    // The measure logic in `intermediateLayout` is skipped in the lookahead pass, as
+    // intermediateLayout is expected to produce intermediate stages of a layout transform.
+    // When the measure block is invoked after lookahead pass, the lookahead size of the
+    // child will be accessible as a parameter to the measure block.
+    this
+        .intermediateLayout { measurable, constraints, lookaheadSize ->
+            outerSizeAnimation.updateTarget(lookaheadSize, sizeAnimationSpec)
+            val (w, h) = outerSizeAnimation.value ?: lookaheadSize
+            measurable
+                .measure(constraints)
+                .run {
+                    layout(w, h) {
+                        place(0, 0)
+                    }
+                }
+        }
+        .then(modifier)
+        .onPlaced { lookaheadScopeCoordinates, layoutCoordinates ->
+            // This block of code has the LookaheadCoordinates of the LookaheadLayout
+            // as the first parameter, and the coordinates of this modifier as the second
+            // parameter.
+
+            // localLookaheadPositionOf returns the *target* position of this
+            // modifier in the LookaheadLayout's local coordinates.
+            val targetOffset = lookaheadScopeCoordinates
+                .localLookaheadPositionOf(
+                    layoutCoordinates
+                )
+                .round()
+            offsetAnimation.updateTarget(targetOffset, positionAnimationSpec)
+
+            // localPositionOf returns the *current* position of this
+            // modifier in the LookaheadLayout's local coordinates.
+            placementOffset = lookaheadScopeCoordinates
+                .localPositionOf(
+                    layoutCoordinates, Offset.Zero
+                )
+                .round()
+        }
+        .intermediateLayout { measurable, _, lookaheadSize ->
+            // When layout changes, the lookahead pass will calculate a new final size for the
+            // child modifier. This lookahead size can be used to animate the size
+            // change, such that the animation starts from the current size and gradually
+            // change towards `lookaheadSize`.
+            sizeAnimation.updateTarget(lookaheadSize, sizeAnimationSpec)
+            // Reads the animation size if the animation is set up. Otherwise (i.e. first
+            // frame), use the lookahead size without animation.
+            val (width, height) = sizeAnimation.value ?: lookaheadSize
+            // Creates a fixed set of constraints using the animated size
+            val animatedConstraints = Constraints.fixed(width, height)
+            // Measure child/children with animated constraints.
+            val placeable = measurable.measure(animatedConstraints)
+            layout(placeable.width, placeable.height) {
+                // offsetAnimation will animate the target position whenever it changes.
+                // In order to place the child at the animated position, we need to offset
+                // the child based on the target and current position in LookaheadLayout.
+                val (x, y) = offsetAnimation.value?.let { it - placementOffset }
+                // If offsetAnimation has not been set up yet (i.e. in the first frame),
+                // skip the animation
+                    ?: (offsetAnimation.target!! - placementOffset)
+                placeable.place(x, y)
+            }
+        }
+}
+
+// Experimenting with a way to initialize animation during measurement && only take the last target
+// change in a frame (if the target was changed multiple times in the same frame) as the
+// animation target.
+internal class DeferredAnimation<T, V : AnimationVector>(
+    coroutineScope: CoroutineScope,
+    vectorConverter: TwoWayConverter<T, V>
+) {
+    val value: T?
+        get() = animatable?.value ?: target
+    var target: T? by mutableStateOf(null)
+        private set
+    private var animationSpec: FiniteAnimationSpec<T> = spring()
+    private var animatable: Animatable<T, V>? = null
+
+    init {
+        coroutineScope.launch {
+            snapshotFlow { target }.collect { target ->
+                if (target != null && target != animatable?.targetValue) {
+                    animatable?.run {
+                        launch { animateTo(target, animationSpec) }
+                    } ?: Animatable(target, vectorConverter).let {
+                        animatable = it
+                    }
+                }
+            }
+        }
+    }
+
+    fun updateTarget(targetValue: T, animationSpec: FiniteAnimationSpec<T>) {
+        target = targetValue
+        this.animationSpec = animationSpec
+    }
+}
\ No newline at end of file
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsModifierDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsModifierDemo.kt
new file mode 100644
index 0000000..2f89ffb
--- /dev/null
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/AnimateBoundsModifierDemo.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.animation.demos.lookahead
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.LookaheadLayout
+import androidx.compose.ui.layout.MeasurePolicy
+import androidx.compose.ui.unit.dp
+import java.lang.Integer.max
+import kotlin.random.Random
+
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+fun AnimateBoundsModifierDemo() {
+    var height by remember {
+        mutableStateOf(200)
+    }
+    var left by remember {
+        mutableStateOf(0)
+    }
+    var top by remember {
+        mutableStateOf(0)
+    }
+    var right by remember {
+        mutableStateOf(0)
+    }
+    var bottom by remember {
+        mutableStateOf(0)
+    }
+    var weight by remember {
+        mutableStateOf(2f)
+    }
+
+    LookaheadLayout(
+        modifier = Modifier
+            .fillMaxSize()
+            .clickable {
+                height = Random.nextInt(10, 300)
+                weight = Random
+                    .nextDouble(0.5, 4.5)
+                    .toFloat()
+
+                left = Random.nextInt(0, 200)
+                top = Random.nextInt(0, 100)
+                right = Random.nextInt(0, 200)
+                bottom = Random.nextInt(0, 100)
+            },
+        content = {
+            Column {
+                Box(
+                    Modifier
+                        .fillMaxHeight(0.5f)
+                        .fillMaxSize()
+                ) {
+                    Box(
+                        Modifier
+                            .background(Color.Gray)
+                            .animateBounds(
+                                Modifier.padding(left.dp, top.dp, right.dp, bottom.dp)
+                            )
+                            .background(Color.Red)
+                            .fillMaxSize()
+                    )
+                }
+                Row(Modifier.fillMaxSize(), verticalAlignment = Alignment.CenterVertically) {
+                    Box(
+                        Modifier
+                            .animateBounds(
+                                Modifier
+                                    .weight(weight)
+                                    .height(height.dp)
+                            )
+                            .background(Color(0xffa2d2ff), RoundedCornerShape(5.dp))
+                    )
+                    Box(
+                        Modifier
+                            .animateBounds(
+                                Modifier
+                                    .weight(1f)
+                                    .height(height.dp)
+                            )
+                            .background(Color(0xfffff3b0))
+                    )
+                }
+            }
+        }, measurePolicy = lookaheadMeasurePolicy
+    )
+}
+
+internal val lookaheadMeasurePolicy = MeasurePolicy { measurables, constraints ->
+    val contentConstraints = constraints.copy(minWidth = 0, minHeight = 0)
+    val placeables = measurables.map { it.measure(contentConstraints) }
+    val maxWidth: Int = max(placeables.maxOf { it.width }, constraints.minWidth)
+    val maxHeight = max(placeables.maxOf { it.height }, constraints.minHeight)
+    // Position the children.
+    layout(maxWidth, maxHeight) {
+        placeables.forEach {
+            it.place(0, 0)
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadMeasurePlaceDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithFlowRowDemo.kt
similarity index 72%
rename from compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadMeasurePlaceDemo.kt
rename to compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithFlowRowDemo.kt
index ae4917b..8a767c5 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadMeasurePlaceDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithFlowRowDemo.kt
@@ -36,85 +36,108 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.LookaheadLayout
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import kotlin.math.max
 
+@OptIn(ExperimentalComposeUiApi::class)
 @Composable
-fun LookaheadMeasurePlaceDemo() {
+fun LookaheadWithFlowRowDemo() {
     Column(
+        modifier = Modifier.padding(10.dp),
         horizontalAlignment = Alignment.CenterHorizontally
     ) {
         var isHorizontal by remember { mutableStateOf(true) }
-        Button(modifier = Modifier.padding(top = 20.dp, bottom = 20.dp),
-            onClick = { isHorizontal = !isHorizontal }) {
-            Text("Toggle")
-        }
-        Column(Modifier.background(Color(0xfffdedac), RoundedCornerShape(10)).padding(10.dp)) {
-            Text("Scene Host")
-            SceneHost(
-                Modifier.height(200.dp).fillMaxWidth().wrapContentSize(Alignment.CenterStart)
-            ) {
-                MyFlowRow {
-                    Box(
-                        Modifier.height(50.dp)
-                            .fillMaxWidth(if (isHorizontal) 0.4f else 1f)
-                            .sharedElement()
-                            .background(colors[0], RoundedCornerShape(10))
-                    )
-                    Box(
-                        Modifier.height(50.dp)
-                            .fillMaxWidth(if (isHorizontal) 0.2f else 0.4f)
-                            .sharedElement()
-                            .background(colors[1], RoundedCornerShape(10))
-                    )
-                    Box(
-                        Modifier.height(50.dp)
-                            .fillMaxWidth(if (isHorizontal) 0.2f else 0.4f)
-                            .sharedElement()
-                            .background(colors[2], RoundedCornerShape(10))
-                    )
-                }
-                Box(Modifier.size(if (isHorizontal) 200.dp else 100.dp))
-            }
+        Column(
+            Modifier
+                .background(Color(0xfffdedac), RoundedCornerShape(10))
+                .padding(10.dp)
+        ) {
+            Text("LookaheadLayout + Modifier.animateBounds")
+            LookaheadLayout(
+                measurePolicy = lookaheadMeasurePolicy,
+                content = {
+                    MyFlowRow(
+                        modifier = Modifier
+                            .height(200.dp)
+                            .fillMaxWidth()
+                            .wrapContentSize(Alignment.CenterStart)
+                    ) {
+                        Box(
+                            Modifier
+                                .height(50.dp)
+                                .animateBounds(
+                                    Modifier.fillMaxWidth(if (isHorizontal) 0.4f else 1f)
+                                )
+                                .background(colors[0], RoundedCornerShape(10))
+                        )
+                        Box(
+                            Modifier
+                                .height(50.dp)
+                                .animateBounds(
+                                    Modifier.fillMaxWidth(if (isHorizontal) 0.2f else 0.4f)
+                                )
+                                .background(colors[1], RoundedCornerShape(10))
+                        )
+                        Box(
+                            Modifier
+                                .height(50.dp)
+                                .animateBounds(
+                                    Modifier.fillMaxWidth(if (isHorizontal) 0.2f else 0.4f)
+                                )
+                                .background(colors[2], RoundedCornerShape(10))
+                        )
+                    }
+                    Box(Modifier.size(if (isHorizontal) 200.dp else 100.dp))
+                })
         }
 
         Spacer(Modifier.size(50.dp))
 
-        Column(Modifier.background(Color(0xfffdedac), RoundedCornerShape(10)).padding(10.dp)) {
+        Column(
+            Modifier
+                .background(Color(0xfffdedac), RoundedCornerShape(10))
+                .padding(10.dp)
+        ) {
             Text("Animating Width")
             MyFlowRow(
-                modifier = Modifier.height(200.dp).fillMaxWidth()
+                modifier = Modifier
+                    .height(200.dp)
+                    .fillMaxWidth()
                     .wrapContentSize(Alignment.CenterStart)
             ) {
                 Box(
-                    Modifier.height(50.dp)
+                    Modifier
+                        .height(50.dp)
                         .fillMaxWidth(animateFloatAsState(if (isHorizontal) 0.4f else 1f).value)
                         .background(colors[0], RoundedCornerShape(10))
                 )
                 Box(
-                    Modifier.height(50.dp)
+                    Modifier
+                        .height(50.dp)
                         .fillMaxWidth(animateFloatAsState(if (isHorizontal) 0.2f else 0.4f).value)
                         .background(colors[1], RoundedCornerShape(10))
                 )
                 Box(
-                    Modifier.height(50.dp)
+                    Modifier
+                        .height(50.dp)
                         .fillMaxWidth(animateFloatAsState(if (isHorizontal) 0.2f else 0.4f).value)
                         .background(colors[2], RoundedCornerShape(10))
                 )
             }
         }
-    }
-}
 
-fun printStack(tag: String) {
-    Thread.currentThread().stackTrace.forEach {
-        println("$tag, $it")
+        Button(modifier = Modifier.padding(top = 20.dp, bottom = 20.dp),
+            onClick = { isHorizontal = !isHorizontal }) {
+            Text("Toggle")
+        }
     }
 }
 
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/ScreenSizeChangeDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/ScreenSizeChangeDemo.kt
index 2009f4c..a62bf4d 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/ScreenSizeChangeDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/ScreenSizeChangeDemo.kt
@@ -33,6 +33,8 @@
 import androidx.compose.foundation.layout.requiredWidth
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.layout.wrapContentWidth
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.Icon
@@ -56,6 +58,7 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clipToBounds
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.text.font.FontWeight
@@ -68,13 +71,16 @@
     // A surface container using the 'background' color from the theme
     var state by remember { mutableStateOf(DisplayState.Tablet) }
     Box(
-        modifier = Modifier.fillMaxSize().background(Color.Black).clickable(
-            interactionSource = remember { MutableInteractionSource() },
-            indication = null
-        ) {
-            state =
-                if (state == DisplayState.Tablet) DisplayState.Compact else DisplayState.Tablet
-        },
+        modifier = Modifier
+            .fillMaxSize()
+            .background(Color.Black)
+            .clickable(
+                interactionSource = remember { MutableInteractionSource() },
+                indication = null
+            ) {
+                state =
+                    if (state == DisplayState.Tablet) DisplayState.Compact else DisplayState.Tablet
+            },
         contentAlignment = Alignment.TopStart
     ) {
         Root(state)
@@ -115,14 +121,18 @@
                 Spacer(Modifier.weight(1f))
                 Icon(
                     Icons.Default.Delete,
-                    modifier = Modifier.padding(2.dp)
-                        .background(Color.White, RoundedCornerShape(3.dp)).padding(6.dp),
+                    modifier = Modifier
+                        .padding(2.dp)
+                        .background(Color.White, RoundedCornerShape(3.dp))
+                        .padding(6.dp),
                     contentDescription = null
                 )
                 Icon(
                     Icons.Default.Menu,
-                    modifier = Modifier.padding(2.dp)
-                        .background(Color.White, RoundedCornerShape(3.dp)).padding(6.dp),
+                    modifier = Modifier
+                        .padding(2.dp)
+                        .background(Color.White, RoundedCornerShape(3.dp))
+                        .padding(6.dp),
                     contentDescription = null
                 )
             }
@@ -136,11 +146,17 @@
 fun Root(state: DisplayState) {
     SceneHost {
         Row(
-            if (state == DisplayState.Compact) {
-                Modifier.requiredWidth(800.dp).fillMaxHeight()
-            } else {
-                Modifier.fillMaxSize()
-            }.sharedElement()
+            Modifier
+                .animateBounds(
+                    if (state == DisplayState.Compact) {
+                        Modifier
+                            .wrapContentSize(align = Alignment.TopStart, unbounded = true)
+                            .requiredWidth(800.dp)
+                            .fillMaxHeight()
+                    } else {
+                        Modifier.fillMaxSize()
+                    }
+                )
                 .background(Color(0xffeae7f2))
                 .padding(top = 10.dp, start = 10.dp, end = 10.dp)
         ) {
@@ -283,7 +299,9 @@
                 cardData.content,
                 fontSize = 13.sp,
                 color = Color.Gray,
-                modifier = Modifier.padding(start = 10.dp).animateSizeAndSkipToFinalLayout()
+                modifier = Modifier
+                    .padding(start = 10.dp)
+                    .animateSizeAndSkipToFinalLayout()
             )
         }
     }
@@ -309,7 +327,9 @@
             messageData.content,
             fontSize = 13.sp,
             color = Color.Gray,
-            modifier = Modifier.padding(start = 10.dp).animateSizeAndSkipToFinalLayout()
+            modifier = Modifier
+                .padding(start = 10.dp)
+                .animateSizeAndSkipToFinalLayout()
         )
         Spacer(Modifier.size(10.dp))
         Row(modifier = Modifier.fillMaxWidth()) {
@@ -341,16 +361,18 @@
 fun SceneScope.NavRail(state: DisplayState) {
     Column(
         Modifier
-            .then(
-                if (state == DisplayState.Tablet) Modifier.width(200.dp) else Modifier.width(
-                    IntrinsicSize.Min
-                )
+            .animateBounds(
+                if (state == DisplayState.Tablet)
+                    Modifier.width(200.dp)
+                else
+                    Modifier.width(IntrinsicSize.Min)
             )
             .padding(top = 20.dp, end = 5.dp)
     ) {
         Row(
             Modifier
-                .fillMaxWidth().animateSizeAndSkipToFinalLayout()
+                .fillMaxWidth()
+                .animateSizeAndSkipToFinalLayout()
                 .padding(5.dp), horizontalArrangement = Arrangement.SpaceBetween
         ) {
             if (state == DisplayState.Tablet) {
@@ -360,7 +382,9 @@
                 imageVector = Icons.Outlined.Menu,
                 contentDescription = null,
                 tint = Color.Gray,
-                modifier = Modifier.width(40.dp).sharedElement()
+                modifier = Modifier
+                    .width(40.dp)
+                    .sharedElement()
             )
         }
         Spacer(modifier = Modifier.size(10.dp))
@@ -381,7 +405,10 @@
             if (state == DisplayState.Tablet) {
                 Text(
                     "Compose",
-                    Modifier.padding(start = 30.dp),
+                    Modifier
+                        .padding(start = 30.dp)
+                        .clipToBounds()
+                        .wrapContentWidth(align = Alignment.CenterHorizontally, unbounded = true),
                     color = Color.Gray,
                     fontWeight = FontWeight.Bold
                 )
@@ -414,6 +441,8 @@
                 text,
                 Modifier
                     .weight(1f)
+                    .clipToBounds()
+                    .wrapContentWidth(align = Alignment.Start, unbounded = true)
                     .padding(start = 15.dp),
                 color = Color.Gray,
                 fontWeight = FontWeight.Bold,