Skip to content

Commit 3b5118f

Browse files
committed
Pre-compute tray position at click time to remove display latency
The 250ms delay before showing the tray popup was needed because the position was computed in the LaunchedEffect, after the click had already propagated through coroutine dispatchers. Instead, compute the position directly in internalPrimaryAction at click time, when the native status item geometry is guaranteed to be available. The LaunchedEffect then uses this pre-computed position immediately without any delay. The polling fallback with delay is kept only for cases where no click occurred (initiallyVisible or programmatic show).
1 parent 635815f commit 3b5118f

1 file changed

Lines changed: 30 additions & 9 deletions

File tree

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

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

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,9 @@ private fun ApplicationScope.TrayAppImplOriginal(
508508
var lastFocusLostAt by remember { mutableStateOf(0L) }
509509
var autoHideEnabledAt by remember { mutableStateOf(0L) }
510510

511+
// Position pre-computed at click time so the LaunchedEffect can use it immediately.
512+
var pendingPosition by remember { mutableStateOf<WindowPosition?>(null) }
513+
511514
val dialogState = rememberDialogState(size = currentWindowSize)
512515
LaunchedEffect(currentWindowSize) { dialogState.size = currentWindowSize }
513516

@@ -541,6 +544,15 @@ private fun ApplicationScope.TrayAppImplOriginal(
541544
if (getOperatingSystem() == WINDOWS && (now - lastFocusLostAt) < 300) {
542545
// ignore immediate re-show after focus loss on Windows
543546
} else {
547+
// Pre-compute position at click time: the native status item
548+
// geometry is guaranteed to be available right now.
549+
runCatching {
550+
val widthPx = currentWindowSize.width.value.toInt()
551+
val heightPx = currentWindowSize.height.value.toInt()
552+
pendingPosition = getTrayWindowPositionForInstance(
553+
tray.instanceKey(), widthPx, heightPx, horizontalOffset, verticalOffset
554+
)
555+
}
544556
trayAppState.show()
545557
}
546558
}
@@ -554,16 +566,25 @@ private fun ApplicationScope.TrayAppImplOriginal(
554566

555567
if (isVisible) {
556568
if (!shouldShowWindow) {
557-
delay(250) // let tray click/dock settle (macOS)
558-
val widthPx = currentWindowSize.width.value.toInt()
559-
val heightPx = currentWindowSize.height.value.toInt()
560-
var position: WindowPosition = WindowPosition.PlatformDefault
561-
val deadline = System.currentTimeMillis() + 3000
562-
while (position is WindowPosition.PlatformDefault && System.currentTimeMillis() < deadline) {
563-
position = getTrayWindowPositionForInstance(
564-
tray.instanceKey(), widthPx, heightPx, horizontalOffset, verticalOffset
565-
)
569+
val preComputed = pendingPosition
570+
pendingPosition = null
571+
572+
val position = if (preComputed != null && preComputed !is WindowPosition.PlatformDefault) {
573+
preComputed
574+
} else {
575+
// Fallback: poll for position (e.g. initiallyVisible or programmatic show)
566576
delay(250)
577+
val widthPx = currentWindowSize.width.value.toInt()
578+
val heightPx = currentWindowSize.height.value.toInt()
579+
var pos: WindowPosition = WindowPosition.PlatformDefault
580+
val deadline = System.currentTimeMillis() + 3000
581+
while (pos is WindowPosition.PlatformDefault && System.currentTimeMillis() < deadline) {
582+
pos = getTrayWindowPositionForInstance(
583+
tray.instanceKey(), widthPx, heightPx, horizontalOffset, verticalOffset
584+
)
585+
if (pos is WindowPosition.PlatformDefault) delay(250)
586+
}
587+
pos
567588
}
568589
dialogState.position = position
569590

0 commit comments

Comments
 (0)