From fa9e3efcadfa64dee79b53d441a7110b3f6395a8 Mon Sep 17 00:00:00 2001 From: Abe White Date: Tue, 15 Oct 2024 12:44:45 -0500 Subject: [PATCH] Layout fixes + fix regression in views with minHeight in ScrollView --- .../SkipUI/Compose/ComposeContainer.swift | 6 +- .../SkipUI/Compose/ComposeLayouts.swift | 58 +++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Sources/SkipUI/SkipUI/Compose/ComposeContainer.swift b/Sources/SkipUI/SkipUI/Compose/ComposeContainer.swift index a7b16651..74ee1bd5 100644 --- a/Sources/SkipUI/SkipUI/Compose/ComposeContainer.swift +++ b/Sources/SkipUI/SkipUI/Compose/ComposeContainer.swift @@ -34,7 +34,7 @@ import androidx.compose.ui.Modifier /// that if the container content uses them, the container itself can recompose with the appropriate expansion to match its /// content. Note that this generally only affects final layout when an expanding child is in a container that is itself in a /// container, and it has to share space with other members of the parent container. -@Composable public func ComposeContainer(axis: Axis? = nil, eraseAxis: Bool = false, scrollAxes: Axis.Set = [], modifier: Modifier = Modifier, fillWidth: Bool = false, fixedWidth: Bool = false, fillHeight: Bool = false, fixedHeight: Bool = false, then: Modifier = Modifier, content: @Composable (Modifier) -> Void) { +@Composable public func ComposeContainer(axis: Axis? = nil, eraseAxis: Bool = false, scrollAxes: Axis.Set = [], modifier: Modifier = Modifier, fillWidth: Bool = false, fixedWidth: Bool = false, minWidth: Bool = false, fillHeight: Bool = false, fixedHeight: Bool = false, minHeight: Bool = false, then: Modifier = Modifier, content: @Composable (Modifier) -> Void) { // Use remembered expansion values to recompose on change let isFillWidth = remember { mutableStateOf(fillWidth) } let isFillHeight = remember { mutableStateOf(fillHeight) } @@ -45,7 +45,7 @@ import androidx.compose.ui.Modifier var modifier = modifier let inheritedLayoutScrollAxes = EnvironmentValues.shared._layoutScrollAxes var totalLayoutScrollAxes = inheritedLayoutScrollAxes - if fixedWidth || axis == .vertical { + if fixedWidth || minWidth || axis == .vertical { totalLayoutScrollAxes.remove(Axis.Set.horizontal) } if !fixedWidth && isFillWidth.value { @@ -57,7 +57,7 @@ import androidx.compose.ui.Modifier modifier = modifier.fillWidth() } } - if fixedHeight || axis == .horizontal { + if fixedHeight || minHeight || axis == .horizontal { totalLayoutScrollAxes.remove(Axis.Set.vertical) } if !fixedHeight && isFillHeight.value { diff --git a/Sources/SkipUI/SkipUI/Compose/ComposeLayouts.swift b/Sources/SkipUI/SkipUI/Compose/ComposeLayouts.swift index ac813a24..b5bd00ce 100644 --- a/Sources/SkipUI/SkipUI/Compose/ComposeLayouts.swift +++ b/Sources/SkipUI/SkipUI/Compose/ComposeLayouts.swift @@ -61,7 +61,7 @@ import androidx.compose.ui.unit.dp } else if minHeight != nil || maxHeight != nil { thenModifier = thenModifier.requiredHeightIn(min: minHeight != nil ? minHeight!.dp : Dp.Unspecified, max: maxHeight != nil ? maxHeight!.dp : Dp.Unspecified) } - ComposeContainer(modifier: context.modifier, fillWidth: maxWidth == Double.infinity, fixedWidth: maxWidth != nil && maxWidth != Double.infinity, fillHeight: maxHeight == Double.infinity, fixedHeight: maxHeight != nil && maxHeight != Double.infinity, then: thenModifier) { modifier in + ComposeContainer(modifier: context.modifier, fillWidth: maxWidth == Double.infinity, fixedWidth: maxWidth != nil && maxWidth != Double.infinity, minWidth: minWidth != nil && minWidth != Double.infinity && minWidth! > 0.0, fillHeight: maxHeight == Double.infinity, fixedHeight: maxHeight != nil && maxHeight != Double.infinity, minHeight: minHeight != nil && minHeight != Double.infinity && minHeight! > 0.0, then: thenModifier) { modifier in let contentContext = context.content() Box(modifier: modifier, contentAlignment: alignment.asComposeAlignment()) { view.Compose(context: contentContext) @@ -80,35 +80,35 @@ import androidx.compose.ui.unit.dp } @Composable func TargetViewLayout(context: ComposeContext, isOverlay: Bool, alignment: Alignment, target: @Composable (ComposeContext) -> Void, dependent: @Composable (ComposeContext) -> Void) { - Layout(modifier: context.modifier, content: { - // ComposeContainer is needed to properly handle content that fills width/height - ComposeContainer { modifier in - target(context.content(modifier: modifier)) - } - // Dependent view lays out with fixed bounds dictated by the target view size - ComposeContainer(fixedWidth: true, fixedHeight: true) { modifier in - dependent(context.content(modifier: modifier)) - } - }) { measurables, constraints in - guard !measurables.isEmpty() else { - return layout(width: 0, height: 0) {} - } - // Base layout entirely on the target view size - let targetPlaceable = measurables[0].measure(constraints) - let dependentConstraints = Constraints(maxWidth: targetPlaceable.width, maxHeight: targetPlaceable.height) - let dependentPlaceables = measurables.drop(1).map { $0.measure(dependentConstraints) } - layout(width: targetPlaceable.width, height: targetPlaceable.height) { - if !isOverlay { - for dependentPlaceable in dependentPlaceables { - let (x, y) = placeView(width: dependentPlaceable.width, height: dependentPlaceable.height, inWidth: targetPlaceable.width, inHeight: targetPlaceable.height, alignment: alignment) - dependentPlaceable.placeRelative(x: x, y: y) - } + // ComposeContainer is needed to properly handle content that fills width/height + ComposeContainer(modifier: context.modifier) { modifier in + Layout(modifier: modifier, content: { + target(context.content()) + // Dependent view lays out with fixed bounds dictated by the target view size + ComposeContainer(fixedWidth: true, fixedHeight: true) { modifier in + dependent(context.content(modifier: modifier)) + } + }) { measurables, constraints in + guard !measurables.isEmpty() else { + return layout(width: 0, height: 0) {} } - targetPlaceable.placeRelative(x: 0, y: 0) - if isOverlay { - for dependentPlaceable in dependentPlaceables { - let (x, y) = placeView(width: dependentPlaceable.width, height: dependentPlaceable.height, inWidth: targetPlaceable.width, inHeight: targetPlaceable.height, alignment: alignment) - dependentPlaceable.placeRelative(x: x, y: y) + // Base layout entirely on the target view size + let targetPlaceable = measurables[0].measure(constraints) + let dependentConstraints = Constraints(maxWidth: targetPlaceable.width, maxHeight: targetPlaceable.height) + let dependentPlaceables = measurables.drop(1).map { $0.measure(dependentConstraints) } + layout(width: targetPlaceable.width, height: targetPlaceable.height) { + if !isOverlay { + for dependentPlaceable in dependentPlaceables { + let (x, y) = placeView(width: dependentPlaceable.width, height: dependentPlaceable.height, inWidth: targetPlaceable.width, inHeight: targetPlaceable.height, alignment: alignment) + dependentPlaceable.placeRelative(x: x, y: y) + } + } + targetPlaceable.placeRelative(x: 0, y: 0) + if isOverlay { + for dependentPlaceable in dependentPlaceables { + let (x, y) = placeView(width: dependentPlaceable.width, height: dependentPlaceable.height, inWidth: targetPlaceable.width, inHeight: targetPlaceable.height, alignment: alignment) + dependentPlaceable.placeRelative(x: x, y: y) + } } } }