Last active
November 12, 2022 07:31
-
-
Save zach-klippenstein/a68b75567d36e1f8e6f11dc6f6e5caf4 to your computer and use it in GitHub Desktop.
Solution to https://kotlinlang.slack.com/archives/CJLTWPH7S/p1668110705848269?thread_ts=1668110653.982419&cid=CJLTWPH7S
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Preview(showBackground = true, showSystemUi = true) | |
@Composable | |
fun App() { | |
Column { | |
MarxistRow { | |
Text("Left Text", Modifier.background(Color.Red)) | |
Text("Right Text", Modifier.background(Color.Blue)) | |
} | |
MarxistRow { | |
Text( | |
"Left Text is longer that to fit screen width for a single line so right text will not be visible", | |
Modifier | |
.background(Color.Red) | |
) | |
Text( | |
"Right Text", | |
Modifier | |
.background(Color.Blue) | |
) | |
} | |
MarxistRow { | |
Text("Left Text", Modifier.background(Color.Red)) | |
Text( | |
"Right Text is longer that to fit screen width so left text will not be visible", | |
Modifier.background(Color.Blue) | |
) | |
} | |
MarxistRow { | |
Text( | |
"Left Text is as long as Right Text so right one will not be visible lorem ipsum lorem ipsum", | |
Modifier.background(Color.Red) | |
) | |
Text( | |
"Right Text is as long as Left Text so right one will not be visible lorem ipsum lorem ipsum", | |
Modifier.background(Color.Blue) | |
) | |
} | |
MarxistRow { | |
Text( | |
"Left Text is as long as Right Text so right one will not be visible lorem ipsum lorem ipsum", | |
Modifier.background(Color.Red) | |
) | |
Text("Center Text", Modifier.background(Color.Green)) | |
Text( | |
"Right Text is as long as Left Text so right one will not be visible lorem ipsum lorem ipsum", | |
Modifier.background(Color.Blue) | |
) | |
} | |
MarxistRow { | |
Text( | |
"Left Text", | |
Modifier.background(Color.Red) | |
) | |
Text("Center Text", Modifier.background(Color.Green)) | |
Text( | |
"Right Text is as long as Left Text so right one will not be visible lorem ipsum lorem ipsum", | |
Modifier.background(Color.Blue) | |
) | |
} | |
} | |
} | |
/** | |
* > From each according to his ability, to each according to his needs | |
* | |
* https://en.wikipedia.org/wiki/From_each_according_to_his_ability,_to_each_according_to_his_needs | |
*/ | |
@Composable | |
fun MarxistRow( | |
modifier: Modifier = Modifier, | |
verticalAlignment: Alignment.Vertical = CenterVertically, | |
content: @Composable () -> Unit | |
) { | |
Layout( | |
content = content, | |
modifier = modifier | |
) { measurables, constraints -> | |
if (measurables.isEmpty()) return@Layout layout(0, 0) {} | |
// Intrinsics are cheaper than full measurement, and can be queried multiple times, unlike | |
// full measurement. This asks each child how wide it would be if it had unlimited width to | |
// fill, given it has to be at least a certain height. For simple text, this will always | |
// just be how wide the text is if every paragraph allowed to be a single, long line. | |
val intrinsicWidths = measurables.map { it.maxIntrinsicWidth(constraints.minHeight) } | |
// In the "worst" case, if every child is too wide, each one will get an equal share of the | |
// total available width. Start with that assumption, and then figure out which children | |
// don't actually need that much space. | |
val worstCaseChildWidth = constraints.maxWidth / measurables.size | |
val (childrenThatFit, childrenThatNeedToWrap) = | |
intrinsicWidths.partition { it <= worstCaseChildWidth } | |
// The smaller children can be packed nice and tight. If we let them be small, but still | |
// used the worst-case-width for larger children, the total row width wouldn't fill the | |
// available space – i.e. the same result you get with a | |
// Row(Modifier.width(IntrinsicSize.Max)) with every child given a weight(1f, fill = false) | |
// modifier. Instead, after packing the small children in, we can take whatever space is | |
// left-over and divide _that_ between the larger children. actualLargeChildWidth will be | |
// wider than worstCaseChildWidth, because the small children didn't use all their space. | |
// Setting to MAX_VALUE if all children are small ensures the math works below. | |
// | |
// TODO Just because a child was too big to fit in worstCaseChildWidth doesn't mean it will | |
// fill the whole actualLargeChildWidth. We should measure each child with 0 min width and | |
// then re-calculate the remaining available space for remaining children on every | |
// iteration based on how much space it officially takes. | |
val consumedWidth = childrenThatFit.sum() | |
val actualLargeChildWidth = if (childrenThatNeedToWrap.isEmpty()) Int.MAX_VALUE else { | |
(constraints.maxWidth - consumedWidth) / childrenThatNeedToWrap.size | |
} | |
val placeables = measurables.mapIndexed { i, measurable -> | |
val intrinsicWidth = intrinsicWidths[i] | |
val childConstraints = if (intrinsicWidth <= actualLargeChildWidth) { | |
// If the child is smaller than its allotted width, it should be exactly that wide. | |
constraints.copy(minWidth = intrinsicWidth, maxWidth = intrinsicWidth) | |
} else { | |
// Children that are too big are given equal shares of the remaining space after | |
// measuring the small children. | |
constraints.copy(minWidth = actualLargeChildWidth, maxWidth = actualLargeChildWidth) | |
} | |
measurable.measure(childConstraints) | |
} | |
// Cache the height calculation because we to use it to calculate the alignment-based y | |
// position for each child below. | |
val height = placeables.maxOf { it.height } | |
layout( | |
width = placeables.sumOf { it.width }, | |
height = height | |
) { | |
var x = 0 | |
placeables.forEach { | |
val y = verticalAlignment.align(it.height, height) | |
it.placeRelative(x, y) | |
x += it.width | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment