Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-menu/.nornir/guide.md

facett-menu — mounting menus & a command palette (host guide)

Class: guide · What it's for: the how-to for a host that wants a menubar, right-click menus, or a Ctrl-K command palette over one action set. The what is in design.md. The crate is a pure leaf — you own what a command does; it owns the surfaces + selection state.

1. Add the dependency

[dependencies]
facett-menu = { path = "../facett-menu", version = "0.1" }

No network, no nornir, no secrets pulled in.

2. Describe your actions as data

use facett_menu::Command;

let cmds = vec![
    Command::new("copy",     "Copy",     "Edit").shortcut("Ctrl+C"),
    Command::new("paste",    "Paste",    "Edit"),
    Command::new("case.new", "New case", "Case").keywords(["create", "investigation"]),
    Command::new("case.open","Open case","Case"),
];

id is stable and headless-addressable; group becomes a menubar dropdown / a palette section; keywords are matched by the palette but never shown; disabled commands are dimmed and never matched.

3a. The Elm Facet — MenuView (menubar + palette in one)

Mount it like any facett component; act on the Effect it returns.

use facett_menu::{MenuView, Msg, Effect};

let mut menu = MenuView::new("Commands", cmds);

// each frame, via the Facet::ui bridge (update applied for you) …
// menu.ui(ui);   // (from `impl Facet`)

// … or drive it yourself and handle the effect:
for eff in menu.update(Msg::Select("case.new".into())) {
    match eff { Effect::Selected(id) => dispatch(&id) }
}

Ctrl-K / Ctrl-Shift-P opens the palette; type to fuzzy-filter, arrows to move (wrapping), Enter to pick, Esc to close. Selecting closes the palette and emits Effect::Selected(id).

3b. The free surfaces (splice anywhere)

If you don't want the whole Facet, the immediate-mode primitives take the same Command slice and return the chosen id:

use facett_menu::{menu_bar, attach_context_menu, CommandPalette};

if let Some(id) = menu_bar(ui, &cmds) { dispatch(&id); }               // top panel

let resp = ui.label("right-click me");
attach_context_menu(&resp, &cmds, |id| dispatch(id));                  // context menu

let mut palette = CommandPalette::default();                           // Ctrl-K overlay
if let Some(id) = palette.ui(ctx, &cmds) { dispatch(&id); }

4. Drive it headless (robot / remote)

Everything is reachable without pixels through the Facet JSON seam or the typed Elm harness:

use facett_core::Facet;

menu.update_json("\"open_palette\"");
menu.update_json("{\"set_query\":\"case\"}");
let hits = menu.state_json()["hits"].clone();   // ["case.new","case.open"] — filtered
menu.update_json("{\"select\":\"case.new\"}");   // records + closes; host acts on it

state_json carries the live hits list so a test asserts the fuzzy filter narrowed. See tests/robot.rs for the full open → filter → select → effect loop, and run the warehouse rows with cargo test -p facett-menu --features testmatrix.

The one rule

Nothing is pointer-only. Give every command a stable id and it is reachable by right-click, by typing, and by Select(id) from code — the same action, three ways.