diff --git a/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testChildIsConstrainedToParentWidth[LTR].png b/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testChildIsConstrainedToParentWidth[LTR].png index 6fb81d1ea9..7a719c4667 100644 --- a/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testChildIsConstrainedToParentWidth[LTR].png +++ b/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testChildIsConstrainedToParentWidth[LTR].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:81a9963a0b5e6092ed757f885315f581fc3a96bceea8cffccd4a5b1bcd9bdb35 -size 8669 +oid sha256:5ff7c5e4f4623567070a4a4cb33c9475f50efab24adfc10288fee4f97b2490c9 +size 8997 diff --git a/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testChildIsConstrainedToParentWidth[RTL].png b/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testChildIsConstrainedToParentWidth[RTL].png index 5e70082ef5..0107a6e0a0 100644 --- a/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testChildIsConstrainedToParentWidth[RTL].png +++ b/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testChildIsConstrainedToParentWidth[RTL].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:599f8910469cc82b82e1e48c002ca292c71c5ca37cab70179305f45c7f35fdca -size 8772 +oid sha256:ff0b3d6f02e88356c68e6ff2130aa5403f8102e2458cfe8b4cf868ddc6361a8c +size 8834 diff --git a/redwood-layout-shared-test/src/commonMain/kotlin/app/cash/redwood/layout/AbstractFlexContainerTest.kt b/redwood-layout-shared-test/src/commonMain/kotlin/app/cash/redwood/layout/AbstractFlexContainerTest.kt index 99095c2b46..dd4ee56b18 100644 --- a/redwood-layout-shared-test/src/commonMain/kotlin/app/cash/redwood/layout/AbstractFlexContainerTest.kt +++ b/redwood-layout-shared-test/src/commonMain/kotlin/app/cash/redwood/layout/AbstractFlexContainerTest.kt @@ -799,6 +799,7 @@ abstract class AbstractFlexContainerTest { val alignStart = HorizontalAlignmentImpl(CrossAxisAlignment.Start) flexContainer(FlexDirection.Column) .apply { + width(Constraint.Fill) modifier = WidthImpl(25.dp) add(widgetFactory.text("ok", alignStart)) // This is under 25.dp in width. add(widgetFactory.text("1 2 3 4", alignStart)) // Each character is under 25.dp in width. @@ -808,6 +809,7 @@ abstract class AbstractFlexContainerTest { flexContainer(FlexDirection.Column) .apply { + width(Constraint.Fill) modifier = WidthImpl(25.dp) add(widgetFactory.text("overflows parent", alignStart)) // This is over 25.dp in width. add(widgetFactory.text("1 2 3 4", alignStart)) // Each character is under 25.dp in width. diff --git a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testDynamicContainerSize.both.png b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testDynamicContainerSize.both.png index f3397b1794..2b544c6c20 100644 --- a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testDynamicContainerSize.both.png +++ b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testDynamicContainerSize.both.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b9a756eddfdc540f4b3780c965f0b5d92126a41d87aaf28cbe99f41e1ff8d79 -size 71109 +oid sha256:d51ef5847b8e55c48c99d466a7cc178c7ebf973cc9f81f55f7baa0f433dcde0c +size 71989 diff --git a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testDynamicContainerSize.single.png b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testDynamicContainerSize.single.png index 680502f40d..cf5da950e5 100644 --- a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testDynamicContainerSize.single.png +++ b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testDynamicContainerSize.single.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d0b0322fa313b5a2e71c2a23ff1af419ca31aa0739230063ae77af0cbfdfd3e -size 67288 +oid sha256:d709a8220f4dbc0b8633ac1977208adb9180660d61d53130c67278eeba7b854d +size 70632 diff --git a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Column_Wrap_Fill.1.png b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Column_Wrap_Fill.1.png index c14e753ba5..beec71a0e5 100644 --- a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Column_Wrap_Fill.1.png +++ b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Column_Wrap_Fill.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:128b3634533162b43d8a49f9402d803b90312d76156ebb1c6d502d8c2679a308 -size 72856 +oid sha256:82bdbd1d36d60e5a11a9cde1c1404b23452abed121af41a84e7fa158a6b127df +size 70543 diff --git a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Column_Wrap_Wrap.1.png b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Column_Wrap_Wrap.1.png index c14e753ba5..beec71a0e5 100644 --- a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Column_Wrap_Wrap.1.png +++ b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Column_Wrap_Wrap.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:128b3634533162b43d8a49f9402d803b90312d76156ebb1c6d502d8c2679a308 -size 72856 +oid sha256:82bdbd1d36d60e5a11a9cde1c1404b23452abed121af41a84e7fa158a6b127df +size 70543 diff --git a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Row_Fill_Wrap.1.png b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Row_Fill_Wrap.1.png index c14e753ba5..9c36376b3b 100644 --- a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Row_Fill_Wrap.1.png +++ b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Row_Fill_Wrap.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:128b3634533162b43d8a49f9402d803b90312d76156ebb1c6d502d8c2679a308 -size 72856 +oid sha256:10a58848d00aaac1a29644157079a888d3ab8e24b2b7a27deaa19b79ef6b7d21 +size 83542 diff --git a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Row_Wrap_Wrap.1.png b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Row_Wrap_Wrap.1.png index c14e753ba5..9c36376b3b 100644 --- a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Row_Wrap_Wrap.1.png +++ b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Row_Wrap_Wrap.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:128b3634533162b43d8a49f9402d803b90312d76156ebb1c6d502d8c2679a308 -size 72856 +oid sha256:10a58848d00aaac1a29644157079a888d3ab8e24b2b7a27deaa19b79ef6b7d21 +size 83542 diff --git a/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewMeasureCallback.kt b/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewMeasureCallback.kt index 5fe4ea6a31..d3f310ad06 100644 --- a/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewMeasureCallback.kt +++ b/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewMeasureCallback.kt @@ -24,6 +24,7 @@ import kotlinx.cinterop.useContents import platform.CoreGraphics.CGSize import platform.CoreGraphics.CGSizeMake import platform.UIKit.UIView +import platform.UIKit.UIViewNoIntrinsicMetric internal class UIViewMeasureCallback(val view: UIView) : MeasureCallback { override fun measure( @@ -34,11 +35,11 @@ internal class UIViewMeasureCallback(val view: UIView) : MeasureCallback { heightMode: MeasureMode, ): Size { val constrainedWidth = when (widthMode) { - MeasureMode.Undefined -> 0.0 + MeasureMode.Undefined -> UIViewNoIntrinsicMetric else -> width.toDouble() } val constrainedHeight = when (heightMode) { - MeasureMode.Undefined -> 0.0 + MeasureMode.Undefined -> UIViewNoIntrinsicMetric else -> height.toDouble() } @@ -54,21 +55,10 @@ internal class UIViewMeasureCallback(val view: UIView) : MeasureCallback { } return Size( - width = sanitizeMeasurement(constrainedWidth, sizeThatFits.width, widthMode), - height = sanitizeMeasurement(constrainedHeight, sizeThatFits.height, heightMode), + width = sizeThatFits.width, + height = sizeThatFits.height, ) } - - private fun sanitizeMeasurement( - constrainedSize: Double, - measuredSize: Float, - measureMode: MeasureMode, - ): Float = when (measureMode) { - MeasureMode.Exactly -> constrainedSize.toFloat() - MeasureMode.AtMost -> measuredSize - MeasureMode.Undefined -> measuredSize - else -> throw AssertionError() - } } private fun CValue.toSize() = useContents { diff --git a/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/YogaUIView.kt b/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/YogaUIView.kt index ea8f891a44..51a4554a28 100644 --- a/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/YogaUIView.kt +++ b/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/YogaUIView.kt @@ -14,6 +14,7 @@ import app.cash.redwood.yoga.Size import kotlinx.cinterop.CValue import kotlinx.cinterop.cValue import kotlinx.cinterop.useContents +import platform.CoreGraphics.CGFloat import platform.CoreGraphics.CGRectMake import platform.CoreGraphics.CGRectZero import platform.CoreGraphics.CGSize @@ -43,12 +44,15 @@ internal class YogaUIView( contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever } - override fun intrinsicContentSize(): CValue { - return calculateLayoutWithSize(Size.UNDEFINED, Size.UNDEFINED) - } + override fun intrinsicContentSize(): CValue = calculateLayout() override fun sizeThatFits(size: CValue): CValue { - return calculateLayoutWithSize(size) + return size.useContents> { + calculateLayout( + width = width.toYogaWithWidthConstraint(), + height = height.toYogaWithHeightConstraint(), + ) + } } override fun layoutSubviews() { @@ -56,31 +60,28 @@ internal class YogaUIView( // Based on the constraints of Fill or Wrap, we // calculate a size that the container should fit in. - val bounds = bounds.useContents { + val boundsSize = bounds.useContents { CGSizeMake(size.width, size.height) } - if (scrollEnabled) { + val contentSize = when { + // If we're not scrolling, the contentSize should equal the size of the view. + !scrollEnabled -> boundsSize + // When scrolling is enabled, we want to calculate and apply the contentSize // separately and have it grow a much as needed in the flexDirection. // This duplicates the calculation we're doing above, and should be // combined into one call. - val scrollSize = bounds.useContents { - if (isColumn()) { - CGSizeMake(width, Size.UNDEFINED.toDouble()) - } else { - CGSizeMake(Size.UNDEFINED.toDouble(), height) - } - } - val contentSize = calculateLayoutWithSize(scrollSize) - setContentSize(contentSize) - calculateLayoutWithSize(bounds) - } else { - // If we're not scrolling, the contentSize should equal the size of the view. - val containerSize = calculateLayoutWithSize(bounds) - setContentSize(containerSize) + isColumn() -> calculateLayout(width = boundsSize.useContents { width.toYoga() }) + else -> calculateLayout(height = boundsSize.useContents { height.toYoga() }) } + setContentSize(contentSize) + calculateLayout( + width = contentSize.useContents { width.toYoga() }, + height = contentSize.useContents { height.toYoga() }, + ) + // Layout the nodes based on the calculatedLayouts above. for (childNode in rootNode.children) { childNode.view.setFrame( @@ -94,24 +95,12 @@ internal class YogaUIView( } } - private fun calculateLayoutWithSize(size: CValue): CValue { - return size.useContents { - calculateLayoutWithSize(width.toYoga(), height.toYoga()) - } - } - - private fun calculateLayoutWithSize(width: Float, height: Float): CValue { - rootNode.requestedWidth = when (widthConstraint) { - Constraint.Fill -> width - else -> Size.UNDEFINED - } - rootNode.requestedMaxWidth = width - - rootNode.requestedHeight = when (heightConstraint) { - Constraint.Fill -> height - else -> Size.UNDEFINED - } - rootNode.requestedMaxHeight = height + private fun calculateLayout( + width: Float = Size.UNDEFINED, + height: Float = Size.UNDEFINED, + ): CValue { + rootNode.requestedWidth = width + rootNode.requestedHeight = height for ((index, node) in rootNode.children.withIndex()) { applyModifier(node, index) @@ -121,13 +110,23 @@ internal class YogaUIView( rootNode.markEverythingDirty() } - rootNode.measureOnly(width, height) + rootNode.measureOnly(Size.UNDEFINED, Size.UNDEFINED) return CGSizeMake(rootNode.width.toDouble(), rootNode.height.toDouble()) } - /** Convert a UIView dimension (a double) to a Yoga dimension (a float). */ - private fun Double.toYoga(): Float { + private fun CGFloat.toYogaWithWidthConstraint() = when (widthConstraint) { + Constraint.Wrap -> Size.UNDEFINED + else -> toYoga() + } + + private fun CGFloat.toYogaWithHeightConstraint() = when (heightConstraint) { + Constraint.Wrap -> Size.UNDEFINED + else -> toYoga() + } + + /** Convert a UIView dimension (a Double) to a Yoga dimension (a Float). */ + private fun CGFloat.toYoga(): Float { return when (this) { UIViewNoIntrinsicMetric -> Size.UNDEFINED else -> this.toFloat() diff --git a/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testChildIsConstrainedToParentWidth.1.png b/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testChildIsConstrainedToParentWidth.1.png new file mode 100644 index 0000000000..699a318449 --- /dev/null +++ b/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testChildIsConstrainedToParentWidth.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0dd2fe148f072751261845d54f593753da2e373144c96782bec46bfb88b3be7d +size 68295 diff --git a/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testRecursiveLayoutHandlesResizes.v2.png b/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testRecursiveLayoutHandlesResizes.v2.png index 1bcb4cd16a..96a67b0a8f 100644 --- a/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testRecursiveLayoutHandlesResizes.v2.png +++ b/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testRecursiveLayoutHandlesResizes.v2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8c6776c1446efaaf445ec36fb55bbcd9e08c5d4d1bae86602cdfe6ec4746b4f7 -size 77390 +oid sha256:106ecd41ecc0fef54ef4d276bd711ca9303c3b25c1d49f9f318bd898bd6f23ae +size 74954