Skip to content

Commit

Permalink
Use different constraints for measure vs layout (#2340)
Browse files Browse the repository at this point in the history
* Use different constraints for measure vs layout

When we measure we treat the inbound dimensions as suggestions.
When we layout, we treat them as constants.

* Simplify the layout code

* Update Compose snapsohts
  • Loading branch information
squarejesse authored Sep 30, 2024
1 parent 1841aed commit d4ca941
Show file tree
Hide file tree
Showing 13 changed files with 68 additions and 74 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,7 @@ abstract class AbstractFlexContainerTest<T : Any> {
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.
Expand All @@ -808,6 +809,7 @@ abstract class AbstractFlexContainerTest<T : Any> {

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.
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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()
}

Expand All @@ -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<CGSize>.toSize() = useContents {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -43,44 +44,44 @@ internal class YogaUIView(
contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever
}

override fun intrinsicContentSize(): CValue<CGSize> {
return calculateLayoutWithSize(Size.UNDEFINED, Size.UNDEFINED)
}
override fun intrinsicContentSize(): CValue<CGSize> = calculateLayout()

override fun sizeThatFits(size: CValue<CGSize>): CValue<CGSize> {
return calculateLayoutWithSize(size)
return size.useContents<CGSize, CValue<CGSize>> {
calculateLayout(
width = width.toYogaWithWidthConstraint(),
height = height.toYogaWithHeightConstraint(),
)
}
}

override fun layoutSubviews() {
super.layoutSubviews()

// 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(
Expand All @@ -94,24 +95,12 @@ internal class YogaUIView(
}
}

private fun calculateLayoutWithSize(size: CValue<CGSize>): CValue<CGSize> {
return size.useContents {
calculateLayoutWithSize(width.toYoga(), height.toYoga())
}
}

private fun calculateLayoutWithSize(width: Float, height: Float): CValue<CGSize> {
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<CGSize> {
rootNode.requestedWidth = width
rootNode.requestedHeight = height

for ((index, node) in rootNode.children.withIndex()) {
applyModifier(node, index)
Expand All @@ -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()
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit d4ca941

Please sign in to comment.