Merge "Allow measurement from within LayoutModifier's placement block" into androidx-main
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/MeasureInPlacementTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/MeasureInPlacementTest.kt
new file mode 100644
index 0000000..031ba54
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/MeasureInPlacementTest.kt
@@ -0,0 +1,170 @@
+/*
+ * 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.ui.layout
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.TestActivity
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.TimeUnit
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MeasureInPlacementTest {
+
+ @Suppress("DEPRECATION")
+ @get:Rule
+ val rule = createAndroidComposeRule<TestActivity>()
+
+ @Before
+ fun setup() {
+ rule.activity.hasFocusLatch.await(5, TimeUnit.SECONDS)
+ }
+
+ /**
+ * Make sure that measurement in the layout modifier's placement block doesn't crash.
+ */
+ @Test
+ fun measureInModifierPlacement() {
+ var childSize = IntSize.Zero
+ rule.setContent {
+ val measureInPlaceModifier = Modifier.layout { measurable, constraints ->
+ layout(100, 100) {
+ val p = measurable.measure(constraints)
+ childSize = IntSize(p.width, p.height)
+ p.place(0, 0)
+ }
+ }
+ Box(
+ Modifier
+ .fillMaxSize()
+ .then(measureInPlaceModifier)
+ ) {
+ Box(Modifier.size(10.dp))
+ }
+ }
+
+ rule.waitForIdle()
+ assertThat(childSize.width).isGreaterThan(0)
+ assertThat(childSize.height).isGreaterThan(0)
+ }
+
+ /**
+ * Make sure that measurement in the layout's placement block doesn't crash.
+ */
+ @Test
+ fun measureInLayoutPlacement() {
+ var childSize = IntSize.Zero
+ rule.setContent {
+ Layout(modifier = Modifier.fillMaxSize(), content = @Composable {
+ Box(Modifier.size(10.dp))
+ }) { measurables, constraints ->
+ layout(100, 100) {
+ val p = measurables[0].measure(constraints)
+ childSize = IntSize(p.width, p.height)
+ p.place(0, 0)
+ }
+ }
+ }
+
+ rule.waitForIdle()
+ assertThat(childSize.width).isGreaterThan(0)
+ assertThat(childSize.height).isGreaterThan(0)
+ }
+
+ /**
+ * Make sure that measurement in the layout modifier's placement block doesn't crash when
+ * LookaheadLayout is used.
+ */
+ @OptIn(ExperimentalComposeUiApi::class)
+ @Test
+ fun measureInModifierPlacementWithLookaheadLayout() {
+ var childSize = IntSize.Zero
+ rule.setContent {
+ LookaheadLayout(content = @Composable {
+ val measureInPlaceModifier = Modifier.layout { measurable, constraints ->
+ layout(100, 100) {
+ val p = measurable.measure(constraints)
+ childSize = IntSize(p.width, p.height)
+ p.place(0, 0)
+ }
+ }
+ Box(
+ Modifier
+ .fillMaxSize()
+ .then(measureInPlaceModifier)
+ ) {
+ Box(Modifier.size(10.dp))
+ }
+ }, measurePolicy = { measurables, constraints ->
+ val p = measurables[0].measure(constraints)
+ layout(p.width, p.height) {
+ p.place(0, 0)
+ }
+ })
+ }
+
+ rule.waitForIdle()
+ assertThat(childSize.width).isGreaterThan(0)
+ assertThat(childSize.height).isGreaterThan(0)
+ }
+
+ /**
+ * Make sure that measurement in the layout's placement block doesn't crash when
+ * LookaheadLayout is used.
+ */
+ @OptIn(ExperimentalComposeUiApi::class)
+ @Test
+ fun measureInLayoutPlacementWithLookaheadLayout() {
+ var childSize = IntSize.Zero
+ rule.setContent {
+ LookaheadLayout(content = @Composable {
+ Layout(modifier = Modifier.fillMaxSize(), content = @Composable {
+ Box(Modifier.size(10.dp))
+ }) { measurables, constraints ->
+ layout(100, 100) {
+ val p = measurables[0].measure(constraints)
+ childSize = IntSize(p.width, p.height)
+ p.place(0, 0)
+ }
+ }
+ }, measurePolicy = { measurables, constraints ->
+ val p = measurables[0].measure(constraints)
+ layout(p.width, p.height) {
+ p.place(0, 0)
+ }
+ })
+ }
+
+ rule.waitForIdle()
+ assertThat(childSize.width).isGreaterThan(0)
+ assertThat(childSize.height).isGreaterThan(0)
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt
index 6d91114..a35e1e0 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt
@@ -260,6 +260,7 @@
(!duringAlignmentLinesQuery && !innerCoordinator.isPlacingForAlignment &&
layoutPending)) {
layoutPending = false
+ val oldLayoutState = layoutState
layoutState = LayoutState.LayingOut
with(layoutNode) {
val owner = requireOwner()
@@ -280,7 +281,7 @@
}
}
}
- layoutState = LayoutState.Idle
+ layoutState = oldLayoutState
if (innerCoordinator.isPlacingForAlignment &&
coordinatesAccessedDuringPlacement
@@ -422,7 +423,9 @@
}
// Post-lookahead (if any) placement
+ layoutState = LayoutState.LayingOut
placeOuterCoordinator(position, zIndex, layerBlock)
+ layoutState = LayoutState.Idle
}
private fun placeOuterCoordinator(
@@ -691,6 +694,7 @@
lookaheadLayoutPending)
) {
lookaheadLayoutPending = false
+ val oldLayoutState = layoutState
layoutState = LayoutState.LookaheadLayingOut
val owner = layoutNode.requireOwner()
owner.snapshotObserver.observeLayoutSnapshotReads(layoutNode) {
@@ -722,7 +726,7 @@
}
}
}
- layoutState = LayoutState.Idle
+ layoutState = oldLayoutState
if (coordinatesAccessedDuringPlacement &&
lookaheadDelegate.isPlacingForAlignment) {
requestLayout()
@@ -880,6 +884,7 @@
zIndex: Float,
layerBlock: (GraphicsLayerScope.() -> Unit)?
) {
+ layoutState = LayoutState.LookaheadLayingOut
placedOnce = true
if (position != lastPosition) {
notifyChildrenUsingCoordinatesWhilePlacing()
@@ -893,6 +898,7 @@
}
}
lastPosition = position
+ layoutState = LayoutState.Idle
}
// We are setting our measuredSize to match the coerced outerCoordinator size, to prevent
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt
index d3349b8..2e5689a 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt
@@ -70,8 +70,9 @@
}
if (layoutPending) {
return relayoutNodes.contains(this) ||
- parent?.measurePending == true ||
- parent?.layoutPending == true ||
+ parent == null ||
+ parent.measurePending ||
+ parent.layoutPending ||
parentLayoutState == LayoutNode.LayoutState.Measuring ||
parentLayoutState == LayoutNode.LayoutState.LayingOut
}
@@ -93,11 +94,12 @@
}
if (lookaheadLayoutPending) {
return relayoutNodes.contains(this) ||
- parent?.lookaheadMeasurePending == true ||
- parent?.lookaheadLayoutPending == true ||
+ parent == null ||
+ parent.lookaheadMeasurePending ||
+ parent.lookaheadLayoutPending ||
parentLayoutState == LayoutNode.LayoutState.LookaheadMeasuring ||
parentLayoutState == LayoutNode.LayoutState.LookaheadLayingOut ||
- (parent?.layoutPending == true && mLookaheadScope!!.root == this)
+ (parent.layoutPending && mLookaheadScope!!.root == this)
}
}
return true