Compose Multiplatform for desktop provides various features for managing windows. You can hide windows in the tray, make them draggable, adapt size, change position, and so on.
Open and close windows
You can use the Window() function to create a regular window. To put it in a composable scope, use Window() in the application entry point:
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
fun main() = application {
Window(onCloseRequest = ::exitApplication) {
// Content of the window
}
}
As a composable function, Window() allows you to change its properties declaratively. For example, you can open a window with one title and change the title later:
You can also open and close windows using simple if conditions. In the following code sample, the application window is automatically closed after completing a task:
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) {
// Do some heavy lifting
delay(2000)
isPerformingTask = false
}
if (isPerformingTask) {
Window(
onCloseRequest = ::exitApplication,
title = "Window 1"
)
{
Text("Performing some tasks. Please wait!")
}
} else {
Window(
onCloseRequest = ::exitApplication,
title = "Window 2"
) {
Text("Hello, World!")
}
}
}
If you want to use custom logic on application exit, such as showing a dialog, you can override the close action using the onCloseRequest callback. In the following code sample, instead of an imperative approach (window.close()), we use a declarative approach and close the window in response to the state change (isOpen = false).
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 },
title = "Important document"
) {
if (isAskingToClose) {
DialogWindow(
onCloseRequest = { isAskingToClose = false },
title = "Close without saving?"
) {
Button(
onClick = { isOpen = false }
) {
Text("Yes")
}
}
}
}
}
}
Work with multiple windows
If an application has multiple windows, you can create a separate class for the application state and open or close windows in response to the mutableStateListOf changes:
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.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 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<MyWindowState>()
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)
}
For a more complex example, see the notepad sample.
Minimize a window to the system tray
To hide the window instead of closing it, you can change the windowState.isVisible state:
You can create a single window application by calling the singleWindowApplication() function.
The singleWindowApplication() function is easier to use but has the following limitations:
The application can have only one window.
You cannot add custom closing logic.
You cannot change the attributes of the window in runtime.
import androidx.compose.ui.window.singleWindowApplication
fun main() = singleWindowApplication {
// Content of the window
}
As an alternative, you can use the Window() composable in the application entry point.
Adaptive window size
When you don't know the size of the expected content and cannot specify the optimal window dimensions in advance, you can set one or both dimensions of WindowSize to Dp.Unspecified. Compose Multiplatform for desktop will automatically adjust the initial size of your window to fit the content:
WindowState is a separate API class for the window placement, current position, and size. The placement attribute allows you to specify how the window is placed on the screen: floating, maximized/minimized, or fullscreen. Any change of the state triggers automatic recomposition. To change the window state, use callbacks or observe it in composables:
If you need to react to the state changes and send a value to another non-composable application level, for example, write it to the database, you can use the snapshotFlow() function. This function captures the current value of a composable's state.
You can use the Window() composable to create a regular window and the DialogWindow() composable for a modal window that locks its parent until the user closes the modal window.
The following code sample demonstrates how to use these composables to combine regular and modal windows:
WindowDraggableArea() can be used inside the singleWindowApplication(), Window(), and DialogWindow() composables only. To call it in another composable function, use a WindowScope as a receiver scope:
To create a transparent window, pass two parameters to the Window() function: transparent=true and undecorated=true. The window must be undecorated because it is impossible to decorate a transparent window.
The following code sample demonstrates how to combine composables to create a transparent window with rounded corners:
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,
// Transparent window must be undecorated
undecorated = true,
) {
Surface(
modifier = Modifier.fillMaxSize().padding(5.dp).shadow(3.dp, RoundedCornerShape(20.dp)),
color = Color.Transparent,
// Window with rounded corners
shape = RoundedCornerShape(20.dp)
) {
Text("Hello World!", color = Color.White)
}
}
}
}
Swing interoperability
Compose Multiplatform for desktop uses Swing under the hood, so you can create a window using Swing directly: