@@ -39,7 +39,6 @@ import java.awt.EventQueue.invokeLater
3939import java.awt.event.WindowEvent
4040import java.awt.event.WindowFocusListener
4141
42-
4342/* *
4443 * TrayApp: High-level API that creates a system tray icon and an undecorated popup window
4544 * that toggles visibility on tray icon click and auto-hides when it loses focus.
@@ -90,7 +89,6 @@ fun ApplicationScope.TrayApp(
9089 )
9190}
9291
93-
9492/* *
9593 * TrayApp overload: accepts a Painter as tray icon.
9694 */
@@ -299,8 +297,6 @@ fun ApplicationScope.TrayApp(
299297 val isVisible by trayAppState.isVisible.collectAsState()
300298 val currentWindowSize by trayAppState.windowSize.collectAsState()
301299
302- // CHANGEMENT 1: `shouldShowWindow` doit TOUJOURS commencer à `false`
303- // pour que nous puissions contrôler précisément le moment de sa première apparition.
304300 var shouldShowWindow by remember { mutableStateOf(false ) }
305301
306302 // Update window size in state if provided through parameter (for backward compatibility)
@@ -394,17 +390,33 @@ fun ApplicationScope.TrayApp(
394390 }
395391 }
396392
397- // CHANGEMENT 2: Cet effet gère les changements de visibilité APRÈS l'initialisation.
393+ // FIX: Consolidated visibility handling with position calculation BEFORE showing the window.
394+ // Added polling loop to wait for a valid (non-default) position.
398395 LaunchedEffect (isVisible) {
399396 if (isVisible) {
400- // Ne pas gérer la visibilité initiale ici, elle est gérée par `LaunchedEffect(Unit)`.
401- // Cet `if` se déclenchera pour les changements de false -> true.
402- if (! shouldShowWindow) { // Seulement si la fenêtre n'est pas déjà affichée
397+ if (! shouldShowWindow) {
398+ val widthPx = currentWindowSize.width.value.toInt()
399+ val heightPx = currentWindowSize.height.value.toInt()
400+ var position: WindowPosition = WindowPosition .PlatformDefault
401+ val deadline = System .currentTimeMillis() + 3000 // Max 3s wait to avoid hanging
402+ while (position is WindowPosition .PlatformDefault && System .currentTimeMillis() < deadline) {
403+ position = getTrayWindowPositionForInstance(
404+ tray.instanceKey(), widthPx, heightPx
405+ )
406+ delay(50 )
407+ }
408+ // If still default after timeout, proceed anyway to avoid invisible window
409+ dialogState.position = position
410+
411+ if (os == WINDOWS ) {
412+ autoHideEnabledAt = System .currentTimeMillis() + 1000
413+ }
414+
415+ // Now safe to show—no glitch.
403416 shouldShowWindow = true
404417 lastShownAt = System .currentTimeMillis()
405418 }
406419 } else {
407- // Gère la disparition (true -> false)
408420 delay(fadeDurationMs.toLong())
409421 shouldShowWindow = false
410422 lastHiddenAt = System .currentTimeMillis()
@@ -428,40 +440,6 @@ fun ApplicationScope.TrayApp(
428440 }
429441 }
430442
431- // CHANGEMENT 3: Cet effet gère UNIQUEMENT l'état de visibilité au démarrage.
432- LaunchedEffect (Unit ) {
433- // Ne s'exécute qu'une seule fois.
434- if (trayAppState.isVisible.value) { // Vérifie si l'état initial est visible.
435-
436- // Étape 1 : Attendre que la position de l'icône soit stable et disponible.
437- if (os == MACOS ) {
438- delay(100 ) // Donne le temps à l'icône de s'installer dans la barre de menus.
439- }
440- if (os == WINDOWS ) {
441- val deadline = System .currentTimeMillis() + 2000
442- val key = tray.instanceKey()
443- while (TrayClickTracker .getLastClickPosition(key) == null && System .currentTimeMillis() < deadline) {
444- delay(50 )
445- }
446- autoHideEnabledAt = System .currentTimeMillis() + 1000
447- }
448-
449- // Étape 2 : Calculer la position finale AVANT de montrer la fenêtre.
450- val widthPx = currentWindowSize.width.value.toInt()
451- val heightPx = currentWindowSize.height.value.toInt()
452- val initialPosition = getTrayWindowPositionForInstance(
453- tray.instanceKey(), widthPx, heightPx
454- )
455-
456- // Étape 3 : Appliquer cette position à l'état du dialogue.
457- dialogState.position = initialPosition
458-
459- // Étape 4 : SEULEMENT MAINTENANT, rendre la fenêtre visible.
460- shouldShowWindow = true
461- lastShownAt = System .currentTimeMillis()
462- }
463- }
464-
465443 DisposableEffect (Unit ) { onDispose { tray.dispose() } }
466444
467445 // Invisible helper window (Compose requirement on some platforms)
@@ -479,7 +457,6 @@ fun ApplicationScope.TrayApp(
479457 ) { }
480458
481459 // === Main popup window (ALWAYS MOUNTED) ===
482- // === Main popup window (ALWAYS MOUNTED) ===
483460 DialogWindow (
484461 onCloseRequest = { requestHide() },
485462 title = " " ,
@@ -491,26 +468,6 @@ fun ApplicationScope.TrayApp(
491468 visible = shouldShowWindow,
492469 state = dialogState,
493470 ) {
494- // Ensure proper initial position exactly WHEN we decide to show the window
495- LaunchedEffect (shouldShowWindow, currentWindowSize) {
496- if (shouldShowWindow) {
497- // ================== CORRECTION POUR MACOS ==================
498- // Sur macOS, il y a une condition de concurrence. On doit attendre un
499- // instant pour que les coordonnées du clic soient mises à jour
500- // avant de tenter de positionner la fenêtre.
501- if (os == MACOS ) {
502- delay(200 )
503- }
504- // =========================================================
505-
506- val widthPx = currentWindowSize.width.value.toInt()
507- val heightPx = currentWindowSize.height.value.toInt()
508- dialogState.position = getTrayWindowPositionForInstance(
509- tray.instanceKey(), widthPx, heightPx
510- )
511- }
512- }
513-
514471 // Attach/Detach platform listeners only while window is actually visible
515472 DisposableEffect (shouldShowWindow) {
516473 if (! shouldShowWindow) {
0 commit comments