mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-31 03:00:02 +00:00
desktop: set hover cursor directly on every hover move; compose pointerHoverIcon is edge-triggered and loses updates when chat items shift under the cursor
This commit is contained in:
+2
@@ -17,4 +17,6 @@ actual fun Modifier.onRightClick(action: () -> Unit): Modifier = this
|
||||
|
||||
actual fun Modifier.desktopPointerHoverIconHand(): Modifier = this
|
||||
|
||||
actual fun desktopSetHoverCursor(hand: Boolean) {}
|
||||
|
||||
actual fun Modifier.desktopOnHovered(action: (Boolean) -> Unit): Modifier = Modifier
|
||||
|
||||
+9
@@ -26,6 +26,15 @@ expect fun Modifier.onRightClick(action: () -> Unit): Modifier
|
||||
|
||||
expect fun Modifier.desktopPointerHoverIconHand(): Modifier
|
||||
|
||||
/**
|
||||
* Directly sets the mouse cursor (hand or text) on desktop, bypassing Compose's pointerHoverIcon.
|
||||
* pointerHoverIcon is edge-triggered (it displays the icon only on a per-node Enter event, or on an
|
||||
* icon change while the node is marked in-bounds) and those edges can be silently lost when chat
|
||||
* items shift/recompose under the cursor, leaving a stale cursor. Calling this on every hover move
|
||||
* keeps the cursor self-correcting. No-op on Android.
|
||||
*/
|
||||
expect fun desktopSetHoverCursor(hand: Boolean)
|
||||
|
||||
expect fun Modifier.desktopOnHovered(action: (Boolean) -> Unit): Modifier
|
||||
|
||||
@Composable
|
||||
|
||||
+4
-6
@@ -356,12 +356,10 @@ fun MarkdownText (
|
||||
},
|
||||
onHover = { offset ->
|
||||
val hasAnnotation: (String) -> Boolean = { tag -> annotatedText.hasStringAnnotations(tag, start = offset, end = offset) }
|
||||
icon.value =
|
||||
if (hasAnnotation("WEB_URL") || hasAnnotation("SIMPLEX_URL") || hasAnnotation("OTHER_URL") || hasAnnotation("SIMPLEX_NAME") || hasAnnotation("SECRET") || hasAnnotation("COMMAND")) {
|
||||
PointerIcon.Hand
|
||||
} else {
|
||||
PointerIcon.Text
|
||||
}
|
||||
val hand = hasAnnotation("WEB_URL") || hasAnnotation("SIMPLEX_URL") || hasAnnotation("OTHER_URL") || hasAnnotation("SIMPLEX_NAME") || hasAnnotation("SECRET") || hasAnnotation("COMMAND")
|
||||
icon.value = if (hand) PointerIcon.Hand else PointerIcon.Text
|
||||
// pointerHoverIcon alone loses updates when items shift/recompose under the cursor, see desktopSetHoverCursor
|
||||
desktopSetHoverCursor(hand)
|
||||
},
|
||||
shouldConsumeEvent = { offset ->
|
||||
annotatedText.hasStringAnnotations(tag = "WEB_URL", start = offset, end = offset)
|
||||
|
||||
+29
@@ -8,7 +8,12 @@ import androidx.compose.ui.*
|
||||
import androidx.compose.ui.draganddrop.*
|
||||
import androidx.compose.ui.draganddrop.DragData
|
||||
import androidx.compose.ui.input.pointer.*
|
||||
import chat.simplex.common.simplexWindowState
|
||||
import java.awt.Component
|
||||
import java.awt.Container
|
||||
import java.awt.Cursor
|
||||
import java.awt.Image
|
||||
import javax.swing.SwingUtilities
|
||||
import java.awt.datatransfer.DataFlavor
|
||||
import java.awt.datatransfer.Transferable
|
||||
import java.awt.image.BufferedImage
|
||||
@@ -81,6 +86,30 @@ actual fun Modifier.onRightClick(action: () -> Unit): Modifier = contextMenuOpen
|
||||
|
||||
actual fun Modifier.desktopPointerHoverIconHand(): Modifier = Modifier.pointerHoverIcon(PointerIcon.Hand)
|
||||
|
||||
// This runs on every mouse move over clickable text, so the canvas lookup is cached:
|
||||
// steady-state cost is a parent-chain check plus an int comparison, no allocations.
|
||||
private var cachedSkiaCanvas: Component? = null
|
||||
|
||||
actual fun desktopSetHoverCursor(hand: Boolean) {
|
||||
val window = simplexWindowState.window ?: return
|
||||
val type = if (hand) Cursor.HAND_CURSOR else Cursor.TEXT_CURSOR
|
||||
var canvas = cachedSkiaCanvas
|
||||
if (canvas == null || !canvas.isDisplayable || SwingUtilities.getWindowAncestor(canvas) !== window) {
|
||||
canvas = findSkiaCanvas(window) ?: return
|
||||
cachedSkiaCanvas = canvas
|
||||
}
|
||||
if (canvas.cursor.type != type) canvas.cursor = Cursor.getPredefinedCursor(type)
|
||||
}
|
||||
|
||||
// Compose writes the pointer icon to its skia canvas component (ComposeSceneMediator.setPointerIcon
|
||||
// -> contentComponent.cursor, a SkiaLayer/SkiaSwingLayer). Writing to the same component keeps
|
||||
// last-write-wins consistent with the framework's own cursor updates (e.g. reset to default on exit).
|
||||
private fun findSkiaCanvas(c: Component): Component? = when {
|
||||
c.javaClass.name.contains("Skia") -> c
|
||||
c is Container -> c.components.firstNotNullOfOrNull { findSkiaCanvas(it) }
|
||||
else -> null
|
||||
}
|
||||
|
||||
actual fun Modifier.desktopOnHovered(action: (Boolean) -> Unit): Modifier =
|
||||
this then Modifier
|
||||
.onPointerEvent(PointerEventType.Enter) { action(true) }
|
||||
|
||||
Reference in New Issue
Block a user