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