Skip to content

Commit 6f63d1c

Browse files
committed
Refactor TrayApp visibility and positioning logic
- Consolidated visibility handling into a single LaunchedEffect. - Added polling loop to ensure valid window position before showing. - Removed redundant initialization code and streamlined state updates.
1 parent ef6a615 commit 6f63d1c

1 file changed

Lines changed: 21 additions & 64 deletions

File tree

  • src/commonMain/kotlin/com/kdroid/composetray/tray/api

src/commonMain/kotlin/com/kdroid/composetray/tray/api/TrayApp.kt

Lines changed: 21 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ import java.awt.EventQueue.invokeLater
3939
import java.awt.event.WindowEvent
4040
import 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

Comments
 (0)