Kotlin Multiplatform Development Help

Desktop-only API

You can use Compose Multiplatform to create macOS, Linux, and Windows desktop applications. This page gives a short overview of the desktop-specific components and events. Each section includes a link to a detailed tutorial.

Components

The system tray

You can use the Tray composable to send notifications to users in the system tray:

import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize 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.Modifier import androidx.compose.ui.geometry.Offset 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 androidx.compose.ui.window.rememberNotification import androidx.compose.ui.window.rememberTrayState fun main() = application { var count by remember { mutableStateOf(0) } var isOpen by remember { mutableStateOf(true) } if (isOpen) { val trayState = rememberTrayState() val notification = rememberNotification("Notification", "Message from MyApp!") Tray( state = trayState, icon = TrayIcon, menu = { Item( "Increment value", onClick = { count++ } ) Item( "Send notification", onClick = { trayState.sendNotification(notification) } ) Item( "Exit", onClick = { isOpen = false } ) } ) Window( onCloseRequest = { isOpen = false }, icon = MyAppIcon ) { // Content: Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Text(text = "Value: $count") } } } } object MyAppIcon : Painter() { override val intrinsicSize = Size(256f, 256f) override fun DrawScope.onDraw() { drawOval(Color.Green, Offset(size.width / 4, 0f), Size(size.width / 2f, size.height)) drawOval(Color.Blue, Offset(0f, size.height / 4), Size(size.width, size.height / 2f)) drawOval(Color.Red, Offset(size.width / 4, size.height / 4), Size(size.width / 2f, size.height / 2f)) } } object TrayIcon : Painter() { override val intrinsicSize = Size(256f, 256f) override fun DrawScope.onDraw() { drawOval(Color(0xFFFFA500)) } }

There are three types of notifications:

  • notify, a simple notification.

  • warn, a warning notification.

  • error, an error notification.

You can also add an application icon to the system tray.

For more information, see the Menu, tray, and notifications tutorial.

You can use the MenuBar composable to create and customize the menu bar for a particular window:

import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize 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.ExperimentalComposeUiApi import androidx.compose.ui.Modifier 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.input.key.Key import androidx.compose.ui.input.key.KeyShortcut import androidx.compose.ui.window.MenuBar import androidx.compose.ui.window.Window import androidx.compose.ui.window.application @OptIn(ExperimentalComposeUiApi::class) fun main() = application { var action by remember { mutableStateOf("Last action: None") } var isOpen by remember { mutableStateOf(true) } if (isOpen) { var isSubmenuShowing by remember { mutableStateOf(false) } Window(onCloseRequest = { isOpen = false }) { MenuBar { Menu("File", mnemonic = 'F') { Item("Copy", onClick = { action = "Last action: Copy" }, shortcut = KeyShortcut(Key.C, ctrl = true)) Item( "Paste", onClick = { action = "Last action: Paste" }, shortcut = KeyShortcut(Key.V, ctrl = true) ) } Menu("Actions", mnemonic = 'A') { CheckboxItem( "Advanced settings", checked = isSubmenuShowing, onCheckedChange = { isSubmenuShowing = !isSubmenuShowing } ) if (isSubmenuShowing) { Menu("Settings") { Item("Setting 1", onClick = { action = "Last action: Setting 1" }) Item("Setting 2", onClick = { action = "Last action: Setting 2" }) } } Separator() Item("About", icon = AboutIcon, onClick = { action = "Last action: About" }) Item("Exit", onClick = { isOpen = false }, shortcut = KeyShortcut(Key.Escape), mnemonic = 'E') } } Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Text(text = action) } } } } object AboutIcon : Painter() { override val intrinsicSize = Size(256f, 256f) override fun DrawScope.onDraw() { drawOval(Color(0xFFFFA500)) } }

For more information, see the Menu, tray, and notifications tutorial.

Events

Tabbing navigation between components

You can set up navigation between components with the Tab keyboard shortcut for the next component and ⇧ + Tab for the previous one.

By default, the tabbed navigation allows you to move between focusable components in the order of their appearance. Focusable components include TextField, OutlinedTextField, and BasicTextField composables, as well as components that use Modifier.clickable, such as Button, IconButton, and MenuItem.

For example, here's a window where users can navigate between five text fields using standard shortcuts:

import androidx.compose.ui.window.application import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.Spacer import androidx.compose.material.OutlinedTextField import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp fun main() = application { Window( state = WindowState(size = DpSize(350.dp, 500.dp)), onCloseRequest = ::exitApplication ) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Column( modifier = Modifier.padding(50.dp) ) { for (x in 1..5) { val text = remember { mutableStateOf("") } OutlinedTextField( value = text.value, singleLine = true, onValueChange = { text.value = it } ) Spacer(modifier = Modifier.height(20.dp)) } } } } }

You can also make a non-focusable component focusable, customize the order of tabbing navigation, and put components into focus.

For more information, see the Tabbing navigation and keyboard focus tutorial.

What's next

Last modified: 17 December 2024