[go: nahoru, domu]

Fixes for layout modifiers

The manner in which LayoutNode reads density from the currently
connected owner is too lazy when it comes to modifier wrapper chain
construction. Always delegate to the LayoutNode rather than capture
eagerly.

Add constraints to LayoutModifier.modifySize so that they can be applied
at each modifier phase.

Change LayoutModifier.modifyPosition to accept the proposed position
and return the new one rather than returning an offset for consistency
with other LayoutModifier methods and added flexibility.

Fix AbsolutePadding modifier behavior when insufficient space is
available and when intrinsic measurements are queried.

Bug: 141464374
Test: AbsolutePaddingModifierTest
Change-Id: Ief4ddb77d602abc2030ad738b48f4070a00e80cf
diff --git a/ui/ui-core/api/0.1.0-dev01.txt b/ui/ui-core/api/0.1.0-dev01.txt
index c01d000..d7e7174 100644
--- a/ui/ui-core/api/0.1.0-dev01.txt
+++ b/ui/ui-core/api/0.1.0-dev01.txt
@@ -503,8 +503,8 @@
     method public androidx.ui.core.IntPx minIntrinsicWidthOf(androidx.ui.core.DensityScope, androidx.ui.core.Measurable measurable, androidx.ui.core.IntPx height);
     method public androidx.ui.core.IntPx? modifyAlignmentLine(androidx.ui.core.DensityScope, androidx.ui.core.AlignmentLine line, androidx.ui.core.IntPx? value);
     method public androidx.ui.core.Constraints modifyConstraints(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints);
-    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
-    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize);
+    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxPosition childPosition, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
+    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints, androidx.ui.core.IntPxSize childSize);
   }
 
   public interface Measurable extends androidx.ui.core.IntrinsicMeasurable {
diff --git a/ui/ui-core/api/current.txt b/ui/ui-core/api/current.txt
index c01d000..d7e7174 100644
--- a/ui/ui-core/api/current.txt
+++ b/ui/ui-core/api/current.txt
@@ -503,8 +503,8 @@
     method public androidx.ui.core.IntPx minIntrinsicWidthOf(androidx.ui.core.DensityScope, androidx.ui.core.Measurable measurable, androidx.ui.core.IntPx height);
     method public androidx.ui.core.IntPx? modifyAlignmentLine(androidx.ui.core.DensityScope, androidx.ui.core.AlignmentLine line, androidx.ui.core.IntPx? value);
     method public androidx.ui.core.Constraints modifyConstraints(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints);
-    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
-    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize);
+    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxPosition childPosition, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
+    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints, androidx.ui.core.IntPxSize childSize);
   }
 
   public interface Measurable extends androidx.ui.core.IntrinsicMeasurable {
diff --git a/ui/ui-core/api/public_plus_experimental_0.1.0-dev01.txt b/ui/ui-core/api/public_plus_experimental_0.1.0-dev01.txt
index c01d000..d7e7174 100644
--- a/ui/ui-core/api/public_plus_experimental_0.1.0-dev01.txt
+++ b/ui/ui-core/api/public_plus_experimental_0.1.0-dev01.txt
@@ -503,8 +503,8 @@
     method public androidx.ui.core.IntPx minIntrinsicWidthOf(androidx.ui.core.DensityScope, androidx.ui.core.Measurable measurable, androidx.ui.core.IntPx height);
     method public androidx.ui.core.IntPx? modifyAlignmentLine(androidx.ui.core.DensityScope, androidx.ui.core.AlignmentLine line, androidx.ui.core.IntPx? value);
     method public androidx.ui.core.Constraints modifyConstraints(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints);
-    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
-    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize);
+    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxPosition childPosition, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
+    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints, androidx.ui.core.IntPxSize childSize);
   }
 
   public interface Measurable extends androidx.ui.core.IntrinsicMeasurable {
diff --git a/ui/ui-core/api/public_plus_experimental_current.txt b/ui/ui-core/api/public_plus_experimental_current.txt
index c01d000..d7e7174 100644
--- a/ui/ui-core/api/public_plus_experimental_current.txt
+++ b/ui/ui-core/api/public_plus_experimental_current.txt
@@ -503,8 +503,8 @@
     method public androidx.ui.core.IntPx minIntrinsicWidthOf(androidx.ui.core.DensityScope, androidx.ui.core.Measurable measurable, androidx.ui.core.IntPx height);
     method public androidx.ui.core.IntPx? modifyAlignmentLine(androidx.ui.core.DensityScope, androidx.ui.core.AlignmentLine line, androidx.ui.core.IntPx? value);
     method public androidx.ui.core.Constraints modifyConstraints(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints);
-    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
-    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize);
+    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxPosition childPosition, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
+    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints, androidx.ui.core.IntPxSize childSize);
   }
 
   public interface Measurable extends androidx.ui.core.IntrinsicMeasurable {
diff --git a/ui/ui-core/api/restricted_0.1.0-dev01.txt b/ui/ui-core/api/restricted_0.1.0-dev01.txt
index c01d000..d7e7174 100644
--- a/ui/ui-core/api/restricted_0.1.0-dev01.txt
+++ b/ui/ui-core/api/restricted_0.1.0-dev01.txt
@@ -503,8 +503,8 @@
     method public androidx.ui.core.IntPx minIntrinsicWidthOf(androidx.ui.core.DensityScope, androidx.ui.core.Measurable measurable, androidx.ui.core.IntPx height);
     method public androidx.ui.core.IntPx? modifyAlignmentLine(androidx.ui.core.DensityScope, androidx.ui.core.AlignmentLine line, androidx.ui.core.IntPx? value);
     method public androidx.ui.core.Constraints modifyConstraints(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints);
-    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
-    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize);
+    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxPosition childPosition, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
+    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints, androidx.ui.core.IntPxSize childSize);
   }
 
   public interface Measurable extends androidx.ui.core.IntrinsicMeasurable {
diff --git a/ui/ui-core/api/restricted_current.txt b/ui/ui-core/api/restricted_current.txt
index c01d000..d7e7174 100644
--- a/ui/ui-core/api/restricted_current.txt
+++ b/ui/ui-core/api/restricted_current.txt
@@ -503,8 +503,8 @@
     method public androidx.ui.core.IntPx minIntrinsicWidthOf(androidx.ui.core.DensityScope, androidx.ui.core.Measurable measurable, androidx.ui.core.IntPx height);
     method public androidx.ui.core.IntPx? modifyAlignmentLine(androidx.ui.core.DensityScope, androidx.ui.core.AlignmentLine line, androidx.ui.core.IntPx? value);
     method public androidx.ui.core.Constraints modifyConstraints(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints);
-    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
-    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize);
+    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxPosition childPosition, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
+    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints, androidx.ui.core.IntPxSize childSize);
   }
 
   public interface Measurable extends androidx.ui.core.IntrinsicMeasurable {
diff --git a/ui/ui-core/src/main/java/androidx/ui/core/Constraints.kt b/ui/ui-core/src/main/java/androidx/ui/core/Constraints.kt
index 9c3a3e4..cd77135 100644
--- a/ui/ui-core/src/main/java/androidx/ui/core/Constraints.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/core/Constraints.kt
@@ -45,18 +45,18 @@
 ) {
     init {
         // TODO(mount/popam): This verification is costly. Can we avoid it sometimes or at least on production?
-        require(minWidth.isFinite()) { "Constraints#minWidth should be finite" }
-        require(minHeight.isFinite()) { "Constraints#minHeight should be finite" }
+        require(minWidth.isFinite()) { "minWidth $minWidth should be finite" }
+        require(minHeight.isFinite()) { "minHeight $minHeight should be finite" }
         require(minWidth <= maxWidth) {
-            "Constraints should be satisfiable, but minWidth > maxWidth"
+            "Constraints should be satisfiable, but minWidth($minWidth) > maxWidth($maxWidth)"
         }
         require(minHeight <= maxHeight) {
-            "Constraints should be satisfiable, but minHeight > maxHeight"
+            "Constraints should be satisfiable, but minHeight($minHeight) > maxHeight($maxHeight)"
         }
-        require(minWidth >= IntPx.Zero) { "Constraints#minWidth should be non-negative" }
-        require(maxWidth >= IntPx.Zero) { "Constraints#maxWidth should be non-negative" }
-        require(minHeight >= IntPx.Zero) { "Constraints#minHeight should be non-negative" }
-        require(maxHeight >= IntPx.Zero) { "Constraints#maxHeight should be non-negative" }
+        require(minWidth >= IntPx.Zero) { "minWidth $minWidth should be non-negative" }
+        require(maxWidth >= IntPx.Zero) { "maxWidth $maxWidth should be non-negative" }
+        require(minHeight >= IntPx.Zero) { "minHeight $minHeight should be non-negative" }
+        require(maxHeight >= IntPx.Zero) { "maxHeight $maxHeight should be non-negative" }
     }
 
     companion object {
diff --git a/ui/ui-core/src/main/java/androidx/ui/core/LayoutModifier.kt b/ui/ui-core/src/main/java/androidx/ui/core/LayoutModifier.kt
index 92d105f..648ee08 100644
--- a/ui/ui-core/src/main/java/androidx/ui/core/LayoutModifier.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/core/LayoutModifier.kt
@@ -26,9 +26,10 @@
     fun DensityScope.modifyConstraints(constraints: Constraints): Constraints
 
     /**
-     * Returns the container size of a modified layout element.
+     * Returns the container size of a modified layout element given the original container
+     * measurement [constraints] and the measured [childSize].
      */
-    fun DensityScope.modifySize(childSize: IntPxSize): IntPxSize
+    fun DensityScope.modifySize(constraints: Constraints, childSize: IntPxSize): IntPxSize
 
     /**
      * Determines the modified minimum intrinsic width of [measurable].
@@ -55,10 +56,11 @@
     fun DensityScope.maxIntrinsicHeightOf(measurable: Measurable, width: IntPx): IntPx
 
     /**
-     * Returns the position offset of a modified child of size [childSize] within a container of
+     * Returns the position of a modified child of size [childSize] within a container of
      * size [containerSize].
      */
     fun DensityScope.modifyPosition(
+        childPosition: IntPxPosition,
         childSize: IntPxSize,
         containerSize: IntPxSize
     ): IntPxPosition
diff --git a/ui/ui-layout/api/0.1.0-dev01.txt b/ui/ui-layout/api/0.1.0-dev01.txt
index 457f8fe..eed16f4 100644
--- a/ui/ui-layout/api/0.1.0-dev01.txt
+++ b/ui/ui-layout/api/0.1.0-dev01.txt
@@ -19,8 +19,8 @@
     method public androidx.ui.core.IntPx minIntrinsicWidthOf(androidx.ui.core.DensityScope, androidx.ui.core.Measurable measurable, androidx.ui.core.IntPx height);
     method public androidx.ui.core.IntPx? modifyAlignmentLine(androidx.ui.core.DensityScope, androidx.ui.core.AlignmentLine line, androidx.ui.core.IntPx? value);
     method public androidx.ui.core.Constraints modifyConstraints(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints);
-    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
-    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize);
+    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxPosition childPosition, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
+    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints, androidx.ui.core.IntPxSize childSize);
   }
 
   public final class AlignKt {
diff --git a/ui/ui-layout/api/current.txt b/ui/ui-layout/api/current.txt
index 457f8fe..eed16f4 100644
--- a/ui/ui-layout/api/current.txt
+++ b/ui/ui-layout/api/current.txt
@@ -19,8 +19,8 @@
     method public androidx.ui.core.IntPx minIntrinsicWidthOf(androidx.ui.core.DensityScope, androidx.ui.core.Measurable measurable, androidx.ui.core.IntPx height);
     method public androidx.ui.core.IntPx? modifyAlignmentLine(androidx.ui.core.DensityScope, androidx.ui.core.AlignmentLine line, androidx.ui.core.IntPx? value);
     method public androidx.ui.core.Constraints modifyConstraints(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints);
-    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
-    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize);
+    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxPosition childPosition, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
+    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints, androidx.ui.core.IntPxSize childSize);
   }
 
   public final class AlignKt {
diff --git a/ui/ui-layout/api/public_plus_experimental_0.1.0-dev01.txt b/ui/ui-layout/api/public_plus_experimental_0.1.0-dev01.txt
index 457f8fe..eed16f4 100644
--- a/ui/ui-layout/api/public_plus_experimental_0.1.0-dev01.txt
+++ b/ui/ui-layout/api/public_plus_experimental_0.1.0-dev01.txt
@@ -19,8 +19,8 @@
     method public androidx.ui.core.IntPx minIntrinsicWidthOf(androidx.ui.core.DensityScope, androidx.ui.core.Measurable measurable, androidx.ui.core.IntPx height);
     method public androidx.ui.core.IntPx? modifyAlignmentLine(androidx.ui.core.DensityScope, androidx.ui.core.AlignmentLine line, androidx.ui.core.IntPx? value);
     method public androidx.ui.core.Constraints modifyConstraints(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints);
-    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
-    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize);
+    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxPosition childPosition, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
+    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints, androidx.ui.core.IntPxSize childSize);
   }
 
   public final class AlignKt {
diff --git a/ui/ui-layout/api/public_plus_experimental_current.txt b/ui/ui-layout/api/public_plus_experimental_current.txt
index 457f8fe..eed16f4 100644
--- a/ui/ui-layout/api/public_plus_experimental_current.txt
+++ b/ui/ui-layout/api/public_plus_experimental_current.txt
@@ -19,8 +19,8 @@
     method public androidx.ui.core.IntPx minIntrinsicWidthOf(androidx.ui.core.DensityScope, androidx.ui.core.Measurable measurable, androidx.ui.core.IntPx height);
     method public androidx.ui.core.IntPx? modifyAlignmentLine(androidx.ui.core.DensityScope, androidx.ui.core.AlignmentLine line, androidx.ui.core.IntPx? value);
     method public androidx.ui.core.Constraints modifyConstraints(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints);
-    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
-    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize);
+    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxPosition childPosition, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
+    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints, androidx.ui.core.IntPxSize childSize);
   }
 
   public final class AlignKt {
diff --git a/ui/ui-layout/api/restricted_0.1.0-dev01.txt b/ui/ui-layout/api/restricted_0.1.0-dev01.txt
index 457f8fe..eed16f4 100644
--- a/ui/ui-layout/api/restricted_0.1.0-dev01.txt
+++ b/ui/ui-layout/api/restricted_0.1.0-dev01.txt
@@ -19,8 +19,8 @@
     method public androidx.ui.core.IntPx minIntrinsicWidthOf(androidx.ui.core.DensityScope, androidx.ui.core.Measurable measurable, androidx.ui.core.IntPx height);
     method public androidx.ui.core.IntPx? modifyAlignmentLine(androidx.ui.core.DensityScope, androidx.ui.core.AlignmentLine line, androidx.ui.core.IntPx? value);
     method public androidx.ui.core.Constraints modifyConstraints(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints);
-    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
-    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize);
+    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxPosition childPosition, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
+    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints, androidx.ui.core.IntPxSize childSize);
   }
 
   public final class AlignKt {
diff --git a/ui/ui-layout/api/restricted_current.txt b/ui/ui-layout/api/restricted_current.txt
index 457f8fe..eed16f4 100644
--- a/ui/ui-layout/api/restricted_current.txt
+++ b/ui/ui-layout/api/restricted_current.txt
@@ -19,8 +19,8 @@
     method public androidx.ui.core.IntPx minIntrinsicWidthOf(androidx.ui.core.DensityScope, androidx.ui.core.Measurable measurable, androidx.ui.core.IntPx height);
     method public androidx.ui.core.IntPx? modifyAlignmentLine(androidx.ui.core.DensityScope, androidx.ui.core.AlignmentLine line, androidx.ui.core.IntPx? value);
     method public androidx.ui.core.Constraints modifyConstraints(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints);
-    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
-    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.IntPxSize childSize);
+    method public androidx.ui.core.IntPxPosition modifyPosition(androidx.ui.core.DensityScope, androidx.ui.core.IntPxPosition childPosition, androidx.ui.core.IntPxSize childSize, androidx.ui.core.IntPxSize containerSize);
+    method public androidx.ui.core.IntPxSize modifySize(androidx.ui.core.DensityScope, androidx.ui.core.Constraints constraints, androidx.ui.core.IntPxSize childSize);
   }
 
   public final class AlignKt {
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AbsolutePaddingModifierTest.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AbsolutePaddingModifierTest.kt
new file mode 100644
index 0000000..a9f2bc0
--- /dev/null
+++ b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AbsolutePaddingModifierTest.kt
@@ -0,0 +1,327 @@
+/*
+ * Copyright 2019 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.ui.layout.test
+
+import androidx.test.filters.SmallTest
+import androidx.ui.core.Dp
+import androidx.ui.core.IntPx
+import androidx.ui.core.OnPositioned
+import androidx.ui.core.PxPosition
+import androidx.ui.core.PxSize
+import androidx.ui.core.dp
+import androidx.ui.core.ipx
+import androidx.ui.core.px
+import androidx.ui.core.toPx
+import androidx.ui.core.withDensity
+import androidx.ui.layout.Center
+import androidx.ui.layout.ConstrainedBox
+import androidx.ui.layout.Container
+import androidx.ui.layout.DpConstraints
+import androidx.compose.Composable
+import androidx.compose.composer
+import androidx.ui.core.Layout
+import androidx.ui.core.Modifier
+import androidx.ui.core.min
+import androidx.ui.layout.AspectRatio
+import androidx.ui.layout.absolutePadding
+import androidx.ui.layout.padding
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+/**
+ * A trivial layout that applies a [Modifier] and measures/lays out a single child
+ * with the same constraints it received.
+ */
+@Composable
+private fun TestBox(modifier: Modifier = Modifier.None, body: @Composable() () -> Unit) {
+    Layout(children = body, modifier = modifier) { measurables, constraints ->
+        require(measurables.size == 1) {
+            "TestBox received ${measurables.size} children; must have exactly 1"
+        }
+        val placeable = measurables.first().measure(constraints)
+        layout(
+            min(placeable.width, constraints.maxWidth),
+            min(placeable.height, constraints.maxHeight)
+        ) {
+            placeable.place(IntPx.Zero, IntPx.Zero)
+        }
+    }
+}
+
+@SmallTest
+@RunWith(JUnit4::class)
+class AbsolutePaddingModifierTest : LayoutTest() {
+
+    /**
+     * Tests that the [absolutePadding] and [padding] factories return equivalent modifiers.
+     */
+    @Test
+    fun allEqualToAbsoluteWithExplicitSides() {
+        Assert.assertEquals(
+            absolutePadding(10.dp, 10.dp, 10.dp, 10.dp),
+            padding(10.dp)
+        )
+    }
+
+    /**
+     * Tests the top-level [padding] modifier factory with a single "all sides" argument,
+     * checking that a uniform padding of all sides is applied to a child when plenty of space is
+     * available for both content and padding.
+     */
+    @Test
+    fun paddingAllAppliedToChild() = withDensity(density) {
+        val padding = 10.dp
+        testPaddingIsAppliedImplementation(padding) { child: @Composable() () -> Unit ->
+            TestBox(modifier = padding(padding)) {
+                child()
+            }
+        }
+    }
+
+    /**
+     * Tests the top-level [absolutePadding] modifier factory with different values for left, top,
+     * right and bottom paddings, checking that this padding is applied as expected when plenty of
+     * space is available for both the content and padding.
+     */
+    @Test
+    fun absolutePaddingAppliedToChild() {
+        val padding = absolutePadding(10.dp, 15.dp, 20.dp, 30.dp)
+        testPaddingWithDifferentInsetsImplementation(
+            padding.left,
+            padding.top,
+            padding.right,
+            padding.bottom
+        ) { child: @Composable() () -> Unit ->
+            TestBox(modifier = padding) {
+                child()
+            }
+        }
+    }
+
+    /**
+     * Tests the result of the [padding] modifier factory when not enough space is available to
+     * accommodate both the padding and the content. In this case, the padding should still be
+     * applied, modifying the final position of the content by its left and top paddings even if it
+     * would result in constraints that the child content is unable or unwilling to satisfy.
+     */
+    @Test
+    fun insufficientSpaceAvailable() = withDensity(density) {
+        val padding = 30.dp
+        testPaddingWithInsufficientSpaceImplementation(padding) { child: @Composable() () -> Unit ->
+            TestBox(modifier = padding(padding)) {
+                child()
+            }
+        }
+    }
+
+    @Test
+    fun intrinsicMeasurements() = withDensity(density) {
+        val padding = 100.ipx.toDp()
+
+        val latch = CountDownLatch(1)
+        var error: Throwable? = null
+        testIntrinsics(@Composable {
+            TestBox(modifier = padding(padding)) {
+                AspectRatio(2f) { }
+            }
+        }) { minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth, maxIntrinsicHeight ->
+            // Padding is applied on both sides of an axis
+            val totalAxisPadding = (padding * 2).toIntPx()
+
+            // When the width/height is measured as 3 x the padding
+            val testDimension = (padding * 3).toIntPx()
+            // The actual dimension for the AspectRatio will be: test dimension - total padding
+            val actualAspectRatioDimension = testDimension - totalAxisPadding
+
+            // When we measure the width first, the height will be half
+            val expectedAspectRatioHeight = actualAspectRatioDimension / 2f
+            // When we measure the height first, the width will be double
+            val expectedAspectRatioWidth = actualAspectRatioDimension * 2
+
+            // Add back the padding on both sides to get the total expected height
+            val expectedTotalHeight = expectedAspectRatioHeight + totalAxisPadding
+            // Add back the padding on both sides to get the total expected height
+            val expectedTotalWidth = expectedAspectRatioWidth + totalAxisPadding
+
+            try {
+                // Min width.
+                assertEquals(totalAxisPadding, minIntrinsicWidth(0.dp.toIntPx()))
+                assertEquals(expectedTotalWidth, minIntrinsicWidth(testDimension))
+                assertEquals(totalAxisPadding, minIntrinsicWidth(IntPx.Infinity))
+                // Min height.
+                assertEquals(totalAxisPadding, minIntrinsicHeight(0.dp.toIntPx()))
+                assertEquals(expectedTotalHeight, minIntrinsicHeight(testDimension))
+                assertEquals(totalAxisPadding, minIntrinsicHeight(IntPx.Infinity))
+                // Max width.
+                assertEquals(totalAxisPadding, maxIntrinsicWidth(0.dp.toIntPx()))
+                assertEquals(expectedTotalWidth, maxIntrinsicWidth(testDimension))
+                assertEquals(totalAxisPadding, maxIntrinsicWidth(IntPx.Infinity))
+                // Max height.
+                assertEquals(totalAxisPadding, maxIntrinsicHeight(0.dp.toIntPx()))
+                assertEquals(expectedTotalHeight, maxIntrinsicHeight(testDimension))
+                assertEquals(totalAxisPadding, maxIntrinsicHeight(IntPx.Infinity))
+            } catch (t: Throwable) {
+                error = t
+            } finally {
+                latch.countDown()
+            }
+        }
+
+        latch.await(1, TimeUnit.SECONDS)
+        error?.let { throw it }
+
+        Unit
+    }
+
+    private fun testPaddingIsAppliedImplementation(
+        padding: Dp,
+        paddingContainer: @Composable() (@Composable() () -> Unit) -> Unit
+    ) = withDensity(density) {
+        val sizeDp = 50.dp
+        val size = sizeDp.toIntPx()
+        val paddingPx = padding.toIntPx()
+
+        val drawLatch = CountDownLatch(1)
+        var childSize = PxSize(-1.px, -1.px)
+        var childPosition = PxPosition(-1.px, -1.px)
+        show {
+            Center {
+                ConstrainedBox(constraints = DpConstraints.tightConstraints(sizeDp, sizeDp)) {
+                    val children = @Composable {
+                        Container {
+                            OnPositioned( coordinates ->
+                                childSize = coordinates.size
+                                childPosition =
+                                    coordinates.localToGlobal(PxPosition(0.px, 0.px))
+                                drawLatch.countDown()
+                            })
+                        }
+                    }
+                    paddingContainer(children)
+                }
+            }
+        }
+        drawLatch.await(1, TimeUnit.SECONDS)
+
+        val root = findAndroidComposeView()
+        waitForDraw(root)
+
+        val innerSize = (size - paddingPx * 2)
+        assertEquals(PxSize(innerSize, innerSize), childSize)
+        val left = ((root.width.ipx - size) / 2) + paddingPx
+        val top = ((root.height.ipx - size) / 2) + paddingPx
+        assertEquals(
+            PxPosition(left.toPx(), top.toPx()),
+            childPosition
+        )
+    }
+
+    private fun testPaddingWithDifferentInsetsImplementation(
+        left: Dp,
+        top: Dp,
+        right: Dp,
+        bottom: Dp,
+        paddingContainer: @Composable() ((@Composable() () -> Unit) -> Unit)
+    ) = withDensity(density) {
+        val sizeDp = 50.dp
+        val size = sizeDp.toIntPx()
+
+        val drawLatch = CountDownLatch(1)
+        var childSize = PxSize(-1.px, -1.px)
+        var childPosition = PxPosition(-1.px, -1.px)
+        show {
+            Center {
+                ConstrainedBox(constraints = DpConstraints.tightConstraints(sizeDp, sizeDp)) {
+                    val children = @Composable {
+                        Container {
+                            OnPositioned( coordinates ->
+                                childSize = coordinates.size
+                                childPosition =
+                                    coordinates.localToGlobal(PxPosition(0.px, 0.px))
+                                drawLatch.countDown()
+                            })
+                        }
+                    }
+                    paddingContainer(children)
+                }
+            }
+        }
+        drawLatch.await(1, TimeUnit.SECONDS)
+
+        val root = findAndroidComposeView()
+        waitForDraw(root)
+
+        val paddingLeft = left.toIntPx()
+        val paddingRight = right.toIntPx()
+        val paddingTop = top.toIntPx()
+        val paddingBottom = bottom.toIntPx()
+        assertEquals(
+            PxSize(
+                size - paddingLeft - paddingRight,
+                size - paddingTop - paddingBottom
+            ),
+            childSize
+        )
+        val viewLeft = ((root.width.ipx - size) / 2) + paddingLeft
+        val viewTop = ((root.height.ipx - size) / 2) + paddingTop
+        assertEquals(
+            PxPosition(viewLeft.toPx(), viewTop.toPx()),
+            childPosition
+        )
+    }
+
+    private fun testPaddingWithInsufficientSpaceImplementation(
+        padding: Dp,
+        paddingContainer: @Composable() (@Composable() () -> Unit) -> Unit
+    ) = withDensity(density) {
+        val sizeDp = 50.dp
+        val size = sizeDp.toIntPx()
+        val paddingPx = padding.toIntPx()
+
+        val drawLatch = CountDownLatch(1)
+        var childSize = PxSize(-1.px, -1.px)
+        var childPosition = PxPosition(-1.px, -1.px)
+        show {
+            Center {
+                ConstrainedBox(constraints = DpConstraints.tightConstraints(sizeDp, sizeDp)) {
+                    paddingContainer {
+                        Container {
+                            OnPositioned( coordinates ->
+                                childSize = coordinates.size
+                                childPosition = coordinates.localToGlobal(PxPosition(0.px, 0.px))
+                                drawLatch.countDown()
+                            })
+                        }
+                    }
+                }
+            }
+        }
+        drawLatch.await(1, TimeUnit.SECONDS)
+
+        val root = findAndroidComposeView()
+        waitForDraw(root)
+
+        assertEquals(PxSize(0.px, 0.px), childSize)
+        val left = ((root.width.ipx - size) / 2) + paddingPx
+        val top = ((root.height.ipx - size) / 2) + paddingPx
+        assertEquals(PxPosition(left.toPx(), top.toPx()), childPosition)
+    }
+}
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Padding.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Padding.kt
index 7884ed1..866e801 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Padding.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Padding.kt
@@ -31,7 +31,9 @@
 import androidx.ui.core.Layout
 import androidx.ui.core.LayoutModifier
 import androidx.ui.core.Measurable
-import androidx.ui.core.withDensity
+import androidx.ui.core.coerceAtLeast
+import androidx.ui.core.coerceIn
+import androidx.ui.core.ipx
 
 /**
  * Padding on all sides.
@@ -68,16 +70,20 @@
     val bottom: Dp = 0.dp
 ) : LayoutModifier {
     override fun DensityScope.minIntrinsicWidthOf(measurable: Measurable, height: IntPx): IntPx =
-        measurable.minIntrinsicWidth(height - (top + bottom).toIntPx())
+        measurable.minIntrinsicWidth((height - (top + bottom).toIntPx()).coerceAtLeast(0.ipx)) +
+                (left + right).toIntPx()
 
     override fun DensityScope.maxIntrinsicWidthOf(measurable: Measurable, height: IntPx): IntPx =
-        measurable.maxIntrinsicWidth(height - (top + bottom).toIntPx())
+        measurable.maxIntrinsicWidth((height - (top + bottom).toIntPx()).coerceAtLeast(0.ipx)) +
+                (left + right).toIntPx()
 
     override fun DensityScope.minIntrinsicHeightOf(measurable: Measurable, width: IntPx): IntPx =
-        measurable.minIntrinsicHeight(width - (left + right).toIntPx())
+        measurable.minIntrinsicHeight((width - (left + right).toIntPx()).coerceAtLeast(0.ipx)) +
+                (top + bottom).toIntPx()
 
     override fun DensityScope.maxIntrinsicHeightOf(measurable: Measurable, width: IntPx): IntPx =
-        measurable.maxIntrinsicHeight(width - (left + right).toIntPx())
+        measurable.maxIntrinsicHeight((width - (left + right).toIntPx()).coerceAtLeast(0.ipx)) +
+                (top + bottom).toIntPx()
 
     override fun DensityScope.modifyConstraints(
         constraints: Constraints
@@ -87,16 +93,20 @@
     )
 
     override fun DensityScope.modifySize(
+        constraints: Constraints,
         childSize: IntPxSize
     ) = IntPxSize(
-        left.toIntPx() + childSize.width + right.toIntPx(),
-        top.toIntPx() + childSize.height + bottom.toIntPx()
+        (left.toIntPx() + childSize.width + right.toIntPx())
+            .coerceIn(constraints.minWidth, constraints.maxWidth),
+        (top.toIntPx() + childSize.height + bottom.toIntPx())
+            .coerceIn(constraints.minHeight, constraints.maxHeight)
     )
 
     override fun DensityScope.modifyPosition(
+        childPosition: IntPxPosition,
         childSize: IntPxSize,
         containerSize: IntPxSize
-    ) = IntPxPosition(left.toIntPx(), top.toIntPx())
+    ) = IntPxPosition(left.toIntPx() + childPosition.x, top.toIntPx() + childPosition.y)
 
     override fun DensityScope.modifyAlignmentLine(line: AlignmentLine, value: IntPx?): IntPx? {
         if (value == null) return null
diff --git a/ui/ui-platform/src/main/java/androidx/ui/core/ComponentNodes.kt b/ui/ui-platform/src/main/java/androidx/ui/core/ComponentNodes.kt
index 2783df18..e71c2e2 100644
--- a/ui/ui-platform/src/main/java/androidx/ui/core/ComponentNodes.kt
+++ b/ui/ui-platform/src/main/java/androidx/ui/core/ComponentNodes.kt
@@ -652,7 +652,7 @@
             // Rebuild layoutNodeWrapper
             val oldPlaceable = layoutNodeWrapper
             layoutNodeWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod, toWrap ->
-                if (mod is LayoutModifier) ModifiedLayoutNode(toWrap, mod, density) else toWrap
+                if (mod is LayoutModifier) ModifiedLayoutNode(toWrap, mod) else toWrap
             }
             // Optimize the case where the layout itself is not modified. A common reason for
             // this is if no wrapping actually occurs above because no LayoutModifiers are
@@ -718,11 +718,10 @@
         override fun get(line: AlignmentLine): IntPx? = calculateAlignmentLines()[line]
     }
 
-    private data class ModifiedLayoutNode(
+    private inner class ModifiedLayoutNode(
         val wrapped: LayoutNodeWrapper,
-        val layoutModifier: LayoutModifier,
-        override val density: Density
-    ) : LayoutNodeWrapper(), DensityScope {
+        val layoutModifier: LayoutModifier
+    ) : LayoutNodeWrapper() {
 
         /**
          * The [Placeable] returned by measuring [wrapped] in [measure].
@@ -731,12 +730,33 @@
          */
         private var measuredPlaceable: Placeable? = null
 
+        /**
+         * The [Constraints] used in the current measurement of this modified node wrapper.
+         * See [withMeasuredConstraints]
+         */
+        private var measuredConstraints: Constraints? = null
+
+        /**
+         * Sets [measuredConstraints] for the duration of [block].
+         */
+        private inline fun <R> withMeasuredConstraints(
+            constraints: Constraints,
+            block: () -> R
+        ): R = try {
+            measuredConstraints = constraints
+            block()
+        } finally {
+            measuredConstraints = null
+        }
+
         // TODO change this once modifiers can take over for parentData
         override val parentData: Any?
             get() = wrapped.parentData
 
         override fun measure(constraints: Constraints): Placeable = with(layoutModifier) {
-            val measureResult = wrapped.measure(modifyConstraints(constraints))
+            val measureResult = withMeasuredConstraints(constraints) {
+                wrapped.measure(modifyConstraints(constraints))
+            }
             measuredPlaceable = measureResult
             this@ModifiedLayoutNode
         }
@@ -762,12 +782,7 @@
 
         override fun performPlace(position: IntPxPosition) {
             val placeable = measuredPlaceable ?: error("Placeable not measured")
-
-            val offset = with(layoutModifier) {
-                modifyPosition(placeable.size, size)
-            }
-
-            placeable.place(position + offset)
+            placeable.place(with(layoutModifier) { modifyPosition(position, placeable.size, size) })
         }
 
         override fun get(line: AlignmentLine): IntPx? = with(layoutModifier) {
@@ -775,7 +790,8 @@
         }
 
         override fun layoutSize(innermostSize: IntPxSize): IntPxSize = with(layoutModifier) {
-            modifySize(wrapped.layoutSize(innermostSize)).also { size = it }
+            val constraints = measuredConstraints ?: error("must be called during measurement")
+            modifySize(constraints, wrapped.layoutSize(innermostSize)).also { size = it }
         }
     }
 
@@ -1131,7 +1147,7 @@
     var y: Px = local.y
     var node: LayoutNode? = this
     while (node != null) {
-        val pos = node.modifiedPosition
+        val pos = node.contentPosition
         x += pos.x.toPx()
         y += pos.y.toPx()
         node = node.parentLayoutNode
@@ -1158,7 +1174,7 @@
         checkNotNull(node) {
             "Current layout is not an ancestor of the provided child layout"
         }
-        val pos = node.modifiedPosition
+        val pos = node.contentPosition
         x += pos.x.toPx()
         y += pos.y.toPx()
         node = node.parentLayoutNode
@@ -1243,9 +1259,9 @@
     private val layoutNode: LayoutNode
 ) : LayoutCoordinates {
 
-    override val position get() = PxPosition(layoutNode.x, layoutNode.y)
+    override val position get() = layoutNode.contentPosition.toPxPosition()
 
-    override val size get() = PxSize(layoutNode.width, layoutNode.height)
+    override val size get() = layoutNode.contentSize.toPxSize()
 
     override val parentCoordinates get() = layoutNode.parentLayoutNode?.coordinates