ui-layout to MPP
Test: ./gradlew buildOnServer
Change-Id: Iad0c0a2dd0004b38518c6080e721e4e39dab0da2
Relnote: N/A
diff --git a/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/AlignmentLine.kt b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/AlignmentLine.kt
new file mode 100644
index 0000000..5525634
--- /dev/null
+++ b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/AlignmentLine.kt
@@ -0,0 +1,169 @@
+/*
+ * 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
+
+import androidx.compose.Composable
+import androidx.compose.Stable
+
+import androidx.ui.core.AlignmentLine
+import androidx.ui.core.Constraints
+import androidx.ui.core.HorizontalAlignmentLine
+import androidx.ui.core.Layout
+import androidx.ui.core.LayoutDirection
+import androidx.ui.core.LayoutModifier
+import androidx.ui.core.Measurable
+import androidx.ui.core.MeasureScope
+import androidx.ui.core.Modifier
+import androidx.ui.unit.Dp
+import androidx.ui.unit.dp
+/**
+ * Note: This composable is on the deprecation path and will be soon replaced with a [Modifier].
+ *
+ * Layout composable that takes a child and tries to position it within itself according to
+ * specified offsets relative to an [alignment line][AlignmentLine], subject to the incoming
+ * layout constraints. The [AlignmentLineOffset] layout will try to size itself to wrap the
+ * child and include the needed padding, such that the distance from the [AlignmentLineOffset]
+ * borders to the [AlignmentLine] of the child will be [before] and [after], respectively.
+ * The [before] and [after] values will be interpreted as offsets on the axis corresponding to
+ * the alignment line.
+ *
+ * @param alignmentLine the alignment line to be used for positioning the child
+ * @param before the offset between the left or top container border and the alignment line
+ * @param after the offset between the bottom or right container border and the alignment line
+ */
+@Deprecated(
+ "AlignmentLineOffset is deprecated. Instead apply Modifier.relativePaddingFrom modifier to " +
+ "the children",
+ replaceWith = ReplaceWith(
+ "Stack(Modifier.relativePaddingFrom(alignmentLine, before, after), children)",
+ "import androidx.ui.layout.relativePaddingFrom",
+ "import androidx.ui.layout.Stack"
+ )
+)
+@Composable
+fun AlignmentLineOffset(
+ alignmentLine: AlignmentLine,
+ modifier: Modifier = Modifier,
+ before: Dp = 0.dp,
+ after: Dp = 0.dp,
+ children: @Composable () -> Unit
+) {
+ Layout(children, modifier) { measurables, constraints ->
+ require(measurables.isNotEmpty()) { "No child found in AlignmentLineOffset" }
+ val placeable = measurables.first().measure(
+ // Loose constraints perpendicular on the alignment line.
+ if (alignmentLine.horizontal) constraints.copy(minHeight = 0)
+ else constraints.copy(minWidth = 0)
+ )
+ val linePosition = placeable[alignmentLine].let {
+ if (it != AlignmentLine.Unspecified) it else 0
+ }
+ val axis = if (alignmentLine.horizontal) placeable.height else placeable.width
+ val axisMax = if (alignmentLine.horizontal) constraints.maxHeight else constraints.maxWidth
+ // Compute padding required to satisfy the total before and after offsets.
+ val paddingBefore = (before.toIntPx() - linePosition).coerceIn(0, axisMax - axis)
+ val paddingAfter = (after.toIntPx() - axis + linePosition).coerceIn(
+ 0,
+ axisMax - axis - paddingBefore
+ )
+ // Calculate the size of the AlignmentLineOffset composable & define layout.
+ val containerWidth =
+ if (alignmentLine.horizontal) placeable.width
+ else paddingBefore + placeable.width + paddingAfter
+ val containerHeight =
+ if (alignmentLine.horizontal) paddingBefore + placeable.height + paddingAfter
+ else placeable.height
+ layout(containerWidth, containerHeight) {
+ val x = if (alignmentLine.horizontal) 0 else paddingBefore
+ val y = if (alignmentLine.horizontal) paddingBefore else 0
+ placeable.placeAbsolute(x, y)
+ }
+ }
+}
+
+/**
+ * Allow the content to be positioned according to the specified offset relative to the
+ * [alignment line][AlignmentLine], subject to the incoming layout constraints.
+ *
+ * The modified layout will include the needed padding, such that the distance from its borders
+ * to the [alignmentLine] of the content box will be [before] and [after], respectively.
+ * The [before] and [after] values will be interpreted as offsets on the axis corresponding to
+ * the alignment line.
+ *
+ * @param alignmentLine the alignment line to be used for positioning the content
+ * @param before the offset between the container's top edge and the horizontal alignment line, or
+ * the container's start edge and the vertical alignment line
+ * @param after the offset between the container's bottom edge and the horizontal alignment line, or
+ * the container's end edge and the vertical alignment line
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.RelativePaddingFromSample
+ */
+@Stable
+fun Modifier.relativePaddingFrom(
+ alignmentLine: AlignmentLine,
+ before: Dp = 0.dp,
+ after: Dp = 0.dp
+): Modifier = this + AlignmentLineOffset(alignmentLine, before, after)
+
+private data class AlignmentLineOffset(
+ val alignmentLine: AlignmentLine,
+ val before: Dp = 0.dp,
+ val after: Dp = 0.dp
+) : LayoutModifier {
+ init {
+ require(before.value >= 0f && after.value >= 0f) {
+ "Padding from alignment line must be non-negative"
+ }
+ }
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints,
+ layoutDirection: LayoutDirection
+ ): MeasureScope.MeasureResult {
+ val placeable = measurable.measure(
+ // Loose constraints perpendicular on the alignment line.
+ if (alignmentLine.horizontal) constraints.copy(minHeight = 0)
+ else constraints.copy(minWidth = 0)
+ )
+ val linePosition = placeable[alignmentLine].let {
+ if (it != AlignmentLine.Unspecified) it else 0
+ }
+ val axis = if (alignmentLine.horizontal) placeable.height else placeable.width
+ val axisMax = if (alignmentLine.horizontal) constraints.maxHeight else constraints.maxWidth
+ // Compute padding required to satisfy the total before and after offsets.
+ val paddingBefore = (before.toIntPx() - linePosition).coerceIn(0, axisMax - axis)
+ val paddingAfter = (after.toIntPx() - axis + linePosition).coerceIn(
+ 0,
+ axisMax - axis - paddingBefore
+ )
+
+ val width =
+ if (alignmentLine.horizontal) placeable.width
+ else paddingBefore + placeable.width + paddingAfter
+ val height =
+ if (alignmentLine.horizontal) paddingBefore + placeable.height + paddingAfter
+ else placeable.height
+ return layout(width, height) {
+ val x = if (alignmentLine.horizontal) 0 else paddingBefore
+ val y = if (alignmentLine.horizontal) paddingBefore else 0
+ placeable.place(x, y)
+ }
+ }
+}
+
+private val AlignmentLine.horizontal: Boolean get() = this is HorizontalAlignmentLine
diff --git a/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/Column.kt b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/Column.kt
new file mode 100644
index 0000000..3761df3
--- /dev/null
+++ b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/Column.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2020 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
+
+import androidx.compose.Composable
+import androidx.compose.Immutable
+import androidx.compose.Stable
+import androidx.ui.core.Alignment
+import androidx.ui.core.Measured
+import androidx.ui.core.Modifier
+import androidx.ui.core.VerticalAlignmentLine
+import androidx.ui.layout.ColumnScope.alignWithSiblings
+import androidx.ui.layout.RowScope.alignWithSiblings
+import androidx.ui.util.annotation.FloatRange
+
+/**
+ * A layout composable that places its children in a vertical sequence. For a layout composable
+ * that places its children in a horizontal sequence, see [Row].
+ *
+ * The layout model is able to assign children heights according to their weights provided
+ * using the [ColumnScope.weight] modifier. If a child is not provided a weight, it will be
+ * asked for its preferred height before the sizes of the children with weights are calculated
+ * proportionally to their weight based on the remaining available space.
+ *
+ * When none of its children have weights, a [Column] will be as small as possible to fit its
+ * children one on top of the other. In order to change the height of the [Column], use the
+ * [Modifier.height] modifiers; e.g. to make it fill the available height [Modifier.fillMaxHeight]
+ * can be used. If at least one child of a [Column] has a [weight][ColumnScope.weight],
+ * the [Column] will fill the available height, so there is no need for [Modifier.fillMaxHeight].
+ * However, if [Column]'s size should be limited, the [Modifier.height] or [Modifier.size] layout
+ * modifiers should be applied.
+ *
+ * When the size of the [Column] is larger than the sum of its children sizes, a
+ * [verticalArrangement] can be specified to define the positioning of the children inside the
+ * [Column]. See [Arrangement] for available positioning behaviors; a custom arrangement can also
+ * be defined using the constructor of [Arrangement].
+ *
+ * Example usage:
+ *
+ * @sample androidx.ui.layout.samples.SimpleColumn
+ *
+ * @param modifier The modifier to be applied to the Column.
+ * @param verticalArrangement The vertical arrangement of the layout's children.
+ * @param horizontalGravity The horizontal gravity of the layout's children.
+ *
+ * @see Column
+ */
+@Composable
+fun Column(
+ modifier: Modifier = Modifier,
+ verticalArrangement: Arrangement.Vertical = Arrangement.Top,
+ horizontalGravity: Alignment.Horizontal = Alignment.Start,
+ children: @Composable ColumnScope.() -> Unit
+) {
+ RowColumnImpl(
+ orientation = LayoutOrientation.Vertical,
+ modifier = modifier,
+ arrangement = { totalSize, size, _ ->
+ verticalArrangement.arrange(totalSize, size)
+ },
+ crossAxisAlignment = CrossAxisAlignment.horizontal(horizontalGravity),
+ crossAxisSize = SizeMode.Wrap,
+ children = { ColumnScope.children() }
+ )
+}
+
+/**
+ * Scope for the children of [Column].
+ */
+@LayoutScopeMarker
+@Immutable
+object ColumnScope {
+ /**
+ * Position the element horizontally within the [Column] according to [align].
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimpleGravityInColumn
+ */
+ @Stable
+ fun Modifier.gravity(align: Alignment.Horizontal) = this + HorizontalGravityModifier(align)
+
+ /**
+ * Position the element horizontally such that its [alignmentLine] aligns with sibling elements
+ * also configured to [alignWithSiblings]. [alignWithSiblings] is a form of [gravity],
+ * so both modifiers will not work together if specified for the same layout.
+ * Within a [Column], all components with [alignWithSiblings] will align horizontally using
+ * the specified [VerticalAlignmentLine]s or values provided using the other
+ * [alignWithSiblings] overload, forming a sibling group.
+ * At least one element of the sibling group will be placed as it had [Alignment.Start] gravity
+ * in [Column], and the alignment of the other siblings will be then determined such that
+ * the alignment lines coincide. Note that if only one element in a [Column] has the
+ * [alignWithSiblings] modifier specified the element will be positioned
+ * as if it had [Alignment.Start] gravity.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimpleRelativeToSiblingsInColumn
+ */
+ @Stable
+ fun Modifier.alignWithSiblings(alignmentLine: VerticalAlignmentLine) =
+ this + SiblingsAlignedModifier.WithAlignmentLine(alignmentLine)
+
+ /**
+ * Size the element's height proportional to its [weight] relative to other weighted sibling
+ * elements in the [Column]. The parent will divide the vertical space remaining after measuring
+ * unweighted child elements and distribute it according to this weight.
+ * When [fill] is true, the element will be forced to occupy the whole height allocated to it.
+ * Otherwise, the element is allowed to be smaller - this will result in [Column] being smaller,
+ * as the unused allocated height will not be redistributed to other siblings.
+ *
+ * @sample androidx.ui.layout.samples.SimpleColumn
+ */
+ @Stable
+ fun Modifier.weight(
+ @FloatRange(from = 0.0, to = 3.4e38 /* POSITIVE_INFINITY */, fromInclusive = false)
+ weight: Float,
+ fill: Boolean = true
+ ): Modifier {
+ require(weight > 0.0) { "invalid weight $weight; must be greater than zero" }
+ return this + LayoutWeightImpl(weight, fill)
+ }
+
+ /**
+ * Position the element horizontally such that the alignment line for the content as
+ * determined by [alignmentLineBlock] aligns with sibling elements also configured to
+ * [alignWithSiblings]. [alignWithSiblings] is a form of [gravity], so both modifiers
+ * will not work together if specified for the same layout.
+ * Within a [Column], all components with [alignWithSiblings] will align horizontally using
+ * the specified [VerticalAlignmentLine]s or values obtained from [alignmentLineBlock],
+ * forming a sibling group.
+ * At least one element of the sibling group will be placed as it had [Alignment.Start] gravity
+ * in [Column], and the alignment of the other siblings will be then determined such that
+ * the alignment lines coincide. Note that if only one element in a [Column] has the
+ * [alignWithSiblings] modifier specified the element will be positioned
+ * as if it had [Alignment.Start] gravity.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimpleRelativeToSiblings
+ */
+ @Stable
+ fun Modifier.alignWithSiblings(
+ alignmentLineBlock: (Measured) -> Int
+ ) = this + SiblingsAlignedModifier.WithAlignmentLineBlock(alignmentLineBlock)
+}
diff --git a/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/DpConstraints.kt b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/DpConstraints.kt
new file mode 100644
index 0000000..fcdbe26
--- /dev/null
+++ b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/DpConstraints.kt
@@ -0,0 +1,174 @@
+/*
+ * 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
+
+import androidx.compose.Immutable
+import androidx.compose.Stable
+import androidx.ui.core.Constraints
+import androidx.ui.unit.Density
+import androidx.ui.unit.Dp
+import androidx.ui.unit.coerceAtLeast
+import androidx.ui.unit.coerceIn
+import androidx.ui.unit.dp
+import androidx.ui.unit.isFinite
+
+/**
+ * Similar to [Constraints], but with constraint values expressed in [Dp].
+ */
+@Immutable
+data class DpConstraints(
+ @Stable
+ val minWidth: Dp = 0.dp,
+ @Stable
+ val maxWidth: Dp = Dp.Infinity,
+ @Stable
+ val minHeight: Dp = 0.dp,
+ @Stable
+ val maxHeight: Dp = Dp.Infinity
+) {
+ init {
+ require(minWidth.isFinite()) { "Constraints#minWidth should be finite" }
+ require(minHeight.isFinite()) { "Constraints#minHeight should be finite" }
+ require(!minWidth.value.isNaN()) { "Constraints#minWidth should not be NaN" }
+ require(!maxWidth.value.isNaN()) { "Constraints#maxWidth should not be NaN" }
+ require(!minHeight.value.isNaN()) { "Constraints#minHeight should not be NaN" }
+ require(!maxHeight.value.isNaN()) { "Constraints#maxHeight should not be NaN" }
+ require(minWidth <= maxWidth) {
+ "Constraints should be satisfiable, but minWidth > maxWidth"
+ }
+ require(minHeight <= maxHeight) {
+ "Constraints should be satisfiable, but minHeight > maxHeight"
+ }
+ require(minWidth >= 0.dp) { "Constraints#minWidth should be non-negative" }
+ require(maxWidth >= 0.dp) { "Constraints#maxWidth should be non-negative" }
+ require(minHeight >= 0.dp) { "Constraints#minHeight should be non-negative" }
+ require(maxHeight >= 0.dp) { "Constraints#maxHeight should be non-negative" }
+ }
+
+ companion object {
+ /**
+ * Creates constraints tight in both dimensions.
+ */
+ @Stable
+ fun fixed(width: Dp, height: Dp) = DpConstraints(width, width, height, height)
+
+ /**
+ * Creates constraints with tight width and loose height.
+ */
+ @Stable
+ fun fixedWidth(width: Dp) = DpConstraints(
+ minWidth = width,
+ maxWidth = width,
+ minHeight = 0.dp,
+ maxHeight = Dp.Infinity
+ )
+
+ /**
+ * Creates constraints with tight height and loose width.
+ */
+ @Stable
+ fun fixedHeight(height: Dp) = DpConstraints(
+ minWidth = 0.dp,
+ maxWidth = Dp.Infinity,
+ minHeight = height,
+ maxHeight = height
+ )
+ }
+}
+
+/**
+ * Whether or not the upper bound on the maximum height.
+ * @see hasBoundedWidth
+ */
+@Stable
+val DpConstraints.hasBoundedHeight get() = maxHeight.isFinite()
+
+/**
+ * Whether or not the upper bound on the maximum width.
+ * @see hasBoundedHeight
+ */
+@Stable
+val DpConstraints.hasBoundedWidth get() = maxWidth.isFinite()
+
+/**
+ * Whether there is exactly one width value that satisfies the constraints.
+ */
+@Stable
+val DpConstraints.hasFixedWidth get() = maxWidth == minWidth
+
+/**
+ * Whether there is exactly one height value that satisfies the constraints.
+ */
+@Stable
+val DpConstraints.hasFixedHeight get() = maxHeight == minHeight
+
+/**
+ * Whether the area of a component respecting these constraints will definitely be 0.
+ * This is true when at least one of maxWidth and maxHeight are 0.
+ */
+@Stable
+val DpConstraints.isZero get() = maxWidth == 0.dp || maxHeight == 0.dp
+
+/**
+ * Whether there is any size that satisfies the current constraints.
+ */
+@Stable
+val DpConstraints.satisfiable get() = minWidth <= maxWidth && minHeight <= maxHeight
+
+/**
+ * Returns the result of coercing the current constraints in a different set of constraints.
+ */
+@Stable
+fun DpConstraints.enforce(otherConstraints: DpConstraints) = DpConstraints(
+ minWidth = minWidth.coerceIn(otherConstraints.minWidth, otherConstraints.maxWidth),
+ maxWidth = maxWidth.coerceIn(otherConstraints.minWidth, otherConstraints.maxWidth),
+ minHeight = minHeight.coerceIn(otherConstraints.minHeight, otherConstraints.maxHeight),
+ maxHeight = maxHeight.coerceIn(otherConstraints.minHeight, otherConstraints.maxHeight)
+)
+
+/**
+ * Returns the DpConstraints obtained by offsetting the current instance with the given values.
+ */
+@Stable
+fun DpConstraints.offset(horizontal: Dp = 0.dp, vertical: Dp = 0.dp) = DpConstraints(
+ (minWidth + horizontal).coerceAtLeast(0.dp),
+ (maxWidth + horizontal).coerceAtLeast(0.dp),
+ (minHeight + vertical).coerceAtLeast(0.dp),
+ (maxHeight + vertical).coerceAtLeast(0.dp)
+)
+
+/**
+ * Creates the [Constraints] corresponding to the current [DpConstraints].
+ */
+@Stable
+fun Density.Constraints(dpConstraints: DpConstraints) = Constraints(
+ minWidth = dpConstraints.minWidth.toIntPx(),
+ maxWidth = dpConstraints.maxWidth.toIntPx(),
+ minHeight = dpConstraints.minHeight.toIntPx(),
+ maxHeight = dpConstraints.maxHeight.toIntPx()
+)
+
+/**
+ * Creates the [DpConstraints] corresponding to the current [Constraints].
+ */
+@Stable
+fun Density.DpConstraints(constraints: Constraints) = DpConstraints(
+ minWidth = constraints.minWidth.toDp(),
+ maxWidth = constraints.maxWidth.toDp(),
+ minHeight = constraints.minHeight.toDp(),
+ maxHeight = constraints.maxHeight.toDp()
+)
diff --git a/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/ExperimentalLayout.kt b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/ExperimentalLayout.kt
new file mode 100644
index 0000000..853706a
--- /dev/null
+++ b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/ExperimentalLayout.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2020 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
+
+@RequiresOptIn("The API of this layout is experimental and is likely to change in the future.")
+annotation class ExperimentalLayout
diff --git a/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/Flow.kt b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/Flow.kt
new file mode 100644
index 0000000..437afd1
--- /dev/null
+++ b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/Flow.kt
@@ -0,0 +1,274 @@
+/*
+ * 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
+
+import androidx.compose.Composable
+import androidx.ui.core.Alignment
+import androidx.ui.core.Constraints
+import androidx.ui.core.Layout
+import androidx.ui.core.Placeable
+import androidx.ui.unit.Dp
+import androidx.ui.unit.IntSize
+import androidx.ui.unit.dp
+import androidx.ui.util.fastForEachIndexed
+import kotlin.math.max
+
+/**
+ * A composable that places its children in a horizontal flow. Unlike [Row], if the
+ * horizontal space is too small to put all the children in one row, multiple rows may be used.
+ *
+ * Note that just like [Row], flex values cannot be used with [FlowRow].
+ *
+ * Example usage:
+ *
+ * @sample androidx.ui.layout.samples.SimpleFlowRow
+ *
+ * @param mainAxisSize The size of the layout in the main axis direction.
+ * @param mainAxisAlignment The alignment of each row's children in the main axis direction.
+ * @param mainAxisSpacing The main axis spacing between the children of each row.
+ * @param crossAxisAlignment The alignment of each row's children in the cross axis direction.
+ * @param crossAxisSpacing The cross axis spacing between the rows of the layout.
+ * @param lastLineMainAxisAlignment Overrides the main axis alignment of the last row.
+ */
+@ExperimentalLayout
+@Composable
+fun FlowRow(
+ mainAxisSize: SizeMode = SizeMode.Wrap,
+ mainAxisAlignment: FlowMainAxisAlignment = FlowMainAxisAlignment.Start,
+ mainAxisSpacing: Dp = 0.dp,
+ crossAxisAlignment: FlowCrossAxisAlignment = FlowCrossAxisAlignment.Start,
+ crossAxisSpacing: Dp = 0.dp,
+ lastLineMainAxisAlignment: FlowMainAxisAlignment = mainAxisAlignment,
+ children: @Composable () -> Unit
+) {
+ Flow(
+ orientation = LayoutOrientation.Horizontal,
+ mainAxisSize = mainAxisSize,
+ mainAxisAlignment = mainAxisAlignment,
+ mainAxisSpacing = mainAxisSpacing,
+ crossAxisAlignment = crossAxisAlignment,
+ crossAxisSpacing = crossAxisSpacing,
+ lastLineMainAxisAlignment = lastLineMainAxisAlignment,
+ children = children
+ )
+}
+
+/**
+ * A composable that places its children in a vertical flow. Unlike [Column], if the
+ * vertical space is too small to put all the children in one column, multiple columns may be used.
+ *
+ * Note that just like [Column], flex values cannot be used with [FlowColumn].
+ *
+ * Example usage:
+ *
+ * @sample androidx.ui.layout.samples.SimpleFlowColumn
+ *
+ * @param mainAxisSize The size of the layout in the main axis direction.
+ * @param mainAxisAlignment The alignment of each column's children in the main axis direction.
+ * @param mainAxisSpacing The main axis spacing between the children of each column.
+ * @param crossAxisAlignment The alignment of each column's children in the cross axis direction.
+ * @param crossAxisSpacing The cross axis spacing between the columns of the layout.
+ * @param lastLineMainAxisAlignment Overrides the main axis alignment of the last column.
+ */
+@ExperimentalLayout
+@Composable
+fun FlowColumn(
+ mainAxisSize: SizeMode = SizeMode.Wrap,
+ mainAxisAlignment: FlowMainAxisAlignment = FlowMainAxisAlignment.Start,
+ mainAxisSpacing: Dp = 0.dp,
+ crossAxisAlignment: FlowCrossAxisAlignment = FlowCrossAxisAlignment.Start,
+ crossAxisSpacing: Dp = 0.dp,
+ lastLineMainAxisAlignment: FlowMainAxisAlignment = mainAxisAlignment,
+ children: @Composable () -> Unit
+) {
+ Flow(
+ orientation = LayoutOrientation.Vertical,
+ mainAxisSize = mainAxisSize,
+ mainAxisAlignment = mainAxisAlignment,
+ mainAxisSpacing = mainAxisSpacing,
+ crossAxisAlignment = crossAxisAlignment,
+ crossAxisSpacing = crossAxisSpacing,
+ lastLineMainAxisAlignment = lastLineMainAxisAlignment,
+ children = children
+ )
+}
+
+/**
+ * Used to specify the alignment of a layout's children, in cross axis direction.
+ */
+enum class FlowCrossAxisAlignment {
+ /**
+ * Place children such that their center is in the middle of the cross axis.
+ */
+ Center,
+ /**
+ * Place children such that their start edge is aligned to the start edge of the cross axis.
+ */
+ Start,
+ /**
+ * Place children such that their end edge is aligned to the end edge of the cross axis.
+ */
+ End,
+}
+
+typealias FlowMainAxisAlignment = MainAxisAlignment
+
+/**
+ * Layout model that arranges its children in a horizontal or vertical flow.
+ */
+@Composable
+private fun Flow(
+ orientation: LayoutOrientation,
+ mainAxisSize: SizeMode,
+ mainAxisAlignment: FlowMainAxisAlignment,
+ mainAxisSpacing: Dp,
+ crossAxisAlignment: FlowCrossAxisAlignment,
+ crossAxisSpacing: Dp,
+ lastLineMainAxisAlignment: FlowMainAxisAlignment,
+ children: @Composable () -> Unit
+) {
+ fun Placeable.mainAxisSize() =
+ if (orientation == LayoutOrientation.Horizontal) width else height
+ fun Placeable.crossAxisSize() =
+ if (orientation == LayoutOrientation.Horizontal) height else width
+
+ Layout(children) { measurables, outerConstraints ->
+ val sequences = mutableListOf<List<Placeable>>()
+ val crossAxisSizes = mutableListOf<Int>()
+ val crossAxisPositions = mutableListOf<Int>()
+
+ var mainAxisSpace = 0
+ var crossAxisSpace = 0
+
+ val currentSequence = mutableListOf<Placeable>()
+ var currentMainAxisSize = 0
+ var currentCrossAxisSize = 0
+
+ val constraints = OrientationIndependentConstraints(outerConstraints, orientation)
+
+ val childConstraints = if (orientation == LayoutOrientation.Horizontal) {
+ Constraints(maxWidth = constraints.mainAxisMax)
+ } else {
+ Constraints(maxHeight = constraints.mainAxisMax)
+ }
+
+ // Return whether the placeable can be added to the current sequence.
+ fun canAddToCurrentSequence(placeable: Placeable) =
+ currentSequence.isEmpty() || currentMainAxisSize + mainAxisSpacing.toIntPx() +
+ placeable.mainAxisSize() <= constraints.mainAxisMax
+
+ // Store current sequence information and start a new sequence.
+ fun startNewSequence() {
+ if (sequences.isNotEmpty()) {
+ crossAxisSpace += crossAxisSpacing.toIntPx()
+ }
+ sequences += currentSequence.toList()
+ crossAxisSizes += currentCrossAxisSize
+ crossAxisPositions += crossAxisSpace
+
+ crossAxisSpace += currentCrossAxisSize
+ mainAxisSpace = max(mainAxisSpace, currentMainAxisSize)
+
+ currentSequence.clear()
+ currentMainAxisSize = 0
+ currentCrossAxisSize = 0
+ }
+
+ for (measurable in measurables) {
+ // Ask the child for its preferred size.
+ val placeable = measurable.measure(childConstraints)
+
+ // Start a new sequence if there is not enough space.
+ if (!canAddToCurrentSequence(placeable)) startNewSequence()
+
+ // Add the child to the current sequence.
+ if (currentSequence.isNotEmpty()) {
+ currentMainAxisSize += mainAxisSpacing.toIntPx()
+ }
+ currentSequence.add(placeable)
+ currentMainAxisSize += placeable.mainAxisSize()
+ currentCrossAxisSize = max(currentCrossAxisSize, placeable.crossAxisSize())
+ }
+
+ if (currentSequence.isNotEmpty()) startNewSequence()
+
+ val mainAxisLayoutSize = if (constraints.mainAxisMax != Constraints.Infinity &&
+ mainAxisSize == SizeMode.Expand
+ ) {
+ constraints.mainAxisMax
+ } else {
+ max(mainAxisSpace, constraints.mainAxisMin)
+ }
+ val crossAxisLayoutSize = max(crossAxisSpace, constraints.crossAxisMin)
+
+ val layoutWidth = if (orientation == LayoutOrientation.Horizontal) {
+ mainAxisLayoutSize
+ } else {
+ crossAxisLayoutSize
+ }
+ val layoutHeight = if (orientation == LayoutOrientation.Horizontal) {
+ crossAxisLayoutSize
+ } else {
+ mainAxisLayoutSize
+ }
+
+ layout(layoutWidth, layoutHeight) {
+ sequences.fastForEachIndexed { i, placeables ->
+ val childrenMainAxisSizes = placeables.mapIndexed { j, placeable ->
+ placeable.mainAxisSize() +
+ if (j < placeables.lastIndex) mainAxisSpacing.toIntPx() else 0
+ }
+ val arrangement = if (i < sequences.lastIndex) {
+ mainAxisAlignment.arrangement
+ } else {
+ lastLineMainAxisAlignment.arrangement
+ }
+ // TODO(soboleva): rtl support
+ // Handle vertical direction
+ val mainAxisPositions = arrangement.arrange(
+ mainAxisLayoutSize,
+ childrenMainAxisSizes
+ )
+ placeables.fastForEachIndexed { j, placeable ->
+ val crossAxis = when (crossAxisAlignment) {
+ FlowCrossAxisAlignment.Start -> 0
+ FlowCrossAxisAlignment.End ->
+ crossAxisSizes[i] - placeable.crossAxisSize()
+ FlowCrossAxisAlignment.Center ->
+ Alignment.Center.align(
+ IntSize(
+ width = 0,
+ height = crossAxisSizes[i] - placeable.crossAxisSize()
+ )
+ ).y
+ }
+ if (orientation == LayoutOrientation.Horizontal) {
+ placeable.placeAbsolute(
+ x = mainAxisPositions[j],
+ y = crossAxisPositions[i] + crossAxis
+ )
+ } else {
+ placeable.placeAbsolute(
+ x = crossAxisPositions[i] + crossAxis,
+ y = mainAxisPositions[j]
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/Intrinsic.kt b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/Intrinsic.kt
new file mode 100644
index 0000000..a16a59c
--- /dev/null
+++ b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/Intrinsic.kt
@@ -0,0 +1,324 @@
+/*
+ * 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
+
+import androidx.compose.Composable
+import androidx.compose.Stable
+import androidx.ui.core.Constraints
+import androidx.ui.core.IntrinsicMeasurable
+import androidx.ui.core.IntrinsicMeasureScope
+import androidx.ui.core.Layout
+import androidx.ui.core.LayoutDirection
+import androidx.ui.core.LayoutModifier
+import androidx.ui.core.Measurable
+import androidx.ui.core.MeasureScope
+import androidx.ui.core.Modifier
+import androidx.ui.core.enforce
+import androidx.ui.unit.IntOffset
+/**
+ * Declare the preferred width of the content to be the same as the min or max intrinsic width of
+ * the content. The incoming measurement [Constraints] may override this value, forcing the content
+ * to be either smaller or larger.
+ *
+ * See [preferredHeight] for options of sizing to intrinsic height.
+ * Also see [preferredWidth] and [preferredWidthIn] for other options to set the preferred width.
+ *
+ * Example usage for min intrinsic:
+ * @sample androidx.ui.layout.samples.SameWidthBoxes
+ *
+ * Example usage for max intrinsic:
+ * @sample androidx.ui.layout.samples.SameWidthTextBoxes
+ */
+@ExperimentalLayout
+@Stable
+fun Modifier.preferredWidth(intrinsicSize: IntrinsicSize) = when (intrinsicSize) {
+ IntrinsicSize.Min -> this + PreferredMinIntrinsicWidthModifier
+ IntrinsicSize.Max -> this + PreferredMaxIntrinsicWidthModifier
+}
+
+/**
+ * Declare the preferred height of the content to be the same as the min or max intrinsic height of
+ * the content. The incoming measurement [Constraints] may override this value, forcing the content
+ * to be either smaller or larger.
+ *
+ * See [preferredWidth] for other options of sizing to intrinsic width.
+ * Also see [preferredHeight] and [preferredHeightIn] for other options to set the preferred height.
+ *
+ * Example usage for min intrinsic:
+ * @sample androidx.ui.layout.samples.MatchParentDividerForText
+ *
+ * Example usage for max intrinsic:
+ * @sample androidx.ui.layout.samples.MatchParentDividerForAspectRatio
+ */
+@ExperimentalLayout
+@Stable
+fun Modifier.preferredHeight(intrinsicSize: IntrinsicSize) = when (intrinsicSize) {
+ IntrinsicSize.Min -> this + PreferredMinIntrinsicHeightModifier
+ IntrinsicSize.Max -> this + PreferredMaxIntrinsicHeightModifier
+}
+
+/**
+ * Intrinsic size used in [preferredWidth] or [preferredHeight] which can refer to width or height.
+ */
+enum class IntrinsicSize { Min, Max }
+
+private object PreferredMinIntrinsicWidthModifier : PreferredIntrinsicSizeModifier {
+ override fun MeasureScope.calculateContentConstraints(
+ measurable: Measurable,
+ constraints: Constraints
+ ): Constraints {
+ val width = measurable.minIntrinsicWidth(constraints.maxHeight)
+ return Constraints.fixedWidth(width)
+ }
+
+ override fun IntrinsicMeasureScope.maxIntrinsicWidth(
+ measurable: IntrinsicMeasurable,
+ height: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.minIntrinsicWidth(height)
+}
+
+private object PreferredMinIntrinsicHeightModifier : PreferredIntrinsicSizeModifier {
+ override fun MeasureScope.calculateContentConstraints(
+ measurable: Measurable,
+ constraints: Constraints
+ ): Constraints {
+ val height = measurable.minIntrinsicHeight(constraints.maxWidth)
+ return Constraints.fixedHeight(height)
+ }
+
+ override fun IntrinsicMeasureScope.maxIntrinsicHeight(
+ measurable: IntrinsicMeasurable,
+ width: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.minIntrinsicHeight(width)
+}
+
+private object PreferredMaxIntrinsicWidthModifier : PreferredIntrinsicSizeModifier {
+ override fun MeasureScope.calculateContentConstraints(
+ measurable: Measurable,
+ constraints: Constraints
+ ): Constraints {
+ val width = measurable.maxIntrinsicWidth(constraints.maxHeight)
+ return Constraints.fixedWidth(width)
+ }
+
+ override fun IntrinsicMeasureScope.minIntrinsicWidth(
+ measurable: IntrinsicMeasurable,
+ height: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.maxIntrinsicWidth(height, layoutDirection)
+}
+
+private object PreferredMaxIntrinsicHeightModifier : PreferredIntrinsicSizeModifier {
+ override fun MeasureScope.calculateContentConstraints(
+ measurable: Measurable,
+ constraints: Constraints
+ ): Constraints {
+ val height = measurable.maxIntrinsicHeight(constraints.maxWidth)
+ return Constraints.fixedHeight(height)
+ }
+
+ override fun IntrinsicMeasureScope.minIntrinsicHeight(
+ measurable: IntrinsicMeasurable,
+ width: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.maxIntrinsicHeight(width)
+}
+
+private interface PreferredIntrinsicSizeModifier : LayoutModifier {
+ fun MeasureScope.calculateContentConstraints(
+ measurable: Measurable,
+ constraints: Constraints
+ ): Constraints
+
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints,
+ layoutDirection: LayoutDirection
+ ): MeasureScope.MeasureResult {
+ val placeable = measurable.measure(
+ calculateContentConstraints(
+ measurable,
+ constraints
+ ).enforce(constraints)
+ )
+ return layout(placeable.width, placeable.height) {
+ placeable.place(IntOffset.Origin)
+ }
+ }
+
+ override fun IntrinsicMeasureScope.minIntrinsicWidth(
+ measurable: IntrinsicMeasurable,
+ height: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.minIntrinsicWidth(height, layoutDirection)
+
+ override fun IntrinsicMeasureScope.minIntrinsicHeight(
+ measurable: IntrinsicMeasurable,
+ width: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.minIntrinsicHeight(width, layoutDirection)
+
+ override fun IntrinsicMeasureScope.maxIntrinsicWidth(
+ measurable: IntrinsicMeasurable,
+ height: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.maxIntrinsicWidth(height, layoutDirection)
+
+ override fun IntrinsicMeasureScope.maxIntrinsicHeight(
+ measurable: IntrinsicMeasurable,
+ width: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.maxIntrinsicHeight(width, layoutDirection)
+}
+
+/**
+ * Layout composable that forces its child to be as wide as its min intrinsic width.
+ * If incoming constraints do not allow this, the closest possible width will be used.
+ */
+@Deprecated("This component is deprecated. " +
+ "Please use the preferredWidth(IntrinsicSize.Min) modifier instead.")
+@Composable
+fun MinIntrinsicWidth(children: @Composable () -> Unit) {
+ Layout(
+ children,
+ minIntrinsicWidthMeasureBlock = { measurables, h ->
+ measurables.firstOrNull()?.minIntrinsicWidth(h) ?: 0
+ },
+ minIntrinsicHeightMeasureBlock = { measurables, w ->
+ measurables.firstOrNull()?.minIntrinsicHeight(w) ?: 0
+ },
+ maxIntrinsicWidthMeasureBlock = { measurables, h ->
+ measurables.firstOrNull()?.minIntrinsicWidth(h) ?: 0
+ },
+ maxIntrinsicHeightMeasureBlock = { measurables, w ->
+ measurables.firstOrNull()?.maxIntrinsicHeight(w) ?: 0
+ }
+ ) { measurables, constraints ->
+ val measurable = measurables.firstOrNull()
+ val width = measurable?.minIntrinsicWidth(constraints.maxHeight, layoutDirection) ?: 0
+ val placeable = measurable?.measure(
+ Constraints.fixedWidth(width).enforce(constraints)
+ )
+ layout(placeable?.width ?: 0, placeable?.height ?: 0) {
+ placeable?.placeAbsolute(0, 0)
+ }
+ }
+}
+
+/**
+ * Layout composable that forces its child to be as tall as its min intrinsic height.
+ * If incoming constraints do not allow this, the closest possible height will be used.
+ */
+@Deprecated("This component is deprecated. " +
+ "Please use the preferredHeight(IntrinsicSize.Min) modifier instead.")
+@Composable
+fun MinIntrinsicHeight(children: @Composable () -> Unit) {
+ Layout(
+ children,
+ minIntrinsicWidthMeasureBlock = { measurables, h ->
+ measurables.firstOrNull()?.minIntrinsicWidth(h) ?: 0
+ },
+ minIntrinsicHeightMeasureBlock = { measurables, w ->
+ measurables.firstOrNull()?.minIntrinsicHeight(w) ?: 0
+ },
+ maxIntrinsicWidthMeasureBlock = { measurables, h ->
+ measurables.firstOrNull()?.maxIntrinsicWidth(h) ?: 0
+ },
+ maxIntrinsicHeightMeasureBlock = { measurables, w ->
+ measurables.firstOrNull()?.minIntrinsicHeight(w) ?: 0
+ }
+ ) { measurables, constraints ->
+ val measurable = measurables.firstOrNull()
+ val height = measurable?.minIntrinsicHeight(constraints.maxWidth) ?: 0
+ val placeable = measurable?.measure(
+ Constraints.fixedHeight(height).enforce(constraints)
+ )
+ layout(placeable?.width ?: 0, placeable?.height ?: 0) {
+ placeable?.placeAbsolute(0, 0)
+ }
+ }
+}
+
+/**
+ * Layout composable that forces its child to be as wide as its max intrinsic width.
+ * If incoming constraints do not allow this, the closest possible width will be used.
+ */
+@Deprecated("This component is deprecated. " +
+ "Please use the preferredWidth(IntrinsicSize.Max) modifier instead.")
+@Composable
+fun MaxIntrinsicWidth(children: @Composable () -> Unit) {
+ Layout(
+ children,
+ minIntrinsicWidthMeasureBlock = { measurables, h ->
+ measurables.firstOrNull()?.maxIntrinsicWidth(h) ?: 0
+ },
+ minIntrinsicHeightMeasureBlock = { measurables, w ->
+ measurables.firstOrNull()?.minIntrinsicHeight(w) ?: 0
+ },
+ maxIntrinsicWidthMeasureBlock = { measurables, h ->
+ measurables.firstOrNull()?.maxIntrinsicWidth(h) ?: 0
+ },
+ maxIntrinsicHeightMeasureBlock = { measurables, w ->
+ measurables.firstOrNull()?.maxIntrinsicHeight(w) ?: 0
+ }
+ ) { measurables, constraints ->
+ val measurable = measurables.firstOrNull()
+ val width = measurable?.maxIntrinsicWidth(constraints.maxHeight) ?: 0
+ val placeable = measurable?.measure(
+ Constraints.fixedWidth(width).enforce(constraints)
+ )
+ layout(placeable?.width ?: 0, placeable?.height ?: 0) {
+ placeable?.placeAbsolute(0, 0)
+ }
+ }
+}
+
+/**
+ * Layout composable that forces its child to be as tall as its max intrinsic height.
+ * If incoming constraints do not allow this, the closest possible height will be used.
+ */
+@Deprecated("This component is deprecated. " +
+ "Please use the preferredHeight(IntrinsicSize.Max) modifier instead.")
+@Composable
+fun MaxIntrinsicHeight(children: @Composable () -> Unit) {
+ Layout(
+ children,
+ minIntrinsicWidthMeasureBlock = { measurables, h ->
+ measurables.firstOrNull()?.minIntrinsicWidth(h) ?: 0
+ },
+ minIntrinsicHeightMeasureBlock = { measurables, w ->
+ measurables.firstOrNull()?.maxIntrinsicHeight(w) ?: 0
+ },
+ maxIntrinsicWidthMeasureBlock = { measurables, h ->
+ measurables.firstOrNull()?.maxIntrinsicWidth(h) ?: 0
+ },
+ maxIntrinsicHeightMeasureBlock = { measurables, w ->
+ measurables.firstOrNull()?.maxIntrinsicHeight(w) ?: 0
+ }
+ ) { measurables, constraints ->
+ val measurable = measurables.firstOrNull()
+ val height = measurable?.maxIntrinsicHeight(constraints.maxWidth) ?: 0
+ val placeable = measurable?.measure(
+ Constraints.fixedHeight(height).enforce(constraints)
+ )
+ layout(placeable?.width ?: 0, placeable?.height ?: 0) {
+ placeable?.placeAbsolute(0, 0)
+ }
+ }
+}
diff --git a/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/LayoutAspectRatio.kt b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/LayoutAspectRatio.kt
new file mode 100644
index 0000000..4c891a2
--- /dev/null
+++ b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/LayoutAspectRatio.kt
@@ -0,0 +1,151 @@
+/*
+ * 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
+
+import androidx.compose.Stable
+import androidx.ui.core.Constraints
+import androidx.ui.core.IntrinsicMeasurable
+import androidx.ui.core.IntrinsicMeasureScope
+import androidx.ui.core.LayoutDirection
+import androidx.ui.core.LayoutModifier
+import androidx.ui.core.Measurable
+import androidx.ui.core.MeasureScope
+import androidx.ui.core.Modifier
+import androidx.ui.core.satisfiedBy
+import androidx.ui.unit.IntSize
+import androidx.ui.util.annotation.FloatRange
+import kotlin.math.roundToInt
+
+/**
+ * Attempts to size the content to match a specified aspect ratio by trying to match one of the
+ * incoming constraints in the following order:
+ * [Constraints.maxWidth], [Constraints.maxHeight], [Constraints.minWidth], [Constraints.minHeight].
+ * The size in the other dimension is determined by the aspect ratio.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimpleAspectRatio
+ *
+ * @param ratio the desired width/height positive ratio
+ */
+@Stable
+fun Modifier.aspectRatio(
+ @FloatRange(from = 0.0, to = 3.4e38 /* POSITIVE_INFINITY */, fromInclusive = false)
+ ratio: Float
+) = this + AspectRatioModifier(ratio)
+
+private data class AspectRatioModifier(val aspectRatio: Float) : LayoutModifier {
+ init {
+ require(aspectRatio > 0) { "aspectRatio $aspectRatio must be > 0" }
+ }
+
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints,
+ layoutDirection: LayoutDirection
+ ): MeasureScope.MeasureResult {
+ val size = constraints.findSizeWith(aspectRatio)
+ val wrappedConstraints = if (size != null) {
+ Constraints.fixed(size.width, size.height)
+ } else {
+ constraints
+ }
+ val placeable = measurable.measure(wrappedConstraints)
+ return layout(placeable.width, placeable.height) {
+ placeable.place(0, 0)
+ }
+ }
+
+ override fun IntrinsicMeasureScope.minIntrinsicWidth(
+ measurable: IntrinsicMeasurable,
+ height: Int,
+ layoutDirection: LayoutDirection
+ ) = if (height != Constraints.Infinity) {
+ (height * aspectRatio).roundToInt()
+ } else {
+ measurable.minIntrinsicWidth(height)
+ }
+
+ override fun IntrinsicMeasureScope.maxIntrinsicWidth(
+ measurable: IntrinsicMeasurable,
+ height: Int,
+ layoutDirection: LayoutDirection
+ ) = if (height != Constraints.Infinity) {
+ (height * aspectRatio).roundToInt()
+ } else {
+ measurable.maxIntrinsicWidth(height)
+ }
+
+ override fun IntrinsicMeasureScope.minIntrinsicHeight(
+ measurable: IntrinsicMeasurable,
+ width: Int,
+ layoutDirection: LayoutDirection
+ ) = if (width != Constraints.Infinity) {
+ (width / aspectRatio).roundToInt()
+ } else {
+ measurable.minIntrinsicHeight(width)
+ }
+
+ override fun IntrinsicMeasureScope.maxIntrinsicHeight(
+ measurable: IntrinsicMeasurable,
+ width: Int,
+ layoutDirection: LayoutDirection
+ ) = if (width != Constraints.Infinity) {
+ (width / aspectRatio).roundToInt()
+ } else {
+ measurable.maxIntrinsicHeight(width)
+ }
+
+ private fun Constraints.findSizeWith(aspectRatio: Float): IntSize? {
+ val maxWidth = this.maxWidth
+ if (maxWidth != Constraints.Infinity) {
+ val height = (maxWidth / aspectRatio).roundToInt()
+ if (height > 0) {
+ val size = IntSize(maxWidth, height)
+ if (satisfiedBy(size)) {
+ return size
+ }
+ }
+ }
+ val maxHeight = this.maxHeight
+ if (maxHeight != Constraints.Infinity) {
+ val width = (maxHeight * aspectRatio).roundToInt()
+ if (width > 0) {
+ val size = IntSize(width, maxHeight)
+ if (satisfiedBy(size)) {
+ return size
+ }
+ }
+ }
+ val minWidth = this.minWidth
+ val height = (minWidth / aspectRatio).roundToInt()
+ if (height > 0) {
+ val size = IntSize(minWidth, height)
+ if (satisfiedBy(size)) {
+ return size
+ }
+ }
+ val minHeight = this.minHeight
+ val width = (minHeight * aspectRatio).roundToInt()
+ if (width > 0) {
+ val size = IntSize(width, minHeight)
+ if (satisfiedBy(size)) {
+ return size
+ }
+ }
+ return null
+ }
+}
diff --git a/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/LayoutDirections.kt b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/LayoutDirections.kt
new file mode 100644
index 0000000..5253a30
--- /dev/null
+++ b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/LayoutDirections.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2020 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
+
+import androidx.ui.core.Constraints
+import androidx.ui.core.IntrinsicMeasurable
+import androidx.ui.core.IntrinsicMeasureScope
+import androidx.ui.core.LayoutDirection
+import androidx.ui.core.LayoutModifier
+import androidx.ui.core.Measurable
+import androidx.ui.core.MeasureScope
+import androidx.ui.core.Modifier
+/**
+ * [Modifier] that changes the [LayoutDirection] of the wrapped layout to [LayoutDirection.Ltr].
+ */
+val Modifier.ltr: Modifier get() = this + LtrModifier
+
+/**
+ * [Modifier] that changes the [LayoutDirection] of the wrapped layout to [LayoutDirection.Rtl].
+ */
+val Modifier.rtl: Modifier get() = this + RtlModifier
+
+private val LtrModifier = LayoutDirectionModifier(LayoutDirection.Ltr)
+
+private val RtlModifier = LayoutDirectionModifier(LayoutDirection.Rtl)
+
+private data class LayoutDirectionModifier(
+ val prescribedLayoutDirection: LayoutDirection
+) : LayoutModifier {
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints,
+ layoutDirection: LayoutDirection
+ ): MeasureScope.MeasureResult {
+ val placeable = measurable.measure(constraints, prescribedLayoutDirection)
+ return layout(placeable.width, placeable.height) {
+ placeable.place(0, 0)
+ }
+ }
+
+ override fun IntrinsicMeasureScope.minIntrinsicWidth(
+ measurable: IntrinsicMeasurable,
+ height: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.minIntrinsicWidth(height, prescribedLayoutDirection)
+
+ override fun IntrinsicMeasureScope.minIntrinsicHeight(
+ measurable: IntrinsicMeasurable,
+ width: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.minIntrinsicHeight(width, prescribedLayoutDirection)
+
+ override fun IntrinsicMeasureScope.maxIntrinsicWidth(
+ measurable: IntrinsicMeasurable,
+ height: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.maxIntrinsicWidth(height, prescribedLayoutDirection)
+
+ override fun IntrinsicMeasureScope.maxIntrinsicHeight(
+ measurable: IntrinsicMeasurable,
+ width: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.maxIntrinsicHeight(width, prescribedLayoutDirection)
+}
diff --git a/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/LayoutOffset.kt b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/LayoutOffset.kt
new file mode 100644
index 0000000..96a7168
--- /dev/null
+++ b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/LayoutOffset.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2020 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
+
+import androidx.compose.Stable
+import androidx.compose.State
+import androidx.compose.mutableStateOf
+import androidx.ui.core.Constraints
+import androidx.ui.core.LayoutDirection
+import androidx.ui.core.LayoutModifier
+import androidx.ui.core.Measurable
+import androidx.ui.core.MeasureScope
+import androidx.ui.core.Modifier
+import androidx.ui.unit.Dp
+import androidx.ui.unit.dp
+import kotlin.math.roundToInt
+
+/**
+ * Offset the content by ([x] dp, [y] dp). The offsets can be positive as well as non positive.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.LayoutOffsetModifier
+ */
+@Stable
+fun Modifier.offset(x: Dp = 0.dp, y: Dp = 0.dp) = this + OffsetModifier(x, y)
+
+/**
+ * Offset the content by ([x] px, [y]px). The offsets can be positive as well as non positive.
+ * This modifier is designed to be used for offsets that change, possibly due to user interactions.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.LayoutOffsetPxModifier
+ */
+fun Modifier.offsetPx(
+ x: State<Float> = mutableStateOf(0f),
+ y: State<Float> = mutableStateOf(0f)
+) = this + OffsetPxModifier(x, y)
+
+private data class OffsetModifier(val x: Dp, val y: Dp) : LayoutModifier {
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints,
+ layoutDirection: LayoutDirection
+ ): MeasureScope.MeasureResult {
+ val placeable = measurable.measure(constraints)
+ return layout(placeable.width, placeable.height) {
+ placeable.place(x.toIntPx(), y.toIntPx())
+ }
+ }
+}
+
+private data class OffsetPxModifier(val x: State<Float>, val y: State<Float>) : LayoutModifier {
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints,
+ layoutDirection: LayoutDirection
+ ): MeasureScope.MeasureResult {
+ val placeable = measurable.measure(constraints)
+ return layout(placeable.width, placeable.height) {
+ placeable.place(x.value.roundToInt(), y.value.roundToInt())
+ }
+ }
+}
diff --git a/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/LayoutPadding.kt b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/LayoutPadding.kt
new file mode 100644
index 0000000..54762d8
--- /dev/null
+++ b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/LayoutPadding.kt
@@ -0,0 +1,189 @@
+/*
+ * 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
+
+import androidx.compose.Immutable
+import androidx.compose.Stable
+import androidx.ui.core.Constraints
+import androidx.ui.core.LayoutDirection
+import androidx.ui.core.Modifier
+import androidx.ui.core.LayoutModifier
+import androidx.ui.core.Measurable
+import androidx.ui.core.MeasureScope
+import androidx.ui.core.constrainHeight
+import androidx.ui.core.constrainWidth
+import androidx.ui.core.offset
+import androidx.ui.unit.Dp
+import androidx.ui.unit.dp
+
+/**
+ * Apply additional space along each edge of the content in [Dp]: [start], [top], [end] and
+ * [bottom]. The start and end edges will be determined by the current [LayoutDirection].
+ * Padding is applied before content measurement and takes precedence; content may only be as large
+ * as the remaining space.
+ *
+ * Negative padding is not permitted. See [offset].
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.PaddingModifier
+ */
+@Stable
+fun Modifier.padding(
+ start: Dp = 0.dp,
+ top: Dp = 0.dp,
+ end: Dp = 0.dp,
+ bottom: Dp = 0.dp
+) = this + PaddingModifier(
+ start = start,
+ top = top,
+ end = end,
+ bottom = bottom,
+ rtlAware = true
+)
+
+/**
+ * Apply [horizontal] dp space along the left and right edges of the content, and [vertical] dp
+ * space along the top and bottom edges.
+ * Padding is applied before content measurement and takes precedence; content may only be as large
+ * as the remaining space.
+ *
+ * Negative padding is not permitted. See [offset].
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SymmetricPaddingModifier
+ */
+@Stable
+fun Modifier.padding(
+ horizontal: Dp = 0.dp,
+ vertical: Dp = 0.dp
+) = this + PaddingModifier(
+ start = horizontal,
+ top = vertical,
+ end = horizontal,
+ bottom = vertical,
+ rtlAware = true
+)
+
+/**
+ * Apply [all] dp of additional space along each edge of the content, left, top, right and bottom.
+ * Padding is applied before content measurement and takes precedence; content may only be as large
+ * as the remaining space.
+ *
+ * Negative padding is not permitted. See [offset].
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.PaddingAllModifier
+ */
+@Stable
+fun Modifier.padding(all: Dp) =
+ this + PaddingModifier(start = all, top = all, end = all, bottom = all, rtlAware = true)
+
+/**
+ * Apply [InnerPadding] to the component as additional space along each edge of the content's left,
+ * top, right and bottom. Padding is applied before content measurement and takes precedence;
+ * content may only be as large as the remaining space.
+ *
+ * Negative padding is not permitted. See [offset].
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.PaddingInnerPaddingModifier
+ */
+fun Modifier.padding(padding: InnerPadding) =
+ this + PaddingModifier(
+ start = padding.start,
+ top = padding.top,
+ end = padding.end,
+ bottom = padding.bottom,
+ rtlAware = true
+ )
+
+/**
+ * Apply additional space along each edge of the content in [Dp]: [left], [top], [right] and
+ * [bottom]. These paddings are applied without regard to the current [LayoutDirection], see
+ * [padding] to apply relative paddings. Padding is applied before content measurement and takes
+ * precedence; content may only be as large as the remaining space.
+ *
+ * Negative padding is not permitted. See [offset].
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.AbsolutePaddingModifier
+ */
+@Stable
+fun Modifier.absolutePadding(
+ left: Dp = 0.dp,
+ top: Dp = 0.dp,
+ right: Dp = 0.dp,
+ bottom: Dp = 0.dp
+) = this + PaddingModifier(
+ start = left,
+ top = top,
+ end = right,
+ bottom = bottom,
+ rtlAware = false
+)
+
+private data class PaddingModifier(
+ val start: Dp = 0.dp,
+ val top: Dp = 0.dp,
+ val end: Dp = 0.dp,
+ val bottom: Dp = 0.dp,
+ val rtlAware: Boolean
+) : LayoutModifier {
+ init {
+ require(start.value >= 0f && top.value >= 0f && end.value >= 0f && bottom.value >= 0f) {
+ "Padding must be non-negative"
+ }
+ }
+
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints,
+ layoutDirection: LayoutDirection
+ ): MeasureScope.MeasureResult {
+ val horizontal = start.toIntPx() + end.toIntPx()
+ val vertical = top.toIntPx() + bottom.toIntPx()
+
+ val placeable = measurable.measure(constraints.offset(-horizontal, -vertical))
+
+ val width = constraints.constrainWidth(placeable.width + horizontal)
+ val height = constraints.constrainHeight(placeable.height + vertical)
+ return layout(width, height) {
+ if (rtlAware) {
+ placeable.place(start.toIntPx(), top.toIntPx())
+ } else {
+ placeable.placeAbsolute(start.toIntPx(), top.toIntPx())
+ }
+ }
+ }
+}
+
+/**
+ * Describes a padding to be applied along the edges inside a box.
+ */
+@Immutable
+data class InnerPadding(
+ @Stable
+ val start: Dp = 0.dp,
+ @Stable
+ val top: Dp = 0.dp,
+ @Stable
+ val end: Dp = 0.dp,
+ @Stable
+ val bottom: Dp = 0.dp
+) {
+ constructor(all: Dp) : this(all, all, all, all)
+}
diff --git a/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/LayoutScopeMarker.kt b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/LayoutScopeMarker.kt
new file mode 100644
index 0000000..09aac5a
--- /dev/null
+++ b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/LayoutScopeMarker.kt
@@ -0,0 +1,20 @@
+/*
+ * 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
+
+@DslMarker
+annotation class LayoutScopeMarker
diff --git a/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/LayoutSize.kt b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/LayoutSize.kt
new file mode 100644
index 0000000..21e2b15
--- /dev/null
+++ b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/LayoutSize.kt
@@ -0,0 +1,605 @@
+/*
+ * 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
+
+import androidx.compose.Stable
+import androidx.ui.core.Alignment
+import androidx.ui.core.Constraints
+import androidx.ui.core.IntrinsicMeasurable
+import androidx.ui.core.IntrinsicMeasureScope
+import androidx.ui.core.LayoutDirection
+import androidx.ui.core.LayoutModifier
+import androidx.ui.core.Measurable
+import androidx.ui.core.MeasureScope
+import androidx.ui.core.Modifier
+import androidx.ui.core.constrainHeight
+import androidx.ui.core.constrainWidth
+import androidx.ui.core.enforce
+import androidx.ui.unit.Density
+import androidx.ui.unit.Dp
+import androidx.ui.unit.IntOffset
+import androidx.ui.unit.IntSize
+import kotlin.math.max
+
+/**
+ * Declare the preferred width of the content to be exactly [width]dp. The incoming measurement
+ * [Constraints] may override this value, forcing the content to be either smaller or larger.
+ *
+ * For a modifier that sets the width of the content regardless of the incoming constraints see
+ * [Modifier.width]. See [preferredHeight] or [preferredSize] to set other preferred dimensions.
+ * See [preferredWidthIn], [preferredHeightIn] or [preferredSizeIn] to set a preferred size range.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimplePreferredWidthModifier
+ */
+@Stable
+fun Modifier.preferredWidth(width: Dp) = preferredSizeIn(minWidth = width, maxWidth = width)
+
+/**
+ * Declare the preferred height of the content to be exactly [height]dp. The incoming measurement
+ * [Constraints] may override this value, forcing the content to be either smaller or larger.
+ *
+ * For a modifier that sets the height of the content regardless of the incoming constraints see
+ * [Modifier.height]. See [preferredWidth] or [preferredSize] to set other preferred dimensions.
+ * See [preferredWidthIn], [preferredHeightIn] or [preferredSizeIn] to set a preferred size range.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimplePreferredHeightModifier
+ */
+@Stable
+fun Modifier.preferredHeight(height: Dp) = preferredSizeIn(minHeight = height, maxHeight = height)
+
+/**
+ * Declare the preferred size of the content to be exactly [size]dp square. The incoming measurement
+ * [Constraints] may override this value, forcing the content to be either smaller or larger.
+ *
+ * For a modifier that sets the size of the content regardless of the incoming constraints, see
+ * [Modifier.size]. See [preferredWidth] or [preferredHeight] to set width or height alone.
+ * See [preferredWidthIn], [preferredHeightIn] or [preferredSizeIn] to set a preferred size range.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimplePreferredSizeModifier
+ */
+@Stable
+fun Modifier.preferredSize(size: Dp) = preferredSizeIn(size, size, size, size)
+
+/**
+ * Declare the preferred size of the content to be exactly [width]dp by [height]dp. The incoming
+ * measurement [Constraints] may override this value, forcing the content to be either smaller or
+ * larger.
+ *
+ * For a modifier that sets the size of the content regardless of the incoming constraints, see
+ * [Modifier.size]. See [preferredWidth] or [preferredHeight] to set width or height alone.
+ * See [preferredWidthIn], [preferredHeightIn] or [preferredSizeIn] to set a preferred size range.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimplePreferredSizeModifier
+ */
+@Stable
+fun Modifier.preferredSize(width: Dp, height: Dp) = preferredSizeIn(
+ minWidth = width,
+ maxWidth = width,
+ minHeight = height,
+ maxHeight = height
+)
+
+/**
+ * Constrain the width of the content to be between [minWidth]dp and [maxWidth]dp as permitted
+ * by the incoming measurement [Constraints]. If the incoming constraints are more restrictive
+ * the requested size will obey the incoming constraints and attempt to be as close as possible
+ * to the preferred size.
+ */
+@Stable
+fun Modifier.preferredWidthIn(
+ minWidth: Dp = Dp.Unspecified,
+ maxWidth: Dp = Dp.Unspecified
+) = preferredSizeIn(minWidth = minWidth, maxWidth = maxWidth)
+
+/**
+ * Constrain the height of the content to be between [minHeight]dp and [maxHeight]dp as permitted
+ * by the incoming measurement [Constraints]. If the incoming constraints are more restrictive
+ * the requested size will obey the incoming constraints and attempt to be as close as possible
+ * to the preferred size.
+ */
+@Stable
+fun Modifier.preferredHeightIn(
+ minHeight: Dp = Dp.Unspecified,
+ maxHeight: Dp = Dp.Unspecified
+) = preferredSizeIn(minHeight = minHeight, maxHeight = maxHeight)
+
+/**
+ * Constrain the size of the content to be within [constraints] as permitted by the incoming
+ * measurement [Constraints]. If the incoming measurement constraints are more restrictive the
+ * requested size will obey the incoming constraints and attempt to be as close as possible to
+ * the preferred size.
+ */
+@Stable
+fun Modifier.preferredSizeIn(constraints: DpConstraints) = preferredSizeIn(
+ constraints.minWidth,
+ constraints.minHeight,
+ constraints.maxWidth,
+ constraints.maxHeight
+)
+
+/**
+ * Constrain the width of the content to be between [minWidth]dp and [maxWidth]dp and the height
+ * of the content to be between [minHeight] and [maxHeight] as permitted by the incoming
+ * measurement [Constraints]. If the incoming constraints are more restrictive the requested size
+ * will obey the incoming constraints and attempt to be as close as possible to the preferred size.
+ */
+@Stable
+fun Modifier.preferredSizeIn(
+ minWidth: Dp = Dp.Unspecified,
+ minHeight: Dp = Dp.Unspecified,
+ maxWidth: Dp = Dp.Unspecified,
+ maxHeight: Dp = Dp.Unspecified
+) = this + SizeModifier(minWidth, minHeight, maxWidth, maxHeight, true)
+
+/**
+ * Declare the width of the content to be exactly [width]dp. The incoming measurement
+ * [Constraints] will not override this value. If the content chooses a size that does not
+ * satisfy the incoming [Constraints], the parent layout will be reported a size coerced
+ * in the [Constraints], and the position of the content will be automatically offset to be
+ * centered on the space assigned to the child by the parent layout under the assumption that
+ * [Constraints] were respected.
+ *
+ * See [widthIn] and [sizeIn] to set a size range.
+ * See [preferredWidth] to set a preferred width, which is only respected when the incoming
+ * constraints allow it.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimpleWidthModifier
+ */
+@Stable
+fun Modifier.width(width: Dp) = sizeIn(minWidth = width, maxWidth = width)
+
+/**
+ * Declare the height of the content to be exactly [height]dp. The incoming measurement
+ * [Constraints] will not override this value. If the content chooses a size that does not
+ * satisfy the incoming [Constraints], the parent layout will be reported a size coerced
+ * in the [Constraints], and the position of the content will be automatically offset to be
+ * centered on the space assigned to the child by the parent layout under the assumption that
+ * [Constraints] were respected.
+ *
+ * See [heightIn] and [sizeIn] to set a size range.
+ * See [preferredHeight] to set a preferred height, which is only respected when the incoming
+ * constraints allow it.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimpleHeightModifier
+ */
+@Stable
+fun Modifier.height(height: Dp) = sizeIn(minHeight = height, maxHeight = height)
+
+/**
+ * Declare the size of the content to be exactly [size]dp width and height. The incoming measurement
+ * [Constraints] will not override this value. If the content chooses a size that does not
+ * satisfy the incoming [Constraints], the parent layout will be reported a size coerced
+ * in the [Constraints], and the position of the content will be automatically offset to be
+ * centered on the space assigned to the child by the parent layout under the assumption that
+ * [Constraints] were respected.
+ *
+ * See [sizeIn] to set a size range.
+ * See [preferredSize] to set a preferred size, which is only respected when the incoming
+ * constraints allow it.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimpleSizeModifier
+ */
+@Stable
+fun Modifier.size(size: Dp) = sizeIn(size, size, size, size)
+
+/**
+ * Declare the size of the content to be exactly [width]dp and [height]dp. The incoming measurement
+ * [Constraints] will not override this value. If the content chooses a size that does not
+ * satisfy the incoming [Constraints], the parent layout will be reported a size coerced
+ * in the [Constraints], and the position of the content will be automatically offset to be
+ * centered on the space assigned to the child by the parent layout under the assumption that
+ * [Constraints] were respected.
+ *
+ * See [sizeIn] to set a size range.
+ * See [preferredSize] to set a preferred size, which is only respected when the incoming
+ * constraints allow it.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimpleWidthModifier
+ */
+@Stable
+fun Modifier.size(width: Dp, height: Dp) = sizeIn(
+ minWidth = width,
+ maxWidth = width,
+ minHeight = height,
+ maxHeight = height
+)
+
+/**
+ * Constrain the width of the content to be between [minWidth]dp and [maxWidth]dp.
+ * If the content chooses a size that does not satisfy the incoming [Constraints], the
+ * parent layout will be reported a size coerced in the [Constraints], and the position
+ * of the content will be automatically offset to be centered on the space assigned to
+ * the child by the parent layout under the assumption that [Constraints] were respected.
+ */
+@Stable
+fun Modifier.widthIn(
+ minWidth: Dp = Dp.Unspecified,
+ maxWidth: Dp = Dp.Unspecified
+) = sizeIn(minWidth = minWidth, maxWidth = maxWidth)
+
+/**
+ * Constrain the height of the content to be between [minHeight]dp and [maxHeight]dp.
+ * If the content chooses a size that does not satisfy the incoming [Constraints], the
+ * parent layout will be reported a size coerced in the [Constraints], and the position
+ * of the content will be automatically offset to be centered on the space assigned to
+ * the child by the parent layout under the assumption that [Constraints] were respected.
+ */
+@Stable
+fun Modifier.heightIn(
+ minHeight: Dp = Dp.Unspecified,
+ maxHeight: Dp = Dp.Unspecified
+) = sizeIn(minHeight = minHeight, maxHeight = maxHeight)
+
+/**
+ * Constrain the size of the content to be within [constraints].
+ * If the content chooses a size that does not satisfy the incoming [Constraints], the
+ * parent layout will be reported a size coerced in the [Constraints], and the position
+ * of the content will be automatically offset to be centered on the space assigned to
+ * the child by the parent layout under the assumption that [Constraints] were respected.
+ */
+@Stable
+fun Modifier.sizeIn(constraints: DpConstraints) = sizeIn(
+ constraints.minWidth,
+ constraints.minHeight,
+ constraints.maxWidth,
+ constraints.maxHeight
+)
+
+/**
+ * Constrain the width of the content to be between [minWidth]dp and [maxWidth]dp, and the
+ * height of the content to be between [minHeight]dp and [maxHeight]dp.
+ * If the content chooses a size that does not satisfy the incoming [Constraints], the
+ * parent layout will be reported a size coerced in the [Constraints], and the position
+ * of the content will be automatically offset to be centered on the space assigned to
+ * the child by the parent layout under the assumption that [Constraints] were respected.
+ */
+@Stable
+fun Modifier.sizeIn(
+ minWidth: Dp = Dp.Unspecified,
+ minHeight: Dp = Dp.Unspecified,
+ maxWidth: Dp = Dp.Unspecified,
+ maxHeight: Dp = Dp.Unspecified
+) = this + SizeModifier(minWidth, minHeight, maxWidth, maxHeight, false)
+
+/**
+ * Have the content fill the [Constraints.maxWidth] of the incoming measurement constraints
+ * by setting the [minimum width][Constraints.minWidth] to be equal to the
+ * [maximum width][Constraints.maxWidth]. If the incoming maximum width is [Constraints.Infinity] this
+ * modifier will have no effect.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimpleFillWidthModifier
+ */
+@Stable
+fun Modifier.fillMaxWidth() = this + FillModifier(Direction.Horizontal)
+
+/**
+ * Have the content fill the [Constraints.maxHeight] of the incoming measurement constraints
+ * by setting the [minimum height][Constraints.minHeight] to be equal to the
+ * [maximum height][Constraints.maxHeight]. If the incoming maximum height is [Constraints.Infinity] this
+ * modifier will have no effect.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimpleFillHeightModifier
+ */
+@Stable
+fun Modifier.fillMaxHeight() = this + FillModifier(Direction.Vertical)
+
+/**
+ * Have the content fill the [Constraints.maxWidth] and [Constraints.maxHeight] of the incoming
+ * measurement constraints by setting the [minimum width][Constraints.minWidth] to be equal to the
+ * [maximum width][Constraints.maxWidth] and the [minimum height][Constraints.minHeight] to be
+ * equal to the [maximum height][Constraints.maxHeight]. If the incoming maximum width or height
+ * is [Constraints.Infinity] this modifier will have no effect in that dimension.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimpleFillModifier
+ */
+@Stable
+fun Modifier.fillMaxSize() = this + FillModifier(Direction.Both)
+
+/**
+ * Allow the content to measure at its desired width without regard for the incoming measurement
+ * [minimum width constraint][Constraints.minWidth]. If the content's measured size is smaller
+ * than the minimum width constraint, [align] it within that minimum width space.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimpleWrapContentHorizontallyAlignedModifier
+ */
+@Stable
+// TODO(popam): avoid recreating modifier for common align
+fun Modifier.wrapContentWidth(align: Alignment.Horizontal = Alignment.CenterHorizontally) = this +
+ AlignmentModifier(Direction.Horizontal) { size, layoutDirection ->
+ IntOffset(align.align(size.width, layoutDirection), 0)
+ }
+
+/**
+ * Allow the content to measure at its desired height without regard for the incoming measurement
+ * [minimum height constraint][Constraints.minHeight]. If the content's measured size is smaller
+ * than the minimum height constraint, [align] it within that minimum height space.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimpleWrapContentVerticallyAlignedModifier
+ */
+// TODO(popam): avoid recreating modifier for common align
+@Stable
+fun Modifier.wrapContentHeight(align: Alignment.Vertical = Alignment.CenterVertically) =
+ this + AlignmentModifier(Direction.Vertical) { size, _ ->
+ IntOffset(0, align.align(size.height))
+ }
+
+/**
+ * Allow the content to measure at its desired size without regard for the incoming measurement
+ * [minimum width][Constraints.minWidth] or [minimum height][Constraints.minHeight] constraints.
+ * If the content's measured size is smaller than the minimum size constraint, [align] it
+ * within that minimum sized space.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimpleWrapContentAlignedModifier
+ */
+@Stable
+fun Modifier.wrapContentSize(align: Alignment = Alignment.Center) =
+ this + AlignmentModifier(Direction.Both) { size, layoutDirection ->
+ align.align(size, layoutDirection)
+ }
+
+/**
+ * Constrain the size of the wrapped layout only when it would be otherwise unconstrained:
+ * the [minWidth] and [minHeight] constraints are only applied when the incoming corresponding
+ * constraint is `0`.
+ * The modifier can be used, for example, to define a default min size of a component,
+ * while still allowing it to be overidden with smaller min sizes across usages.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.DefaultMinSizeConstraintsSample
+ */
+@Stable
+fun Modifier.defaultMinSizeConstraints(
+ minWidth: Dp = Dp.Unspecified,
+ minHeight: Dp = Dp.Unspecified
+) = this + UnspecifiedConstraintsModifier(minWidth, minHeight)
+
+private data class FillModifier(private val direction: Direction) : LayoutModifier {
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints,
+ layoutDirection: LayoutDirection
+ ): MeasureScope.MeasureResult {
+ val wrappedConstraints = Constraints(
+ minWidth = if (constraints.hasBoundedWidth && direction != Direction.Vertical) {
+ constraints.maxWidth
+ } else {
+ constraints.minWidth
+ },
+ maxWidth = constraints.maxWidth,
+ minHeight = if (constraints.hasBoundedHeight && direction != Direction.Horizontal) {
+ constraints.maxHeight
+ } else {
+ constraints.minHeight
+ },
+ maxHeight = constraints.maxHeight
+ )
+ val placeable = measurable.measure(wrappedConstraints)
+
+ return layout(placeable.width, placeable.height) {
+ placeable.place(0, 0)
+ }
+ }
+}
+
+private data class SizeModifier(
+ private val minWidth: Dp = Dp.Unspecified,
+ private val minHeight: Dp = Dp.Unspecified,
+ private val maxWidth: Dp = Dp.Unspecified,
+ private val maxHeight: Dp = Dp.Unspecified,
+ private val enforceIncoming: Boolean
+) : LayoutModifier {
+ private val Density.targetConstraints
+ get() = Constraints(
+ minWidth = if (minWidth != Dp.Unspecified) minWidth.toIntPx() else 0,
+ minHeight = if (minHeight != Dp.Unspecified) minHeight.toIntPx() else 0,
+ maxWidth = if (maxWidth != Dp.Unspecified) maxWidth.toIntPx() else Constraints.Infinity,
+ maxHeight = if (maxHeight != Dp.Unspecified) {
+ maxHeight.toIntPx()
+ } else {
+ Constraints.Infinity
+ }
+ )
+
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints,
+ layoutDirection: LayoutDirection
+ ): MeasureScope.MeasureResult {
+ val wrappedConstraints = targetConstraints.let { targetConstraints ->
+ if (enforceIncoming) {
+ targetConstraints.enforce(constraints)
+ } else {
+ val resolvedMinWidth = if (minWidth != Dp.Unspecified) {
+ targetConstraints.minWidth
+ } else {
+ constraints.minWidth.coerceAtMost(targetConstraints.maxWidth)
+ }
+ val resolvedMaxWidth = if (maxWidth != Dp.Unspecified) {
+ targetConstraints.maxWidth
+ } else {
+ constraints.maxWidth.coerceAtLeast(targetConstraints.minWidth)
+ }
+ val resolvedMinHeight = if (minHeight != Dp.Unspecified) {
+ targetConstraints.minHeight
+ } else {
+ constraints.minHeight.coerceAtMost(targetConstraints.maxHeight)
+ }
+ val resolvedMaxHeight = if (maxHeight != Dp.Unspecified) {
+ targetConstraints.maxHeight
+ } else {
+ constraints.maxHeight.coerceAtLeast(targetConstraints.minHeight)
+ }
+ Constraints(
+ resolvedMinWidth,
+ resolvedMaxWidth,
+ resolvedMinHeight,
+ resolvedMaxHeight
+ )
+ }
+ }
+ val placeable = measurable.measure(wrappedConstraints)
+ return layout(placeable.width, placeable.height) {
+ placeable.place(0, 0)
+ }
+ }
+
+ override fun IntrinsicMeasureScope.minIntrinsicWidth(
+ measurable: IntrinsicMeasurable,
+ height: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.minIntrinsicWidth(height, layoutDirection).let {
+ val constraints = targetConstraints
+ constraints.constrainWidth(it)
+ }
+
+ override fun IntrinsicMeasureScope.maxIntrinsicWidth(
+ measurable: IntrinsicMeasurable,
+ height: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.maxIntrinsicWidth(height, layoutDirection).let {
+ val constraints = targetConstraints
+ constraints.constrainWidth(it)
+ }
+
+ override fun IntrinsicMeasureScope.minIntrinsicHeight(
+ measurable: IntrinsicMeasurable,
+ width: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.minIntrinsicHeight(width, layoutDirection).let {
+ val constraints = targetConstraints
+ constraints.constrainHeight(it)
+ }
+
+ override fun IntrinsicMeasureScope.maxIntrinsicHeight(
+ measurable: IntrinsicMeasurable,
+ width: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.maxIntrinsicHeight(width, layoutDirection).let {
+ val constraints = targetConstraints
+ constraints.constrainHeight(it)
+ }
+}
+
+private data class AlignmentModifier(
+ private val direction: Direction,
+ private val alignmentCallback: (IntSize, LayoutDirection) -> IntOffset
+) : LayoutModifier {
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints,
+ layoutDirection: LayoutDirection
+ ): MeasureScope.MeasureResult {
+ val wrappedConstraints = when (direction) {
+ Direction.Both -> constraints.copy(minWidth = 0, minHeight = 0)
+ Direction.Horizontal -> constraints.copy(minWidth = 0)
+ Direction.Vertical -> constraints.copy(minHeight = 0)
+ }
+ val placeable = measurable.measure(wrappedConstraints)
+ val wrapperWidth = max(constraints.minWidth, placeable.width)
+ val wrapperHeight = max(constraints.minHeight, placeable.height)
+ return layout(
+ wrapperWidth,
+ wrapperHeight
+ ) {
+ val position = alignmentCallback(
+ IntSize(wrapperWidth - placeable.width, wrapperHeight - placeable.height),
+ layoutDirection
+ )
+ placeable.placeAbsolute(position)
+ }
+ }
+}
+
+private data class UnspecifiedConstraintsModifier(
+ val minWidth: Dp = Dp.Unspecified,
+ val minHeight: Dp = Dp.Unspecified
+) : LayoutModifier {
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints,
+ layoutDirection: LayoutDirection
+ ): MeasureScope.MeasureResult {
+ val wrappedConstraints = Constraints(
+ if (minWidth != Dp.Unspecified && constraints.minWidth == 0) {
+ minWidth.toIntPx().coerceAtMost(constraints.maxWidth)
+ } else {
+ constraints.minWidth
+ },
+ constraints.maxWidth,
+ if (minHeight != Dp.Unspecified && constraints.minHeight == 0) {
+ minHeight.toIntPx().coerceAtMost(constraints.maxHeight)
+ } else {
+ constraints.minHeight
+ },
+ constraints.maxHeight
+ )
+ val placeable = measurable.measure(wrappedConstraints)
+ return layout(placeable.width, placeable.height) {
+ placeable.place(0, 0)
+ }
+ }
+
+ override fun IntrinsicMeasureScope.minIntrinsicWidth(
+ measurable: IntrinsicMeasurable,
+ height: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.minIntrinsicWidth(height).coerceAtLeast(
+ if (minWidth != Dp.Unspecified) minWidth.toIntPx() else 0
+ )
+
+ override fun IntrinsicMeasureScope.maxIntrinsicWidth(
+ measurable: IntrinsicMeasurable,
+ height: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.maxIntrinsicWidth(height).coerceAtLeast(
+ if (minWidth != Dp.Unspecified) minWidth.toIntPx() else 0
+ )
+
+ override fun IntrinsicMeasureScope.minIntrinsicHeight(
+ measurable: IntrinsicMeasurable,
+ width: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.minIntrinsicHeight(width).coerceAtLeast(
+ if (minHeight != Dp.Unspecified) minHeight.toIntPx() else 0
+ )
+
+ override fun IntrinsicMeasureScope.maxIntrinsicHeight(
+ measurable: IntrinsicMeasurable,
+ width: Int,
+ layoutDirection: LayoutDirection
+ ) = measurable.maxIntrinsicHeight(width).coerceAtLeast(
+ if (minHeight != Dp.Unspecified) minHeight.toIntPx() else 0
+ )
+}
+
+internal enum class Direction {
+ Vertical, Horizontal, Both
+}
diff --git a/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/Row.kt b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/Row.kt
new file mode 100644
index 0000000..ece82731
--- /dev/null
+++ b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/Row.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2020 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
+
+import androidx.compose.Composable
+import androidx.compose.Immutable
+import androidx.compose.Stable
+import androidx.ui.core.Alignment
+import androidx.ui.core.HorizontalAlignmentLine
+import androidx.ui.core.Measured
+import androidx.ui.core.Modifier
+import androidx.ui.util.annotation.FloatRange
+
+/**
+ * A layout composable that places its children in a horizontal sequence. For a layout composable
+ * that places its children in a vertical sequence, see [Column].
+ *
+ * The layout model is able to assign children widths according to their weights provided
+ * using the [RowScope.weight] modifier. If a child is not provided a weight, it will be
+ * asked for its preferred width before the sizes of the children with weights are calculated
+ * proportionally to their weight based on the remaining available space.
+ *
+ * When none of its children have weights, a [Row] will be as small as possible to fit its
+ * children one next to the other. In order to change the width of the [Row], use the
+ * [Modifier.width] modifiers; e.g. to make it fill the available width [Modifier.fillMaxWidth]
+ * can be used. If at least one child of a [Row] has a [weight][RowScope.weight], the [Row] will
+ * fill the available width, so there is no need for [Modifier.fillMaxWidth]. However, if [Row]'s
+ * size should be limited, the [Modifier.width] or [Modifier.size] layout modifiers should be
+ * applied.
+ *
+ * When the size of the [Row] is larger than the sum of its children sizes, a
+ * [horizontalArrangement] can be specified to define the positioning of the children inside
+ * the [Row]. See [Arrangement] for available positioning behaviors; a custom arrangement can
+ * also be defined using the constructor of [Arrangement].
+ *
+ * Example usage:
+ *
+ * @sample androidx.ui.layout.samples.SimpleRow
+ *
+ * @param modifier The modifier to be applied to the Row.
+ * @param horizontalArrangement The horizontal arrangement of the layout's children.
+ * @param verticalGravity The vertical gravity of the layout's children.
+ *
+ * @see Column
+ */
+@Composable
+fun Row(
+ modifier: Modifier = Modifier,
+ horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
+ verticalGravity: Alignment.Vertical = Alignment.Top,
+ children: @Composable RowScope.() -> Unit
+) {
+ RowColumnImpl(
+ orientation = LayoutOrientation.Horizontal,
+ modifier = modifier,
+ arrangement = { totalSize, size, layoutDirection ->
+ horizontalArrangement.arrange(totalSize, size, layoutDirection)
+ },
+ crossAxisAlignment = CrossAxisAlignment.vertical(verticalGravity),
+ crossAxisSize = SizeMode.Wrap,
+ children = { RowScope.children() }
+ )
+}
+
+/**
+ * Scope for the children of [Row].
+ */
+@LayoutScopeMarker
+@Immutable
+object RowScope {
+ /**
+ * Position the element vertically within the [Row] according to [align].
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimpleGravityInRow
+ */
+ @Stable
+ fun Modifier.gravity(align: Alignment.Vertical) = this + VerticalGravityModifier(align)
+
+ /**
+ * Position the element vertically such that its [alignmentLine] aligns with sibling elements
+ * also configured to [alignWithSiblings]. [alignWithSiblings] is a form of [gravity],
+ * so both modifiers will not work together if specified for the same layout.
+ * [alignWithSiblings] can be used to align two layouts by baseline inside a [Row],
+ * using `alignWithSiblings(FirstBaseline)`.
+ * Within a [Row], all components with [alignWithSiblings] will align vertically using
+ * the specified [HorizontalAlignmentLine]s or values provided using the other
+ * [alignWithSiblings] overload, forming a sibling group.
+ * At least one element of the sibling group will be placed as it had [Alignment.Top] gravity
+ * in [Row], and the alignment of the other siblings will be then determined such that
+ * the alignment lines coincide. Note that if only one element in a [Row] has the
+ * [alignWithSiblings] modifier specified the element will be positioned
+ * as if it had [Alignment.Top] gravity.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimpleRelativeToSiblingsInRow
+ */
+ @Stable
+ fun Modifier.alignWithSiblings(alignmentLine: HorizontalAlignmentLine) =
+ this + SiblingsAlignedModifier.WithAlignmentLine(alignmentLine)
+
+ /**
+ * Size the element's width proportional to its [weight] relative to other weighted sibling
+ * elements in the [Row]. The parent will divide the horizontal space remaining after measuring
+ * unweighted child elements and distribute it according to this weight.
+ * When [fill] is true, the element will be forced to occupy the whole width allocated to it.
+ * Otherwise, the element is allowed to be smaller - this will result in [Row] being smaller,
+ * as the unused allocated width will not be redistributed to other siblings.
+ */
+ @Stable
+ fun Modifier.weight(
+ @FloatRange(from = 0.0, to = 3.4e38 /* POSITIVE_INFINITY */, fromInclusive = false)
+ weight: Float,
+ fill: Boolean = true
+ ): Modifier {
+ require(weight > 0.0) { "invalid weight $weight; must be greater than zero" }
+ return this + LayoutWeightImpl(weight, fill)
+ }
+
+ /**
+ * Position the element vertically such that the alignment line for the content as
+ * determined by [alignmentLineBlock] aligns with sibling elements also configured to
+ * [alignWithSiblings]. [alignWithSiblings] is a form of [gravity], so both modifiers
+ * will not work together if specified for the same layout.
+ * Within a [Row], all components with [alignWithSiblings] will align vertically using
+ * the specified [HorizontalAlignmentLine]s or values obtained from [alignmentLineBlock],
+ * forming a sibling group.
+ * At least one element of the sibling group will be placed as it had [Alignment.Top] gravity
+ * in [Row], and the alignment of the other siblings will be then determined such that
+ * the alignment lines coincide. Note that if only one element in a [Row] has the
+ * [alignWithSiblings] modifier specified the element will be positioned
+ * as if it had [Alignment.Top] gravity.
+ *
+ * Example usage:
+ * @sample androidx.ui.layout.samples.SimpleRelativeToSiblings
+ */
+ @Stable
+ fun Modifier.alignWithSiblings(
+ alignmentLineBlock: (Measured) -> Int
+ ) = this + SiblingsAlignedModifier.WithAlignmentLineBlock(alignmentLineBlock)
+}
diff --git a/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/RowColumnImpl.kt b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/RowColumnImpl.kt
new file mode 100644
index 0000000..e968409
--- /dev/null
+++ b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/RowColumnImpl.kt
@@ -0,0 +1,1140 @@
+/*
+ * 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
+
+import androidx.compose.Composable
+import androidx.compose.Immutable
+import androidx.compose.Stable
+import androidx.ui.core.Alignment
+import androidx.ui.core.AlignmentLine
+import androidx.ui.core.Constraints
+import androidx.ui.core.IntrinsicMeasurable
+import androidx.ui.core.IntrinsicMeasureBlock2
+import androidx.ui.core.Layout
+import androidx.ui.core.LayoutDirection
+import androidx.ui.core.Measured
+import androidx.ui.core.Modifier
+import androidx.ui.core.ParentDataModifier
+import androidx.ui.core.Placeable
+import androidx.ui.layout.LayoutOrientation.Horizontal
+import androidx.ui.layout.LayoutOrientation.Vertical
+import androidx.ui.unit.Density
+import androidx.ui.util.fastForEach
+import kotlin.math.max
+import kotlin.math.roundToInt
+import kotlin.math.sign
+
+/**
+ * Shared implementation for [Row] and [Column].
+ */
+@Composable
+internal fun RowColumnImpl(
+ orientation: LayoutOrientation,
+ modifier: Modifier = Modifier,
+ arrangement: (Int, List<Int>, LayoutDirection) -> List<Int>,
+ crossAxisSize: SizeMode,
+ crossAxisAlignment: CrossAxisAlignment,
+ children: @Composable () -> Unit
+) {
+ fun Placeable.mainAxisSize() =
+ if (orientation == LayoutOrientation.Horizontal) width else height
+
+ fun Placeable.crossAxisSize() =
+ if (orientation == LayoutOrientation.Horizontal) height else width
+
+ Layout(
+ children,
+ modifier = modifier,
+ minIntrinsicWidthMeasureBlock = MinIntrinsicWidthMeasureBlock(orientation),
+ minIntrinsicHeightMeasureBlock = MinIntrinsicHeightMeasureBlock(orientation),
+ maxIntrinsicWidthMeasureBlock = MaxIntrinsicWidthMeasureBlock(orientation),
+ maxIntrinsicHeightMeasureBlock = MaxIntrinsicHeightMeasureBlock(orientation)
+ ) { measurables, outerConstraints ->
+ val constraints = OrientationIndependentConstraints(outerConstraints, orientation)
+
+ var totalWeight = 0f
+ var fixedSpace = 0
+ var crossAxisSpace = 0
+
+ var anyAlignWithSiblings = false
+ val placeables = arrayOfNulls<Placeable>(measurables.size)
+ val rowColumnParentData = Array(measurables.size) { measurables[it].data }
+
+ // First measure children with zero weight.
+ for (i in measurables.indices) {
+ val child = measurables[i]
+ val parentData = rowColumnParentData[i]
+ val weight = parentData.weight
+
+ if (weight > 0f) {
+ totalWeight += weight
+ } else {
+ val mainAxisMax = constraints.mainAxisMax
+ val placeable = child.measure(
+
+ // Ask for preferred main axis size.
+ constraints.copy(
+ mainAxisMin = 0,
+ mainAxisMax = if (mainAxisMax == Constraints.Infinity) {
+ Constraints.Infinity
+ } else {
+ mainAxisMax - fixedSpace
+ },
+ crossAxisMin = 0
+ ).toBoxConstraints(orientation)
+ )
+ fixedSpace += placeable.mainAxisSize()
+ crossAxisSpace = max(crossAxisSpace, placeable.crossAxisSize())
+ anyAlignWithSiblings = anyAlignWithSiblings || parentData.isRelative
+ placeables[i] = placeable
+ }
+ }
+
+ // Then measure the rest according to their weights in the remaining main axis space.
+ val targetSpace = if (totalWeight > 0f && constraints.mainAxisMax != Constraints.Infinity) {
+ constraints.mainAxisMax
+ } else {
+ constraints.mainAxisMin
+ }
+
+ val weightUnitSpace = if (totalWeight > 0) {
+ (targetSpace.toFloat() - fixedSpace.toFloat()) / totalWeight
+ } else {
+ 0f
+ }
+
+ var remainder = targetSpace - fixedSpace - rowColumnParentData.sumBy {
+ (weightUnitSpace * it.weight).roundToInt()
+ }
+
+ var weightedSpace = 0
+
+ for (i in measurables.indices) {
+ if (placeables[i] == null) {
+ val child = measurables[i]
+ val parentData = rowColumnParentData[i]
+ val weight = parentData.weight
+ check(weight > 0) { "All weights <= 0 should have placeables" }
+ val remainderUnit = remainder.sign
+ remainder -= remainderUnit
+ val childMainAxisSize = max(
+ 0,
+ (weightUnitSpace * weight).roundToInt() + remainderUnit
+ )
+ val placeable = child.measure(
+ OrientationIndependentConstraints(
+ if (parentData.fill && childMainAxisSize != Constraints.Infinity) {
+ childMainAxisSize
+ } else {
+ 0
+ },
+ childMainAxisSize,
+ 0,
+ constraints.crossAxisMax
+ ).toBoxConstraints(orientation)
+ )
+ weightedSpace += placeable.mainAxisSize()
+ crossAxisSpace = max(crossAxisSpace, placeable.crossAxisSize())
+ anyAlignWithSiblings = anyAlignWithSiblings || parentData.isRelative
+ placeables[i] = placeable
+ }
+ }
+
+ var beforeCrossAxisAlignmentLine = 0
+ var afterCrossAxisAlignmentLine = 0
+ if (anyAlignWithSiblings) {
+ for (i in placeables.indices) {
+ val placeable = placeables[i]!!
+ val parentData = rowColumnParentData[i]
+ val alignmentLinePosition = parentData.crossAxisAlignment
+ ?.calculateAlignmentLinePosition(placeable)
+ if (alignmentLinePosition != null) {
+ beforeCrossAxisAlignmentLine = max(
+ beforeCrossAxisAlignmentLine,
+ alignmentLinePosition.let { if (it != AlignmentLine.Unspecified) it else 0 }
+ )
+ afterCrossAxisAlignmentLine = max(
+ afterCrossAxisAlignmentLine,
+ placeable.crossAxisSize() -
+ (alignmentLinePosition.let {
+ if (it != AlignmentLine.Unspecified) {
+ it
+ } else {
+ placeable.crossAxisSize()
+ }
+ })
+ )
+ }
+ }
+ }
+
+ // Compute the Row or Column size and position the children.
+ val mainAxisLayoutSize =
+ if (totalWeight > 0f && constraints.mainAxisMax != Constraints.Infinity) {
+ constraints.mainAxisMax
+ } else {
+ max(fixedSpace + weightedSpace, constraints.mainAxisMin)
+ }
+ val crossAxisLayoutSize = if (constraints.crossAxisMax != Constraints.Infinity &&
+ crossAxisSize == SizeMode.Expand
+ ) {
+ constraints.crossAxisMax
+ } else {
+ max(
+ crossAxisSpace,
+ max(
+ constraints.crossAxisMin,
+ beforeCrossAxisAlignmentLine + afterCrossAxisAlignmentLine
+ )
+ )
+ }
+ val layoutWidth = if (orientation == LayoutOrientation.Horizontal) {
+ mainAxisLayoutSize
+ } else {
+ crossAxisLayoutSize
+ }
+ val layoutHeight = if (orientation == LayoutOrientation.Horizontal) {
+ crossAxisLayoutSize
+ } else {
+ mainAxisLayoutSize
+ }
+
+ layout(layoutWidth, layoutHeight) {
+ val childrenMainAxisSize = placeables.map { it!!.mainAxisSize() }
+ val mainAxisPositions = arrangement(
+ mainAxisLayoutSize,
+ childrenMainAxisSize,
+ layoutDirection
+ )
+
+ placeables.forEachIndexed { index, placeable ->
+ placeable!!
+ val parentData = rowColumnParentData[index]
+ val childCrossAlignment = parentData.crossAxisAlignment ?: crossAxisAlignment
+
+ val crossAxis = childCrossAlignment.align(
+ size = crossAxisLayoutSize - placeable.crossAxisSize(),
+ layoutDirection = if (orientation == LayoutOrientation.Horizontal) {
+ LayoutDirection.Ltr
+ } else {
+ layoutDirection
+ },
+ placeable = placeable,
+ beforeCrossAxisAlignmentLine = beforeCrossAxisAlignmentLine
+ )
+
+ if (orientation == LayoutOrientation.Horizontal) {
+ placeable.placeAbsolute(mainAxisPositions[index], crossAxis)
+ } else {
+ placeable.placeAbsolute(crossAxis, mainAxisPositions[index])
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Used to specify the arrangement of the layout's children in [Row] or [Column] in the main axis
+ * direction (horizontal and vertical, respectively).
+ */
+@Immutable
+object Arrangement {
+ /**
+ * Used to specify the vertical arrangement of the layout's children in a [Column].
+ */
+ interface Vertical {
+ /**
+ * Vertically places the layout children inside the [Column].
+ *
+ * @param totalSize Available space that can be occupied by the children.
+ * @param size A list of sizes of all children.
+ */
+ fun arrange(totalSize: Int, size: List<Int>): List<Int>
+ }
+
+ /**
+ * Used to specify the horizontal arrangement of the layout's children in a [Row].
+ */
+ interface Horizontal {
+ /**
+ * Horizontally places the layout children inside the [Row].
+ *
+ * @param totalSize Available space that can be occupied by the children.
+ * @param size A list of sizes of all children.
+ * @param layoutDirection A layout direction, left-to-right or right-to-left, of the parent
+ * layout that should be taken into account when determining positions of the children.
+ */
+ fun arrange(totalSize: Int, size: List<Int>, layoutDirection: LayoutDirection): List<Int>
+ }
+
+ /**
+ * Place children horizontally such that they are as close as possible to the beginning of the
+ * main axis.
+ */
+ object Start : Horizontal {
+ override fun arrange(
+ totalSize: Int,
+ size: List<Int>,
+ layoutDirection: LayoutDirection
+ ) = if (layoutDirection == LayoutDirection.Ltr) {
+ placeLeftOrTop(size)
+ } else {
+ placeRightOrBottom(totalSize, size.asReversed()).asReversed()
+ }
+ }
+
+ /**
+ * Place children horizontally such that they are as close as possible to the end of the main
+ * axis.
+ */
+ object End : Horizontal {
+ override fun arrange(
+ totalSize: Int,
+ size: List<Int>,
+ layoutDirection: LayoutDirection
+ ) = if (layoutDirection == LayoutDirection.Ltr) {
+ placeRightOrBottom(totalSize, size)
+ } else {
+ placeLeftOrTop(size.asReversed()).asReversed()
+ }
+ }
+
+ /**
+ * Place children vertically such that they are as close as possible to the top of the main
+ * axis.
+ */
+ object Top : Vertical {
+ override fun arrange(totalSize: Int, size: List<Int>) = placeLeftOrTop(size)
+ }
+
+ /**
+ * Place children vertically such that they are as close as possible to the bottom of the main
+ * axis.
+ */
+ object Bottom : Vertical {
+ override fun arrange(totalSize: Int, size: List<Int>) = placeRightOrBottom(totalSize, size)
+ }
+
+ /**
+ * Place children such that they are as close as possible to the middle of the main axis.
+ */
+ object Center : Vertical, Horizontal {
+ override fun arrange(
+ totalSize: Int,
+ size: List<Int>,
+ layoutDirection: LayoutDirection
+ ) = if (layoutDirection == LayoutDirection.Ltr) {
+ placeCenter(totalSize, size)
+ } else {
+ placeCenter(totalSize, size.asReversed()).asReversed()
+ }
+
+ override fun arrange(totalSize: Int, size: List<Int>) = placeCenter(totalSize, size)
+ }
+
+ /**
+ * Place children such that they are spaced evenly across the main axis, including free
+ * space before the first child and after the last child.
+ */
+ object SpaceEvenly : Vertical, Horizontal {
+ override fun arrange(
+ totalSize: Int,
+ size: List<Int>,
+ layoutDirection: LayoutDirection
+ ) = if (layoutDirection == LayoutDirection.Ltr) {
+ placeSpaceEvenly(totalSize, size)
+ } else {
+ placeSpaceEvenly(totalSize, size.asReversed()).asReversed()
+ }
+
+ override fun arrange(totalSize: Int, size: List<Int>) = placeSpaceEvenly(totalSize, size)
+ }
+
+ /**
+ * Place children such that they are spaced evenly across the main axis, without free
+ * space before the first child or after the last child.
+ */
+ object SpaceBetween : Vertical, Horizontal {
+ override fun arrange(
+ totalSize: Int,
+ size: List<Int>,
+ layoutDirection: LayoutDirection
+ ) = if (layoutDirection == LayoutDirection.Ltr) {
+ placeSpaceBetween(totalSize, size)
+ } else {
+ placeSpaceBetween(totalSize, size.asReversed()).asReversed()
+ }
+
+ override fun arrange(totalSize: Int, size: List<Int>) = placeSpaceBetween(totalSize, size)
+ }
+
+ /**
+ * Place children such that they are spaced evenly across the main axis, including free
+ * space before the first child and after the last child, but half the amount of space
+ * existing otherwise between two consecutive children.
+ */
+ object SpaceAround : Vertical, Horizontal {
+ override fun arrange(
+ totalSize: Int,
+ size: List<Int>,
+ layoutDirection: LayoutDirection
+ ) = if (layoutDirection == LayoutDirection.Ltr) {
+ placeSpaceAround(totalSize, size)
+ } else {
+ placeSpaceAround(totalSize, size.asReversed()).asReversed()
+ }
+
+ override fun arrange(totalSize: Int, size: List<Int>) = placeSpaceAround(totalSize, size)
+ }
+
+ internal fun placeRightOrBottom(totalSize: Int, size: List<Int>): List<Int> {
+ val consumedSize = size.fold(0) { a, b -> a + b }
+ val positions = mutableListOf<Int>()
+ var current = totalSize - consumedSize
+ size.fastForEach {
+ positions.add(current)
+ current += it
+ }
+ return positions
+ }
+
+ internal fun placeLeftOrTop(size: List<Int>): List<Int> {
+ val positions = mutableListOf<Int>()
+ var current = 0
+ size.fastForEach {
+ positions.add(current)
+ current += it
+ }
+ return positions
+ }
+
+ internal fun placeCenter(totalSize: Int, size: List<Int>): List<Int> {
+ val consumedSize = size.fold(0) { a, b -> a + b }
+ val positions = mutableListOf<Int>()
+ var current = (totalSize - consumedSize).toFloat() / 2
+ size.fastForEach {
+ positions.add(current.roundToInt())
+ current += it.toFloat()
+ }
+ return positions
+ }
+
+ internal fun placeSpaceEvenly(totalSize: Int, size: List<Int>): List<Int> {
+ val consumedSize = size.fold(0) { a, b -> a + b }
+ val gapSize = (totalSize - consumedSize).toFloat() / (size.size + 1)
+ val positions = mutableListOf<Int>()
+ var current = gapSize
+ size.fastForEach {
+ positions.add(current.roundToInt())
+ current += it.toFloat() + gapSize
+ }
+ return positions
+ }
+
+ internal fun placeSpaceBetween(totalSize: Int, size: List<Int>): List<Int> {
+ val consumedSize = size.fold(0) { a, b -> a + b }
+ val gapSize = if (size.size > 1) {
+ (totalSize - consumedSize).toFloat() / (size.size - 1)
+ } else {
+ 0f
+ }
+ val positions = mutableListOf<Int>()
+ var current = 0f
+ size.fastForEach {
+ positions.add(current.roundToInt())
+ current += it.toFloat() + gapSize
+ }
+ return positions
+ }
+
+ internal fun placeSpaceAround(totalSize: Int, size: List<Int>): List<Int> {
+ val consumedSize = size.fold(0) { a, b -> a + b }
+ val gapSize = if (size.isNotEmpty()) {
+ (totalSize - consumedSize).toFloat() / size.size
+ } else {
+ 0f
+ }
+ val positions = mutableListOf<Int>()
+ var current = gapSize / 2
+ size.fastForEach {
+ positions.add(current.roundToInt())
+ current += it.toFloat() + gapSize
+ }
+ return positions
+ }
+}
+
+@Immutable
+object AbsoluteArrangement {
+ /**
+ * Place children horizontally such that they are as close as possible to the left edge of
+ * the [Row].
+ *
+ * Unlike [Arrangement.Start], in RTL context positions of the children will not be
+ * mirrored and as such children will appear in the order they are put inside the [Row].
+ */
+ object Left : Arrangement.Horizontal {
+ override fun arrange(
+ totalSize: Int,
+ size: List<Int>,
+ layoutDirection: LayoutDirection
+ ) = Arrangement.placeLeftOrTop(size)
+ }
+
+ /**
+ * Place children such that they are as close as possible to the middle of the [Row].
+ *
+ * Unlike [Arrangement.Center], in RTL context positions of the children will not be
+ * mirrored and as such children will appear in the order they are put inside the [Row].
+ */
+ object Center : Arrangement.Horizontal {
+ override fun arrange(
+ totalSize: Int,
+ size: List<Int>,
+ layoutDirection: LayoutDirection
+ ) = Arrangement.placeCenter(totalSize, size)
+ }
+
+ /**
+ * Place children horizontally such that they are as close as possible to the right edge of
+ * the [Row].
+ *
+ * Unlike [Arrangement.End], in RTL context positions of the children will not be
+ * mirrored and as such children will appear in the order they are put inside the [Row].
+ */
+ object Right : Arrangement.Horizontal {
+ override fun arrange(
+ totalSize: Int,
+ size: List<Int>,
+ layoutDirection: LayoutDirection
+ ) = Arrangement.placeRightOrBottom(totalSize, size)
+ }
+
+ /**
+ * Place children such that they are spaced evenly across the main axis, without free
+ * space before the first child or after the last child.
+ *
+ * Unlike [Arrangement.SpaceBetween], in RTL context positions of the children will not be
+ * mirrored and as such children will appear in the order they are put inside the [Row].
+ */
+ object SpaceBetween : Arrangement.Horizontal {
+ override fun arrange(
+ totalSize: Int,
+ size: List<Int>,
+ layoutDirection: LayoutDirection
+ ) = Arrangement.placeSpaceBetween(totalSize, size)
+ }
+
+ /**
+ * Place children such that they are spaced evenly across the main axis, including free
+ * space before the first child and after the last child.
+ *
+ * Unlike [Arrangement.SpaceEvenly], in RTL context positions of the children will not be
+ * mirrored and as such children will appear in the order they are put inside the [Row].
+ */
+ object SpaceEvenly : Arrangement.Horizontal {
+ override fun arrange(
+ totalSize: Int,
+ size: List<Int>,
+ layoutDirection: LayoutDirection
+ ) = Arrangement.placeSpaceEvenly(totalSize, size)
+ }
+
+ /**
+ * Place children such that they are spaced evenly horizontally, including free
+ * space before the first child and after the last child, but half the amount of space
+ * existing otherwise between two consecutive children.
+ *
+ * Unlike [Arrangement.SpaceAround], in RTL context positions of the children will not be
+ * mirrored and as such children will appear in the order they are put inside the [Row].
+ */
+ object SpaceAround : Arrangement.Horizontal {
+ override fun arrange(
+ totalSize: Int,
+ size: List<Int>,
+ layoutDirection: LayoutDirection
+ ) = Arrangement.placeSpaceAround(totalSize, size)
+ }
+}
+
+/**
+ * [Row] will be [Horizontal], [Column] is [Vertical].
+ */
+internal enum class LayoutOrientation {
+ Horizontal,
+ Vertical
+}
+
+/**
+ * Used to specify how a layout chooses its own size when multiple behaviors are possible.
+ */
+// TODO(popam): remove this when Flow is reworked
+enum class SizeMode {
+ /**
+ * Minimize the amount of free space by wrapping the children,
+ * subject to the incoming layout constraints.
+ */
+ Wrap,
+ /**
+ * Maximize the amount of free space by expanding to fill the available space,
+ * subject to the incoming layout constraints.
+ */
+ Expand
+}
+
+/**
+ * Used to specify the alignment of a layout's children, in main axis direction.
+ */
+enum class MainAxisAlignment(internal val arrangement: Arrangement.Vertical) {
+ // TODO(soboleva) support RTl in Flow
+ // workaround for now - use Arrangement that equals to previous Arrangement
+ /**
+ * Place children such that they are as close as possible to the middle of the main axis.
+ */
+ Center(Arrangement.Center),
+
+ /**
+ * Place children such that they are as close as possible to the start of the main axis.
+ */
+ Start(Arrangement.Top),
+
+ /**
+ * Place children such that they are as close as possible to the end of the main axis.
+ */
+ End(Arrangement.Bottom),
+
+ /**
+ * Place children such that they are spaced evenly across the main axis, including free
+ * space before the first child and after the last child.
+ */
+ SpaceEvenly(Arrangement.SpaceEvenly),
+
+ /**
+ * Place children such that they are spaced evenly across the main axis, without free
+ * space before the first child or after the last child.
+ */
+ SpaceBetween(Arrangement.SpaceBetween),
+
+ /**
+ * Place children such that they are spaced evenly across the main axis, including free
+ * space before the first child and after the last child, but half the amount of space
+ * existing otherwise between two consecutive children.
+ */
+ SpaceAround(Arrangement.SpaceAround);
+}
+
+/**
+ * Used to specify the alignment of a layout's children, in cross axis direction.
+ */
+// TODO(popam): refine this API surface with modifiers - add type safety for alignment orientation.
+@Immutable
+sealed class CrossAxisAlignment {
+ /**
+ * Aligns to [size]. If this is a vertical alignment, [layoutDirection] should be
+ * [LayoutDirection.Ltr].
+ *
+ * @param size The remaining space (total size - content size) in the container.
+ * @param layoutDirection The layout direction of the content if horizontal or
+ * [LayoutDirection.Ltr] if vertical.
+ * @param placeable The item being aligned.
+ * @param beforeCrossAxisAlignmentLine The space before the cross-axis alignment line if
+ * an alignment line is being used or 0 if no alignment line is being used.
+ */
+ internal abstract fun align(
+ size: Int,
+ layoutDirection: LayoutDirection,
+ placeable: Placeable,
+ beforeCrossAxisAlignmentLine: Int
+ ): Int
+
+ /**
+ * Returns `true` if this is [Relative].
+ */
+ internal open val isRelative: Boolean
+ get() = false
+
+ /**
+ * Returns the alignment line position relative to the left/top of the space or `null` if
+ * this alignment doesn't rely on alignment lines.
+ */
+ internal open fun calculateAlignmentLinePosition(placeable: Placeable): Int? = null
+
+ companion object {
+ /**
+ * Place children such that their center is in the middle of the cross axis.
+ */
+ @Stable
+ val Center: CrossAxisAlignment = CenterCrossAxisAlignment
+ /**
+ * Place children such that their start edge is aligned to the start edge of the cross
+ * axis. TODO(popam): Consider rtl directionality.
+ */
+ @Stable
+ val Start: CrossAxisAlignment = StartCrossAxisAlignment
+ /**
+ * Place children such that their end edge is aligned to the end edge of the cross
+ * axis. TODO(popam): Consider rtl directionality.
+ */
+ @Stable
+ val End: CrossAxisAlignment = EndCrossAxisAlignment
+
+ /**
+ * Align children by their baseline.
+ */
+ fun AlignmentLine(alignmentLine: AlignmentLine): CrossAxisAlignment =
+ AlignmentLineCrossAxisAlignment(AlignmentLineProvider.Value(alignmentLine))
+
+ /**
+ * Align children relative to their siblings using the alignment line provided as a
+ * parameter using [AlignmentLineProvider].
+ */
+ internal fun Relative(alignmentLineProvider: AlignmentLineProvider): CrossAxisAlignment =
+ AlignmentLineCrossAxisAlignment(alignmentLineProvider)
+
+ /**
+ * Align children with vertical alignment.
+ */
+ internal fun vertical(vertical: Alignment.Vertical): CrossAxisAlignment =
+ VerticalCrossAxisAlignment(vertical)
+
+ /**
+ * Align children with horizontal alignment.
+ */
+ internal fun horizontal(horizontal: Alignment.Horizontal): CrossAxisAlignment =
+ HorizontalCrossAxisAlignment(horizontal)
+ }
+
+ private object CenterCrossAxisAlignment : CrossAxisAlignment() {
+ override fun align(
+ size: Int,
+ layoutDirection: LayoutDirection,
+ placeable: Placeable,
+ beforeCrossAxisAlignmentLine: Int
+ ): Int {
+ return size / 2
+ }
+ }
+
+ private object StartCrossAxisAlignment : CrossAxisAlignment() {
+ override fun align(
+ size: Int,
+ layoutDirection: LayoutDirection,
+ placeable: Placeable,
+ beforeCrossAxisAlignmentLine: Int
+ ): Int {
+ return if (layoutDirection == LayoutDirection.Ltr) 0 else size
+ }
+ }
+
+ private object EndCrossAxisAlignment : CrossAxisAlignment() {
+ override fun align(
+ size: Int,
+ layoutDirection: LayoutDirection,
+ placeable: Placeable,
+ beforeCrossAxisAlignmentLine: Int
+ ): Int {
+ return if (layoutDirection == LayoutDirection.Ltr) size else 0
+ }
+ }
+
+ private class AlignmentLineCrossAxisAlignment(
+ val alignmentLineProvider: AlignmentLineProvider
+ ) : CrossAxisAlignment() {
+ override val isRelative: Boolean
+ get() = true
+
+ override fun calculateAlignmentLinePosition(placeable: Placeable): Int? {
+ return alignmentLineProvider.calculateAlignmentLinePosition(placeable)
+ }
+
+ override fun align(
+ size: Int,
+ layoutDirection: LayoutDirection,
+ placeable: Placeable,
+ beforeCrossAxisAlignmentLine: Int
+ ): Int {
+ val alignmentLinePosition =
+ alignmentLineProvider.calculateAlignmentLinePosition(placeable)
+ return if (alignmentLinePosition != null) {
+ val line = beforeCrossAxisAlignmentLine - alignmentLinePosition
+ if (layoutDirection == LayoutDirection.Rtl) {
+ size - line
+ } else {
+ line
+ }
+ } else {
+ 0
+ }
+ }
+ }
+
+ private class VerticalCrossAxisAlignment(
+ val vertical: Alignment.Vertical
+ ) : CrossAxisAlignment() {
+ override fun align(
+ size: Int,
+ layoutDirection: LayoutDirection,
+ placeable: Placeable,
+ beforeCrossAxisAlignmentLine: Int
+ ): Int {
+ return vertical.align(size)
+ }
+ }
+
+ private class HorizontalCrossAxisAlignment(
+ val horizontal: Alignment.Horizontal
+ ) : CrossAxisAlignment() {
+ override fun align(
+ size: Int,
+ layoutDirection: LayoutDirection,
+ placeable: Placeable,
+ beforeCrossAxisAlignmentLine: Int
+ ): Int {
+ return horizontal.align(size, layoutDirection)
+ }
+ }
+}
+
+/**
+ * Box [Constraints], but which abstract away width and height in favor of main axis and cross axis.
+ */
+internal data class OrientationIndependentConstraints(
+ val mainAxisMin: Int,
+ val mainAxisMax: Int,
+ val crossAxisMin: Int,
+ val crossAxisMax: Int
+) {
+ constructor(c: Constraints, orientation: LayoutOrientation) : this(
+ if (orientation === LayoutOrientation.Horizontal) c.minWidth else c.minHeight,
+ if (orientation === LayoutOrientation.Horizontal) c.maxWidth else c.maxHeight,
+ if (orientation === LayoutOrientation.Horizontal) c.minHeight else c.minWidth,
+ if (orientation === LayoutOrientation.Horizontal) c.maxHeight else c.maxWidth
+ )
+
+ // Creates a new instance with the same main axis constraints and maximum tight cross axis.
+ fun stretchCrossAxis() = OrientationIndependentConstraints(
+ mainAxisMin,
+ mainAxisMax,
+ if (crossAxisMax != Constraints.Infinity) crossAxisMax else crossAxisMin,
+ crossAxisMax
+ )
+
+ // Given an orientation, resolves the current instance to traditional constraints.
+ fun toBoxConstraints(orientation: LayoutOrientation) =
+ if (orientation === LayoutOrientation.Horizontal) {
+ Constraints(mainAxisMin, mainAxisMax, crossAxisMin, crossAxisMax)
+ } else {
+ Constraints(crossAxisMin, crossAxisMax, mainAxisMin, mainAxisMax)
+ }
+
+ // Given an orientation, resolves the max width constraint this instance represents.
+ fun maxWidth(orientation: LayoutOrientation) =
+ if (orientation === LayoutOrientation.Horizontal) {
+ mainAxisMax
+ } else {
+ crossAxisMax
+ }
+
+ // Given an orientation, resolves the max height constraint this instance represents.
+ fun maxHeight(orientation: LayoutOrientation) =
+ if (orientation === LayoutOrientation.Horizontal) {
+ crossAxisMax
+ } else {
+ mainAxisMax
+ }
+}
+
+private val IntrinsicMeasurable.data: RowColumnParentData?
+ get() = parentData as? RowColumnParentData
+
+private val RowColumnParentData?.weight: Float
+ get() = this?.weight ?: 0f
+
+private val RowColumnParentData?.fill: Boolean
+ get() = this?.fill ?: true
+
+private val RowColumnParentData?.crossAxisAlignment: CrossAxisAlignment?
+ get() = this?.crossAxisAlignment
+
+private val RowColumnParentData?.isRelative: Boolean
+ get() = this.crossAxisAlignment?.isRelative ?: false
+
+private /*inline*/ fun MinIntrinsicWidthMeasureBlock(orientation: LayoutOrientation) =
+ if (orientation == LayoutOrientation.Horizontal) {
+ IntrinsicMeasureBlocks.HorizontalMinWidth
+ } else {
+ IntrinsicMeasureBlocks.VerticalMinWidth
+ }
+
+private /*inline*/ fun MinIntrinsicHeightMeasureBlock(orientation: LayoutOrientation) =
+ if (orientation == LayoutOrientation.Horizontal) {
+ IntrinsicMeasureBlocks.HorizontalMinHeight
+ } else {
+ IntrinsicMeasureBlocks.VerticalMinHeight
+ }
+
+private /*inline*/ fun MaxIntrinsicWidthMeasureBlock(orientation: LayoutOrientation) =
+ if (orientation == LayoutOrientation.Horizontal) {
+ IntrinsicMeasureBlocks.HorizontalMaxWidth
+ } else {
+ IntrinsicMeasureBlocks.VerticalMaxWidth
+ }
+
+private /*inline*/ fun MaxIntrinsicHeightMeasureBlock(orientation: LayoutOrientation) =
+ if (orientation == LayoutOrientation.Horizontal) {
+ IntrinsicMeasureBlocks.HorizontalMaxHeight
+ } else {
+ IntrinsicMeasureBlocks.VerticalMaxHeight
+ }
+
+private object IntrinsicMeasureBlocks {
+ val HorizontalMinWidth: IntrinsicMeasureBlock2 = { measurables, availableHeight ->
+ intrinsicSize(
+ measurables,
+ { h -> minIntrinsicWidth(h) },
+ { w -> maxIntrinsicHeight(w) },
+ availableHeight,
+ LayoutOrientation.Horizontal,
+ LayoutOrientation.Horizontal
+ )
+ }
+ val VerticalMinWidth: IntrinsicMeasureBlock2 = { measurables, availableHeight ->
+ intrinsicSize(
+ measurables,
+ { h -> minIntrinsicWidth(h) },
+ { w -> maxIntrinsicHeight(w) },
+ availableHeight,
+ LayoutOrientation.Vertical,
+ LayoutOrientation.Horizontal
+ )
+ }
+ val HorizontalMinHeight: IntrinsicMeasureBlock2 = { measurables, availableWidth ->
+ intrinsicSize(
+ measurables,
+ { w -> minIntrinsicHeight(w) },
+ { h -> maxIntrinsicWidth(h) },
+ availableWidth,
+ LayoutOrientation.Horizontal,
+ LayoutOrientation.Vertical
+ )
+ }
+ val VerticalMinHeight: IntrinsicMeasureBlock2 = { measurables, availableWidth ->
+ intrinsicSize(
+ measurables,
+ { w -> minIntrinsicHeight(w) },
+ { h -> maxIntrinsicWidth(h) },
+ availableWidth,
+ LayoutOrientation.Vertical,
+ LayoutOrientation.Vertical
+ )
+ }
+ val HorizontalMaxWidth: IntrinsicMeasureBlock2 = { measurables, availableHeight ->
+ intrinsicSize(
+ measurables,
+ { h -> maxIntrinsicWidth(h) },
+ { w -> maxIntrinsicHeight(w) },
+ availableHeight,
+ LayoutOrientation.Horizontal,
+ LayoutOrientation.Horizontal
+ )
+ }
+ val VerticalMaxWidth: IntrinsicMeasureBlock2 = { measurables, availableHeight ->
+ intrinsicSize(
+ measurables,
+ { h -> maxIntrinsicWidth(h) },
+ { w -> maxIntrinsicHeight(w) },
+ availableHeight,
+ LayoutOrientation.Vertical,
+ LayoutOrientation.Horizontal
+ )
+ }
+ val HorizontalMaxHeight: IntrinsicMeasureBlock2 = { measurables, availableWidth ->
+ intrinsicSize(
+ measurables,
+ { w -> maxIntrinsicHeight(w) },
+ { h -> maxIntrinsicWidth(h) },
+ availableWidth,
+ LayoutOrientation.Horizontal,
+ LayoutOrientation.Vertical
+ )
+ }
+ val VerticalMaxHeight: IntrinsicMeasureBlock2 = { measurables, availableWidth ->
+ intrinsicSize(
+ measurables,
+ { w -> maxIntrinsicHeight(w) },
+ { h -> maxIntrinsicWidth(h) },
+ availableWidth,
+ LayoutOrientation.Vertical,
+ LayoutOrientation.Vertical
+ )
+ }
+}
+
+private fun intrinsicSize(
+ children: List<IntrinsicMeasurable>,
+ intrinsicMainSize: IntrinsicMeasurable.(Int) -> Int,
+ intrinsicCrossSize: IntrinsicMeasurable.(Int) -> Int,
+ crossAxisAvailable: Int,
+ layoutOrientation: LayoutOrientation,
+ intrinsicOrientation: LayoutOrientation
+) = if (layoutOrientation == intrinsicOrientation) {
+ intrinsicMainAxisSize(children, intrinsicMainSize, crossAxisAvailable)
+} else {
+ intrinsicCrossAxisSize(children, intrinsicCrossSize, intrinsicMainSize, crossAxisAvailable)
+}
+
+private fun intrinsicMainAxisSize(
+ children: List<IntrinsicMeasurable>,
+ mainAxisSize: IntrinsicMeasurable.(Int) -> Int,
+ crossAxisAvailable: Int
+): Int {
+ var weightUnitSpace = 0
+ var fixedSpace = 0
+ var totalWeight = 0f
+ children.fastForEach { child ->
+ val weight = child.data.weight
+ val size = child.mainAxisSize(crossAxisAvailable)
+ if (weight == 0f) {
+ fixedSpace += size
+ } else if (weight > 0f) {
+ totalWeight += weight
+ weightUnitSpace = max(weightUnitSpace, (size / weight).roundToInt())
+ }
+ }
+ return (weightUnitSpace * totalWeight).roundToInt() + fixedSpace
+}
+
+private fun intrinsicCrossAxisSize(
+ children: List<IntrinsicMeasurable>,
+ mainAxisSize: IntrinsicMeasurable.(Int) -> Int,
+ crossAxisSize: IntrinsicMeasurable.(Int) -> Int,
+ mainAxisAvailable: Int
+): Int {
+ var fixedSpace = 0
+ var crossAxisMax = 0
+ var totalWeight = 0f
+ children.fastForEach { child ->
+ val weight = child.data.weight
+ if (weight == 0f) {
+ val mainAxisSpace = child.mainAxisSize(Constraints.Infinity)
+ fixedSpace += mainAxisSpace
+ crossAxisMax = max(crossAxisMax, child.crossAxisSize(mainAxisSpace))
+ } else if (weight > 0f) {
+ totalWeight += weight
+ }
+ }
+
+ val weightUnitSpace = if (totalWeight == 0f) {
+ 0
+ } else if (mainAxisAvailable == Constraints.Infinity) {
+ Constraints.Infinity
+ } else {
+ (max(mainAxisAvailable - fixedSpace, 0) / totalWeight).roundToInt()
+ }
+
+ children.fastForEach { child ->
+ val weight = child.data.weight
+ if (weight > 0f) {
+ crossAxisMax = max(
+ crossAxisMax,
+ child.crossAxisSize((weightUnitSpace * weight).roundToInt())
+ )
+ }
+ }
+ return crossAxisMax
+}
+
+internal data class LayoutWeightImpl(val weight: Float, val fill: Boolean) : ParentDataModifier {
+ override fun Density.modifyParentData(parentData: Any?) =
+ ((parentData as? RowColumnParentData) ?: RowColumnParentData()).also {
+ it.weight = weight
+ it.fill = fill
+ }
+}
+
+internal sealed class SiblingsAlignedModifier : ParentDataModifier {
+ abstract override fun Density.modifyParentData(parentData: Any?): Any?
+
+ internal data class WithAlignmentLineBlock(val block: (Measured) -> Int) :
+ SiblingsAlignedModifier() {
+ override fun Density.modifyParentData(parentData: Any?): Any? {
+ return ((parentData as? RowColumnParentData) ?: RowColumnParentData()).also {
+ it.crossAxisAlignment =
+ CrossAxisAlignment.Relative(AlignmentLineProvider.Block(block))
+ }
+ }
+ }
+
+ internal data class WithAlignmentLine(val line: AlignmentLine) :
+ SiblingsAlignedModifier() {
+ override fun Density.modifyParentData(parentData: Any?): Any? {
+ return ((parentData as? RowColumnParentData) ?: RowColumnParentData()).also {
+ it.crossAxisAlignment =
+ CrossAxisAlignment.Relative(AlignmentLineProvider.Value(line))
+ }
+ }
+ }
+}
+
+internal data class HorizontalGravityModifier(
+ val horizontal: Alignment.Horizontal
+) : ParentDataModifier {
+ override fun Density.modifyParentData(parentData: Any?): RowColumnParentData {
+ return ((parentData as? RowColumnParentData) ?: RowColumnParentData()).also {
+ it.crossAxisAlignment = CrossAxisAlignment.horizontal(horizontal)
+ }
+ }
+}
+
+internal data class VerticalGravityModifier(
+ val vertical: Alignment.Vertical
+) : ParentDataModifier {
+ override fun Density.modifyParentData(parentData: Any?): RowColumnParentData {
+ return ((parentData as? RowColumnParentData) ?: RowColumnParentData()).also {
+ it.crossAxisAlignment = CrossAxisAlignment.vertical(vertical)
+ }
+ }
+}
+
+/**
+ * Parent data associated with children.
+ */
+internal data class RowColumnParentData(
+ var weight: Float = 0f,
+ var fill: Boolean = true,
+ var crossAxisAlignment: CrossAxisAlignment? = null
+)
+
+/**
+ * Provides the alignment line.
+ */
+internal sealed class AlignmentLineProvider {
+ abstract fun calculateAlignmentLinePosition(placeable: Placeable): Int?
+ data class Block(val lineProviderBlock: (Measured) -> Int) : AlignmentLineProvider() {
+ override fun calculateAlignmentLinePosition(
+ placeable: Placeable
+ ): Int? {
+ return lineProviderBlock(Measured(placeable))
+ }
+ }
+
+ data class Value(val line: AlignmentLine) : AlignmentLineProvider() {
+ override fun calculateAlignmentLinePosition(placeable: Placeable): Int? {
+ return placeable[line]
+ }
+ }
+}
diff --git a/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/Spacer.kt b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/Spacer.kt
new file mode 100644
index 0000000..8a098b3
--- /dev/null
+++ b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/Spacer.kt
@@ -0,0 +1,43 @@
+/*
+ * 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
+
+import androidx.compose.Composable
+import androidx.compose.emptyContent
+import androidx.ui.core.Layout
+import androidx.ui.core.Modifier
+import androidx.ui.core.hasFixedHeight
+import androidx.ui.core.hasFixedWidth
+
+/**
+ * Component that represents an empty space layout, whose size can be defined using the [LayoutWidth],
+ * [LayoutHeight] and [LayoutSize] modifiers.
+ *
+ * @sample androidx.ui.layout.samples.SpacerExample
+ *
+ * @param modifier modifiers to set to this spacer
+ */
+@Composable
+fun Spacer(modifier: Modifier) {
+ Layout(emptyContent(), modifier) { _, constraints ->
+ with(constraints) {
+ val width = if (hasFixedWidth) maxWidth else 0
+ val height = if (hasFixedHeight) maxHeight else 0
+ layout(width, height) {}
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/Stack.kt b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/Stack.kt
new file mode 100644
index 0000000..078531b
--- /dev/null
+++ b/ui/ui-layout/src/commonMain/kotlin/androidx/ui/layout/Stack.kt
@@ -0,0 +1,146 @@
+/*
+ * 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
+
+import androidx.compose.Composable
+import androidx.compose.Immutable
+import androidx.compose.Stable
+import androidx.ui.core.Alignment
+import androidx.ui.core.Constraints
+import androidx.ui.core.Layout
+import androidx.ui.core.Measurable
+import androidx.ui.core.Modifier
+import androidx.ui.core.ParentDataModifier
+import androidx.ui.core.Placeable
+import androidx.ui.unit.Density
+import androidx.ui.unit.IntSize
+import kotlin.math.max
+
+/**
+ * A composable that positions its children relative to its edges.
+ * The component is useful for drawing children that overlap. The children will always be
+ * drawn in the order they are specified in the body of the [Stack].
+ * Use [StackScope.gravity] modifier to define the position of the target element inside the
+ * [Stack] box.
+ *
+ * Example usage:
+ *
+ * @sample androidx.ui.layout.samples.SimpleStack
+ */
+@Composable
+fun Stack(
+ modifier: Modifier = Modifier,
+ children: @Composable StackScope.() -> Unit
+) {
+ val stackChildren: @Composable () -> Unit = { StackScope().children() }
+
+ Layout(stackChildren, modifier = modifier) { measurables, constraints ->
+ val placeables = arrayOfNulls<Placeable>(measurables.size)
+ // First measure aligned children to get the size of the layout.
+ val childConstraints = constraints.copy(minWidth = 0, minHeight = 0)
+ (0 until measurables.size).filter { i -> !measurables[i].stretch }.forEach { i ->
+ placeables[i] = measurables[i].measure(childConstraints)
+ }
+ val (stackWidth, stackHeight) = with(placeables.filterNotNull()) {
+ Pair(
+ max(maxBy { it.width }?.width ?: 0, constraints.minWidth),
+ max(maxBy { it.height }?.height ?: 0, constraints.minHeight)
+ )
+ }
+
+ // Now measure stretch children.
+ (0 until measurables.size).filter { i -> measurables[i].stretch }.forEach { i ->
+ // infinity check is needed for intrinsic measurements
+ val minWidth = if (stackWidth != Constraints.Infinity) stackWidth else 0
+ val minHeight = if (stackHeight != Constraints.Infinity) stackHeight else 0
+ placeables[i] = measurables[i].measure(
+ Constraints(minWidth, stackWidth, minHeight, stackHeight)
+ )
+ }
+
+ // Position the children.
+ layout(stackWidth, stackHeight) {
+ (0 until measurables.size).forEach { i ->
+ val measurable = measurables[i]
+ val childData = measurable.stackChildData
+ val placeable = placeables[i]!!
+
+ val position = childData.alignment.align(
+ IntSize(
+ stackWidth - placeable.width,
+ stackHeight - placeable.height
+ ),
+ layoutDirection
+ )
+ placeable.placeAbsolute(position.x, position.y)
+ }
+ }
+ }
+}
+
+/**
+ * A StackScope provides a scope for the children of a [Stack].
+ */
+@LayoutScopeMarker
+@Immutable
+class StackScope {
+ /**
+ * Pull the content element to a specific [Alignment] within the [Stack].
+ */
+ @Stable
+ fun Modifier.gravity(align: Alignment) = this + StackGravityModifier(align)
+
+ /**
+ * Size the element to match the size of the [Stack] after all other content elements have
+ * been measured.
+ *
+ * The element using this modifier does not take part in defining the size of the [Stack].
+ * Instead, it matches the size of the [Stack] after all other children (not using
+ * matchParentSize() modifier) have been measured to obtain the [Stack]'s size.
+ * In contrast, a general-purpose [Modifier.fillMaxSize] modifier, which makes an element
+ * occupy all available space, will take part in defining the size of the [Stack]. Consequently,
+ * using it for an element inside a [Stack] will make the [Stack] itself always fill the
+ * available space.
+ */
+ @Stable
+ fun Modifier.matchParentSize() = this + StretchGravityModifier
+
+ internal companion object {
+ @Stable
+ val StretchGravityModifier: ParentDataModifier =
+ StackGravityModifier(Alignment.Center, true)
+ }
+}
+
+private data class StackChildData(
+ val alignment: Alignment,
+ val stretch: Boolean = false
+)
+
+private val Measurable.stackChildData: StackChildData
+ get() = (parentData as? StackChildData) ?: StackChildData(Alignment.TopStart)
+private val Measurable.stretch: Boolean
+ get() = stackChildData.stretch
+
+private data class StackGravityModifier(
+ val alignment: Alignment,
+ val stretch: Boolean = false
+) : ParentDataModifier {
+ override fun Density.modifyParentData(parentData: Any?): StackChildData {
+ return ((parentData as? StackChildData) ?: StackChildData(alignment, stretch))
+ }
+}