# Top level windows management ## What is covered In this tutorial, we will show you how to work with windows using Compose for Desktop. We represent the window state in a shape suitable for Compose-style state manipulations and automatically map it to the operating system window state. Top-level windows can be conditionally created in other composable functions and their window manager state can also be manipulated using states produced by the `rememberWindowState()` function. ## Open and close windows The main function for creating windows is `Window`. This function should be used in a Composable scope. The easiest way to create a Composable scope is to use the `application` function: ```kotlin import androidx.compose.ui.window.Window import androidx.compose.ui.window.application fun main() = application { Window(onCloseRequest = ::exitApplication) { // Content } } ``` `Window` is a Composable function, meaning you can change its properties in a declarative way: ```kotlin import androidx.compose.material.Button import androidx.compose.material.Text import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.window.Window import androidx.compose.ui.window.application fun main() = application { var fileName by remember { mutableStateOf("Untitled") } Window(onCloseRequest = ::exitApplication, title = "$fileName - Editor") { Button(onClick = { fileName = "note.txt" }) { Text("Save") } } } ``` Window properties ## Open and close windows (conditionally) You can also close/open windows using a simple `if` statement. When the `Window` leaves the composition (`isPerformingTask` becomes `false`) – the native window automatically closes. ```kotlin import androidx.compose.material.Text import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import kotlinx.coroutines.delay fun main() = application { var isPerformingTask by remember { mutableStateOf(true) } LaunchedEffect(Unit) { delay(2000) // Do some heavy lifting isPerformingTask = false } if (isPerformingTask) { Window(onCloseRequest = ::exitApplication) { Text("Performing some tasks. Please wait!") } } else { Window(onCloseRequest = ::exitApplication) { Text("Hello, World!") } } } ``` Window splash If the window requires some custom logic on close (for example, to show a dialog), you can override the close action using `onCloseRequest`. Instead of the imperative approach to closing the window (`window.close()`) we use a declarative approach - closing the window in response to a change of the state (`isOpen = false`).. ```kotlin import androidx.compose.material.Button import androidx.compose.material.Text import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.window.DialogWindow import androidx.compose.ui.window.Window import androidx.compose.ui.window.application fun main() = application { var isOpen by remember { mutableStateOf(true) } var isAskingToClose by remember { mutableStateOf(false) } if (isOpen) { Window( onCloseRequest = { isAskingToClose = true } ) { if (isAskingToClose) { DialogWindow( onCloseRequest = { isAskingToClose = false }, title = "Close the document without saving?", ) { Button( onClick = { isOpen = false } ) { Text("Yes") } } } } } } ``` Ask to close ## Hide the window into the tray If you don't need to close the window and just need to hide it (for example to the tray), you can change the `windowState.isVisible` state: ```kotlin import androidx.compose.material.Text import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.window.Tray import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import kotlinx.coroutines.delay fun main() = application { var isVisible by remember { mutableStateOf(true) } Window( onCloseRequest = { isVisible = false }, visible = isVisible, title = "Counter", ) { var counter by remember { mutableStateOf(0) } LaunchedEffect(Unit) { while (true) { counter++ delay(1000) } } Text(counter.toString()) } if (!isVisible) { Tray( TrayIcon, tooltip = "Counter", onAction = { isVisible = true }, menu = { Item("Exit", onClick = ::exitApplication) }, ) } } object TrayIcon : Painter() { override val intrinsicSize = Size(256f, 256f) override fun DrawScope.onDraw() { drawOval(Color(0xFFFFA500)) } } ``` Hide instead of closing ## Open and close multiple windows If an application has multiple windows, then it is better to put its state into a separate class and open/close window in response to `mutableStateListOf` changes (see [notepad example](https://github.com/JetBrains/compose-multiplatform/tree/master/examples/notepad) for more complex use cases): ```kotlin import androidx.compose.runtime.Composable import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.remember import androidx.compose.ui.window.ApplicationScope import androidx.compose.ui.window.MenuBar import androidx.compose.ui.window.Window import androidx.compose.ui.window.application fun main() = application { val applicationState = remember { MyApplicationState() } for (window in applicationState.windows) { key(window) { MyWindow(window) } } } @Composable private fun ApplicationScope.MyWindow( state: MyWindowState ) = Window(onCloseRequest = state::close, title = state.title) { MenuBar { Menu("File") { Item("New window", onClick = state.openNewWindow) Item("Exit", onClick = state.exit) } } } private class MyApplicationState { val windows = mutableStateListOf() init { windows += MyWindowState("Initial window") } fun openNewWindow() { windows += MyWindowState("Window ${windows.size}") } fun exit() { windows.clear() } private fun MyWindowState( title: String ) = MyWindowState( title, openNewWindow = ::openNewWindow, exit = ::exit, windows::remove ) } private class MyWindowState( val title: String, val openNewWindow: () -> Unit, val exit: () -> Unit, private val close: (MyWindowState) -> Unit ) { fun close() = close(this) } ``` Multiple windows ## Function `singleWindowApplication` There is a simplified function for creating a single window application: ```kotlin import androidx.compose.ui.window.singleWindowApplication fun main() = singleWindowApplication { // Content } ``` Use it if: - your application has only one window - you don't need custom closing logic - you don't need to change the window parameters after it is already created ## Adaptive window size Sometimes we want to show some content as a whole without knowing in advance what exactly will be shown, meaning that we don’t know the optimal window dimensions for it. By setting one or both dimensions of your window’s WindowSize to Dp.Unspecified, Compose for Desktop will automatically adjust the initial size of your window in that dimension to accommodate its content: ```kotlin import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.Text import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState fun main() = application { Window( onCloseRequest = ::exitApplication, state = rememberWindowState(width = Dp.Unspecified, height = Dp.Unspecified), title = "Adaptive", resizable = false ) { Column(Modifier.background(Color(0xFFEEEEEE))) { Row { Text("label 1", Modifier.size(100.dp, 100.dp).padding(10.dp).background(Color.White)) Text("label 2", Modifier.size(150.dp, 200.dp).padding(5.dp).background(Color.White)) Text("label 3", Modifier.size(200.dp, 300.dp).padding(25.dp).background(Color.White)) } } } } ``` Adaptive window size ## Changing the state (maximized, minimized, fullscreen, size, position) of the window. Some states of the native window are moved into a separate API class, `WindowState`. You can change its properties in callbacks or observe it in Composable's. When some state is changed (window size or position), Composable function will be automatically recomposed. ```kotlin import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.material.Checkbox import androidx.compose.material.Text import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowPlacement import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState fun main() = application { val state = rememberWindowState(placement = WindowPlacement.Maximized) Window(onCloseRequest = ::exitApplication, state) { Column { Row(verticalAlignment = Alignment.CenterVertically) { Checkbox( state.placement == WindowPlacement.Fullscreen, { state.placement = if (it) { WindowPlacement.Fullscreen } else { WindowPlacement.Floating } } ) Text("isFullscreen") } Row(verticalAlignment = Alignment.CenterVertically) { Checkbox( state.placement == WindowPlacement.Maximized, { state.placement = if (it) { WindowPlacement.Maximized } else { WindowPlacement.Floating } } ) Text("isMaximized") } Row(verticalAlignment = Alignment.CenterVertically) { Checkbox(state.isMinimized, { state.isMinimized = !state.isMinimized }) Text("isMinimized") } Text( "Position ${state.position}", Modifier.clickable { val position = state.position if (position is WindowPosition.Absolute) { state.position = position.copy(x = state.position.x + 10.dp) } } ) Text( "Size ${state.size}", Modifier.clickable { state.size = state.size.copy(width = state.size.width + 10.dp) } ) } } } ``` Changing the state ## Listening the state of the window Reading the state in composition is useful when you need to update UI, but there are cases when you need to react to the state changes and send a value to another non-composable level of your application (write it to the database, for example): ```kotlin import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.unit.DpSize import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach fun main() = application { val state = rememberWindowState() Window(onCloseRequest = ::exitApplication, state) { // Content LaunchedEffect(state) { snapshotFlow { state.size } .onEach(::onWindowResize) .launchIn(this) snapshotFlow { state.position } .filter { it.isSpecified } .onEach(::onWindowRelocate) .launchIn(this) } } } private fun onWindowResize(size: DpSize) { println("onWindowResize $size") } private fun onWindowRelocate(position: WindowPosition) { println("onWindowRelocate $position") } ``` ## Dialogs There are two types of window – modal and regular. Below are the functions for creating each: 1. `Window` – regular window type. 2. `DialogWindow` – modal window type. This type locks its parent window until the user is finished working with it and closes the modal window. You can see an example of both types of window below. ```kotlin import androidx.compose.material.Button import androidx.compose.material.Text import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.window.DialogWindow import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberDialogState fun main() = application { Window( onCloseRequest = ::exitApplication, ) { var isDialogOpen by remember { mutableStateOf(false) } Button(onClick = { isDialogOpen = true }) { Text(text = "Open dialog") } if (isDialogOpen) { DialogWindow( onCloseRequest = { isDialogOpen = false }, state = rememberDialogState(position = WindowPosition(Alignment.Center)) ) { // Dialog's content } } } } ``` ## Swing interoperability Because Compose for Desktop uses Swing under the hood, it is possible to create a window using Swing directly: ```kotlin import androidx.compose.ui.awt.ComposeWindow import java.awt.Dimension import javax.swing.JFrame import javax.swing.SwingUtilities fun main() = SwingUtilities.invokeLater { ComposeWindow().apply { size = Dimension(300, 300) defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE setContent { // Content } isVisible = true } } ``` You can also access ComposeWindow in the Composable `Window` scope: ```kotlin import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.window.singleWindowApplication import java.awt.datatransfer.DataFlavor import java.awt.dnd.DnDConstants import java.awt.dnd.DropTarget import java.awt.dnd.DropTargetAdapter import java.awt.dnd.DropTargetDropEvent fun main() = singleWindowApplication { LaunchedEffect(Unit) { window.dropTarget = DropTarget().apply { addDropTargetListener(object : DropTargetAdapter() { override fun drop(event: DropTargetDropEvent) { event.acceptDrop(DnDConstants.ACTION_COPY); val fileName = event.transferable.getTransferData(DataFlavor.javaFileListFlavor) println(fileName) } }) } } } ``` If you need a dialog that is implemented in Swing, you can wrap it into a Composable function: ```kotlin import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.window.AwtWindow import androidx.compose.ui.window.application import java.awt.FileDialog import java.awt.Frame fun main() = application { var isOpen by remember { mutableStateOf(true) } if (isOpen) { FileDialog( onCloseRequest = { isOpen = false println("Result $it") } ) } } @Composable private fun FileDialog( parent: Frame? = null, onCloseRequest: (result: String?) -> Unit ) = AwtWindow( create = { object : FileDialog(parent, "Choose a file", LOAD) { override fun setVisible(value: Boolean) { super.setVisible(value) if (value) { onCloseRequest(file) } } } }, dispose = FileDialog::dispose ) ``` ## Draggable window area If you window is undecorated and you want to add a custom draggable titlebar to it (or make the whole window draggable), you can use `DraggableWindowArea`: ```kotlin import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.window.WindowDraggableArea import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.application fun main() = application { Window(onCloseRequest = ::exitApplication, undecorated = true) { WindowDraggableArea { Box(Modifier.fillMaxWidth().height(48.dp).background(Color.DarkGray)) } } } ``` Note that `WindowDraggableArea` can be used only inside `singleWindowApplication`, `Window` and `DialogWindow`. If you need to use it in another Composable function, pass `WindowScope` as a receiver there: ```kotlin import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.window.WindowDraggableArea import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowScope import androidx.compose.ui.window.application fun main() = application { Window(onCloseRequest = ::exitApplication, undecorated = true) { AppWindowTitleBar() } } @Composable private fun WindowScope.AppWindowTitleBar() = WindowDraggableArea { Box(Modifier.fillMaxWidth().height(48.dp).background(Color.DarkGray)) } ``` Draggable area ## Transparent windows (e.g. allows to make windows of a custom form) To create a transparent window it is enough to pass two parameners to the Window function: `transparent=true` and `undecorate=true` (it is not possible to decorate a transparent Window). Common scenario is to combine transparent window with a Surface of a custom form. Below is an example of a round-cornered Window. ```kotlin import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Surface import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.material.Text import androidx.compose.runtime.* fun main() = application { var isOpen by remember { mutableStateOf(true) } if (isOpen) { Window( onCloseRequest = { isOpen = false }, title = "Transparent Window Example", transparent = true, undecorated = true, //transparent window must be undecorated ) { Surface( modifier = Modifier.fillMaxSize().padding(5.dp).shadow(3.dp, RoundedCornerShape(20.dp)), color = Color(55, 55, 55), shape = RoundedCornerShape(20.dp) //window has round corners now ) { Text("Hello World!", color = Color.White) } } } } ``` _**Important note:** Window transparency is implemented based on JDK implementation, that contains **known issue on Linux** in case of moving a Window between two monitors with different density. So when you move an App, the Window stops being transparent. And it seems nothing can be done with this situation on Compose side. [An issue about it](https://github.com/JetBrains/compose-multiplatform/issues/1339)_