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}