From 5859a082d2b055d8983287b53e96023838a4fd88 Mon Sep 17 00:00:00 2001 From: avi Date: Tue, 15 Sep 2026 06:52:30 -0500 Subject: [PATCH] Desktop: make Ctrl+-/= zoom actually work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two compounding bugs: 1. Nodes skipped in a plain recomposition kept their old captured density, so changing the zoom state left the tree unscaled — wrap the CompositionLocalProvider in key(zoom) to force full-tree recompose. 2. Compose focus routing missed the shortcut depending on which child held focus — add an AWT KeyboardFocusManager dispatcher below Compose (also accepts Ctrl+Up/Down, numpad +/-, Ctrl+0 reset). User-confirmed zooming works on the live app. --- .../main/kotlin/com/shonar/desktop/Main.kt | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/shonar/desktop/Main.kt b/app/src/main/kotlin/com/shonar/desktop/Main.kt index cccc3c6..8ed0154 100644 --- a/app/src/main/kotlin/com/shonar/desktop/Main.kt +++ b/app/src/main/kotlin/com/shonar/desktop/Main.kt @@ -5,7 +5,9 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Surface import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.key import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -35,6 +37,41 @@ fun main() = application { val screen by state.screen.collectAsState() // Ctrl+= / Ctrl+- zoom the whole app; Ctrl+0 resets (after a zoom). var zoom by remember { mutableStateOf(1f) } + // Belt-and-braces: Compose's focus routing can miss shortcuts depending + // on which child holds focus, so also hook the AWT key pipeline directly. + // Debug print lets us confirm from the dev log whether keys reach the JVM. + DisposableEffect(Unit) { + val mgr = java.awt.KeyboardFocusManager.getCurrentKeyboardFocusManager() + val disp = java.awt.KeyEventDispatcher { e -> + if (e.id == java.awt.event.KeyEvent.KEY_PRESSED && + e.modifiers == java.awt.event.KeyEvent.CTRL_MASK + ) { + when (e.keyCode) { + java.awt.event.KeyEvent.VK_EQUALS, + java.awt.event.KeyEvent.VK_PLUS, + java.awt.event.KeyEvent.VK_ADD, + java.awt.event.KeyEvent.VK_UP -> { + zoom = (zoom * 1.1f).coerceAtMost(3f) + System.err.println("[zoom] ${zoom}"); true + } + java.awt.event.KeyEvent.VK_MINUS, + java.awt.event.KeyEvent.VK_SUBTRACT, + java.awt.event.KeyEvent.VK_DOWN -> { + zoom = (zoom / 1.1f).coerceAtLeast(0.4f) + System.err.println("[zoom] ${zoom}"); true + } + java.awt.event.KeyEvent.VK_0, + java.awt.event.KeyEvent.VK_NUMPAD0 -> { + if (zoom != 1f) { zoom = 1f; System.err.println("[zoom] reset"); true } + else false + } + else -> false + } + } else false + } + mgr.addKeyEventDispatcher(disp) + onDispose { mgr.removeKeyEventDispatcher(disp) } + } Window( onCloseRequest = ::exitApplication, title = "SHONAR Desktop" + run { @@ -56,10 +93,10 @@ fun main() = application { Modifier.fillMaxSize().onPreviewKeyEvent { e -> if (e.type != KeyEventType.KeyDown || !e.isCtrlPressed) return@onPreviewKeyEvent false when (e.key) { - Key.Equals, Key.Plus, Key.NumPadAdd -> { + Key.Equals, Key.Plus, Key.NumPadAdd, Key.DirectionUp -> { zoom = (zoom * 1.1f).coerceAtMost(3f); true } - Key.Minus, Key.NumPadSubtract -> { + Key.Minus, Key.NumPadSubtract, Key.DirectionDown -> { zoom = (zoom / 1.1f).coerceAtLeast(0.4f); true } Key.Zero, Key.NumPad0 -> { @@ -69,6 +106,10 @@ fun main() = application { } }, ) { + // key(zoom) forces the whole tree to recompose when zoom + // changes: nodes skipped in a plain recomposition keep their + // old captured density, which made Ctrl+-/= a no-op. + key(zoom) { CompositionLocalProvider( LocalDensity provides ScaledDensity(baseDensity, zoom), ) { @@ -81,6 +122,7 @@ fun main() = application { } } } + } } } }