desktop: shorten comments in hover cursor fix

This commit is contained in:
Narasimha-sc
2026-07-15 11:23:52 +00:00
parent 11eb0ca880
commit 92f39fb1d8
4 changed files with 10 additions and 17 deletions
@@ -29,12 +29,9 @@ expect fun Modifier.desktopPointerHoverIconHand(): Modifier
/**
* Directly sets the mouse cursor on desktop ([PointerIcon.Hand], [PointerIcon.Text], anything else
* means default). Compose's pointerHoverIcon displays icons only on Enter/Exit edges or on icon
* change while marked in-bounds — edges that are silently lost when chat items shift/recompose
* under the cursor, leaving a stale cursor. Calling this on every hover move (and resetting on
* exit) keeps it self-correcting. No-op on Android.
* Verified against Compose 1.8.2, re-verify on upgrade (JetBrains/compose-multiplatform #2091,
* #1314, #3750). Details: plans/2026-07-13-fix-command-hover-cursor.md
* means default); no-op on Android. Compose's pointerHoverIcon is edge-triggered and silently loses
* updates when chat items shift/recompose under the cursor; calling this on every hover move (and
* resetting on exit) keeps the cursor correct. Details: plans/2026-07-13-fix-command-hover-cursor.md
*/
expect fun desktopSetHoverCursor(icon: PointerIcon)
@@ -359,13 +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 when items shift/recompose under the cursor, see desktopSetHoverCursor
// pointerHoverIcon alone loses updates, see desktopSetHoverCursor
desktopSetHoverCursor(newIcon)
},
onHoverExit = {
// also reset icon.value, or the pointerHoverIcon node re-displays a stale Hand on its next Enter;
// values differ on purpose: Text is this node's neutral icon, Default is what the cursor
// should be outside the text
// 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
icon.value = PointerIcon.Text
desktopSetHoverCursor(PointerIcon.Default)
},
@@ -93,14 +93,13 @@ suspend fun PointerInputScope.detectGesture(
}
suspend fun PointerInputScope.detectCursorMove(onExit: () -> Unit = {}, onMove: (Offset) -> Unit = {},) {
// Single scope: re-entering awaitPointerEventScope per event (as forEachGesture did) drops events.
// Enter/Release matter too: a stationary cursor gets no Move when content shifts or after a click.
// Single scope: per-event scope re-entry (old forEachGesture) drops events; Enter/Release cover stationary cursors.
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 keep receiving events outside their bounds, both while held and at release
// pressed nodes receive events outside their bounds (during drag and at release)
if (event.changes.none { it.pressed } && pos.x >= 0 && pos.y >= 0 && pos.x < size.width && pos.y < size.height) {
onMove(pos)
}
@@ -88,8 +88,7 @@ actual fun Modifier.onRightClick(action: () -> Unit): Modifier = contextMenuOpen
actual fun Modifier.desktopPointerHoverIconHand(): Modifier = Modifier.pointerHoverIcon(PointerIcon.Hand)
// Runs on every mouse move, so the canvas lookup (and its failure) is cached per window;
// weak refs avoid retaining a recreated window's scene graph.
// hot path: canvas lookup (and its failure) cached per window, weakly to not retain a recreated window
private var cachedSkiaCanvas: WeakReference<Component>? = null
private var skiaCanvasMissingIn: WeakReference<Window>? = null
@@ -115,8 +114,7 @@ actual fun desktopSetHoverCursor(icon: PointerIcon) {
if (canvas.cursor.type != type) canvas.cursor = Cursor.getPredefinedCursor(type)
}
// Compose writes pointer icons to its skia canvas component (ComposeSceneMediator.setPointerIcon);
// writing to the same component keeps last-write-wins consistent with the framework's own updates.
// the same component Compose writes pointer icons to (ComposeSceneMediator.setPointerIcon)
private fun findSkiaCanvas(c: Component): Component? = when {
c.javaClass.name.contains("Skia") -> c
c is Container -> c.components.firstNotNullOfOrNull { findSkiaCanvas(it) }