diff --git a/.gitignore b/.gitignore index 45e53e61..f28bb23b 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,4 @@ google-services.json freeline.py freeline/ freeline_project_description.json +.hotswan/ diff --git a/balloon-compose/src/main/kotlin/com/skydoves/balloon/compose/Balloon.kt b/balloon-compose/src/main/kotlin/com/skydoves/balloon/compose/Balloon.kt index b6026595..a06bbd74 100644 --- a/balloon-compose/src/main/kotlin/com/skydoves/balloon/compose/Balloon.kt +++ b/balloon-compose/src/main/kotlin/com/skydoves/balloon/compose/Balloon.kt @@ -153,21 +153,23 @@ public fun Balloon( // Calculate width constraints for balloon content (issue #779) val horizontalPadding = builder.paddingLeft + builder.paddingRight + builder.marginLeft + builder.marginRight + // The balloon renders in a PopupWindow (floating overlay), not in the parent layout. + // Use screen dimensions instead of parent constraints for measurement. val maxContentWidth = when { builder.widthRatio > 0f -> (screenWidth * builder.widthRatio - horizontalPadding).toInt() builder.maxWidthRatio > 0f -> (screenWidth * builder.maxWidthRatio - horizontalPadding).toInt() - else -> constraints.maxWidth - horizontalPadding + else -> (screenWidth - horizontalPadding) }.coerceAtLeast(0) // Measure balloon content if needed (only when content exists and size not yet calculated) if (isComposableContent && balloonComposeView.balloonLayoutInfo.value == null) { val balloonContentConstraints = Constraints( minWidth = 0, - maxWidth = maxContentWidth.coerceAtMost(constraints.maxWidth), + maxWidth = maxContentWidth.coerceAtMost(screenWidth), minHeight = 0, - maxHeight = constraints.maxHeight, + maxHeight = (screenWidth * 2).coerceAtLeast(0), ) val balloonPlaceables = subcompose("balloon_measurement") { // balloonContent is guaranteed non-null here since isComposableContent is true diff --git a/balloon-compose/src/main/kotlin/com/skydoves/balloon/compose/BalloonModifier.kt b/balloon-compose/src/main/kotlin/com/skydoves/balloon/compose/BalloonModifier.kt index bdab4ed6..8af2a196 100644 --- a/balloon-compose/src/main/kotlin/com/skydoves/balloon/compose/BalloonModifier.kt +++ b/balloon-compose/src/main/kotlin/com/skydoves/balloon/compose/BalloonModifier.kt @@ -229,24 +229,17 @@ public fun Modifier.balloon( Layout( content = { balloonContent() }, measurePolicy = { measurables, constraints -> - val isUnboundedWidth = constraints.maxWidth == Constraints.Infinity - val isUnboundedHeight = constraints.maxHeight == Constraints.Infinity || - constraints.maxHeight == 0 - - val effectiveMaxWidth = if (fixedWidthMode) { - // Fixed mode: always use screenWidth so ratio/explicit widths aren't clamped by anchor. - screenWidth - } else if (isUnboundedWidth) { - // Unbounded (e.g., KMM, ScrollView): fall back to screenWidth. - screenWidth - } else { - // Bounded non-fixed mode: respect the parent constraints (e.g., XML+ComposeView). - constraints.maxWidth.coerceAtMost(screenWidth) - } + // The balloon renders in a PopupWindow (floating overlay), not in the parent layout. + // Therefore, parent constraints must NOT limit the balloon content measurement. + // The Layout here is purely for measurement (layout(0, 0) {}), so we always use + // screen dimensions as the upper bound. This fixes: + // - #943: width incorrectly clamped to parent constraints in XML+ComposeView/KMM + // - #952: height incorrectly clamped to parent's fixed height + // - #963: negative constraints.maxHeight from ConstraintLayout on Chromebooks + val effectiveMaxWidth = screenWidth val effectiveMaxHeight = when { builder.height != BalloonSizeSpec.WRAP -> builder.height - isUnboundedHeight -> screenWidth * 2 - else -> constraints.maxHeight + else -> screenWidth * 2 }.coerceAtLeast(0) val maxContentWidth = when { @@ -264,10 +257,8 @@ public fun Modifier.balloon( }.coerceAtLeast(0) val targetWidth = if (fixedWidthMode) { - // IMPORTANT: do NOT clamp to constraints.maxWidth (anchor width) maxContentWidth } else { - // Non-fixed mode: respect parent constraints. maxContentWidth.coerceAtMost((effectiveMaxWidth - horizontalPadding).coerceAtLeast(0)) }.coerceAtLeast(0)