Skip to main content

tess/overlay/
mod.rs

1//! Overlay subsystem: full-screen popups that take over the body+status
2//! area (file picker, help). App holds an `Option<Box<dyn Overlay>>`;
3//! events route through it first when present.
4
5use std::borrow::Cow;
6use crossterm::event::{KeyEvent, MouseEvent};
7
8use crate::input::Command;
9
10pub mod picker;
11pub mod help;
12pub mod tag_picker;
13
14#[derive(Debug)]
15pub struct OverlayFrame {
16    /// Body lines (no styling info — overlay renders are plain text in v1).
17    pub body: Vec<String>,
18    /// Status line (rendered as the last row, like the regular status).
19    pub status: String,
20}
21
22#[derive(Debug)]
23pub enum OverlayOutcome {
24    /// Keep the overlay open, just redraw.
25    Stay,
26    /// Close the overlay; no follow-up command.
27    Close,
28    /// Close the overlay, then dispatch one command through the normal app loop.
29    CloseAnd(Command),
30    /// Stay open; dispatch one command, then call `refresh` on the overlay
31    /// so it can re-derive its visible set from the new app state.
32    Apply(Command),
33    /// Stay open; flash a short message in the status row for ~1.5 seconds.
34    Refuse(&'static str),
35}
36
37/// Borrowed slice of app state that overlays need at refresh time. Kept
38/// narrow so the trait doesn't pull in the whole app.
39pub struct OverlayContext<'a> {
40    pub file_set: &'a crate::file_set::FileSet,
41}
42
43pub trait Overlay {
44    fn handle_key(&mut self, key: KeyEvent) -> OverlayOutcome;
45    fn handle_mouse(&mut self, _ev: MouseEvent, _body_rows: u16) -> OverlayOutcome {
46        OverlayOutcome::Stay
47    }
48    /// Render against a (width, height) viewport. Height includes the status row.
49    fn render(&self, width: u16, height: u16) -> OverlayFrame;
50    fn title(&self) -> Cow<'_, str>;
51    /// Called after `Apply(cmd)` dispatches, so the overlay can re-derive
52    /// state (e.g. picker rebuilds visible after a DropFileAt).
53    fn refresh(&mut self, _ctx: OverlayContext) {}
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    // A trivial overlay used only to verify the trait compiles and dispatch
61    // path can box it.
62    struct Noop;
63    impl Overlay for Noop {
64        fn handle_key(&mut self, _k: KeyEvent) -> OverlayOutcome { OverlayOutcome::Close }
65        fn render(&self, _w: u16, _h: u16) -> OverlayFrame {
66            OverlayFrame { body: vec![], status: String::new() }
67        }
68        fn title(&self) -> Cow<'_, str> { Cow::Borrowed("noop") }
69    }
70
71    #[test]
72    fn overlay_trait_is_object_safe() {
73        let _: Box<dyn Overlay> = Box::new(Noop);
74    }
75}