Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Menu Bar

Group: Chrome

Code

fn action(id: &str, label: &str) -> ContextMenuItem {
    ContextMenuItem {
        id: Some(WidgetId::new(id)),
        label: StyledText::plain(label),
        ..Default::default()
    }
}

fn separator() -> ContextMenuItem {
    ContextMenuItem::default()
}

fn flat_menus() -> Vec<MenuDef> {
    vec![
        MenuDef {
            id: WidgetId::new("file"),
            label: "&File".into(),
            disabled: false,
            items: vec![
                action("new", "New File"),
                action("open", "Open File"),
                action("save", "Save"),
                separator(),
                action("quit", "Quit"),
            ],
        },
        MenuDef {
            id: WidgetId::new("edit"),
            label: "&Edit".into(),
            disabled: false,
            items: vec![action("undo", "Undo"), action("redo", "Redo")],
        },
    ]
}

fn submenu_menus() -> Vec<MenuDef> {
    vec![MenuDef {
        id: WidgetId::new("view"),
        label: "&View".into(),
        disabled: false,
        items: vec![
            action("sidebar", "Toggle Sidebar"),
            separator(),
            ContextMenuItem {
                id: Some(WidgetId::new("export")),
                label: StyledText::plain("Export"),
                submenu: Some(vec![
                    action("export-png", "PNG"),
                    action("export-svg", "SVG"),
                ]),
                ..Default::default()
            },
        ],
    }]
}

pub struct MenuBarDemo {
    menus: [MenuSystem; 2],
    last_action: [String; 2],
}

impl MenuBarDemo {
    pub fn new() -> Self {
        Self {
            menus: [
                MenuSystem::new(flat_menus()),
                MenuSystem::new(submenu_menus()),
            ],
            last_action: [
                "click a menu or press Alt+F/E".into(),
                "click View, then hover Export".into(),
            ],
        }
    }

    fn bar_rect(area: Rect, backend: &dyn Backend) -> Rect {
        Rect::new(area.x, area.y, area.width, backend.line_height())
    }

    fn hint_bar(&self, variant: usize) -> StatusBar {
        StatusBar {
            id: WidgetId::new(format!("gallery:menu-bar:{variant}:hint")),
            left_segments: vec![StatusBarSegment {
                text: format!(" {} ", self.last_action[variant]),
                fg: Color::rgb(220, 220, 220),
                bg: Color::rgb(30, 30, 30),
                bold: false,
                action_id: None,
            }],
            right_segments: vec![],
        }
    }
}

impl Default for MenuBarDemo {
    fn default() -> Self {
        Self::new()
    }
}

impl Demo for MenuBarDemo {
    fn name(&self) -> &'static str {
        "Menu Bar"
    }

    fn group(&self) -> &'static str {
        "Chrome"
    }

    fn variants(&self) -> &'static [&'static str] {
        &["Dropdown", "Submenu cascade"]
    }

    fn render(&self, variant: usize, backend: &mut dyn Backend, area: Rect) {
        let bar_rect = Self::bar_rect(area, backend);
        self.menus[variant].render(backend, bar_rect);

        let lh = backend.line_height();
        let hint_rect = Rect::new(area.x, area.y + lh, area.width, lh);
        let _ = backend.draw_status_bar_interactive(
            hint_rect,
            &self.hint_bar(variant),
            &InteractionState::new(),
        );
    }

    fn handle(
        &mut self,
        variant: usize,
        event: &UiEvent,
        backend: &mut dyn Backend,
        area: Rect,
    ) -> Reaction {
        let bar_rect = Self::bar_rect(area, backend);
        match self.menus[variant].handle(event, backend, bar_rect) {
            MenuEvent::Activated(id) => {
                self.last_action[variant] = format!("activated: {}", id.as_str());
                Reaction::Redraw
            }
            MenuEvent::StateChanged | MenuEvent::Consumed => Reaction::Redraw,
            MenuEvent::Ignored => Reaction::Continue,
        }
    }

    fn source(&self) -> &'static str {
        extract_region(SOURCE)
    }

    fn data(&self, variant: usize) -> serde_json::Value {
        serde_json::json!({
            "menu_open": self.menus[variant].is_open(),
            "last_action": self.last_action[variant],
        })
    }

    fn caps_note(&self, _variant: usize, _caps: &BackendCaps) -> Option<String> {
        None
    }
}

Backends

VariantTUIGTKmacOSWindows
Dropdown
Submenu cascade