From 1bee2d175760d24b40dde55ab3825e8ecd9d066d Mon Sep 17 00:00:00 2001 From: Saad Nadeem Date: Sun, 18 Oct 2026 21:44:29 -0400 Subject: [PATCH] fix(ui): release held mouse buttons when a Compose screen closes or loses focus --- .../internal/ui/compose/ComposeScreen.kt | 41 +++++++++++-------- .../ui/hud/screens/HudDesignStudio.kt | 10 +++-- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/minecraft/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/compose/ComposeScreen.kt b/minecraft/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/compose/ComposeScreen.kt index d287792c2..acfe3a34e 100644 --- a/minecraft/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/compose/ComposeScreen.kt +++ b/minecraft/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/compose/ComposeScreen.kt @@ -606,6 +606,8 @@ abstract class ComposeScreen( showing = false sceneContextOrNull?.sync() ComposeSceneContextImpl.resetPointerIcon() + releaseHeldButtons() + consumedButtons.clear() releaseScene() super.removed() } @@ -663,6 +665,8 @@ abstract class ComposeScreen( val metricsChanged = syncSceneMetrics() val focused = client.isWindowActive + if (wasFocused && !focused) releaseHeldButtons() + wasFocused = focused if (focused) { val pointerPosition = pointerPosition() if (pointerPosition != lastPointer) { @@ -839,24 +843,29 @@ abstract class ComposeScreen( protected open fun handleMouseClicked(button: Int): Boolean = false + private var wasFocused = true + + /** + * Compose never receives a release that happens after this screen closes or the window loses focus, so buttons are + * treated as held forever blocking all other mouse input + */ + private fun releaseHeldButtons() { + // cancel first so these releases only clear the held mouse buttons instead of clicking + withScene { it.cancelPointerInput() } + sendMouseButtonEvent(PointerEventType.Release, InputConstants.MOUSE_BUTTON_LEFT) + sendMouseButtonEvent(PointerEventType.Release, InputConstants.MOUSE_BUTTON_RIGHT) + sendMouseButtonEvent(PointerEventType.Release, InputConstants.MOUSE_BUTTON_MIDDLE) + } + private fun sendMouseButtonEvent(type: PointerEventType, button: Int) { - withScene { - it.sendPointerEvent( - type, - //? if sdl { - button = when (button) { - InputConstants.MOUSE_BUTTON_LEFT -> PointerButton.Primary - InputConstants.MOUSE_BUTTON_RIGHT -> PointerButton.Secondary - InputConstants.MOUSE_BUTTON_MIDDLE -> PointerButton.Tertiary - else -> PointerButton(button - 1) - }, - //?} else { - /*// PointerButton indices match GLFW button numbers (Primary = 0, Secondary = 1, ...) - button = if (button >= 0) PointerButton(button) else null, - *///?} - position = pointerPosition() - ) + val pointerButton = when (button) { + InputConstants.MOUSE_BUTTON_LEFT -> PointerButton.Primary + InputConstants.MOUSE_BUTTON_RIGHT -> PointerButton.Secondary + InputConstants.MOUSE_BUTTON_MIDDLE -> PointerButton.Tertiary + // side buttons are handled before this, and Compose ignores other clicks while one is held + else -> return } + withScene { it.sendPointerEvent(type, button = pointerButton, position = pointerPosition()) } } private fun sendScrollEvent(scrollX: Double, scrollY: Double) { diff --git a/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/hud/screens/HudDesignStudio.kt b/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/hud/screens/HudDesignStudio.kt index 73ac096d8..52bdf442d 100644 --- a/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/hud/screens/HudDesignStudio.kt +++ b/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/hud/screens/HudDesignStudio.kt @@ -3268,8 +3268,9 @@ private fun LegacyHudPreviewCard( } } } - .safePointerEvent(PointerEventType.Release) { - if (!dragStarted && pressPos != null) { + .safePointerEvent(PointerEventType.Release) { event -> + // ignore the fake release Compose sends when a press is interrupted, such as by alt-tabbing + if (!dragStarted && pressPos != null && event.changes.none { it.isConsumed }) { onCardClick(hud) } pressPos = null @@ -3350,8 +3351,9 @@ private fun ComposeHudPreviewCard( } } } - .safePointerEvent(PointerEventType.Release) { - if (!dragStarted && pressPos != null) { + .safePointerEvent(PointerEventType.Release) { event -> + // ignore the fake release Compose sends when a press is interrupted, such as by alt-tabbing + if (!dragStarted && pressPos != null && event.changes.none { it.isConsumed }) { onCardClick(hud) } pressPos = null