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

nordisk/facett

facett-menu/examples/menu_demo.rs

//! **menu_demo** — the facett overlay menu system, live. One [`Command`] set
//! drives all three surfaces:
//!
//! - a **menu bar** (File / Edit / View / Case dropdowns) across the top,
//! - a **right-click context menu** on the central content (a small graph facet),
//! - a **command palette** opened with **Ctrl-K** (or Ctrl-Shift-P): fuzzy-filter,
//!   arrow keys to move (wrapping), Enter to run, Esc to close — the selected row
//!   gets the theme accent + glow.
//!
//! A theme switcher proves every surface is palette-driven, and the last fired
//! command id is shown so you can see right-click / palette / menu-bar all land
//! the same action. The central content is a real [`facett_core::Facet`], so the
//! `$FACETT_TRAIL` harness path stays exercised.
//!
//! Run: `cargo run -p facett-menu --example menu_demo`

use eframe::egui;
use facett_core::{Facet, FacetCaps, Scene, Theme, draw, hash_color, set_theme};
use facett_menu::{Command, CommandPalette, attach_context_menu, menu_bar};

/// The uniform command registry — the same list feeds the bar, the context menu,
/// and the palette. `cut` is disabled to show dimming / unmatchability.
fn commands() -> Vec<Command> {
    vec![
        Command::new("file.new", "New file", "File").shortcut("Ctrl+N"),
        Command::new("file.open", "Open…", "File").shortcut("Ctrl+O"),
        Command::new("file.save", "Save", "File").shortcut("Ctrl+S"),
        Command::new("copy", "Copy", "Edit").shortcut("Ctrl+C").keywords(["clipboard"]),
        Command::new("cut", "Cut", "Edit").shortcut("Ctrl+X").enabled(false),
        Command::new("paste", "Paste", "Edit").shortcut("Ctrl+V").keywords(["clipboard"]),
        Command::new("view.zoom_in", "Zoom in", "View").shortcut("Ctrl+="),
        Command::new("view.zoom_out", "Zoom out", "View").shortcut("Ctrl+-"),
        Command::new("view.reset", "Reset view", "View"),
        Command::new("case.new", "New case", "Case").keywords(["create", "investigation"]),
        Command::new("case.open", "Open case", "Case"),
        Command::new("case.close", "Close case", "Case"),
    ]
}

/// A tiny graph facet so the central content is a real `Facet` (harness-testable).
struct GraphFacet {
    scene: Scene,
}

impl Default for GraphFacet {
    fn default() -> Self {
        let mut scene = Scene::new();
        let a = scene.node("Nordkapp", hash_color("Nordkapp"));
        let b = scene.node("Port", hash_color("Port"));
        let c = scene.node("AIS gap", hash_color("AIS gap"));
        scene.edge(a, b);
        scene.edge(a, c);
        Self { scene }
    }
}

impl Facet for GraphFacet {
    fn title(&self) -> &str {
        "menu-demo"
    }
    fn ui(&mut self, ui: &mut egui::Ui) {
        draw(ui, &self.scene, facett_core::Layout::Force, "empty");
    }
    fn state_json(&self) -> serde_json::Value {
        serde_json::json!({ "nodes": self.scene.nodes.len(), "edges": self.scene.edges.len() })
    }
    fn caps(&self) -> FacetCaps {
        FacetCaps::NONE.themeable().selectable()
    }
}

struct Demo {
    theme_idx: usize,
    palette: CommandPalette,
    content: GraphFacet,
    last_fired: Option<String>,
}

impl Default for Demo {
    fn default() -> Self {
        Self { theme_idx: 1, palette: CommandPalette::default(), content: GraphFacet::default(), last_fired: None }
    }
}

impl eframe::App for Demo {
    fn ui(&mut self, root: &mut egui::Ui, _frame: &mut eframe::Frame) {
        let ctx = root.ctx().clone();
        set_theme(&ctx, Theme::ALL[self.theme_idx]());
        let cmds = commands();

        egui::Panel::top("menubar").show(root, |ui| {
            // Theme switcher.
            ui.horizontal_wrapped(|ui| {
                ui.label("Theme:");
                for (i, ctor) in Theme::ALL.iter().enumerate() {
                    ui.selectable_value(&mut self.theme_idx, i, ctor().name);
                }
            });
            // The dropdown menu bar (File / Edit / View / Case).
            if let Some(id) = menu_bar(ui, &cmds) {
                self.last_fired = Some(id);
            }
            ui.label("Ctrl-K (or Ctrl-Shift-P) opens the command palette · right-click the graph for a context menu");
            ui.label(format!("Last fired: {}", self.last_fired.as_deref().unwrap_or("—")));
        });

        egui::CentralPanel::default().show(root, |ui| {
            let resp = ui.interact(ui.max_rect(), ui.id().with("content"), egui::Sense::click());
            // Right-click context menu, built from the same Command set.
            attach_context_menu(&resp, &cmds, |id| {
                self.last_fired = Some(id.to_string());
            });
            self.content.ui(ui);
        });

        // Draw the palette overlay LAST so it lands on top of everything.
        if let Some(id) = self.palette.ui(&ctx, &cmds) {
            self.last_fired = Some(id);
        }
    }
}

fn main() -> eframe::Result {
    let options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default().with_inner_size(egui::vec2(720.0, 560.0)),
        ..Default::default()
    };
    eframe::run_native("facett · menu_demo", options, Box::new(|_cc| Ok(Box::<Demo>::default())))
}