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
16.11k stars 1.17k forks source link

TooltipArea in SelectionContainer crash #2055

Closed gnawf closed 3 weeks ago

gnawf commented 2 years ago

On a Desktop application on MacOS using

plugins {
    kotlin("jvm") version "1.6.10"
    id("org.jetbrains.compose") version "1.1.0"
}

Steps to reproduce

  1. Run code
  2. Hover over text
  3. Wait for tooltip
  4. Click in the tooltip bounds
  5. App crashes because it tries to select the text in the tooltip

Code to reproduce

import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.TooltipArea
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application

@Composable
@Preview
fun App() {
    MaterialTheme {
        Column(
            modifier = Modifier
                .padding(32.dp)
        ) {
            SelectionContainer {
                @OptIn(ExperimentalFoundationApi::class)
                TooltipArea(
                    tooltip = {
                        Card(
                            // Flip colors for high contrast on other elements
                            backgroundColor = MaterialTheme.colors.onSurface,
                            contentColor = MaterialTheme.colors.surface,
                        ) {
                            Row(
                                verticalAlignment = Alignment.CenterVertically,
                                modifier = Modifier
                                    .padding(8.dp)
                            ) {
                                Text("Click on ")
                                Icon(Icons.Default.Info, contentDescription = "Info Icon")
                                Text(" to inspect")
                            }
                        }
                    },
                    content = {
                        Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
                    },
                )
            }
        }
    }
}

fun main() = application {
    Window(onCloseRequest = ::exitApplication) {
        App()
    }
}

Stacktrace

Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: layouts are not part of the same hierarchy
    at androidx.compose.ui.node.LayoutNodeWrapper.findCommonAncestor$ui(LayoutNodeWrapper.kt:879)
    at androidx.compose.ui.node.LayoutNodeWrapper.localPositionOf-R5De75A(LayoutNodeWrapper.kt:437)
    at androidx.compose.foundation.text.selection.SelectionManager.convertToContainerCoordinates-Q7Q5hAU(SelectionManager.kt:592)
    at androidx.compose.foundation.text.selection.SelectionManager.access$convertToContainerCoordinates-Q7Q5hAU(SelectionManager.kt:58)
    at androidx.compose.foundation.text.selection.SelectionManager$2.invoke-d-4ec7I(SelectionManager.kt:196)
    at androidx.compose.foundation.text.selection.SelectionManager$2.invoke(SelectionManager.kt:194)
    at androidx.compose.foundation.text.selection.SelectionRegistrarImpl.notifySelectionUpdateStart-d-4ec7I(SelectionRegistrarImpl.kt:177)
    at androidx.compose.foundation.text.TextController$update$mouseSelectionObserver$1.onStart-3MmeM6k(CoreText.kt:239)
    at androidx.compose.foundation.text.selection.TextSelectionMouseDetectorKt$mouseSelectionDetector$2$1.invokeSuspend(TextSelectionMouseDetector.kt:112)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:178)
    at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
    at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
    at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
    at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
    at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:328)
    at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter$PointerEventHandlerCoroutine.offerPointerEvent(SuspendingPointerInputFilter.kt:529)
    at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter.dispatchPointerEvent(SuspendingPointerInputFilter.kt:422)
    at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter.onPointerEvent-H0pRuoY(SuspendingPointerInputFilter.kt:435)
    at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:285)
    at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:272)
    at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:272)
    at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:272)
    at androidx.compose.ui.input.pointer.NodeParent.dispatchMainEventPass(HitPathTracker.kt:152)
    at androidx.compose.ui.input.pointer.HitPathTracker.dispatchChanges(HitPathTracker.kt:89)
    at androidx.compose.ui.input.pointer.PointerInputEventProcessor.process-BIzXfog(PointerInputEventProcessor.kt:80)
    at androidx.compose.ui.platform.SkiaBasedOwner.processPointerInput-gBdvCQM$ui(SkiaBasedOwner.skiko.kt:321)
    at androidx.compose.ui.platform.SkiaBasedOwner.processPointerInput-gBdvCQM$ui$default(SkiaBasedOwner.skiko.kt:320)
    at androidx.compose.ui.ComposeScene.onMousePressed(ComposeScene.skiko.kt:481)
    at androidx.compose.ui.ComposeScene.processPointerInput(ComposeScene.skiko.kt:463)
    at androidx.compose.ui.ComposeScene.sendPointerEvent-Kr8mkKM(ComposeScene.skiko.kt:451)
    at androidx.compose.ui.ComposeScene.sendPointerEvent-Kr8mkKM$default(ComposeScene.skiko.kt:421)
    at androidx.compose.ui.awt.ComposeLayer_desktopKt.onMouseEvent(ComposeLayer.desktop.kt:357)
    at androidx.compose.ui.awt.ComposeLayer_desktopKt.access$onMouseEvent(ComposeLayer.desktop.kt:1)
    at androidx.compose.ui.awt.ComposeLayer$onMouseEvent$1.invoke(ComposeLayer.desktop.kt:285)
    at androidx.compose.ui.awt.ComposeLayer$onMouseEvent$1.invoke(ComposeLayer.desktop.kt:282)
    at androidx.compose.ui.awt.ComposeLayer.catchExceptions(ComposeLayer.desktop.kt:89)
    at androidx.compose.ui.awt.ComposeLayer.onMouseEvent(ComposeLayer.desktop.kt:282)
    at androidx.compose.ui.awt.ComposeLayer.access$onMouseEvent(ComposeLayer.desktop.kt:70)
    at androidx.compose.ui.awt.ComposeLayer$3.mousePressed(ComposeLayer.desktop.kt:262)
    at java.desktop/java.awt.Component.processMouseEvent(Component.java:6632)
    at java.desktop/java.awt.Component.processEvent(Component.java:6400)
    at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:5011)
    at java.desktop/java.awt.Component.dispatchEvent(Component.java:4843)
    at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:772)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:95)
    at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:745)
    at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:743)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
    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)
gnawf commented 2 years ago

For those looking for a workaround, you'll have to wrap your tooltip content or offending content with DisableSelection.

gnawf commented 2 years ago

Ok ran into this issue again, this time with context menus.

If you have a context menu inside a SelectionContainer then you can't click on the menu items without it crashing…

import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.ContextMenuArea
import androidx.compose.foundation.ContextMenuItem
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application

@OptIn(ExperimentalFoundationApi::class)
@Composable
@Preview
fun App() {
    MaterialTheme {
        Column(
            modifier = Modifier
                .padding(32.dp)
        ) {
            SelectionContainer {
                ContextMenuArea(
                    items = {
                        listOf(
                            ContextMenuItem(
                                "Click me",
                                onClick = {
                                    println("Hello World")
                                },
                            )
                        )
                    },
                ) {
                    Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
                }
            }
        }
    }
}

fun main() = application {
    Window(onCloseRequest = ::exitApplication) {
        App()
    }
}
okushnikov commented 3 months ago

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