Closed yevhenii-nadtochii closed 5 months ago
Hello, @yevhenii-nadtochii, thanks for submitting the issue.
Unfortunately, for now we don't have such API for desired behavior. window.toFront()
don't do that work that you expect, a real window isn't visible at that moment.
Right now, as a solution it seems like you may use window.addHierarchyListener
in your case.
I've tried this:
window.addHierarchyListener {
window.toFront()
window.requestFocus()
}
But the window remains inactive.
I've also tried with runDistributable
(i.e., alwaysOnTop
doesn't work if I run the app from IDEA). The result is the same.
Is there any way to check if the window is on the front, a boolean function?
You can use this to cause your window to move to front and become focusable when shown:
LaunchedEffect(Unit) {
focus.requestFocus()
}
DisposableEffect(window) {
val listener = object: ComponentAdapter() {
override fun componentShown(e: ComponentEvent?) {
super.componentShown(e)
window.toFront()
window.requestFocus()
}
}
window.addComponentListener(listener)
onDispose {
window.removeComponentListener(listener)
}
}
@m-sasha unfortunately this doesn't help.
Can you post a new reproducer that uses that workaround?
Try this:
fun main() = application {
var isVisible by remember { mutableStateOf(false) }
Tray(
icon = stopwatch(),
menu = {
Item("Show Window", onClick = { isVisible = true })
Item("Quit", onClick = ::exitApplication)
}
)
Window(
onCloseRequest = { isVisible = false },
visible = isVisible,
title = "Stopwatch App",
icon = stopwatch(),
alwaysOnTop = true,
) {
var textValue by remember { mutableStateOf("") }
val focus = remember { FocusRequester() }
TextField(
value = textValue,
onValueChange = { textValue = it },
modifier = Modifier.focusRequester(focus)
)
LaunchedEffect(Unit) {
focus.requestFocus()
}
DisposableEffect(window) {
val componentListener = object: ComponentAdapter() {
override fun componentShown(e: ComponentEvent?) {
window.toFront()
}
}
val windowListener = object: WindowAdapter() {
override fun windowActivated(e: WindowEvent?) {
SwingUtilities.invokeLater {
focus.requestFocus()
}
}
}
window.addComponentListener(componentListener)
window.addWindowListener(windowListener)
onDispose {
window.removeComponentListener(componentListener)
window.removeWindowListener(windowListener)
}
}
}
}
This workaround does better.
If the currently active app is AppKt
, then both the window and the field become focused. So, you can hit Show window
and start typing in the field right away.
But still no changes if the currently active app is another one, which is usually the case for tray apps.
Ok, I found the magic incantation, it's Desktop.getDesktop().requestForeground()
:
fun main() = application {
var isVisible by remember { mutableStateOf(false) }
Tray(
icon = stopwatch(),
menu = {
Item("Show Window", onClick = { isVisible = true })
Item("Quit", onClick = ::exitApplication)
}
)
Window(
onCloseRequest = { isVisible = false },
visible = isVisible,
title = "Stopwatch App",
icon = stopwatch(),
alwaysOnTop = true,
) {
var textValue by remember { mutableStateOf("") }
val focus = remember { FocusRequester() }
TextField(
value = textValue,
onValueChange = { textValue = it },
modifier = Modifier.focusRequester(focus)
)
LaunchedEffect(Unit) {
focus.requestFocus()
}
LaunchedEffect(isVisible) {
if (isVisible) {
Desktop.getDesktop().requestForeground(true)
}
}
DisposableEffect(window) {
val windowListener = object: WindowAdapter() {
override fun windowActivated(e: WindowEvent?) {
SwingUtilities.invokeLater {
focus.requestFocus()
}
}
}
window.addWindowListener(windowListener)
onDispose {
window.removeWindowListener(windowListener)
}
}
}
}
@m-sasha The magic did the trick! Thank you 🙂
There's interesting detail. I've noticed that sometimes a window still can't get active. One out of 3–7 attempts fails. Turns out that the launched effect is not always executed upon updates of isVisible
variable (why?) and requestForeground()
is not called at all.
Moving it out of Window
composable finally solved the problem.
Closing this, as it's not an issue with Compose, but with AWT.
Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.
A shown window fails to become active and gain focus. The window is created with
alwaysOnTop = true
, so it should gain focus automatically as shown. But direct calls towindow.requestFocus()
andwindow.toFront
also have no effect.Maybe it is because the app is a background application from the start (
Tray
+LSUIElement = true
for MacOS). Tray menu hasShow window
item. And a user usually have another active application at the moment he/she clicks aShow window
item from a tray menu.Code snippet
```kotlin fun main() = application { var isVisible by remember { mutableStateOf(true) } Tray( icon = stopwatch(), menu = { Item("Show Window", onClick = { isVisible = true }) Item("Quit", onClick = ::exitApplication) } ) Window( onCloseRequest = { isVisible = false }, visible = isVisible, title = "Stopwatch App", icon = stopwatch(), alwaysOnTop = true, ) { var textValue by remember { mutableStateOf("") } val focus = remember { FocusRequester() } TextField( value = textValue, onValueChange = { textValue = it }, modifier = Modifier.focusRequester(focus) ) LaunchedEffect(isVisible) { if (isVisible) { window.toFront() // Can't gain focus, the window remains inactive. focus.requestFocus() // Thus, the focus is NOT passed to the text field. } } } } ```Affected platforms
Versions
To Reproduce
window-focus-reproducer.zip
When the currently active app is NOT
MainKt
, the window never gets focused on showing up. When the currently active app isMainKt
, the window gets focused half the time.Expected behavior Top most window is active when it is shown, especially when this is requested explicitly by
window.toFront()
orwindow.requestFocus()
.