[go: nahoru, domu]

Add convenience Modifier.layout() modifier

Test: new test
Fixes: 161355194
Relnote: "Added Modifier.layout() that allows to create a custom layout modifier conveniently"
Change-Id: I73b699f2434a2c8ca0400fca1c331997c09a44e9
diff --git a/ui/ui-core/api/0.1.0-dev16.txt b/ui/ui-core/api/0.1.0-dev16.txt
index 2f3d540..4d3d265 100644
--- a/ui/ui-core/api/0.1.0-dev16.txt
+++ b/ui/ui-core/api/0.1.0-dev16.txt
@@ -627,6 +627,10 @@
     method public default int minIntrinsicWidth(androidx.ui.core.IntrinsicMeasureScope, androidx.ui.core.IntrinsicMeasurable measurable, int height, androidx.compose.ui.unit.LayoutDirection layoutDirection);
   }
 
+  public final class LayoutModifierKt {
+    method public static androidx.ui.core.Modifier layout(androidx.ui.core.Modifier, kotlin.jvm.functions.Function3<? super androidx.ui.core.MeasureScope,? super androidx.ui.core.Measurable,? super androidx.compose.ui.unit.Constraints,? extends androidx.ui.core.MeasureScope.MeasureResult> measure);
+  }
+
   @androidx.ui.core.ExperimentalLayoutNodeApi public final class LayoutNode implements androidx.ui.core.Measurable androidx.ui.core.Remeasurement {
     ctor public LayoutNode();
     method public void attach(androidx.ui.core.Owner owner);
diff --git a/ui/ui-core/api/current.txt b/ui/ui-core/api/current.txt
index 2f3d540..4d3d265 100644
--- a/ui/ui-core/api/current.txt
+++ b/ui/ui-core/api/current.txt
@@ -627,6 +627,10 @@
     method public default int minIntrinsicWidth(androidx.ui.core.IntrinsicMeasureScope, androidx.ui.core.IntrinsicMeasurable measurable, int height, androidx.compose.ui.unit.LayoutDirection layoutDirection);
   }
 
+  public final class LayoutModifierKt {
+    method public static androidx.ui.core.Modifier layout(androidx.ui.core.Modifier, kotlin.jvm.functions.Function3<? super androidx.ui.core.MeasureScope,? super androidx.ui.core.Measurable,? super androidx.compose.ui.unit.Constraints,? extends androidx.ui.core.MeasureScope.MeasureResult> measure);
+  }
+
   @androidx.ui.core.ExperimentalLayoutNodeApi public final class LayoutNode implements androidx.ui.core.Measurable androidx.ui.core.Remeasurement {
     ctor public LayoutNode();
     method public void attach(androidx.ui.core.Owner owner);
diff --git a/ui/ui-core/api/public_plus_experimental_0.1.0-dev16.txt b/ui/ui-core/api/public_plus_experimental_0.1.0-dev16.txt
index 2f3d540..4d3d265 100644
--- a/ui/ui-core/api/public_plus_experimental_0.1.0-dev16.txt
+++ b/ui/ui-core/api/public_plus_experimental_0.1.0-dev16.txt
@@ -627,6 +627,10 @@
     method public default int minIntrinsicWidth(androidx.ui.core.IntrinsicMeasureScope, androidx.ui.core.IntrinsicMeasurable measurable, int height, androidx.compose.ui.unit.LayoutDirection layoutDirection);
   }
 
+  public final class LayoutModifierKt {
+    method public static androidx.ui.core.Modifier layout(androidx.ui.core.Modifier, kotlin.jvm.functions.Function3<? super androidx.ui.core.MeasureScope,? super androidx.ui.core.Measurable,? super androidx.compose.ui.unit.Constraints,? extends androidx.ui.core.MeasureScope.MeasureResult> measure);
+  }
+
   @androidx.ui.core.ExperimentalLayoutNodeApi public final class LayoutNode implements androidx.ui.core.Measurable androidx.ui.core.Remeasurement {
     ctor public LayoutNode();
     method public void attach(androidx.ui.core.Owner owner);
diff --git a/ui/ui-core/api/public_plus_experimental_current.txt b/ui/ui-core/api/public_plus_experimental_current.txt
index 2f3d540..4d3d265 100644
--- a/ui/ui-core/api/public_plus_experimental_current.txt
+++ b/ui/ui-core/api/public_plus_experimental_current.txt
@@ -627,6 +627,10 @@
     method public default int minIntrinsicWidth(androidx.ui.core.IntrinsicMeasureScope, androidx.ui.core.IntrinsicMeasurable measurable, int height, androidx.compose.ui.unit.LayoutDirection layoutDirection);
   }
 
+  public final class LayoutModifierKt {
+    method public static androidx.ui.core.Modifier layout(androidx.ui.core.Modifier, kotlin.jvm.functions.Function3<? super androidx.ui.core.MeasureScope,? super androidx.ui.core.Measurable,? super androidx.compose.ui.unit.Constraints,? extends androidx.ui.core.MeasureScope.MeasureResult> measure);
+  }
+
   @androidx.ui.core.ExperimentalLayoutNodeApi public final class LayoutNode implements androidx.ui.core.Measurable androidx.ui.core.Remeasurement {
     ctor public LayoutNode();
     method public void attach(androidx.ui.core.Owner owner);
diff --git a/ui/ui-core/api/restricted_0.1.0-dev16.txt b/ui/ui-core/api/restricted_0.1.0-dev16.txt
index 4606ee4d..c720a68 100644
--- a/ui/ui-core/api/restricted_0.1.0-dev16.txt
+++ b/ui/ui-core/api/restricted_0.1.0-dev16.txt
@@ -690,6 +690,10 @@
     method public default int minIntrinsicWidth(androidx.ui.core.IntrinsicMeasureScope, androidx.ui.core.IntrinsicMeasurable measurable, int height, androidx.compose.ui.unit.LayoutDirection layoutDirection);
   }
 
+  public final class LayoutModifierKt {
+    method public static androidx.ui.core.Modifier layout(androidx.ui.core.Modifier, kotlin.jvm.functions.Function3<? super androidx.ui.core.MeasureScope,? super androidx.ui.core.Measurable,? super androidx.compose.ui.unit.Constraints,? extends androidx.ui.core.MeasureScope.MeasureResult> measure);
+  }
+
   @androidx.ui.core.ExperimentalLayoutNodeApi public final class LayoutNode implements androidx.ui.core.Measurable androidx.ui.core.Remeasurement {
     ctor public LayoutNode();
     method public void attach(androidx.ui.core.Owner owner);
diff --git a/ui/ui-core/api/restricted_current.txt b/ui/ui-core/api/restricted_current.txt
index 4606ee4d..c720a68 100644
--- a/ui/ui-core/api/restricted_current.txt
+++ b/ui/ui-core/api/restricted_current.txt
@@ -690,6 +690,10 @@
     method public default int minIntrinsicWidth(androidx.ui.core.IntrinsicMeasureScope, androidx.ui.core.IntrinsicMeasurable measurable, int height, androidx.compose.ui.unit.LayoutDirection layoutDirection);
   }
 
+  public final class LayoutModifierKt {
+    method public static androidx.ui.core.Modifier layout(androidx.ui.core.Modifier, kotlin.jvm.functions.Function3<? super androidx.ui.core.MeasureScope,? super androidx.ui.core.Measurable,? super androidx.compose.ui.unit.Constraints,? extends androidx.ui.core.MeasureScope.MeasureResult> measure);
+  }
+
   @androidx.ui.core.ExperimentalLayoutNodeApi public final class LayoutNode implements androidx.ui.core.Measurable androidx.ui.core.Remeasurement {
     ctor public LayoutNode();
     method public void attach(androidx.ui.core.Owner owner);
diff --git a/ui/ui-core/samples/src/main/java/androidx/ui/core/samples/LayoutSample.kt b/ui/ui-core/samples/src/main/java/androidx/ui/core/samples/LayoutSample.kt
index 99bb875..c5516bf 100644
--- a/ui/ui-core/samples/src/main/java/androidx/ui/core/samples/LayoutSample.kt
+++ b/ui/ui-core/samples/src/main/java/androidx/ui/core/samples/LayoutSample.kt
@@ -25,6 +25,12 @@
 import androidx.ui.core.id
 import androidx.ui.core.layoutId
 import androidx.compose.foundation.Box
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Stack
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.offset
+import androidx.ui.core.layout
 import androidx.ui.core.measureBlocksOf
 
 @Sampled
@@ -165,3 +171,22 @@
         }
     }
 }
+
+@Sampled
+@Composable
+fun ConvenienceLayoutModifierSample() {
+    Stack(
+        modifier = Modifier
+            .background(Color.Gray)
+            .layout { measurable, constraints ->
+            // an example modifier that adds 50 pixels of vertical padding
+            val padding = 50
+            val placeable = measurable.measure(constraints.offset(vertical = -padding))
+            this.layout(placeable.width, placeable.height + padding) {
+                placeable.place(0, padding)
+            }
+        }
+    ) {
+        Stack(Modifier.fillMaxSize().background(Color.DarkGray)) {}
+    }
+}
diff --git a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/test/AndroidLayoutDrawTest.kt b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/test/AndroidLayoutDrawTest.kt
index 92c34cc..b95a937 100644
--- a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/test/AndroidLayoutDrawTest.kt
+++ b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/test/AndroidLayoutDrawTest.kt
@@ -90,6 +90,10 @@
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
+import androidx.ui.core.LayoutCoordinates
+import androidx.ui.core.layout
+import androidx.ui.core.onPositioned
+import androidx.ui.core.positionInRoot
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNotNull
@@ -355,7 +359,8 @@
                             this@draw.drawContent()
 
                             // Fill bottom half with innerColor -- should be clipped
-                            drawRect(model.innerColor,
+                            drawRect(
+                                model.innerColor,
                                 topLeft = Offset(0f, size.height / 2f),
                                 size = Size(size.width, size.height / 2f)
                             )
@@ -1508,27 +1513,30 @@
 
                 layout(Modifier.assertLines(10, 20))
                 layout(Modifier.assertLines(30, 30).offset(20.toDp(), 10.toDp()))
-                layout(Modifier
-                    .assertLines(30, 30)
-                    .drawLayer()
-                    .offset(20.toDp(), 10.toDp())
+                layout(
+                    Modifier
+                        .assertLines(30, 30)
+                        .drawLayer()
+                        .offset(20.toDp(), 10.toDp())
                 )
-                layout(Modifier
-                    .assertLines(30, 30)
-                    .background(Color.Blue)
-                    .drawLayer()
-                    .offset(20.toDp(), 10.toDp())
-                    .drawLayer()
-                    .background(Color.Blue)
+                layout(
+                    Modifier
+                        .assertLines(30, 30)
+                        .background(Color.Blue)
+                        .drawLayer()
+                        .offset(20.toDp(), 10.toDp())
+                        .drawLayer()
+                        .background(Color.Blue)
                 )
-                layout(Modifier
-                    .background(Color.Blue)
-                    .assertLines(30, 30)
-                    .background(Color.Blue)
-                    .drawLayer()
-                    .offset(20.toDp(), 10.toDp())
-                    .drawLayer()
-                    .background(Color.Blue)
+                layout(
+                    Modifier
+                        .background(Color.Blue)
+                        .assertLines(30, 30)
+                        .background(Color.Blue)
+                        .drawLayer()
+                        .offset(20.toDp(), 10.toDp())
+                        .drawLayer()
+                        .background(Color.Blue)
                 )
                 Wrap(
                     Modifier
@@ -2076,6 +2084,98 @@
         )
     }
 
+    @Test
+    fun layoutModifier_convenienceApi() {
+        val size = 100
+        val offset = 15f
+        val latch = CountDownLatch(1)
+        var resultCoordinates: LayoutCoordinates? = null
+
+        activityTestRule.runOnUiThreadIR {
+            activity.setContent {
+                FixedSize(
+                    size = size,
+                    modifier = Modifier
+                        .layout { measurable, constraints ->
+                            val placeable = measurable.measure(constraints)
+                            layout(placeable.width, placeable.height) {
+                                placeable.place(Offset(offset, offset))
+                            }
+                        }.onPositioned {
+                            resultCoordinates = it
+                            latch.countDown()
+                        }
+                )
+            }
+        }
+
+        assertTrue(latch.await(1, TimeUnit.SECONDS))
+
+        activity.runOnUiThread {
+            assertEquals(size, resultCoordinates?.size?.height)
+            assertEquals(size, resultCoordinates?.size?.width)
+            assertEquals(Offset(offset, offset), resultCoordinates?.positionInRoot)
+        }
+    }
+
+    @Test
+    fun layoutModifier_convenienceApi_equivalent() {
+        val size = 100
+        val offset = 15f
+        val latch = CountDownLatch(2)
+
+        var convenienceCoordinates: LayoutCoordinates? = null
+        var coordinates: LayoutCoordinates? = null
+
+        activityTestRule.runOnUiThreadIR {
+            activity.setContent {
+                FixedSize(
+                    size = size,
+                    modifier = Modifier
+                        .layout { measurable, constraints ->
+                            val placeable = measurable.measure(constraints)
+                            layout(placeable.width, placeable.height) {
+                                placeable.place(Offset(offset, offset))
+                            }
+                        }.onPositioned {
+                            convenienceCoordinates = it
+                            latch.countDown()
+                        }
+                )
+
+                val layoutModifier = object : LayoutModifier {
+                    override fun MeasureScope.measure(
+                        measurable: Measurable,
+                        constraints: Constraints,
+                        layoutDirection: LayoutDirection
+                    ): MeasureScope.MeasureResult {
+                        val placeable = measurable.measure(constraints)
+                        return layout(placeable.width, placeable.height) {
+                            placeable.place(Offset(offset, offset))
+                        }
+                    }
+                }
+                FixedSize(
+                    size = size,
+                    modifier = layoutModifier.plus(
+                        onPositioned {
+                            coordinates = it
+                            latch.countDown()
+                        }
+                    )
+                )
+            }
+        }
+
+        assertTrue(latch.await(1, TimeUnit.SECONDS))
+
+        activity.runOnUiThread {
+            assertEquals(coordinates?.size?.height, convenienceCoordinates?.size?.height)
+            assertEquals(coordinates?.size?.width, convenienceCoordinates?.size?.width)
+            assertEquals(coordinates?.positionInRoot, convenienceCoordinates?.positionInRoot)
+        }
+    }
+
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     @Test
     fun modifier_combinedModifiers() {
@@ -2623,16 +2723,20 @@
             linearLayout.orientation = LinearLayout.VERTICAL
             val child = FrameLayout(activity)
             activity.setContentView(linearLayout)
-            linearLayout.addView(child, LinearLayout.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT,
-                1f
-            ))
-            linearLayout.addView(View(activity), LinearLayout.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                0,
-                10000f
-            ))
+            linearLayout.addView(
+                child, LinearLayout.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT,
+                    1f
+                )
+            )
+            linearLayout.addView(
+                View(activity), LinearLayout.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    0,
+                    10000f
+                )
+            )
             child.viewTreeObserver.addOnPreDrawListener {
                 actualHeight = child.measuredHeight
                 latch.countDown()
diff --git a/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/LayoutModifier.kt b/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/LayoutModifier.kt
index 35b4b71..6ea7d98 100644
--- a/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/LayoutModifier.kt
+++ b/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/LayoutModifier.kt
@@ -253,3 +253,30 @@
     private enum class IntrinsicMinMax { Min, Max }
     private enum class IntrinsicWidthHeight { Width, Height }
 }
+
+/**
+ * Creates a [LayoutModifier] that allows changing how the wrapped element is measured and laid out.
+ *
+ * This is a convenience API of creating a custom [LayoutModifier] modifier, without having to
+ * create a class or an object that implements the [LayoutModifier] interface. The intrinsic
+ * measurements follow the default logic provided by the [LayoutModifier].
+ *
+ * Example usage:
+ *
+ * @sample androidx.ui.core.samples.ConvenienceLayoutModifierSample
+ *
+ * @see androidx.ui.core.LayoutModifier
+ */
+fun Modifier.layout(
+    measure: MeasureScope.(Measurable, Constraints) -> MeasureScope.MeasureResult
+) = this.then(LayoutModifierImpl(measure))
+
+private data class LayoutModifierImpl(
+    val measure: MeasureScope.(Measurable, Constraints) -> MeasureScope.MeasureResult
+) : LayoutModifier {
+    override fun MeasureScope.measure(
+        measurable: Measurable,
+        constraints: Constraints,
+        layoutDirection: LayoutDirection
+    ) = measure(measurable, constraints)
+}
\ No newline at end of file