Nucleus
Window & toolkits

Menus contextuels

Remplacez les menus contextuels dessinés par Compose par le menu de l'OS — NSMenu sous macOS, flyout Fluent sous Windows, Adwaita ou Breeze sous Linux.

Par défaut, Compose Desktop dessine ses propres menus contextuels, qui ne ressemblent pas au bureau sur lequel ils tournent. Nucleus peut les remplacer par le menu de la plateforme : un vrai NSMenu sous macOS, et un flyout Compose stylé Fluent sous Windows, Adwaita sur les bureaux GTK ou Breeze sur les bureaux Qt. Cette page couvre l'activation, les types d'entrées et le chemin par lequel les entrées arrivent au menu.

Activer le menu natif

Renseignez nativeContextMenu sur la fenêtre :

import dev.nucleusframework.application.DecoratedWindow
import dev.nucleusframework.application.nucleusApplication

fun main() = nucleusApplication {
    DecoratedWindow(
        onCloseRequest = ::exitApplication,
        title = "MyApp",
        nativeContextMenu = true,
    ) {
        // Les champs de texte affichent désormais le menu Couper/Copier/Coller de l'OS
    }
}

Cela couvre ContextMenuArea, le menu Couper/Copier/Coller des champs de texte et les suggestions orthographiques. Le paramètre existe aussi sur HostedWindow.

Le menu natif est une fonctionnalité du backend Tao. Sur les backends AWT, le paramètre est accepté et ignoré : le même code y conserve les menus Compose.

Vous ne choisissez pas de style. Le menu suit la plateforme et, sous Linux, le toolkit de la session — Breeze sur les bureaux Qt, Adwaita partout ailleurs.

Ajouter vos propres entrées

Construisez les entrées avec NucleusContextMenuItem, qui étend le ContextMenuItem de Compose avec une icône et un libellé de raccourci :

import androidx.compose.foundation.ContextMenuArea
import androidx.compose.foundation.ContextMenuItem
import dev.nucleusframework.application.contextmenu.ContextMenuIcon
import dev.nucleusframework.application.contextmenu.NucleusContextMenuDivider
import dev.nucleusframework.application.contextmenu.NucleusContextMenuItem

ContextMenuArea(
    items = {
        listOf(
            NucleusContextMenuItem(
                label = "Afficher dans le gestionnaire de fichiers",
                icon = ContextMenuIcon.Folder,
                onClick = { revealInFileManager(path) },
            ),
            NucleusContextMenuDivider,
            NucleusContextMenuItem(
                label = "Supprimer",
                icon = ContextMenuIcon.Delete,
                shortcut = "Suppr",
                onClick = { delete(path) },
            ),
        )
    },
) {
    FileRow(path)
}

NucleusContextMenuDivider est le séparateur, et NucleusContextMenuSubmenu imbrique un groupe. Sa lambda items est évaluée à l'ouverture du menu : un sous-menu peut donc lister un contenu changeant.

import dev.nucleusframework.application.contextmenu.NucleusContextMenuSubmenu

NucleusContextMenuSubmenu(label = "Ouvrir un fichier récent") { recentFiles.map { fileItem(it) } }

Afficher les raccourcis clavier

Une entrée dotée d'une icône standard reçoit gratuitement le libellé de raccourci de la plateforme — ⌘C sous macOS, Ctrl+C ailleurs :

NucleusContextMenuItem(label = "Copier", icon = ContextMenuIcon.Copy, onClick = ::copy)

Passez shortcut pour remplacer le libellé, ou shortcut = "" pour supprimer celui par défaut. ContextMenuIcon.stockShortcut() renvoie le libellé associé à une icône.

Les libellés de raccourci sont dessinés par les flyouts Windows et Linux. Le NSMenu de macOS n'exploite pas encore ce champ.

Détecter le toolkit Linux

Le style de menu Linux est déduit de la session, que vous pouvez aussi lire vous-même — par exemple pour accorder un widget indépendant au bureau :

import dev.nucleusframework.core.runtime.LinuxUiToolkit

when (LinuxUiToolkit.Current) {
    LinuxUiToolkit.Qt -> // KDE Plasma, LXQt, Deepin, …
    LinuxUiToolkit.Gtk -> // GNOME, Xfce, Cinnamon, MATE, …
}

LinuxUiToolkit.Current lit XDG_CURRENT_DESKTOP, DESKTOP_SESSION et XDG_SESSION_DESKTOP, puis se replie sur KDE_FULL_SESSION et QT_QPA_PLATFORMTHEME. Les bureaux inconnus et les gestionnaires de fenêtres en mosaïque donnent Gtk.

Fonctionnement

Activer nativeContextMenu installe deux représentations Compose pour le sous-arbre de la fenêtre : NativeContextMenuRepresentation pour ContextMenuArea et NativeTextContextMenu pour les champs de texte. Toutes deux parcourent la liste de ContextMenuItem fournie par Compose et la convertissent en arbre de ContextMenuEntry via le ContextMenuItemInterpreter présent dans la portée.

Cet arbre est ensuite rendu par plateforme. macOS construit un vrai NSMenu via menu-macos et l'affiche ; Windows et Linux dessinent un flyout Compose à la densité de l'OS, ce qui préserve l'échelle système même dans un sous-arbre qui redéfinit LocalDensity.

Les entrées proviennent de contributeurs indépendants — le Couper/Copier/Coller du champ, vos ajouts, les suggestions orthographiques — donc la liste est normalisée avant affichage : les séparateurs en tête, en fin et consécutifs sont supprimés, récursivement dans les sous-menus. Une liste vide referme le menu au lieu de faire clignoter un cadre vide.

Référence de l'API

Package dev.nucleusframework.application.contextmenu, livré dans nucleus-application.

Construire des entrées

SymboleDescription
NucleusContextMenuItem(label, enabled, icon, shortcut, onClick)Un ContextMenuItem avec icône et libellé de raccourci optionnels.
NucleusContextMenuDividerEntrée séparateur.
NucleusContextMenuSubmenu(label) { items }Groupe imbriqué ; items est évalué à l'ouverture du menu.
ContextMenuIconCut, Copy, Paste, SelectAll, Delete, Folder, SfSymbol(name).
ContextMenuIcon.stockShortcut()Libellé de raccourci de la plateforme pour les icônes d'édition, sinon null.

Piloter le menu

SymboleDescription
NativeContextMenuProvider(enabled) { }Installe le menu natif sur un sous-arbre, sans passer par le paramètre de fenêtre.
isNativeContextMenuSupportedSi la plateforme peut l'afficher.
LocalNativeContextMenuSi le sous-arbre utilise actuellement le menu natif.
LocalContextMenuDividerLe séparateur dessiné par le rendu actif, ou null. Lisez-le quand vous assemblez des listes d'entrées.
NativeContextMenuRepresentation / NativeTextContextMenuLes représentations installées par le provider.

Interpréter les entrées

SymboleDescription
ContextMenuEntryItem(label, enabled, icon, onClick, shortcut), Separator, Submenu(label, items).
ContextMenuItemInterpreterConvertit un ContextMenuItem Compose en ContextMenuEntry.
DefaultContextMenuItemInterpreterLa conversion par défaut.
LocalContextMenuItemInterpreterRedéfinit la conversion pour un sous-arbre.

Primitives de popup macOS

Package dev.nucleusframework.menu.macos, livré dans menu-macos.

SymboleDescription
NativePopupMenuItemEntry(title, enabled, icon, onClick), Separator, Submenu(title, items).
isNativePopupMenuAvailableSi le pont natif s'est chargé.
popUpNativeMenu(items)Affiche un NSMenu à la position du curseur.

Appelez popUpNativeMenu hors du thread UI de Tao. AppKit exécute une boucle de tracking imbriquée pendant que le menu est ouvert et, à l'intérieur d'un callback d'événement Tao, cette boucle affame la boucle d'événements — animations, Dispatchers.Main et delay s'arrêtent jusqu'à la fermeture du menu.

Notes

  • Les icônes ne se rendent pas de la même façon partout : Breeze dessine des vecteurs de 16 dp, Fluent utilise les glyphes Segoe Fluent Icons, et Adwaita suit la convention GTK sans icônes dans les menus contextuels. ContextMenuIcon.SfSymbol ne concerne que macOS.
  • Le flyout se referme quand la fenêtre propriétaire perd le focus. La surveillance des clics extérieurs ne voit que ce processus : le focus est le seul signal remonté par tous les backends, et il couvre aussi Alt+Tab et les clics sur la barre des tâches.
  • Sous macOS, le menu requiert la bibliothèque native menu-macos ; si elle ne se charge pas, isNativeContextMenuSupported vaut false et les menus Compose sont utilisés.

Et ensuite