diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt index 556fa04832..90c96b7ffc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt @@ -359,12 +359,12 @@ fun MarkdownText ( val hand = hasAnnotation("WEB_URL") || hasAnnotation("SIMPLEX_URL") || hasAnnotation("OTHER_URL") || hasAnnotation("SIMPLEX_NAME") || hasAnnotation("SECRET") || hasAnnotation("COMMAND") val newIcon = if (hand) PointerIcon.Hand else PointerIcon.Text icon.value = newIcon - // pointerHoverIcon alone loses updates, see desktopSetHoverCursor + // pointerHoverIcon alone is unreliable, see desktopSetHoverCursor desktopSetHoverCursor(newIcon) }, onHoverExit = { - // reset icon.value too, or its node re-displays a stale Hand on next Enter; - // Text = this node's neutral icon, Default = cursor outside the text + // reset icon.value too, or pointerHoverIcon re-displays a stale Hand on the next Enter; + // Text is the neutral icon inside text, Default is the cursor after leaving it icon.value = PointerIcon.Text desktopSetHoverCursor(PointerIcon.Default) }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/GestureDetector.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/GestureDetector.kt index 21c450ce27..a73f1b383e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/GestureDetector.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/GestureDetector.kt @@ -93,13 +93,14 @@ suspend fun PointerInputScope.detectGesture( } suspend fun PointerInputScope.detectCursorMove(onExit: () -> Unit = {}, onMove: (Offset) -> Unit = {},) { - // Single scope: per-event scope re-entry (old forEachGesture) drops events; Enter/Release cover stationary cursors. + // One scope for all events: re-entering awaitPointerEventScope per event loses events. + // Enter/Release update hover when the pointer is stationary (content moved under it, or a click completed). awaitPointerEventScope { while (true) { val event = awaitPointerEvent() if (event.type == PointerEventType.Move || event.type == PointerEventType.Enter || event.type == PointerEventType.Release) { val pos = event.changes[0].position - // pressed nodes receive events outside their bounds (during drag and at release) + // ignore events while a button is down or outside bounds: a pressed node receives them even after the pointer leaves it if (event.changes.none { it.pressed } && pos.x >= 0 && pos.y >= 0 && pos.x < size.width && pos.y < size.height) { onMove(pos) } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt index 0049fe0a41..639b829f71 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt @@ -88,7 +88,7 @@ actual fun Modifier.onRightClick(action: () -> Unit): Modifier = contextMenuOpen actual fun Modifier.desktopPointerHoverIconHand(): Modifier = Modifier.pointerHoverIcon(PointerIcon.Hand) -// hot path: canvas lookup (and its failure) cached per window, weakly to not retain a recreated window +// called on every hover move: cache the canvas (and lookup failure) per window; weak refs let a closed window be GCed private var cachedSkiaCanvas: WeakReference? = null private var skiaCanvasMissingIn: WeakReference? = null