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}