compose-multiplatform icon indicating copy to clipboard operation
compose-multiplatform copied to clipboard

TooltipArea in SelectionContainer crash

Open gnawf opened this issue 3 years ago • 2 comments

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 avatar May 11 '22 10:05 gnawf

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

gnawf avatar May 11 '22 23:05 gnawf

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()
    }
}

gnawf avatar Sep 07 '22 01:09 gnawf

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

okushnikov avatar Jul 14 '24 17:07 okushnikov