From 9c9c4e1bcac7ffc812c7a17019c417eb35763ce6 Mon Sep 17 00:00:00 2001 From: Richard Zadorozny Date: Wed, 21 Jan 2026 15:56:54 -0500 Subject: [PATCH] Add multi-window offset support --- .../kotlin/com/openai/snapo/desktop/Main.kt | 27 ++++++++++++++----- .../openai/snapo/desktop/WindowPreferences.kt | 11 ++++++-- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/snapo-desktop-compose/src/main/kotlin/com/openai/snapo/desktop/Main.kt b/snapo-desktop-compose/src/main/kotlin/com/openai/snapo/desktop/Main.kt index 042a10d..d27f86b 100644 --- a/snapo-desktop-compose/src/main/kotlin/com/openai/snapo/desktop/Main.kt +++ b/snapo-desktop-compose/src/main/kotlin/com/openai/snapo/desktop/Main.kt @@ -10,7 +10,12 @@ import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.isSpecified import androidx.compose.ui.window.Window +import androidx.compose.ui.window.WindowPosition +import androidx.compose.ui.window.WindowState import androidx.compose.ui.window.application import com.openai.snapo.desktop.di.AppGraph import com.openai.snapo.desktop.ui.SnapOContextMenuProviders @@ -50,8 +55,12 @@ fun main() { } } - fun openNewWindow() { - windows.add(createInspectorWindow()) + fun openNewWindow(parentState: WindowState?) { + val initialPosition = parentState + ?.position + ?.takeIf { it.isSpecified && it.x.isSpecified && it.y.isSpecified } + ?.let { WindowPosition(it.x + NewWindowOffset, it.y + NewWindowOffset) } + windows.add(createInspectorWindow(initialPosition = initialPosition)) } fun closeWindow(window: InspectorWindow) { @@ -64,7 +73,7 @@ fun main() { windows.forEach { window -> key(window.id) { - val windowState = rememberPersistedWindowState() + val windowState = rememberPersistedWindowState(initialPosition = window.initialPosition) Window( onCloseRequest = { closeWindow(window) }, title = "Snap-O Network Inspector", @@ -72,7 +81,7 @@ fun main() { ) { SnapOMenuBar( controller = updateController, - onNewWindow = ::openNewWindow, + onNewWindow = { openNewWindow(windowState) }, onCheckForUpdates = { scope.launch { updateController.checkForUpdates(UpdateCheckSource.Manual) @@ -118,8 +127,14 @@ private fun openSnapOUpdate() { private data class InspectorWindow( val id: String = UUID.randomUUID().toString(), val graph: AppGraph, + val initialPosition: WindowPosition? = null, ) -private fun createInspectorWindow(): InspectorWindow { - return InspectorWindow(graph = createGraph()) +private fun createInspectorWindow(initialPosition: WindowPosition? = null): InspectorWindow { + return InspectorWindow( + graph = createGraph(), + initialPosition = initialPosition, + ) } + +private val NewWindowOffset: Dp = 100.dp diff --git a/snapo-desktop-compose/src/main/kotlin/com/openai/snapo/desktop/WindowPreferences.kt b/snapo-desktop-compose/src/main/kotlin/com/openai/snapo/desktop/WindowPreferences.kt index 09d5a84..4e5eff5 100644 --- a/snapo-desktop-compose/src/main/kotlin/com/openai/snapo/desktop/WindowPreferences.kt +++ b/snapo-desktop-compose/src/main/kotlin/com/openai/snapo/desktop/WindowPreferences.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.window.WindowPlacement import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.WindowState import androidx.compose.ui.window.rememberWindowState +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import java.util.prefs.Preferences @@ -30,18 +31,24 @@ private data class WindowPreferences( ) @Composable -internal fun rememberPersistedWindowState(): WindowState { +internal fun rememberPersistedWindowState(initialPosition: WindowPosition? = null): WindowState { val prefs = remember { Preferences.userRoot().node(WindowPrefNode) } val storedPreferences = remember { loadWindowPreferences(prefs) } + val startingPosition = if (initialPosition != null && initialPosition.isSpecified) { + initialPosition + } else { + storedPreferences.position + } val windowState = rememberWindowState( placement = storedPreferences.placement, - position = storedPreferences.position, + position = startingPosition, size = storedPreferences.size, ) PersistWindowStateEffect(windowState = windowState, prefs = prefs) return windowState } +@OptIn(FlowPreview::class) @Composable private fun PersistWindowStateEffect(windowState: WindowState, prefs: Preferences) { LaunchedEffect(windowState, prefs) {