Files
amethyst/.claude/skills/desktop-expert/references/desktop-compose-apis.md
T
2025-12-30 15:18:48 +02:00

11 KiB

Desktop Compose APIs Catalog

Complete reference for Compose Multiplatform Desktop-only APIs.

Window Management

application

Root entry point for desktop apps.

fun main() = application {
    Window(onCloseRequest = ::exitApplication) {
        Text("Hello Desktop")
    }
}

Window

Creates a window.

Window(
    onCloseRequest: () -> Unit,
    state: WindowState = rememberWindowState(),
    visible: Boolean = true,
    title: String = "Untitled",
    icon: Painter? = null,
    undecorated: Boolean = false,
    transparent: Boolean = false,
    resizable: Boolean = true,
    enabled: Boolean = true,
    focusable: Boolean = true,
    alwaysOnTop: Boolean = false,
    onPreviewKeyEvent: ((KeyEvent) -> Boolean) = { false },
    onKeyEvent: ((KeyEvent) -> Boolean) = { false },
    content: @Composable FrameWindowScope.() -> Unit
)

Example:

val windowState = rememberWindowState(
    width = 1200.dp,
    height = 800.dp,
    position = WindowPosition.Aligned(Alignment.Center)
)

Window(
    onCloseRequest = ::exitApplication,
    state = windowState,
    title = "My App",
    resizable = true
) {
    // Content
}

rememberWindowState

Manages window size and position.

@Composable
fun rememberWindowState(
    placement: WindowPlacement = WindowPlacement.Floating,
    isMinimized: Boolean = false,
    position: WindowPosition = WindowPosition.PlatformDefault,
    width: Dp = Dp.Unspecified,
    height: Dp = Dp.Unspecified
): WindowState

WindowPlacement:

  • Floating - Normal window
  • Maximized - Fullscreen
  • Fullscreen - Fullscreen without decorations

WindowPosition:

  • PlatformDefault - OS decides
  • Aligned(alignment) - Center, TopStart, etc.
  • Absolute(x, y) - Fixed position in pixels

DialogWindow

Modal dialog.

DialogWindow(
    onCloseRequest: () -> Unit,
    state: DialogState = rememberDialogState(),
    visible: Boolean = true,
    title: String = "Dialog",
    icon: Painter? = null,
    undecorated: Boolean = false,
    transparent: Boolean = false,
    resizable: Boolean = true,
    enabled: Boolean = true,
    focusable: Boolean = true,
    content: @Composable DialogWindowScope.() -> Unit
)

Example:

var showDialog by remember { mutableStateOf(false) }

if (showDialog) {
    DialogWindow(
        onCloseRequest = { showDialog = false },
        title = "Confirm"
    ) {
        Column(Modifier.padding(16.dp)) {
            Text("Are you sure?")
            Row {
                Button(onClick = { showDialog = false }) {
                    Text("Cancel")
                }
                Button(onClick = { /* confirm */ }) {
                    Text("OK")
                }
            }
        }
    }
}

MenuBar

MenuBar

Native menu bar for windows.

@Composable
fun FrameWindowScope.MenuBar(
    content: @Composable MenuBarScope.() -> Unit
)

Example:

Window(onCloseRequest = ::exitApplication) {
    MenuBar {
        Menu("File") {
            Item("New", onClick = { /* ... */ })
            Item("Open", onClick = { /* ... */ })
            Separator()
            Item("Quit", onClick = ::exitApplication)
        }
        Menu("Edit") {
            Item("Copy", onClick = { /* ... */ })
            Item("Paste", onClick = { /* ... */ })
        }
    }
}

Menu

Top-level menu.

@Composable
fun MenuBarScope.Menu(
    text: String,
    mnemonic: Char? = null,
    enabled: Boolean = true,
    content: @Composable MenuScope.() -> Unit
)

Item

Menu item.

@Composable
fun MenuScope.Item(
    text: String,
    onClick: () -> Unit,
    shortcut: KeyShortcut? = null,
    mnemonic: Char? = null,
    enabled: Boolean = true,
    icon: Painter? = null
)

With keyboard shortcut:

Item(
    text = "Save",
    onClick = { save() },
    shortcut = KeyShortcut(Key.S, ctrl = true),
    icon = painterResource("save.png")
)

Separator

Menu separator line.

@Composable
fun MenuScope.Separator()

CheckboxItem

Toggleable menu item.

@Composable
fun MenuScope.CheckboxItem(
    text: String,
    checked: Boolean,
    onCheckedChange: (Boolean) -> Unit,
    shortcut: KeyShortcut? = null,
    mnemonic: Char? = null,
    enabled: Boolean = true
)

Example:

var darkMode by remember { mutableStateOf(false) }

Menu("View") {
    CheckboxItem(
        text = "Dark Mode",
        checked = darkMode,
        onCheckedChange = { darkMode = it },
        shortcut = KeyShortcut(Key.D, ctrl = true)
    )
}

RadioButtonItem

Radio button menu item.

@Composable
fun MenuScope.RadioButtonItem(
    text: String,
    selected: Boolean,
    onClick: () -> Unit,
    shortcut: KeyShortcut? = null,
    mnemonic: Char? = null,
    enabled: Boolean = true
)

System Tray

Tray

System tray icon with menu.

@Composable
fun ApplicationScope.Tray(
    icon: Painter,
    state: TrayState = rememberTrayState(),
    tooltip: String? = null,
    onAction: () -> Unit = {},
    menu: @Composable MenuScope.() -> Unit = {}
)

Example:

application {
    var isVisible by remember { mutableStateOf(true) }

    Tray(
        icon = painterResource("tray-icon.png"),
        tooltip = "My App",
        onAction = { isVisible = true },
        menu = {
            Item("Show Window", onClick = { isVisible = true })
            Separator()
            Item("Quit", onClick = ::exitApplication)
        }
    )

    if (isVisible) {
        Window(
            onCloseRequest = { isVisible = false },
            title = "App"
        ) {
            // Content
        }
    }
}

rememberTrayState

Manages tray state.

@Composable
fun rememberTrayState(): TrayState

Notifications

Notification (via Tray)

Show desktop notifications through tray.

val trayState = rememberTrayState()

Tray(
    icon = painterResource("icon.png"),
    state = trayState
)

// Send notification
LaunchedEffect(Unit) {
    trayState.sendNotification(
        Notification(
            title = "Message",
            message = "You have a new message",
            type = Notification.Type.Info
        )
    )
}

Notification types:

  • Info - Information
  • Warning - Warning
  • Error - Error

Keyboard

KeyShortcut

Keyboard shortcut definition.

data class KeyShortcut(
    val key: Key,
    val ctrl: Boolean = false,
    val meta: Boolean = false,
    val alt: Boolean = false,
    val shift: Boolean = false
)

Examples:

// Ctrl+S (Windows/Linux)
KeyShortcut(Key.S, ctrl = true)

// Cmd+S (macOS)
KeyShortcut(Key.S, meta = true)

// Ctrl+Shift+N
KeyShortcut(Key.N, ctrl = true, shift = true)

// Alt+F4
KeyShortcut(Key.F4, alt = true)

onPreviewKeyEvent / onKeyEvent

Window-level keyboard handlers.

Window(
    onCloseRequest = ::exitApplication,
    onPreviewKeyEvent = { event ->
        if (event.key == Key.Escape && event.type == KeyEventType.KeyDown) {
            // Handle Escape
            true  // Consume event
        } else {
            false  // Propagate
        }
    }
) {
    // Content
}

Mouse

PointerMoveFilter (Deprecated, use Modifier.pointerInput)

Box(
    modifier = Modifier
        .pointerInput(Unit) {
            awaitPointerEventScope {
                while (true) {
                    val event = awaitPointerEvent()
                    // Handle mouse events
                }
            }
        }
)

Mouse cursor

Box(
    modifier = Modifier.pointerHoverIcon(
        icon = PointerIcon(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR))
    )
) {
    Text("Hover me")
}

Cursor types:

  • DEFAULT_CURSOR
  • HAND_CURSOR
  • TEXT_CURSOR
  • CROSSHAIR_CURSOR
  • WAIT_CURSOR
  • MOVE_CURSOR
  • E_RESIZE_CURSOR, W_RESIZE_CURSOR, etc.

Drag & Drop (Experimental)

onExternalDrag

Handle drag-and-drop from external sources.

Box(
    modifier = Modifier
        .size(200.dp)
        .background(Color.LightGray)
        .onExternalDrag(
            onDragStart = { externalDragValue ->
                println("Drag started")
            },
            onDrag = { externalDragValue ->
                println("Dragging: ${externalDragValue.dragData}")
            },
            onDragExit = {
                println("Drag exited")
            },
            onDrop = { externalDragValue ->
                val dragData = externalDragValue.dragData
                when (dragData) {
                    is DragData.FilesList -> {
                        println("Files dropped: ${dragData.readFiles()}")
                    }
                    is DragData.Text -> {
                        println("Text dropped: ${dragData.readText()}")
                    }
                    else -> {}
                }
            }
        )
) {
    Text("Drop files here", Modifier.align(Alignment.Center))
}

Resources

painterResource

Load images from resources.

val icon = painterResource("icon.png")

Icon(
    painter = icon,
    contentDescription = "App icon"
)

Resource location: src/jvmMain/resources/


Platform Integration

Desktop.getDesktop() (AWT)

Access system desktop features (not Compose API, but commonly used).

import java.awt.Desktop
import java.net.URI

// Open URL in browser
if (Desktop.isDesktopSupported()) {
    Desktop.getDesktop().browse(URI("https://example.com"))
}

// Open file with default app
Desktop.getDesktop().open(File("/path/to/file.pdf"))

// Open email client
Desktop.getDesktop().mail(URI("mailto:user@example.com"))

FileDialog (AWT)

File picker dialogs.

import java.awt.FileDialog
import java.awt.Frame

// Open file
val fileDialog = FileDialog(Frame(), "Select file", FileDialog.LOAD)
fileDialog.isVisible = true
val selectedFile = fileDialog.file
val directory = fileDialog.directory

// Save file
val saveDialog = FileDialog(Frame(), "Save file", FileDialog.SAVE)
saveDialog.file = "document.txt"
saveDialog.isVisible = true

SwingPanel (Interop)

Embed Swing components in Compose.

import androidx.compose.ui.awt.SwingPanel
import javax.swing.JButton

SwingPanel(
    factory = {
        JButton("Swing Button").apply {
            addActionListener {
                println("Swing button clicked")
            }
        }
    },
    modifier = Modifier.size(200.dp, 50.dp)
)

ComposePanel (Reverse Interop)

Embed Compose in Swing.

import androidx.compose.ui.awt.ComposePanel
import javax.swing.JFrame

val frame = JFrame("Swing Frame")
val composePanel = ComposePanel()

composePanel.setContent {
    Text("Compose in Swing")
}

frame.contentPane.add(composePanel)
frame.setSize(400, 300)
frame.isVisible = true

Version Requirements

  • Kotlin: 2.0+
  • Compose Multiplatform: 1.7.0+
  • JVM Target: 11+ (recommend 21)

See also: