[go: nahoru, domu]

Add positioning to material DropdownMenu

Bug: 135741815
Test: MenuTest
Change-Id: I3bbe5b3fe851a8713b07c31a37f74dd1315ae0c5
diff --git a/ui/ui-core/api/0.1.0-dev12.txt b/ui/ui-core/api/0.1.0-dev12.txt
index 8e1d102..e4a85b3 100644
--- a/ui/ui-core/api/0.1.0-dev12.txt
+++ b/ui/ui-core/api/0.1.0-dev12.txt
@@ -771,9 +771,14 @@
   public final class PopupKt {
     method public static void DropdownPopup(androidx.ui.core.DropDownAlignment dropDownAlignment = androidx.ui.core.DropDownAlignment.Start, androidx.ui.unit.IntPxPosition offset = IntPxPosition(IntPx.Zero, IntPx.Zero), boolean isFocusable = false, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static void Popup(androidx.ui.core.Alignment alignment = Alignment.TopStart, androidx.ui.unit.IntPxPosition offset = IntPxPosition(IntPx.Zero, IntPx.Zero), boolean isFocusable = false, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void Popup(androidx.ui.core.PopupPositionProvider popupPositionProvider, boolean isFocusable = false, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static boolean isPopupLayout(android.view.View view, String? testTag = null);
   }
 
+  public interface PopupPositionProvider {
+    method public androidx.ui.unit.IntPxPosition calculatePosition(androidx.ui.unit.IntPxPosition parentLayoutPosition, androidx.ui.unit.IntPxSize parentLayoutSize, androidx.ui.core.LayoutDirection layoutDirection, androidx.ui.unit.IntPxSize popupSize);
+  }
+
   public final class Ref<T> {
     ctor public Ref();
     method public T? getValue();
diff --git a/ui/ui-core/api/current.txt b/ui/ui-core/api/current.txt
index 8e1d102..e4a85b3 100644
--- a/ui/ui-core/api/current.txt
+++ b/ui/ui-core/api/current.txt
@@ -771,9 +771,14 @@
   public final class PopupKt {
     method public static void DropdownPopup(androidx.ui.core.DropDownAlignment dropDownAlignment = androidx.ui.core.DropDownAlignment.Start, androidx.ui.unit.IntPxPosition offset = IntPxPosition(IntPx.Zero, IntPx.Zero), boolean isFocusable = false, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static void Popup(androidx.ui.core.Alignment alignment = Alignment.TopStart, androidx.ui.unit.IntPxPosition offset = IntPxPosition(IntPx.Zero, IntPx.Zero), boolean isFocusable = false, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void Popup(androidx.ui.core.PopupPositionProvider popupPositionProvider, boolean isFocusable = false, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static boolean isPopupLayout(android.view.View view, String? testTag = null);
   }
 
+  public interface PopupPositionProvider {
+    method public androidx.ui.unit.IntPxPosition calculatePosition(androidx.ui.unit.IntPxPosition parentLayoutPosition, androidx.ui.unit.IntPxSize parentLayoutSize, androidx.ui.core.LayoutDirection layoutDirection, androidx.ui.unit.IntPxSize popupSize);
+  }
+
   public final class Ref<T> {
     ctor public Ref();
     method public T? getValue();
diff --git a/ui/ui-core/api/public_plus_experimental_0.1.0-dev12.txt b/ui/ui-core/api/public_plus_experimental_0.1.0-dev12.txt
index 379ecc8..2086675 100644
--- a/ui/ui-core/api/public_plus_experimental_0.1.0-dev12.txt
+++ b/ui/ui-core/api/public_plus_experimental_0.1.0-dev12.txt
@@ -773,9 +773,14 @@
   public final class PopupKt {
     method public static void DropdownPopup(androidx.ui.core.DropDownAlignment dropDownAlignment = androidx.ui.core.DropDownAlignment.Start, androidx.ui.unit.IntPxPosition offset = IntPxPosition(IntPx.Zero, IntPx.Zero), boolean isFocusable = false, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static void Popup(androidx.ui.core.Alignment alignment = Alignment.TopStart, androidx.ui.unit.IntPxPosition offset = IntPxPosition(IntPx.Zero, IntPx.Zero), boolean isFocusable = false, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void Popup(androidx.ui.core.PopupPositionProvider popupPositionProvider, boolean isFocusable = false, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static boolean isPopupLayout(android.view.View view, String? testTag = null);
   }
 
+  public interface PopupPositionProvider {
+    method public androidx.ui.unit.IntPxPosition calculatePosition(androidx.ui.unit.IntPxPosition parentLayoutPosition, androidx.ui.unit.IntPxSize parentLayoutSize, androidx.ui.core.LayoutDirection layoutDirection, androidx.ui.unit.IntPxSize popupSize);
+  }
+
   public final class Ref<T> {
     ctor public Ref();
     method public T? getValue();
diff --git a/ui/ui-core/api/public_plus_experimental_current.txt b/ui/ui-core/api/public_plus_experimental_current.txt
index 379ecc8..2086675 100644
--- a/ui/ui-core/api/public_plus_experimental_current.txt
+++ b/ui/ui-core/api/public_plus_experimental_current.txt
@@ -773,9 +773,14 @@
   public final class PopupKt {
     method public static void DropdownPopup(androidx.ui.core.DropDownAlignment dropDownAlignment = androidx.ui.core.DropDownAlignment.Start, androidx.ui.unit.IntPxPosition offset = IntPxPosition(IntPx.Zero, IntPx.Zero), boolean isFocusable = false, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static void Popup(androidx.ui.core.Alignment alignment = Alignment.TopStart, androidx.ui.unit.IntPxPosition offset = IntPxPosition(IntPx.Zero, IntPx.Zero), boolean isFocusable = false, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void Popup(androidx.ui.core.PopupPositionProvider popupPositionProvider, boolean isFocusable = false, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static boolean isPopupLayout(android.view.View view, String? testTag = null);
   }
 
+  public interface PopupPositionProvider {
+    method public androidx.ui.unit.IntPxPosition calculatePosition(androidx.ui.unit.IntPxPosition parentLayoutPosition, androidx.ui.unit.IntPxSize parentLayoutSize, androidx.ui.core.LayoutDirection layoutDirection, androidx.ui.unit.IntPxSize popupSize);
+  }
+
   public final class Ref<T> {
     ctor public Ref();
     method public T? getValue();
diff --git a/ui/ui-core/api/restricted_0.1.0-dev12.txt b/ui/ui-core/api/restricted_0.1.0-dev12.txt
index 47967f7..93112c3 100644
--- a/ui/ui-core/api/restricted_0.1.0-dev12.txt
+++ b/ui/ui-core/api/restricted_0.1.0-dev12.txt
@@ -827,9 +827,14 @@
   public final class PopupKt {
     method public static void DropdownPopup(androidx.ui.core.DropDownAlignment dropDownAlignment = androidx.ui.core.DropDownAlignment.Start, androidx.ui.unit.IntPxPosition offset = IntPxPosition(IntPx.Zero, IntPx.Zero), boolean isFocusable = false, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static void Popup(androidx.ui.core.Alignment alignment = Alignment.TopStart, androidx.ui.unit.IntPxPosition offset = IntPxPosition(IntPx.Zero, IntPx.Zero), boolean isFocusable = false, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void Popup(androidx.ui.core.PopupPositionProvider popupPositionProvider, boolean isFocusable = false, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static boolean isPopupLayout(android.view.View view, String? testTag = null);
   }
 
+  public interface PopupPositionProvider {
+    method public androidx.ui.unit.IntPxPosition calculatePosition(androidx.ui.unit.IntPxPosition parentLayoutPosition, androidx.ui.unit.IntPxSize parentLayoutSize, androidx.ui.core.LayoutDirection layoutDirection, androidx.ui.unit.IntPxSize popupSize);
+  }
+
   public final class Ref<T> {
     ctor public Ref();
     method public T? getValue();
diff --git a/ui/ui-core/api/restricted_current.txt b/ui/ui-core/api/restricted_current.txt
index 47967f7..93112c3 100644
--- a/ui/ui-core/api/restricted_current.txt
+++ b/ui/ui-core/api/restricted_current.txt
@@ -827,9 +827,14 @@
   public final class PopupKt {
     method public static void DropdownPopup(androidx.ui.core.DropDownAlignment dropDownAlignment = androidx.ui.core.DropDownAlignment.Start, androidx.ui.unit.IntPxPosition offset = IntPxPosition(IntPx.Zero, IntPx.Zero), boolean isFocusable = false, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static void Popup(androidx.ui.core.Alignment alignment = Alignment.TopStart, androidx.ui.unit.IntPxPosition offset = IntPxPosition(IntPx.Zero, IntPx.Zero), boolean isFocusable = false, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void Popup(androidx.ui.core.PopupPositionProvider popupPositionProvider, boolean isFocusable = false, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static boolean isPopupLayout(android.view.View view, String? testTag = null);
   }
 
+  public interface PopupPositionProvider {
+    method public androidx.ui.unit.IntPxPosition calculatePosition(androidx.ui.unit.IntPxPosition parentLayoutPosition, androidx.ui.unit.IntPxSize parentLayoutSize, androidx.ui.core.LayoutDirection layoutDirection, androidx.ui.unit.IntPxSize popupSize);
+  }
+
   public final class Ref<T> {
     ctor public Ref();
     method public T? getValue();
diff --git a/ui/ui-core/src/androidTest/java/androidx/ui/core/PopupTest.kt b/ui/ui-core/src/androidTest/java/androidx/ui/core/PopupTest.kt
index b0114c9..b2714d1 100644
--- a/ui/ui-core/src/androidTest/java/androidx/ui/core/PopupTest.kt
+++ b/ui/ui-core/src/androidTest/java/androidx/ui/core/PopupTest.kt
@@ -37,8 +37,6 @@
 import androidx.ui.unit.dp
 import androidx.ui.unit.ipx
 import androidx.ui.unit.isFinite
-import androidx.ui.unit.toPxPosition
-import androidx.ui.unit.toPxSize
 import com.google.common.truth.Truth
 import org.hamcrest.CoreMatchers.instanceOf
 import org.hamcrest.Description
@@ -493,17 +491,14 @@
            y = parentGlobalPosition.y + offset.y
         */
         val expectedPositionTopStart = IntPxPosition(IntPx(60), IntPx(60))
-        val popupPositionProperties = PopupPositionProperties(
-            offset = offset
-        )
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
 
-        val positionTopStart = calculatePopupGlobalPosition(
-            popupPositionProperties,
-            Alignment.TopStart
-        )
+        val positionTopStart =
+            AlignmentOffsetPositionProvider(Alignment.TopStart, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Ltr,
+                popupSize
+            )
 
         Truth.assertThat(positionTopStart).isEqualTo(expectedPositionTopStart)
     }
@@ -515,18 +510,14 @@
            y = parentGlobalPosition.y + offset.y
         */
         val expectedPositionTopStart = IntPxPosition(IntPx(100), IntPx(60))
-        val popupPositionProperties = PopupPositionProperties(
-            offset = offset
-        )
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
-        popupPositionProperties.parentLayoutDirection = LayoutDirection.Rtl
 
-        val positionTopStart = calculatePopupGlobalPosition(
-            popupPositionProperties,
-            Alignment.TopStart
-        )
+        val positionTopStart =
+            AlignmentOffsetPositionProvider(Alignment.TopStart, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Rtl,
+                popupSize
+            )
 
         Truth.assertThat(positionTopStart).isEqualTo(expectedPositionTopStart)
     }
@@ -538,17 +529,14 @@
            y = parentGlobalPosition.y + offset.y
         */
         val expectedPositionTopCenter = IntPxPosition(IntPx(90), IntPx(60))
-        val popupPositionProperties = PopupPositionProperties(
-            offset = offset
-        )
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
 
-        val positionTopCenter = calculatePopupGlobalPosition(
-            popupPositionProperties,
-            Alignment.TopCenter
-        )
+        val positionTopCenter =
+            AlignmentOffsetPositionProvider(Alignment.TopCenter, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Ltr,
+                popupSize
+            )
 
         Truth.assertThat(positionTopCenter).isEqualTo(expectedPositionTopCenter)
     }
@@ -560,18 +548,14 @@
            y = parentGlobalPosition.y + offset.y
         */
         val expectedPositionTopCenter = IntPxPosition(IntPx(70), IntPx(60))
-        val popupPositionProperties = PopupPositionProperties(
-            offset = offset
-        )
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
-        popupPositionProperties.parentLayoutDirection = LayoutDirection.Rtl
 
-        val positionTopCenter = calculatePopupGlobalPosition(
-            popupPositionProperties,
-            Alignment.TopCenter
-        )
+        val positionTopCenter =
+            AlignmentOffsetPositionProvider(Alignment.TopCenter, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Rtl,
+                popupSize
+            )
 
         Truth.assertThat(positionTopCenter).isEqualTo(expectedPositionTopCenter)
     }
@@ -583,17 +567,14 @@
            y = parentGlobalPosition.y + offset.y
         */
         val expectedPositionTopEnd = IntPxPosition(IntPx(120), IntPx(60))
-        val popupPositionProperties = PopupPositionProperties(
-            offset = offset
-        )
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
 
-        val positionTopEnd = calculatePopupGlobalPosition(
-            popupPositionProperties,
-            Alignment.TopEnd
-        )
+        val positionTopEnd =
+            AlignmentOffsetPositionProvider(Alignment.TopEnd, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Ltr,
+                popupSize
+            )
 
         Truth.assertThat(positionTopEnd).isEqualTo(expectedPositionTopEnd)
     }
@@ -605,18 +586,14 @@
            y = parentGlobalPosition.y + offset.y
         */
         val expectedPositionTopEnd = IntPxPosition(IntPx(40), IntPx(60))
-        val popupPositionProperties = PopupPositionProperties(
-            offset = offset
-        )
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
-        popupPositionProperties.parentLayoutDirection = LayoutDirection.Rtl
 
-        val positionTopEnd = calculatePopupGlobalPosition(
-            popupPositionProperties,
-            Alignment.TopEnd
-        )
+        val positionTopEnd =
+            AlignmentOffsetPositionProvider(Alignment.TopEnd, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Rtl,
+                popupSize
+            )
 
         Truth.assertThat(positionTopEnd).isEqualTo(expectedPositionTopEnd)
     }
@@ -628,19 +605,16 @@
            y = parentGlobalPosition.y + offset.y + parentSize.y / 2 - popupSize.y / 2
         */
         val expectedPositionCenterEnd = IntPxPosition(IntPx(120), IntPx(100))
-        val popupPositionProperties = PopupPositionProperties(
-            offset = offset
-        )
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
 
-        val positionBottomEnd = calculatePopupGlobalPosition(
-            popupPositionProperties,
-            Alignment.CenterEnd
-        )
+        val positionCenterEnd =
+            AlignmentOffsetPositionProvider(Alignment.CenterEnd, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Ltr,
+                popupSize
+            )
 
-        Truth.assertThat(positionBottomEnd).isEqualTo(expectedPositionCenterEnd)
+        Truth.assertThat(positionCenterEnd).isEqualTo(expectedPositionCenterEnd)
     }
 
     @Test
@@ -650,20 +624,16 @@
            y = parentGlobalPosition.y + offset.y + parentSize.y / 2 - popupSize.y / 2
         */
         val expectedPositionCenterEnd = IntPxPosition(IntPx(40), IntPx(100))
-        val popupPositionProperties = PopupPositionProperties(
-            offset = offset
-        )
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
-        popupPositionProperties.parentLayoutDirection = LayoutDirection.Rtl
 
-        val positionBottomEnd = calculatePopupGlobalPosition(
-            popupPositionProperties,
-            Alignment.CenterEnd
-        )
+        val positionCenterEnd =
+            AlignmentOffsetPositionProvider(Alignment.CenterEnd, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Rtl,
+                popupSize
+            )
 
-        Truth.assertThat(positionBottomEnd).isEqualTo(expectedPositionCenterEnd)
+        Truth.assertThat(positionCenterEnd).isEqualTo(expectedPositionCenterEnd)
     }
 
     @Test
@@ -673,19 +643,16 @@
            y = parentGlobalPosition.y + offset.y + parentSize.y - popupSize.y
         */
         val expectedPositionBottomEnd = IntPxPosition(IntPx(120), IntPx(140))
-        val popupPositionProperties = PopupPositionProperties(
-            offset = offset
-        )
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
 
-        val positionBottomCenter = calculatePopupGlobalPosition(
-            popupPositionProperties,
-            Alignment.BottomEnd
-        )
+        val positionBottomEnd =
+            AlignmentOffsetPositionProvider(Alignment.BottomEnd, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Ltr,
+                popupSize
+            )
 
-        Truth.assertThat(positionBottomCenter).isEqualTo(expectedPositionBottomEnd)
+        Truth.assertThat(positionBottomEnd).isEqualTo(expectedPositionBottomEnd)
     }
 
     @Test
@@ -694,20 +661,17 @@
            x = parentGlobalPosition.x + parentSize.x - popupSize.x + offset.x
            y = parentGlobalPosition.y + offset.y + parentSize.y - popupSize.y
         */
-        val expectedPositionBottomEnd = IntPxPosition(IntPx(120), IntPx(140))
-        val popupPositionProperties = PopupPositionProperties(
-            offset = offset
-        )
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
+        val expectedPositionBottomEnd = IntPxPosition(IntPx(40), IntPx(140))
 
-        val positionBottomCenter = calculatePopupGlobalPosition(
-            popupPositionProperties,
-            Alignment.BottomEnd
-        )
+        val positionBottomEnd =
+            AlignmentOffsetPositionProvider(Alignment.BottomEnd, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Rtl,
+                popupSize
+            )
 
-        Truth.assertThat(positionBottomCenter).isEqualTo(expectedPositionBottomEnd)
+        Truth.assertThat(positionBottomEnd).isEqualTo(expectedPositionBottomEnd)
     }
 
     @Test
@@ -717,17 +681,14 @@
            y = parentGlobalPosition.y + offset.y + parentSize.y - popupSize.y
         */
         val expectedPositionBottomCenter = IntPxPosition(IntPx(90), IntPx(140))
-        val popupPositionProperties = PopupPositionProperties(
-            offset = offset
-        )
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
 
-        val positionBottomCenter = calculatePopupGlobalPosition(
-            popupPositionProperties,
-            Alignment.BottomCenter
-        )
+        val positionBottomCenter =
+            AlignmentOffsetPositionProvider(Alignment.BottomCenter, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Ltr,
+                popupSize
+            )
 
         Truth.assertThat(positionBottomCenter).isEqualTo(expectedPositionBottomCenter)
     }
@@ -739,18 +700,14 @@
            y = parentGlobalPosition.y + offset.y + parentSize.y - popupSize.y
         */
         val expectedPositionBottomCenter = IntPxPosition(IntPx(70), IntPx(140))
-        val popupPositionProperties = PopupPositionProperties(
-            offset = offset
-        )
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
-        popupPositionProperties.parentLayoutDirection = LayoutDirection.Rtl
 
-        val positionBottomCenter = calculatePopupGlobalPosition(
-            popupPositionProperties,
-            Alignment.BottomCenter
-        )
+        val positionBottomCenter =
+            AlignmentOffsetPositionProvider(Alignment.BottomCenter, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Rtl,
+                popupSize
+            )
 
         Truth.assertThat(positionBottomCenter).isEqualTo(expectedPositionBottomCenter)
     }
@@ -762,17 +719,14 @@
            y = parentGlobalPosition.y + offset.y + parentSize.y - popupSize.y
         */
         val expectedPositionBottomStart = IntPxPosition(IntPx(60), IntPx(140))
-        val popupPositionProperties = PopupPositionProperties(
-            offset = offset
-        )
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
 
-        val positionBottomStart = calculatePopupGlobalPosition(
-            popupPositionProperties,
-            Alignment.BottomStart
-        )
+        val positionBottomStart =
+            AlignmentOffsetPositionProvider(Alignment.BottomStart, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Ltr,
+                popupSize
+            )
 
         Truth.assertThat(positionBottomStart).isEqualTo(expectedPositionBottomStart)
     }
@@ -784,18 +738,14 @@
            y = parentGlobalPosition.y + offset.y + parentSize.y - popupSize.y
         */
         val expectedPositionBottomStart = IntPxPosition(IntPx(100), IntPx(140))
-        val popupPositionProperties = PopupPositionProperties(
-            offset = offset
-        )
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
-        popupPositionProperties.parentLayoutDirection = LayoutDirection.Rtl
 
-        val positionBottomStart = calculatePopupGlobalPosition(
-            popupPositionProperties,
-            Alignment.BottomStart
-        )
+        val positionBottomStart =
+            AlignmentOffsetPositionProvider(Alignment.BottomStart, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Rtl,
+                popupSize
+            )
 
         Truth.assertThat(positionBottomStart).isEqualTo(expectedPositionBottomStart)
     }
@@ -807,17 +757,14 @@
            y = parentGlobalPosition.y + offset.y + parentSize.y / 2 - popupSize.y / 2
         */
         val expectedPositionCenterStart = IntPxPosition(IntPx(60), IntPx(100))
-        val popupPositionProperties = PopupPositionProperties(
-            offset = offset
-        )
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
 
-        val positionCenterStart = calculatePopupGlobalPosition(
-            popupPositionProperties,
-            Alignment.CenterStart
-        )
+        val positionCenterStart =
+            AlignmentOffsetPositionProvider(Alignment.CenterStart, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Ltr,
+                popupSize
+            )
 
         Truth.assertThat(positionCenterStart).isEqualTo(expectedPositionCenterStart)
     }
@@ -829,18 +776,14 @@
            y = parentGlobalPosition.y + offset.y + parentSize.y / 2 - popupSize.y / 2
         */
         val expectedPositionCenterStart = IntPxPosition(IntPx(100), IntPx(100))
-        val popupPositionProperties = PopupPositionProperties(
-            offset = offset
-        )
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
-        popupPositionProperties.parentLayoutDirection = LayoutDirection.Rtl
 
-        val positionCenterStart = calculatePopupGlobalPosition(
-            popupPositionProperties,
-            Alignment.CenterStart
-        )
+        val positionCenterStart =
+            AlignmentOffsetPositionProvider(Alignment.CenterStart, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Rtl,
+                popupSize
+            )
 
         Truth.assertThat(positionCenterStart).isEqualTo(expectedPositionCenterStart)
     }
@@ -852,17 +795,14 @@
            y = parentGlobalPosition.y + offset.y + parentSize.y / 2 - popupSize.y / 2
         */
         val expectedPositionCenter = IntPxPosition(IntPx(90), IntPx(100))
-        val popupPositionProperties = PopupPositionProperties(
-            offset = offset
-        )
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
 
-        val positionCenter = calculatePopupGlobalPosition(
-            popupPositionProperties,
-            Alignment.Center
-        )
+        val positionCenter =
+            AlignmentOffsetPositionProvider(Alignment.Center, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Ltr,
+                popupSize
+            )
 
         Truth.assertThat(positionCenter).isEqualTo(expectedPositionCenter)
     }
@@ -874,18 +814,14 @@
            y = parentGlobalPosition.y + offset.y + parentSize.y / 2 - popupSize.y / 2
         */
         val expectedPositionCenter = IntPxPosition(IntPx(70), IntPx(100))
-        val popupPositionProperties = PopupPositionProperties(
-            offset = offset
-        )
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
-        popupPositionProperties.parentLayoutDirection = LayoutDirection.Rtl
 
-        val positionCenter = calculatePopupGlobalPosition(
-            popupPositionProperties,
-            Alignment.Center
-        )
+        val positionCenter =
+            AlignmentOffsetPositionProvider(Alignment.Center, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Rtl,
+                popupSize
+            )
 
         Truth.assertThat(positionCenter).isEqualTo(expectedPositionCenter)
     }
@@ -918,15 +854,14 @@
            y = parentGlobalPosition.y + offset.y + parentSize.y
         */
         val expectedPositionLeft = IntPxPosition(IntPx(60), IntPx(160))
-        val popupPositionProperties = PopupPositionProperties(offset = offset)
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
 
-        val positionLeft = calculateDropdownPopupPosition(
-            popupPositionProperties,
-            DropDownAlignment.Start
-        )
+        val positionLeft =
+            DropdownPositionProvider(DropDownAlignment.Start, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Ltr,
+                popupSize
+            )
 
         Truth.assertThat(positionLeft).isEqualTo(expectedPositionLeft)
     }
@@ -938,16 +873,14 @@
            y = parentGlobalPosition.y + offset.y + parentSize.y
         */
         val expectedPosition = IntPxPosition(IntPx(100), IntPx(160))
-        val popupPositionProperties = PopupPositionProperties(offset = offset)
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
-        popupPositionProperties.parentLayoutDirection = LayoutDirection.Rtl
 
-        val positionLeft = calculateDropdownPopupPosition(
-            popupPositionProperties,
-            DropDownAlignment.Start
-        )
+        val positionLeft =
+            DropdownPositionProvider(DropDownAlignment.Start, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Rtl,
+                popupSize
+            )
 
         Truth.assertThat(positionLeft).isEqualTo(expectedPosition)
     }
@@ -959,15 +892,14 @@
            y = parentGlobalPosition.y + offset.y + parentSize.y
         */
         val expectedPositionRight = IntPxPosition(IntPx(160), IntPx(160))
-        val popupPositionProperties = PopupPositionProperties(offset = offset)
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
 
-        val positionRight = calculateDropdownPopupPosition(
-            popupPositionProperties,
-            DropDownAlignment.End
-        )
+        val positionRight =
+            DropdownPositionProvider(DropDownAlignment.End, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Ltr,
+                popupSize
+            )
 
         Truth.assertThat(positionRight).isEqualTo(expectedPositionRight)
     }
@@ -979,16 +911,14 @@
            y = parentGlobalPosition.y + offset.y + parentSize.y
         */
         val expectedPositionRight = IntPxPosition(IntPx(0), IntPx(160))
-        val popupPositionProperties = PopupPositionProperties(offset = offset)
-        popupPositionProperties.parentPosition = parentGlobalPosition.toPxPosition()
-        popupPositionProperties.parentSize = parentSize.toPxSize()
-        popupPositionProperties.childrenSize = popupSize.toPxSize()
-        popupPositionProperties.parentLayoutDirection = LayoutDirection.Rtl
 
-        val positionRight = calculateDropdownPopupPosition(
-            popupPositionProperties,
-            DropDownAlignment.End
-        )
+        val positionRight =
+            DropdownPositionProvider(DropDownAlignment.End, offset).calculatePosition(
+                parentGlobalPosition,
+                parentSize,
+                LayoutDirection.Rtl,
+                popupSize
+            )
 
         Truth.assertThat(positionRight).isEqualTo(expectedPositionRight)
     }
diff --git a/ui/ui-core/src/main/java/androidx/ui/core/Popup.kt b/ui/ui-core/src/main/java/androidx/ui/core/Popup.kt
index 9f598bc..b46f0f9 100644
--- a/ui/ui-core/src/main/java/androidx/ui/core/Popup.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/core/Popup.kt
@@ -26,6 +26,7 @@
 import android.widget.FrameLayout
 import androidx.compose.Composable
 import androidx.compose.Composition
+import androidx.compose.Immutable
 import androidx.compose.Providers
 import androidx.compose.ambientOf
 import androidx.compose.currentComposer
@@ -41,11 +42,9 @@
 import androidx.ui.unit.IntPxPosition
 import androidx.ui.unit.IntPxSize
 import androidx.ui.unit.PxPosition
-import androidx.ui.unit.PxSize
 import androidx.ui.unit.ipx
 import androidx.ui.unit.max
 import androidx.ui.unit.round
-import androidx.ui.unit.toPxSize
 import org.jetbrains.annotations.TestOnly
 
 /**
@@ -73,19 +72,14 @@
     onDismissRequest: (() -> Unit)? = null,
     children: @Composable () -> Unit
 ) {
-    // Memoize the object, but change the value of the properties if a recomposition happens
-    val popupPositionProperties = remember {
-        PopupPositionProperties(
-            offset = offset
-        )
+    val popupPositioner = remember(alignment, offset) {
+        AlignmentOffsetPositionProvider(alignment, offset)
     }
-    popupPositionProperties.offset = offset
 
     Popup(
+        popupPositionProvider = popupPositioner,
         isFocusable = isFocusable,
         >
-        popupPositionProperties = popupPositionProperties,
-        calculatePopupPosition = { calculatePopupGlobalPosition(it, alignment) },
         children = children
     )
 }
@@ -113,19 +107,14 @@
     onDismissRequest: (() -> Unit)? = null,
     children: @Composable () -> Unit
 ) {
-    // Memoize the object, but change the value of the properties if a recomposition happens
-    val popupPositionProperties = remember {
-        PopupPositionProperties(
-            offset = offset
-        )
+    val popupPositioner = remember(dropDownAlignment, offset) {
+        DropdownPositionProvider(dropDownAlignment, offset)
     }
-    popupPositionProperties.offset = offset
 
     Popup(
+        popupPositionProvider = popupPositioner,
         isFocusable = isFocusable,
         >
-        popupPositionProperties = popupPositionProperties,
-        calculatePopupPosition = { calculateDropdownPopupPosition(it, dropDownAlignment) },
         children = children
     )
 }
@@ -140,12 +129,31 @@
     Providers(PopupTestTagAmbient provides tag, children = children)
 }
 
+internal class PopupPositionProperties {
+    var parentPosition = IntPxPosition.Origin
+    var parentSize = IntPxSize.Zero
+    var childrenSize = IntPxSize.Zero
+    var parentLayoutDirection: LayoutDirection = LayoutDirection.Ltr
+}
+
+/**
+ * Opens a popup with the given content.
+ *
+ * The popup is positioned using a custom [popupPositionProvider].
+ *
+ * @sample androidx.ui.core.samples.PopupSample
+ *
+ * @param popupPositionProvider Provides the screen position of the popup.
+ * @param isFocusable Indicates if the popup can grab the focus.
+ * @param onDismissRequest Executes when the popup tries to dismiss itself. This happens when
+ * the popup is focusable and the user clicks outside.
+ * @param children The content to be displayed inside the popup.
+ */
 @Composable
-private fun Popup(
-    isFocusable: Boolean,
+fun Popup(
+    popupPositionProvider: PopupPositionProvider,
+    isFocusable: Boolean = false,
     onDismissRequest: (() -> Unit)? = null,
-    popupPositionProperties: PopupPositionProperties,
-    calculatePopupPosition: ((PopupPositionProperties) -> IntPxPosition),
     children: @Composable () -> Unit
 ) {
     val context = ContextAmbient.current
@@ -154,6 +162,7 @@
     val owner = OwnerAmbient.current
     val providedTestTag = PopupTestTagAmbient.current
 
+    val popupPositionProperties = remember { PopupPositionProperties() }
     val popupLayout = remember(isFocusable) {
         escapeCompose {
             PopupLayout(
@@ -162,12 +171,12 @@
                 popupIsFocusable = isFocusable,
                 >
                 popupPositionProperties = popupPositionProperties,
-                calculatePopupPosition = calculatePopupPosition,
+                popupPositionProvider = popupPositionProvider,
                 testTag = providedTestTag
             )
         }
     }
-    popupLayout.calculatePopupPosition = calculatePopupPosition
+    popupLayout.popupPositionProvider = popupPositionProvider
 
     var composition: Composition? = null
 
@@ -177,11 +186,11 @@
     Layout(children = emptyContent(), modifier = Modifier.onPositioned { childCoordinates ->
         val coordinates = childCoordinates.parentCoordinates!!
         // Get the global position of the parent
-        val layoutPosition = coordinates.localToGlobal(PxPosition.Origin)
+        val layoutPosition = coordinates.localToGlobal(PxPosition.Origin).round()
         val layoutSize = coordinates.size
 
         popupLayout.popupPositionProperties.parentPosition = layoutPosition
-        popupLayout.popupPositionProperties.parentSize = layoutSize.toPxSize()
+        popupLayout.popupPositionProperties.parentSize = layoutSize
 
         // Update the popup's position
         popupLayout.updatePosition()
@@ -196,7 +205,7 @@
             Semantics(container = true, properties = { this.popup = true }) {
                 SimpleStack(Modifier.onPositioned {
                     // Get the size of the content
-                    popupLayout.popupPositionProperties.childrenSize = it.size.toPxSize()
+                    popupLayout.popupPositionProperties.childrenSize = it.size
 
                     // Update the popup's position
                     popupLayout.updatePosition()
@@ -253,7 +262,7 @@
  * @param composeView The parent view of the popup which is the AndroidComposeView.
  * @param popupIsFocusable Indicates if the popup can grab the focus.
  * @param onDismissRequest Executed when the popup tries to dismiss itself.
- * @param calculatePopupPosition The logic of positioning the popup relative to its parent.
+ * @param popupPositionProvider The logic of positioning the popup relative to its parent.
  */
 @SuppressLint("ViewConstructor")
 private class PopupLayout(
@@ -262,7 +271,7 @@
     val popupIsFocusable: Boolean,
     val onDismissRequest: (() -> Unit)? = null,
     var popupPositionProperties: PopupPositionProperties,
-    var calculatePopupPosition: ((PopupPositionProperties) -> IntPxPosition),
+    var popupPositionProvider: PopupPositionProvider,
     var testTag: String
 ) : FrameLayout(context) {
     val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
@@ -279,7 +288,12 @@
      * relative to its parent.
      */
     fun updatePosition() {
-        val popupGlobalPosition = calculatePopupPosition(popupPositionProperties)
+        val popupGlobalPosition = popupPositionProvider.calculatePosition(
+            popupPositionProperties.parentPosition,
+            popupPositionProperties.parentSize,
+            popupPositionProperties.parentLayoutDirection,
+            popupPositionProperties.childrenSize
+        )
 
         params.x = popupGlobalPosition.x.value
         params.y = popupGlobalPosition.y.value
@@ -360,13 +374,25 @@
     }
 }
 
-internal data class PopupPositionProperties(
-    var offset: IntPxPosition
-) {
-    var parentPosition = PxPosition.Origin
-    var parentSize = PxSize.Zero
-    var childrenSize = PxSize.Zero
-    var parentLayoutDirection: LayoutDirection = LayoutDirection.Ltr
+/**
+ * Calculates the position of a [Popup] on screen.
+ */
+@Immutable
+interface PopupPositionProvider {
+    /**
+     * Calculates the position of a [Popup] on screen.
+     *
+     * @param parentLayoutPosition The position of the parent wrapper layout on screen.
+     * @param parentLayoutSize The size of the parent wrapper layout.
+     * @param layoutDirection The layout direction of the parent wrapper layout.
+     * @param popupSize The size of the popup to be positioned.
+     */
+    fun calculatePosition(
+        parentLayoutPosition: IntPxPosition,
+        parentLayoutSize: IntPxSize,
+        layoutDirection: LayoutDirection,
+        popupSize: IntPxSize
+    ): IntPxPosition
 }
 
 /**
@@ -378,106 +404,100 @@
     End
 }
 
-internal fun calculatePopupGlobalPosition(
-    popupPositionProperties: PopupPositionProperties,
-    alignment: Alignment
-): IntPxPosition {
-    val layoutDirection = popupPositionProperties.parentLayoutDirection
+internal class AlignmentOffsetPositionProvider(
+    val alignment: Alignment,
+    val offset: IntPxPosition
+) : PopupPositionProvider {
+    override fun calculatePosition(
+        parentLayoutPosition: IntPxPosition,
+        parentLayoutSize: IntPxSize,
+        layoutDirection: LayoutDirection,
+        popupSize: IntPxSize
+    ): IntPxPosition {
+        // TODO: Decide which is the best way to round to result without reimplementing Alignment.align
+        var popupGlobalPosition = IntPxPosition(IntPx.Zero, IntPx.Zero)
 
-    // TODO: Decide which is the best way to round to result without reimplementing Alignment.align
-    var popupGlobalPosition = IntPxPosition(IntPx.Zero, IntPx.Zero)
+        // Get the aligned point inside the parent
+        val parentAlignmentPoint = alignment.align(
+            IntPxSize(parentLayoutSize.width, parentLayoutSize.height),
+            layoutDirection
+        )
+        // Get the aligned point inside the child
+        val relativePopupPos = alignment.align(
+            IntPxSize(popupSize.width, popupSize.height),
+            layoutDirection
+        )
 
-    // Get the aligned point inside the parent
-    val parentAlignmentPoint = alignment.align(
-        IntPxSize(
-            popupPositionProperties.parentSize.width.round(),
-            popupPositionProperties.parentSize.height.round()
-        ),
-        layoutDirection
-    )
-    // Get the aligned point inside the child
-    val relativePopupPos = alignment.align(
-        IntPxSize(
-            popupPositionProperties.childrenSize.width.round(),
-            popupPositionProperties.childrenSize.height.round()
-        ),
-        layoutDirection
-    )
+        // Add the global position of the parent
+        popupGlobalPosition += IntPxPosition(parentLayoutPosition.x, parentLayoutPosition.y)
 
-    // Add the global position of the parent
-    popupGlobalPosition += IntPxPosition(
-        popupPositionProperties.parentPosition.x.round(),
-        popupPositionProperties.parentPosition.y.round()
-    )
+        // Add the distance between the parent's top left corner and the alignment point
+        popupGlobalPosition += parentAlignmentPoint
 
-    // Add the distance between the parent's top left corner and the alignment point
-    popupGlobalPosition += parentAlignmentPoint
+        // Subtract the distance between the children's top left corner and the alignment point
+        popupGlobalPosition -= IntPxPosition(relativePopupPos.x, relativePopupPos.y)
 
-    // Subtract the distance between the children's top left corner and the alignment point
-    popupGlobalPosition -= IntPxPosition(relativePopupPos.x, relativePopupPos.y)
+        // Add the user offset
+        val resolvedOffset = IntPxPosition(
+            offset.x * (if (layoutDirection == LayoutDirection.Ltr) 1 else -1),
+            offset.y
+        )
+        popupGlobalPosition += resolvedOffset
 
-    // Add the user offset
-    val offset = IntPxPosition(
-        popupPositionProperties.offset.x * (if (layoutDirection == LayoutDirection.Ltr) 1 else -1),
-        popupPositionProperties.offset.y
-    )
-    popupGlobalPosition += offset
-
-    return popupGlobalPosition
+        return popupGlobalPosition
+    }
 }
 
-internal fun calculateDropdownPopupPosition(
-    popupPositionProperties: PopupPositionProperties,
-    dropDownAlignment: DropDownAlignment
-): IntPxPosition {
-    val layoutDirection = popupPositionProperties.parentLayoutDirection
+internal class DropdownPositionProvider(
+    val dropDownAlignment: DropDownAlignment,
+    val offset: IntPxPosition
+) : PopupPositionProvider {
+    override fun calculatePosition(
+        parentLayoutPosition: IntPxPosition,
+        parentLayoutSize: IntPxSize,
+        layoutDirection: LayoutDirection,
+        popupSize: IntPxSize
+    ): IntPxPosition {
+        var popupGlobalPosition = IntPxPosition(IntPx.Zero, IntPx.Zero)
 
-    var popupGlobalPosition = IntPxPosition(IntPx.Zero, IntPx.Zero)
+        // Add the global position of the parent
+        popupGlobalPosition += IntPxPosition(parentLayoutPosition.x, parentLayoutPosition.y)
 
-    // Add the global position of the parent
-    popupGlobalPosition += IntPxPosition(
-        popupPositionProperties.parentPosition.x.round(),
-        popupPositionProperties.parentPosition.y.round()
-    )
-
-    /*
-    * In LTR context aligns popup's left edge with the parent's left edge for Start alignment and
-    * parent's right edge for End alignment.
-    * In RTL context aligns popup's right edge with the parent's right edge for Start alignment and
-    * parent's left edge for End alignment.
-    */
-    val alignmentPositionX =
-        if (dropDownAlignment == DropDownAlignment.Start) {
-            if (layoutDirection == LayoutDirection.Ltr) {
-                0.ipx
+        /*
+        * In LTR context aligns popup's left edge with the parent's left edge for Start alignment and
+        * parent's right edge for End alignment.
+        * In RTL context aligns popup's right edge with the parent's right edge for Start alignment and
+        * parent's left edge for End alignment.
+        */
+        val alignmentPositionX =
+            if (dropDownAlignment == DropDownAlignment.Start) {
+                if (layoutDirection == LayoutDirection.Ltr) {
+                    0.ipx
+                } else {
+                    parentLayoutSize.width - popupSize.width
+                }
             } else {
-                popupPositionProperties.parentSize.width.round() -
-                    popupPositionProperties.childrenSize.width.round()
+                if (layoutDirection == LayoutDirection.Ltr) {
+                    parentLayoutSize.width
+                } else {
+                    -popupSize.width
+                }
             }
-        } else {
-            if (layoutDirection == LayoutDirection.Ltr) {
-                popupPositionProperties.parentSize.width.round()
-            } else {
-                -popupPositionProperties.childrenSize.width.round()
-            }
+
+        // The popup's position relative to the parent's top left corner
+        val dropdownAlignmentPosition = IntPxPosition(alignmentPositionX, parentLayoutSize.height)
+
+        popupGlobalPosition += dropdownAlignmentPosition
+
+        // Add the user offset
+        val resolvedOffset = IntPxPosition(
+            offset.x * (if (layoutDirection == LayoutDirection.Ltr) 1 else -1),
+            offset.y
+        )
+        popupGlobalPosition += resolvedOffset
+
+        return popupGlobalPosition
     }
-
-    // The popup's position relative to the parent's top left corner
-    val dropdownAlignmentPosition = IntPxPosition(
-        alignmentPositionX,
-        popupPositionProperties.parentSize.height.round()
-    )
-
-    popupGlobalPosition += dropdownAlignmentPosition
-
-    // Add the user offset
-    val offset = IntPxPosition(
-        popupPositionProperties.offset.x * (if (layoutDirection == LayoutDirection.Ltr) 1 else -1),
-        popupPositionProperties.offset.y
-    )
-    popupGlobalPosition += offset
-
-    return popupGlobalPosition
 }
 
 /**
diff --git a/ui/ui-material/api/0.1.0-dev12.txt b/ui/ui-material/api/0.1.0-dev12.txt
index 6862a59..95144f1 100644
--- a/ui/ui-material/api/0.1.0-dev12.txt
+++ b/ui/ui-material/api/0.1.0-dev12.txt
@@ -165,7 +165,7 @@
   }
 
   public final class MenuKt {
-    method public static void DropdownMenu(kotlin.jvm.functions.Function0<kotlin.Unit> toggle, boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, androidx.ui.core.Modifier toggleModifier = Modifier, androidx.ui.core.Modifier dropdownModifier = Modifier, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> dropdownContent);
+    method public static void DropdownMenu-1DTJxuk(kotlin.jvm.functions.Function0<kotlin.Unit> toggle, boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, androidx.ui.core.Modifier toggleModifier = Modifier, long dropdownOffset = Position(0.dp, 0.dp), androidx.ui.core.Modifier dropdownModifier = Modifier, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> dropdownContent);
     method public static void DropdownMenuItem(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.ui.core.Modifier modifier = Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
diff --git a/ui/ui-material/api/current.txt b/ui/ui-material/api/current.txt
index 6862a59..95144f1 100644
--- a/ui/ui-material/api/current.txt
+++ b/ui/ui-material/api/current.txt
@@ -165,7 +165,7 @@
   }
 
   public final class MenuKt {
-    method public static void DropdownMenu(kotlin.jvm.functions.Function0<kotlin.Unit> toggle, boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, androidx.ui.core.Modifier toggleModifier = Modifier, androidx.ui.core.Modifier dropdownModifier = Modifier, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> dropdownContent);
+    method public static void DropdownMenu-1DTJxuk(kotlin.jvm.functions.Function0<kotlin.Unit> toggle, boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, androidx.ui.core.Modifier toggleModifier = Modifier, long dropdownOffset = Position(0.dp, 0.dp), androidx.ui.core.Modifier dropdownModifier = Modifier, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> dropdownContent);
     method public static void DropdownMenuItem(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.ui.core.Modifier modifier = Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
diff --git a/ui/ui-material/api/public_plus_experimental_0.1.0-dev12.txt b/ui/ui-material/api/public_plus_experimental_0.1.0-dev12.txt
index 6862a59..95144f1 100644
--- a/ui/ui-material/api/public_plus_experimental_0.1.0-dev12.txt
+++ b/ui/ui-material/api/public_plus_experimental_0.1.0-dev12.txt
@@ -165,7 +165,7 @@
   }
 
   public final class MenuKt {
-    method public static void DropdownMenu(kotlin.jvm.functions.Function0<kotlin.Unit> toggle, boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, androidx.ui.core.Modifier toggleModifier = Modifier, androidx.ui.core.Modifier dropdownModifier = Modifier, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> dropdownContent);
+    method public static void DropdownMenu-1DTJxuk(kotlin.jvm.functions.Function0<kotlin.Unit> toggle, boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, androidx.ui.core.Modifier toggleModifier = Modifier, long dropdownOffset = Position(0.dp, 0.dp), androidx.ui.core.Modifier dropdownModifier = Modifier, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> dropdownContent);
     method public static void DropdownMenuItem(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.ui.core.Modifier modifier = Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
diff --git a/ui/ui-material/api/public_plus_experimental_current.txt b/ui/ui-material/api/public_plus_experimental_current.txt
index 6862a59..95144f1 100644
--- a/ui/ui-material/api/public_plus_experimental_current.txt
+++ b/ui/ui-material/api/public_plus_experimental_current.txt
@@ -165,7 +165,7 @@
   }
 
   public final class MenuKt {
-    method public static void DropdownMenu(kotlin.jvm.functions.Function0<kotlin.Unit> toggle, boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, androidx.ui.core.Modifier toggleModifier = Modifier, androidx.ui.core.Modifier dropdownModifier = Modifier, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> dropdownContent);
+    method public static void DropdownMenu-1DTJxuk(kotlin.jvm.functions.Function0<kotlin.Unit> toggle, boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, androidx.ui.core.Modifier toggleModifier = Modifier, long dropdownOffset = Position(0.dp, 0.dp), androidx.ui.core.Modifier dropdownModifier = Modifier, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> dropdownContent);
     method public static void DropdownMenuItem(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.ui.core.Modifier modifier = Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
diff --git a/ui/ui-material/api/restricted_0.1.0-dev12.txt b/ui/ui-material/api/restricted_0.1.0-dev12.txt
index 1548f01..a766d05 100644
--- a/ui/ui-material/api/restricted_0.1.0-dev12.txt
+++ b/ui/ui-material/api/restricted_0.1.0-dev12.txt
@@ -166,7 +166,7 @@
   }
 
   public final class MenuKt {
-    method public static void DropdownMenu(kotlin.jvm.functions.Function0<kotlin.Unit> toggle, boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, androidx.ui.core.Modifier toggleModifier = Modifier, androidx.ui.core.Modifier dropdownModifier = Modifier, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> dropdownContent);
+    method public static void DropdownMenu-1DTJxuk(kotlin.jvm.functions.Function0<kotlin.Unit> toggle, boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, androidx.ui.core.Modifier toggleModifier = Modifier, long dropdownOffset = Position(0.dp, 0.dp), androidx.ui.core.Modifier dropdownModifier = Modifier, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> dropdownContent);
     method public static void DropdownMenuItem(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.ui.core.Modifier modifier = Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
diff --git a/ui/ui-material/api/restricted_current.txt b/ui/ui-material/api/restricted_current.txt
index 1548f01..a766d05 100644
--- a/ui/ui-material/api/restricted_current.txt
+++ b/ui/ui-material/api/restricted_current.txt
@@ -166,7 +166,7 @@
   }
 
   public final class MenuKt {
-    method public static void DropdownMenu(kotlin.jvm.functions.Function0<kotlin.Unit> toggle, boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, androidx.ui.core.Modifier toggleModifier = Modifier, androidx.ui.core.Modifier dropdownModifier = Modifier, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> dropdownContent);
+    method public static void DropdownMenu-1DTJxuk(kotlin.jvm.functions.Function0<kotlin.Unit> toggle, boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, androidx.ui.core.Modifier toggleModifier = Modifier, long dropdownOffset = Position(0.dp, 0.dp), androidx.ui.core.Modifier dropdownModifier = Modifier, kotlin.jvm.functions.Function1<? super androidx.ui.layout.ColumnScope,kotlin.Unit> dropdownContent);
     method public static void DropdownMenuItem(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.ui.core.Modifier modifier = Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MenuDemo.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MenuDemo.kt
index 3c12512..2bf97f1 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MenuDemo.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MenuDemo.kt
@@ -26,15 +26,49 @@
 import androidx.ui.foundation.Text
 import androidx.ui.layout.fillMaxSize
 import androidx.ui.layout.wrapContentSize
-import androidx.ui.material.Divider
+import androidx.ui.core.LayoutDirection
+import androidx.ui.layout.Stack
+import androidx.ui.layout.ltr
 import androidx.ui.material.DropdownMenu
 import androidx.ui.material.DropdownMenuItem
 import androidx.ui.material.IconButton
 import androidx.ui.material.icons.Icons
 import androidx.ui.material.icons.filled.MoreVert
+import androidx.ui.unit.IntPxPosition
+import androidx.ui.unit.IntPxSize
+import androidx.ui.unit.Position
+import androidx.ui.unit.dp
 
 @Composable
 fun MenuDemo() {
+    Stack(Modifier.ltr) {
+        for (i in 0..10) {
+            for (j in 0..10) {
+                MenuInstance(
+                    Modifier.fillMaxSize().wrapContentSize(
+                        object : Alignment {
+                            override fun align(
+                                size: IntPxSize,
+                                layoutDirection: LayoutDirection
+                            ) = IntPxPosition(size.width * i / 10f, size.height * j / 10f)
+                        }
+                    )
+                )
+            }
+        }
+    }
+}
+
+@Composable
+fun MenuInstance(modifier: Modifier = Modifier) {
+    val options = listOf(
+        "Refresh",
+        "Settings",
+        "Send Feedback",
+        "Help",
+        "Signout"
+    )
+
     var expanded by state { false }
 
     val iconButton = @Composable {
@@ -46,17 +80,13 @@
         expanded = expanded,
          expanded = false },
         toggle = iconButton,
-        toggleModifier = Modifier.fillMaxSize().wrapContentSize(Alignment.TopStart)
+        dropdownOffset = Position(-12.dp, -12.dp),
+        toggleModifier = modifier
     ) {
-        DropdownMenuItem( /* Handle refresh! */ }) {
-            Text("Refresh")
-        }
-        DropdownMenuItem( /* Handle settings! */ }) {
-            Text("Settings")
-        }
-        Divider()
-        DropdownMenuItem( /* Handle send feedback! */ }) {
-            Text("Send Feedback")
+        options.forEach {
+            DropdownMenuItem( {
+                Text(it)
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/MenuTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/MenuTest.kt
index 9fd340d3..c62e3fa 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/MenuTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/MenuTest.kt
@@ -16,10 +16,12 @@
 
 package androidx.ui.material
 
+import android.util.DisplayMetrics
 import androidx.compose.getValue
 import androidx.compose.mutableStateOf
 import androidx.compose.setValue
 import androidx.test.filters.MediumTest
+import androidx.ui.core.LayoutDirection
 import androidx.ui.core.Modifier
 import androidx.ui.core.TestTag
 import androidx.ui.foundation.Box
@@ -38,7 +40,12 @@
 import androidx.ui.test.isPopup
 import androidx.ui.test.runOnIdleCompose
 import androidx.ui.test.waitForIdle
+import androidx.ui.unit.Density
+import androidx.ui.unit.IntPxPosition
+import androidx.ui.unit.IntPxSize
+import androidx.ui.unit.Position
 import androidx.ui.unit.dp
+import androidx.ui.unit.ipx
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
@@ -121,4 +128,111 @@
             )
         }
     }
+
+    @Test
+    fun menu_positioning_bottomEnd() {
+        val screenWidth = 500
+        val screenHeight = 1000
+        val density = Density(1f)
+        val displayMetrics = DisplayMetrics().apply {
+            widthPixels = screenWidth
+            heightPixels = screenHeight
+        }
+        val anchorPosition = IntPxPosition(100.ipx, 200.ipx)
+        val anchorSize = IntPxSize(10.ipx, 20.ipx)
+        val inset = with(density) { MenuElevation.toIntPx() }
+        val offsetX = 20
+        val offsetY = 40
+        val popupSize = IntPxSize(50.ipx, 80.ipx)
+
+        val ltrPosition = DropdownMenuPositionProvider(
+            Position(offsetX.dp, offsetY.dp),
+            density,
+            displayMetrics
+        ).calculatePosition(
+            anchorPosition,
+            anchorSize,
+            LayoutDirection.Ltr,
+            popupSize
+        )
+
+        assertThat(ltrPosition.x).isEqualTo(
+            anchorPosition.x + anchorSize.width - inset + offsetX.ipx
+        )
+        assertThat(ltrPosition.y).isEqualTo(
+            anchorPosition.y + anchorSize.height - inset + offsetY.ipx
+        )
+
+        val rtlPosition = DropdownMenuPositionProvider(
+            Position(offsetX.dp, offsetY.dp),
+            density,
+            displayMetrics
+        ).calculatePosition(
+            anchorPosition,
+            anchorSize,
+            LayoutDirection.Rtl,
+            popupSize
+        )
+
+        assertThat(rtlPosition.x).isEqualTo(
+            anchorPosition.x - popupSize.width + inset - offsetX.ipx
+        )
+        assertThat(rtlPosition.y).isEqualTo(
+            anchorPosition.y + anchorSize.height - inset + offsetY.ipx
+        )
+    }
+
+    @Test
+    fun menu_positioning_topStart() {
+        val screenWidth = 500
+        val screenHeight = 1000
+        val density = Density(1f)
+        val displayMetrics = DisplayMetrics().apply {
+            widthPixels = screenWidth
+            heightPixels = screenHeight
+        }
+        val anchorPosition = IntPxPosition(450.ipx, 950.ipx)
+        val anchorPositionRtl = IntPxPosition(50.ipx, 950.ipx)
+        val anchorSize = IntPxSize(10.ipx, 20.ipx)
+        val inset = with(density) { MenuElevation.toIntPx() }
+        val offsetX = 20
+        val offsetY = 40
+        val popupSize = IntPxSize(150.ipx, 80.ipx)
+
+        val ltrPosition = DropdownMenuPositionProvider(
+            Position(offsetX.dp, offsetY.dp),
+            density,
+            displayMetrics
+        ).calculatePosition(
+            anchorPosition,
+            anchorSize,
+            LayoutDirection.Ltr,
+            popupSize
+        )
+
+        assertThat(ltrPosition.x).isEqualTo(
+            anchorPosition.x - popupSize.width + inset - offsetX.ipx
+        )
+        assertThat(ltrPosition.y).isEqualTo(
+            anchorPosition.y - popupSize.height + inset - offsetY.ipx
+        )
+
+        val rtlPosition = DropdownMenuPositionProvider(
+            Position(offsetX.dp, offsetY.dp),
+            density,
+            displayMetrics
+        ).calculatePosition(
+            anchorPositionRtl,
+            anchorSize,
+            LayoutDirection.Rtl,
+            popupSize
+        )
+
+        assertThat(rtlPosition.x).isEqualTo(
+            anchorPositionRtl.x + anchorSize.width - inset + offsetX.ipx
+        )
+        assertThat(rtlPosition.y).isEqualTo(
+            anchorPositionRtl.y - popupSize.height + inset - offsetY.ipx
+        )
+    }
 }
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Menu.kt b/ui/ui-material/src/main/java/androidx/ui/material/Menu.kt
index a0715098..d5d4dd7 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Menu.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Menu.kt
@@ -16,17 +16,23 @@
 
 package androidx.ui.material
 
+import android.util.DisplayMetrics
 import androidx.animation.FloatPropKey
 import androidx.animation.LinearOutSlowInEasing
 import androidx.animation.transitionDefinition
 import androidx.compose.Composable
+import androidx.compose.Immutable
 import androidx.compose.getValue
 import androidx.compose.setValue
 import androidx.compose.state
 import androidx.ui.animation.Transition
+import androidx.ui.core.ContextAmbient
 import androidx.ui.core.DensityAmbient
-import androidx.ui.core.DropdownPopup
+import androidx.ui.core.LayoutDirection
 import androidx.ui.core.Modifier
+import androidx.ui.core.Popup
+import androidx.ui.core.PopupPositionProvider
+import androidx.ui.unit.Position
 import androidx.ui.core.drawLayer
 import androidx.ui.foundation.Box
 import androidx.ui.foundation.ContentGravity
@@ -40,8 +46,11 @@
 import androidx.ui.layout.preferredSizeIn
 import androidx.ui.layout.preferredWidth
 import androidx.ui.material.ripple.ripple
+import androidx.ui.unit.Density
 import androidx.ui.unit.IntPxPosition
+import androidx.ui.unit.IntPxSize
 import androidx.ui.unit.dp
+import androidx.ui.unit.ipx
 
 /**
  * A Material Design [dropdown menu](https://material.io/components/menus#dropdown-menu).
@@ -52,6 +61,12 @@
  * [DropdownMenuItem] can be used to achieve items as defined by the Material Design spec.
  * [onDismissRequest] will be called when the menu should close - for example when there is a
  * tap outside the menu, or when the back key is pressed.
+ * The menu will do a best effort to be fully visible on screen. It will try to expand
+ * horizontally, depending on layout direction, to the end of the [toggle], then to the start of
+ * the [toggle], and then screen end-aligned. Vertically, it will try to expand to the bottom
+ * of the [toggle], then from the top of the [toggle], and then screen top-aligned. A
+ * [dropdownOffset] can be provided to adjust the positioning of the menu for cases when the
+ * layout bounds of the [toggle] do not coincide with its visual bounds.
  *
  * Example usage:
  * @sample androidx.ui.material.samples.MenuSample
@@ -60,6 +75,7 @@
  * @param expanded Whether the menu is currently open or dismissed
  * @param onDismissRequest Called when the menu should be dismiss
  * @param toggleModifier The modifier to be applied to the toggle
+ * @param dropdownOffset Offset to be added to the position of the menu
  * @param dropdownModifier Modifier to be applied to the menu content
  */
 @Composable
@@ -68,6 +84,7 @@
     expanded: Boolean,
     onDismissRequest: () -> Unit,
     toggleModifier: Modifier = Modifier,
+    dropdownOffset: Position = Position(0.dp, 0.dp),
     dropdownModifier: Modifier = Modifier,
     dropdownContent: @Composable ColumnScope.() -> Unit
 ) {
@@ -78,14 +95,16 @@
         toggle()
 
         if (visibleMenu) {
-            DropdownPopup(
+            val popupPositionProvider = DropdownMenuPositionProvider(
+                dropdownOffset,
+                DensityAmbient.current,
+                ContextAmbient.current.resources.displayMetrics
+            )
+
+            Popup(
                 isFocusable = true,
                 >
-                offset = with(DensityAmbient.current) {
-                    // Compensate for the padding added below.
-                    // TODO(popam, b/156890315): add elevation to Popup
-                    IntPxPosition(-MenuElevation.toIntPx(), -MenuElevation.toIntPx())
-                }
+                popupPositionProvider = popupPositionProvider
             ) {
                 Transition(
                     definition = DropdownMenuOpenCloseTransition,
@@ -159,6 +178,7 @@
     }
 }
 
+// Size constants.
 internal val MenuElevation = 8.dp
 internal val DropdownMenuHorizontalPadding = 16.dp
 internal val DropdownMenuVerticalPadding = 8.dp
@@ -166,6 +186,7 @@
 internal val DropdownMenuItemDefaultMaxWidth = 280.dp
 internal val DropdownMenuItemDefaultMinHeight = 48.dp
 
+// Menu open/close animation.
 private val Scale = FloatPropKey()
 private val Alpha = FloatPropKey()
 internal val InTransitionDuration = 120
@@ -203,3 +224,56 @@
         }
     }
 }
+
+// Menu positioning.
+
+/**
+ * Calculates the position of a Material [DropdownMenu].
+ */
+// TODO(popam): Investigate if this can/should consider the app window size rather than screen size
+@Immutable
+internal data class DropdownMenuPositionProvider(
+    val contentOffset: Position,
+    val density: Density,
+    val displayMetrics: DisplayMetrics
+) : PopupPositionProvider {
+    override fun calculatePosition(
+        parentLayoutPosition: IntPxPosition,
+        parentLayoutSize: IntPxSize,
+        layoutDirection: LayoutDirection,
+        popupSize: IntPxSize
+    ): IntPxPosition {
+        // The padding inset that accommodates elevation, needs to be taken into account.
+        val inset = with(density) { MenuElevation.toIntPx() }
+        val realPopupWidth = popupSize.width - inset * 2
+        val realPopupHeight = popupSize.height - inset * 2
+        val contentOffsetX = with(density) { contentOffset.x.toIntPx() }
+        val contentOffsetY = with(density) { contentOffset.y.toIntPx() }
+        val parentRight = parentLayoutPosition.x + parentLayoutSize.width
+        val parentBottom = parentLayoutPosition.y + parentLayoutSize.height
+
+        // Compute horizontal position.
+        val toRight = parentRight + contentOffsetX
+        val toLeft = parentLayoutPosition.x - contentOffsetX - realPopupWidth
+        val toDisplayRight = displayMetrics.widthPixels.ipx - realPopupWidth
+        val toDisplayLeft = 0.ipx
+        val x = if (layoutDirection == LayoutDirection.Ltr) {
+            sequenceOf(toRight, toLeft, toDisplayRight)
+        } else {
+            sequenceOf(toLeft, toRight, toDisplayLeft)
+        }.firstOrNull {
+            it >= 0.ipx && it + realPopupWidth <= displayMetrics.widthPixels.ipx
+        } ?: toLeft
+
+        // Compute vertical position.
+        val toBottom = parentBottom + contentOffsetY
+        val toTop = parentLayoutPosition.y - contentOffsetY - realPopupHeight
+        val toCenter = parentLayoutPosition.y - realPopupHeight / 2
+        val toDisplayBottom = displayMetrics.heightPixels.ipx - realPopupHeight
+        val y = sequenceOf(toBottom, toTop, toCenter, toDisplayBottom).firstOrNull {
+            it >= 0.ipx && it + realPopupHeight <= displayMetrics.heightPixels.ipx
+        } ?: toTop
+
+        return IntPxPosition(x - inset, y - inset)
+    }
+}
diff --git a/ui/ui-unit/api/0.1.0-dev12.txt b/ui/ui-unit/api/0.1.0-dev12.txt
index a66bda0..05a1680 100644
--- a/ui/ui-unit/api/0.1.0-dev12.txt
+++ b/ui/ui-unit/api/0.1.0-dev12.txt
@@ -386,6 +386,12 @@
     method public inline operator androidx.ui.unit.IntPxSize times(int other);
     property public final inline int height;
     property public final inline int width;
+    field public static final androidx.ui.unit.IntPxSize.Companion! Companion;
+  }
+
+  public static final class IntPxSize.Companion {
+    method public androidx.ui.unit.IntPxSize getZero();
+    property public final androidx.ui.unit.IntPxSize Zero;
   }
 
   public final inline class IntSize {
diff --git a/ui/ui-unit/api/current.txt b/ui/ui-unit/api/current.txt
index a66bda0..05a1680 100644
--- a/ui/ui-unit/api/current.txt
+++ b/ui/ui-unit/api/current.txt
@@ -386,6 +386,12 @@
     method public inline operator androidx.ui.unit.IntPxSize times(int other);
     property public final inline int height;
     property public final inline int width;
+    field public static final androidx.ui.unit.IntPxSize.Companion! Companion;
+  }
+
+  public static final class IntPxSize.Companion {
+    method public androidx.ui.unit.IntPxSize getZero();
+    property public final androidx.ui.unit.IntPxSize Zero;
   }
 
   public final inline class IntSize {
diff --git a/ui/ui-unit/api/public_plus_experimental_0.1.0-dev12.txt b/ui/ui-unit/api/public_plus_experimental_0.1.0-dev12.txt
index a66bda0..05a1680 100644
--- a/ui/ui-unit/api/public_plus_experimental_0.1.0-dev12.txt
+++ b/ui/ui-unit/api/public_plus_experimental_0.1.0-dev12.txt
@@ -386,6 +386,12 @@
     method public inline operator androidx.ui.unit.IntPxSize times(int other);
     property public final inline int height;
     property public final inline int width;
+    field public static final androidx.ui.unit.IntPxSize.Companion! Companion;
+  }
+
+  public static final class IntPxSize.Companion {
+    method public androidx.ui.unit.IntPxSize getZero();
+    property public final androidx.ui.unit.IntPxSize Zero;
   }
 
   public final inline class IntSize {
diff --git a/ui/ui-unit/api/public_plus_experimental_current.txt b/ui/ui-unit/api/public_plus_experimental_current.txt
index a66bda0..05a1680 100644
--- a/ui/ui-unit/api/public_plus_experimental_current.txt
+++ b/ui/ui-unit/api/public_plus_experimental_current.txt
@@ -386,6 +386,12 @@
     method public inline operator androidx.ui.unit.IntPxSize times(int other);
     property public final inline int height;
     property public final inline int width;
+    field public static final androidx.ui.unit.IntPxSize.Companion! Companion;
+  }
+
+  public static final class IntPxSize.Companion {
+    method public androidx.ui.unit.IntPxSize getZero();
+    property public final androidx.ui.unit.IntPxSize Zero;
   }
 
   public final inline class IntSize {
diff --git a/ui/ui-unit/api/restricted_0.1.0-dev12.txt b/ui/ui-unit/api/restricted_0.1.0-dev12.txt
index a01b3de..71cbf10 100644
--- a/ui/ui-unit/api/restricted_0.1.0-dev12.txt
+++ b/ui/ui-unit/api/restricted_0.1.0-dev12.txt
@@ -390,6 +390,12 @@
     method public inline operator androidx.ui.unit.IntPxSize times(int other);
     property public final inline int height;
     property public final inline int width;
+    field public static final androidx.ui.unit.IntPxSize.Companion! Companion;
+  }
+
+  public static final class IntPxSize.Companion {
+    method public androidx.ui.unit.IntPxSize getZero();
+    property public final androidx.ui.unit.IntPxSize Zero;
   }
 
   public final inline class IntSize {
diff --git a/ui/ui-unit/api/restricted_current.txt b/ui/ui-unit/api/restricted_current.txt
index a01b3de..71cbf10 100644
--- a/ui/ui-unit/api/restricted_current.txt
+++ b/ui/ui-unit/api/restricted_current.txt
@@ -390,6 +390,12 @@
     method public inline operator androidx.ui.unit.IntPxSize times(int other);
     property public final inline int height;
     property public final inline int width;
+    field public static final androidx.ui.unit.IntPxSize.Companion! Companion;
+  }
+
+  public static final class IntPxSize.Companion {
+    method public androidx.ui.unit.IntPxSize getZero();
+    property public final androidx.ui.unit.IntPxSize Zero;
   }
 
   public final inline class IntSize {
diff --git a/ui/ui-unit/src/commonMain/kotlin/androidx/ui/unit/IntPx.kt b/ui/ui-unit/src/commonMain/kotlin/androidx/ui/unit/IntPx.kt
index b85bb37..8e773cb0 100644
--- a/ui/ui-unit/src/commonMain/kotlin/androidx/ui/unit/IntPx.kt
+++ b/ui/ui-unit/src/commonMain/kotlin/androidx/ui/unit/IntPx.kt
@@ -279,6 +279,13 @@
         IntPxSize(width = width / other, height = height / other)
 
     override fun toString(): String = "$width x $height"
+
+    companion object {
+        /**
+         * [IntPxSize] with zero values.
+         */
+        val Zero = IntPxSize(0.ipx, 0.ipx)
+    }
 }
 
 /**