Nucleus
OS integration

Apps ancrées au tray

TrayApp ancre une fenêtre popup Compose à une icône du tray système pour construire des apps menu bar en Kotlin.

TrayApp ancre une fenêtre Compose complète à une icône du tray système : vous cliquez gauche sur l'icône et la fenêtre s'ouvre à côté, vous cliquez ailleurs et elle se ferme. C'est le pattern menu bar app utilisé par des outils comme Bartender, iStat Menus et Hidden Bar.

Expérimental

TrayApp est en alpha et l'API peut changer. Activez-le avec @OptIn(ExperimentalTrayAppApi::class).

Ajouter la dépendance

TrayApp est inclus dans composenativetray. Si cette dépendance est sur votre classpath, aucun artefact supplémentaire n'est nécessaire.

Créer une tray app

Passez une icône, un tooltip, une taille de fenêtre et le contenu Compose à afficher dans la popup :

@OptIn(ExperimentalTrayAppApi::class)
fun main() = application {
    TrayApp(
        icon = Icons.Default.Dashboard,
        tooltip = "Quick dashboard",
        windowSize = DpSize(300.dp, 400.dp),
    ) {
        Column(Modifier.fillMaxSize().padding(16.dp)) {
            Text("Dashboard", style = MaterialTheme.typography.h6)
            Spacer(Modifier.height(8.dp))
            Text("CPU: 42%")
            Text("RAM: 8.2 GB")
        }
    }
}

Fonctionnement

TrayApp combine une icône tray et une fenêtre Compose transparente, sans décoration et toujours au-dessus. La bibliothèque suit la position de l'icône à l'écran et place la fenêtre à côté avec l'offset adapté à chaque système d'exploitation.

Le clic-en-dehors-pour-fermer est branché sur l'événement de perte de focus de l'OS : la popup se ferme comme les fenêtres menu bar natives de chaque bureau.

Piloter la visibilité et la taille avec l'état

Conservez un TrayAppState pour afficher, masquer, redimensionner ou changer le mode de dismiss depuis votre propre code :

@OptIn(ExperimentalTrayAppApi::class)
fun main() = application {
    val state = rememberTrayAppState(
        initialWindowSize = DpSize(350.dp, 500.dp),
        initiallyVisible = false,
        initialDismissMode = TrayWindowDismissMode.AUTO,
    )

    TrayApp(
        icon = Icons.Default.Dashboard,
        tooltip = "Dashboard",
        state = state,
    ) {
        Column {
            Text("Dashboard")
            Button(onClick = { state.hide() }) { Text("Fermer") }
            Button(onClick = { state.setWindowSize(500.dp, 600.dp) }) { Text("Redimensionner") }
        }
    }
}

Ajouter un menu contextuel

Le clic gauche ouvre la popup ; le clic droit ouvre un menu classique :

@OptIn(ExperimentalTrayAppApi::class)
fun main() = application {
    TrayApp(
        icon = Icons.Default.Dashboard,
        tooltip = "Dashboard",
        menu = {
            Item(label = "Réglages") { openSettings() }
            Divider()
            Item(label = "Quitter") { exitProcess(0) }
        },
    ) {
        Text("Contenu de la popup")
    }
}

Masquer l'icône du Dock sur macOS

Une tray app n'a généralement pas besoin d'icône dans le Dock. Sur macOS, mettez LSUIElement = true dans l'Info.plist de l'app. Le plugin Gradle Nucleus expose macOS.infoPlist.extraKeysRawXml pour cela :

build.gradle.kts
nucleus {
    application {
        nativeDistributions {
            macOS {
                infoPlist {
                    extraKeysRawXml = """
                        <key>LSUIElement</key>
                        <true/>
                    """.trimIndent()
                }
            }
        }
    }
}

L'app tourne alors sans entrée dans le Dock ni barre de menus — seule l'icône du tray reste.

Référence de l'API

Options de fenêtre de TrayApp

ParamètreDéfautNotes
windowSizeDpSize(300.dp, 200.dp)taille initiale
visibleOnStartfalseaffiche tout de suite
enterTransition / exitTransitiondéfaut plateformeanimations
transparenttruefond transparent
undecoratedtruesans cadre
resizablefalseredimensionnement utilisateur
horizontalOffset / verticalOffset0 / défaut plateformedécalage depuis l'icône

TrayAppState

APIDescription
isVisible: StateFlow<Boolean>visibilité courante
show() / hide() / toggle()contrôle impératif
setWindowSize(size)redimensionne à la volée
setDismissMode(mode)AUTO (clic dehors ferme) ou MANUAL
onVisibilityChanged(cb)observer les transitions

Et ensuite

  • System tray — l'API d'icône tray sur laquelle repose TrayApp.
  • DSL menu du tray — construire le menu du clic droit.
  • Single instance — un re-launch depuis le .app ré-affiche la popup au lieu de démarrer un second process.