JetBrains / compose-multiplatform

Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.
https://jetbrains.com/lp/compose-multiplatform
Apache License 2.0
15.95k stars 1.16k forks source link

Scrollbar Can't represent a size of 2147483647 in Constraints #2420

Open wuhuagang opened 1 year ago

wuhuagang commented 1 year ago

Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: Can't represent a size of 2147483647 in Constraints at androidx.compose.ui.unit.Constraints$Companion.bitsNeedForSize(Constraints.kt:408) at androidx.compose.ui.unit.Constraints$Companion.createConstraints-Zbe2FdA$ui_unit(Constraints.kt:371) at androidx.compose.ui.unit.Constraints$Companion.fixed-JhjzzOo(Constraints.kt:319) at androidx.compose.foundation.Scrollbar_desktopKt$horizontalMeasurePolicy$1.measure-3p2s80s(Scrollbar.desktop.kt:610) at androidx.compose.ui.node.InnerPlaceable.measure-BRTryo0(InnerPlaceable.kt:55) at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:131) at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:131) at androidx.compose.foundation.layout.PaddingModifier.measure-3p2s80s(Padding.kt:364) at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.kt:39) at androidx.compose.foundation.layout.FillModifier.measure-3p2s80s(Size.kt:658) at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.kt:39) at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.measure-BRTryo0(DelegatingLayoutNodeWrapper.kt:131) at androidx.compose.ui.node.OuterMeasurablePlaceable$remeasure$2.invoke(OuterMeasurablePlaceable.kt:99) at androidx.compose.ui.node.OuterMeasurablePlaceable$remeasure$2.invoke(OuterMeasurablePlaceable.kt:98) at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:1798) at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:121) at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui(OwnerSnapshotObserver.kt:88) at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui(OwnerSnapshotObserver.kt:76) at androidx.compose.ui.node.OuterMeasurablePlaceable.remeasure-BRTryo0(OuterMeasurablePlaceable.kt:98) at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui(LayoutNode.kt:1317) at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui$default(LayoutNode.kt:1313) at androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure(MeasureAndLayoutDelegate.kt:170) at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:228) at androidx.compose.ui.node.MeasureAndLayoutDelegate.access$remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:38) at androidx.compose.ui.node.MeasureAndLayoutDelegate.measureAndLayout(MeasureAndLayoutDelegate.kt:201) at androidx.compose.ui.platform.SkiaBasedOwner.measureAndLayout(SkiaBasedOwner.skiko.kt:245) at androidx.compose.ui.node.Owner$DefaultImpls.measureAndLayout$default(Owner.kt:182) at androidx.compose.ui.ComposeScene.render(ComposeScene.skiko.kt:381) at androidx.compose.ui.awt.ComposeLayer$1$onRender$1.invoke(ComposeLayer.desktop.kt:237) at androidx.compose.ui.awt.ComposeLayer$1$onRender$1.invoke(ComposeLayer.desktop.kt:236) at androidx.compose.ui.awt.ComposeLayer.catchExceptions(ComposeLayer.desktop.kt:89) at androidx.compose.ui.awt.ComposeLayer.access$catchExceptions(ComposeLayer.desktop.kt:70) at androidx.compose.ui.awt.ComposeLayer$1.onRender(ComposeLayer.desktop.kt:236) at org.jetbrains.skiko.SkiaLayer.update$skiko(SkiaLayer.awt.kt:441) at org.jetbrains.skiko.redrawer.Direct3DRedrawer.update(Direct3DRedrawer.kt:54) at org.jetbrains.skiko.redrawer.Direct3DRedrawer.access$update(Direct3DRedrawer.kt:10) at org.jetbrains.skiko.redrawer.Direct3DRedrawer$frameDispatcher$1.invokeSuspend(Direct3DRedrawer.kt:28) at org.jetbrains.skiko.redrawer.Direct3DRedrawer$frameDispatcher$1.invoke(Direct3DRedrawer.kt) at org.jetbrains.skiko.redrawer.Direct3DRedrawer$frameDispatcher$1.invoke(Direct3DRedrawer.kt) at org.jetbrains.skiko.FrameDispatcher$job$1.invokeSuspend(FrameDispatcher.kt:33) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at java.desktop/java.awt.event.InvocationEvent.dispatch$$$capture(InvocationEvent.java:318) at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java) at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:771) at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:722) at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:716) at java.base/java.security.AccessController.doPrivileged(AccessController.java:399) at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86) at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:741) at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203) at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124) at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113) at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109) at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90) Disconnected from the target VM, address: '127.0.0.1:62502', transport: 'socket'

Process finished with exit code 0

eymar commented 1 year ago

Could you please provide a small sample to reproduce the crash?

mgroth0 commented 11 months ago

I have an even smaller number: (794673 instead of 2147483647)

Exception in thread "AWT-EventQueue-0 @coroutine#13" java.lang.IllegalArgumentException: Can't represent a size of 794673 in Constraints
    at androidx.compose.ui.unit.Constraints$Companion.bitsNeedForSize(Constraints.kt:403)
    at androidx.compose.ui.unit.Constraints$Companion.createConstraints-Zbe2FdA$ui_unit(Constraints.kt:363)
    at androidx.compose.ui.unit.Constraints$Companion.fixed-JhjzzOo(Constraints.kt:314)
    at androidx.compose.foundation.text.modifiers.TextAnnotatedStringNode.measure-3p2s80s(TextAnnotatedStringNode.kt:287)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116)
    at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure-3p2s80s(GraphicsLayerModifier.kt:646)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116)
    at androidx.compose.foundation.layout.FillNode.measure-3p2s80s(Size.kt:698)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116)
    at androidx.compose.foundation.layout.PaddingNode.measure-3p2s80s(Padding.kt:397)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1499)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1495)
    at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2300)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:471)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:234)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui(OwnerSnapshotObserver.kt:133)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui(OwnerSnapshotObserver.kt:113)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1495)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:35)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:560)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0(LayoutNodeLayoutDelegate.kt:539)
    at androidx.compose.foundation.layout.BoxKt$boxMeasurePolicy$1.measure-3p2s80s(Box.kt:114)
    at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:126)
    at androidx.compose.foundation.ScrollingLayoutNode.measure-3p2s80s(Scroll.kt:389)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116)
    at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure-3p2s80s(GraphicsLayerModifier.kt:646)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1499)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1495)
    at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2300)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:471)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:234)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui(OwnerSnapshotObserver.kt:133)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui(OwnerSnapshotObserver.kt:113)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1495)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:35)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:560)
    at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui(LayoutNode.kt:1140)
    at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui$default(LayoutNode.kt:1131)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure-sdFAvZA(MeasureAndLayoutDelegate.kt:323)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:458)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.access$remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:39)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.measureAndLayout(MeasureAndLayoutDelegate.kt:344)
    at androidx.compose.ui.platform.SkiaBasedOwner.measureAndLayout(SkiaBasedOwner.skiko.kt:246)
    at androidx.compose.ui.node.Owner.measureAndLayout$default(Owner.kt:223)
    at androidx.compose.ui.ComposeScene.render(ComposeScene.skiko.kt:546)
    at androidx.compose.ui.awt.ComposeBridge$skikoView$1$onRender$1.invoke(ComposeBridge.desktop.kt:156)
    at androidx.compose.ui.awt.ComposeBridge$skikoView$1$onRender$1.invoke(ComposeBridge.desktop.kt:155)
    at androidx.compose.ui.awt.ComposeBridge.catchExceptions(ComposeBridge.desktop.kt:128)
    at androidx.compose.ui.awt.ComposeBridge.access$catchExceptions(ComposeBridge.desktop.kt:59)
    at androidx.compose.ui.awt.ComposeBridge$skikoView$1.onRender(ComposeBridge.desktop.kt:155)
    at org.jetbrains.skiko.SkiaLayer.update$skiko(SkiaLayer.awt.kt:548)
    at org.jetbrains.skiko.redrawer.AWTRedrawer.update(AWTRedrawer.kt:54)
    at org.jetbrains.skiko.redrawer.MetalRedrawer$frameDispatcher$1.invokeSuspend(MetalRedrawer.kt:83)
    at org.jetbrains.skiko.redrawer.MetalRedrawer$frameDispatcher$1.invoke(MetalRedrawer.kt)
    at org.jetbrains.skiko.redrawer.MetalRedrawer$frameDispatcher$1.invoke(MetalRedrawer.kt)
    at org.jetbrains.skiko.FrameDispatcher$job$1.invokeSuspend(FrameDispatcher.kt:33)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
    at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318)
    at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:773)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:720)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:714)
    at java.base/java.security.AccessController.executePrivileged(AccessController.java:778)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:87)
    at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:742)
    at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
    Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [CoroutineId(13), "coroutine#13":StandaloneCoroutine{Cancelling}@6b3dd778, SwingDispatcher@43153f9d]
sunny-chung commented 10 months ago

As #3799 is closed, I will continue the discussion here.

Setting a string with 300K length to a Text() component would reproduce the constraint issue. Although this limitation is what Compose for Android designed, because it might not (?) make sense to display such volume in an Android mobile device. But in desktop, I think it is a valid use case. It does not make sense that a desktop application cannot handle displaying 1MB data. It is expected Google would reject or put in a very low priority for this issue, but in the desktop world this is a larger issue.

I think an alternative is to provide a LazyText (or make Text lazy) with some care of scrolling API design, or expose the text layout API so that developers can split long strings into multiple Text components embedded in a LazyColumn/Row.

It seems there already exists workaround for other use cases, e.g. giant image - cropped by visible area with nested scrolling, long list - use LazyColumn.

sunny-chung commented 10 months ago

With compose desktop 1.5.2, I only encountered this issue with Text, not scrollbars.

I am able to implement a workaround for long Text composable.

var contentWidthInPx by remember { mutableStateOf<Int?>(null) }
val textStyle = LocalTextStyle.current.copy(
    fontSize = LocalFont.current.bodyFontSize,
    fontFamily = FontFamily.Monospace,
)
val density = LocalDensity.current
val fontFamilyResolver = LocalFontFamilyResolver.current
val numCharsInALine = if (contentWidthInPx != null) {
    remember(contentWidthInPx) {
        Paragraph(
            text = "0".repeat(1000),
            style = textStyle,
            constraints = Constraints(maxWidth = contentWidthInPx!!),
            density = density,
            fontFamilyResolver = fontFamilyResolver,
        ).getLineEnd(0)
    }
} else {
    0
}
val textsSplitted = if (contentWidthInPx != null && numCharsInALine > 0) {
    text.split('\n')
        .flatMap {
            it.chunked(numCharsInALine)
        }
        .withIndex()
        .groupBy { it.index / 100 }
        .map { it.value.joinToString("\n") { it.value } }
} else {
    listOf(" ")
}
LazyColumn(modifier = Modifier.onGloballyPositioned {
    contentWidthInPx = it.size.width
}) {
    textsSplitted.forEach {
        item {
            Text(
                text = it,
                fontFamily = FontFamily.Monospace
            )
        }
    }
}

Problems:

  1. If the long Text is within a lazy container, the composable structure needs to be rewritten to avoid nested scrolling. (Can be very painful!)
  2. Does not work well with SelectionContainer. New line characters are incorrect.
  3. If font family is not monospaced, the text measure step needs to be twisted.
okushnikov commented 2 months ago

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.