@@ -287,6 +287,9 @@ fun ApplicationScope.TrayApp(
287287 // Track if window has been created at least once
288288 var windowCreated by remember { mutableStateOf(false ) }
289289
290+ // Initial position for the dialog state, calculated for initial visible
291+ var initialPosition by remember { mutableStateOf(WindowPosition (- 10000 .dp, - 10000 .dp)) }
292+
290293 // System information
291294 val isDark = isMenuBarInDarkMode()
292295 val os = getOperatingSystem()
@@ -393,7 +396,9 @@ fun ApplicationScope.TrayApp(
393396 if (isVisible) {
394397 // Show window immediately
395398 shouldShowWindow = true
396- windowCreated = true
399+ if (! windowCreated) {
400+ windowCreated = true
401+ }
397402 lastShownAt = System .currentTimeMillis()
398403 } else {
399404 // Hide window after fade animation completes
@@ -440,6 +445,15 @@ fun ApplicationScope.TrayApp(
440445 autoHideEnabledAt = System .currentTimeMillis() + 1000
441446 }
442447
448+ // Calculate initial position after delays
449+ val widthPx = windowSize.width.value.toInt()
450+ val heightPx = windowSize.height.value.toInt()
451+ initialPosition = getTrayWindowPositionForInstance(
452+ tray.instanceKey(),
453+ widthPx,
454+ heightPx
455+ ) as WindowPosition .Absolute
456+
443457 shouldShowWindow = true
444458 windowCreated = true
445459 lastShownAt = System .currentTimeMillis()
@@ -468,16 +482,11 @@ fun ApplicationScope.TrayApp(
468482 // Main popup window - always mounted once created to preserve states
469483 // Uses hybrid approach: moves off-screen instead of unmounting
470484 if (windowCreated) {
471- // Calculate position: off-screen when hidden, correct position when visible
472- val widthPx = windowSize.width.value.toInt()
473- val heightPx = windowSize.height.value.toInt()
474- val windowPosition = if (shouldShowWindow) {
475- // Visible: use correct position near tray icon
476- getTrayWindowPositionForInstance(tray.instanceKey(), widthPx, heightPx)
477- } else {
478- // Hidden: move far off-screen to avoid taskbar appearance
479- WindowPosition (- 10000 .dp, - 10000 .dp)
480- }
485+ // Use the pre-calculated initial position
486+ val dialogState = rememberDialogState(
487+ position = initialPosition,
488+ size = windowSize
489+ )
481490
482491 DialogWindow (
483492 onCloseRequest = { requestHide() },
@@ -488,7 +497,7 @@ fun ApplicationScope.TrayApp(
488497 alwaysOnTop = shouldShowWindow, // Only on top when visible
489498 transparent = true ,
490499 visible = true , // Always visible to the system
491- state = rememberDialogState(position = windowPosition, size = windowSize)
500+ state = dialogState
492501 ) {
493502 DisposableEffect (Unit ) {
494503 // Set window name for monitoring
@@ -542,9 +551,26 @@ fun ApplicationScope.TrayApp(
542551 }
543552 }
544553
545- // React to visibility changes
546- LaunchedEffect (shouldShowWindow) {
554+ // React to visibility changes and reposition window
555+ // IMPORTANT: Position is recalculated each time window shows because:
556+ // - The tray icon might have moved (user moved taskbar, changed resolution, etc.)
557+ // - Window size might have changed
558+ LaunchedEffect (shouldShowWindow, windowSize) {
547559 if (shouldShowWindow) {
560+ // Recalculate position when showing (tray icon might have moved)
561+ // or when window size changes
562+ val widthPx = windowSize.width.value.toInt()
563+ val heightPx = windowSize.height.value.toInt()
564+ val newPosition = getTrayWindowPositionForInstance(
565+ tray.instanceKey(),
566+ widthPx,
567+ heightPx
568+ )
569+ dialogState.position = newPosition
570+
571+ // Update size if changed
572+ dialogState.size = windowSize
573+
548574 // Window is becoming visible
549575 runCatching { WindowVisibilityMonitor .recompute() }
550576 invokeLater {
@@ -557,6 +583,9 @@ fun ApplicationScope.TrayApp(
557583 }
558584 }
559585 } else {
586+ // Move window off-screen when hiding
587+ dialogState.position = WindowPosition (- 10000 .dp, - 10000 .dp)
588+
560589 // Window is becoming hidden
561590 runCatching { WindowVisibilityMonitor .recompute() }
562591 }
0 commit comments