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

Help Overlay

Group: Overlays

Code

const EXPLORER_PANEL: &str = "gallery:help:explorer";
const GIT_PANEL: &str = "gallery:help:git";
const SETTINGS_PANEL: &str = "gallery:help:settings";

pub struct HelpOverlayDemo {
    registry: HelpRegistry,
    overlay: HelpOverlayController,
}

impl HelpOverlayDemo {
    pub fn new() -> Self {
        let mut registry = HelpRegistry::new();
        registry.register(
            EXPLORER_PANEL,
            ViewHelp::new("Explorer")
                .with_notes(vec![
                    HelpNote::new("\u{25cf}", "File has unsaved changes"),
                    HelpNote::new("M", "File modified since last commit"),
                ])
                .with_actions(vec![
                    HelpAction::new("explorer.new_file", "New File", "Create a new file")
                        .with_accelerator("Ctrl+N"),
                    HelpAction::new(
                        "explorer.reveal",
                        "Reveal in Finder",
                        "Show the selected file on disk",
                    )
                    .with_accelerator("Ctrl+Shift+R"),
                ]),
        );
        registry.register(
            GIT_PANEL,
            ViewHelp::new("Source Control")
                .with_notes(vec![
                    HelpNote::new("M", "Modified"),
                    HelpNote::new("A", "Added"),
                    HelpNote::new("U", "Untracked"),
                ])
                .with_actions(vec![
                    HelpAction::new("git.commit", "Commit", "Commit staged changes")
                        .with_accelerator("Ctrl+Enter"),
                    HelpAction::new(
                        "git.discard",
                        "Discard Changes",
                        "Revert the selected file to HEAD",
                    ),
                ]),
        );
        // `SETTINGS_PANEL` is deliberately never registered — the
        // "no help available" fallback this demo exists to show.

        Self {
            registry,
            overlay: HelpOverlayController::new().with_id("gallery:help:cheatsheet"),
        }
    }

    fn panel_id(variant: usize) -> &'static str {
        match variant {
            0 => EXPLORER_PANEL,
            1 => GIT_PANEL,
            _ => SETTINGS_PANEL,
        }
    }

    fn panel_label(variant: usize) -> &'static str {
        match variant {
            0 => "Explorer",
            1 => "Source Control",
            _ => "Settings (no help registered)",
        }
    }

    fn active_view_help(&self, variant: usize) -> Option<&ViewHelp> {
        self.registry.get(Self::panel_id(variant))
    }

    fn status_bar(variant: usize, open: bool) -> StatusBar {
        let hint = if open {
            " Esc or ? to close "
        } else {
            " ? = open help for this panel "
        };
        StatusBar {
            id: WidgetId::new("gallery:help:status"),
            left_segments: vec![StatusBarSegment {
                text: format!(" Panel: {} ", Self::panel_label(variant)),
                fg: Color::rgb(200, 200, 200),
                bg: Color::rgb(30, 30, 30),
                bold: false,
                action_id: None,
            }],
            right_segments: vec![StatusBarSegment {
                text: hint.into(),
                fg: Color::rgb(140, 200, 255),
                bg: Color::rgb(20, 40, 70),
                bold: true,
                action_id: None,
            }],
        }
    }
}

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

impl Demo for HelpOverlayDemo {
    fn name(&self) -> &'static str {
        "Help Overlay"
    }

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

    fn variants(&self) -> &'static [&'static str] {
        &["Explorer", "Source Control", "Settings (no help)"]
    }

    fn render(&self, variant: usize, backend: &mut dyn Backend, area: Rect) {
        let lh = backend.line_height();
        let bar_rect = Rect::new(area.x, area.y, area.width, lh);
        let _ = backend.draw_status_bar_interactive(
            bar_rect,
            &Self::status_bar(variant, self.overlay.is_open()),
            &InteractionState::new(),
        );

        self.overlay
            .render(area, backend, self.active_view_help(variant));
    }

    fn handle(
        &mut self,
        _variant: usize,
        event: &UiEvent,
        _backend: &mut dyn Backend,
        _area: Rect,
    ) -> Reaction {
        match self.overlay.handle(event) {
            HelpOverlayEvent::Opened | HelpOverlayEvent::Closed | HelpOverlayEvent::Consumed => {
                Reaction::Redraw
            }
            HelpOverlayEvent::Ignored => Reaction::Continue,
        }
    }

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

    fn data(&self, variant: usize) -> serde_json::Value {
        serde_json::json!({
            "panel": Self::panel_label(variant),
            "has_help": self.active_view_help(variant).is_some(),
            "overlay_open": self.overlay.is_open(),
        })
    }

    fn caps_note(&self, _variant: usize, _caps: &BackendCaps) -> Option<String> {
        // `HelpOverlayController` is an in-canvas compose controller with
        // no `Backend` capability flag — it works identically on every
        // backend, including the Settings variant's no-help fallback.
        None
    }
}

Backends

VariantTUIGTKmacOSWindows
Explorer
Source Control
Settings (no help)