Skip to main content

pinstar/
overlay.rs

1//! Marquee drag state + translucent fill (ported from clin's `ui::canvas_overlay`).
2
3use ratatui::{Frame, layout::Rect, style::Color};
4
5pub struct MarqueeState {
6    pub start: Option<(f64, f64)>,
7    pub end: Option<(f64, f64)>,
8    pub threshold_cells: u32,
9}
10
11impl MarqueeState {
12    pub fn new(threshold_cells: u32) -> Self {
13        Self {
14            start: None,
15            end: None,
16            threshold_cells,
17        }
18    }
19
20    pub fn on_down(&mut self, x: f64, y: f64) {
21        self.start = Some((x, y));
22        self.end = Some((x, y));
23    }
24
25    pub fn on_drag(&mut self, x: f64, y: f64) {
26        self.end = Some((x, y));
27    }
28
29    pub fn is_dragging_screen(
30        &self,
31        sx_now: u16,
32        sy_now: u16,
33        sx_start: u16,
34        sy_start: u16,
35    ) -> bool {
36        let dx = sx_now.abs_diff(sx_start);
37        let dy = sy_now.abs_diff(sy_start);
38        u32::from(dx) + u32::from(dy) > self.threshold_cells
39    }
40
41    pub fn commit_rect(&self) -> Option<(f64, f64, f64, f64)> {
42        let (sx, sy) = self.start?;
43        let (ex, ey) = self.end?;
44        Some((sx.min(ex), sy.min(ey), sx.max(ex), sy.max(ey)))
45    }
46
47    pub fn clear(&mut self) {
48        self.start = None;
49        self.end = None;
50    }
51}
52
53/// Marquee fill color shared by hosts. Matches `graf`'s muting logic exactly.
54pub fn muted_canvas_selection_fill(
55    selection_indicator: Option<Color>,
56    accent: Color,
57    highlight_bg: Color,
58) -> Color {
59    let base = selection_indicator.unwrap_or(Color::Reset);
60    match base {
61        Color::Rgb(r, g, b) => Color::Rgb(r / 4, g / 4, b / 4),
62        Color::Reset => Color::DarkGray,
63        _ => match accent {
64            Color::Cyan => Color::Rgb(0, 68, 68),
65            Color::Green => Color::Rgb(0, 68, 34),
66            Color::Yellow => Color::Rgb(68, 68, 0),
67            Color::Magenta => Color::Rgb(68, 0, 68),
68            Color::Red => Color::Rgb(68, 0, 0),
69            Color::Blue => Color::Rgb(0, 0, 68),
70            _ => highlight_bg,
71        },
72    }
73}
74
75/// Translucent marquee fill preserving every underlying glyph and foreground.
76pub fn draw_canvas_rect_filled(frame: &mut Frame, rect: Rect, fill: Color) {
77    let buf = frame.buffer_mut();
78    for row in rect.y..rect.y.saturating_add(rect.height) {
79        for col in rect.x..rect.x.saturating_add(rect.width) {
80            if let Some(cell) = buf.cell_mut((col, row)) {
81                cell.set_bg(fill);
82            }
83        }
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn on_down_sets_start_end() {
93        let mut m = MarqueeState::new(3);
94        m.on_down(1.0, 2.0);
95        assert_eq!(m.start, Some((1.0, 2.0)));
96        assert_eq!(m.end, Some((1.0, 2.0)));
97    }
98
99    #[test]
100    fn on_drag_updates_end() {
101        let mut m = MarqueeState::new(3);
102        m.on_down(0.0, 0.0);
103        m.on_drag(5.0, 7.0);
104        assert_eq!(m.end, Some((5.0, 7.0)));
105    }
106
107    #[test]
108    fn clear_nukes_both() {
109        let mut m = MarqueeState::new(3);
110        m.on_down(0.0, 0.0);
111        m.clear();
112        assert!(m.start.is_none());
113        assert!(m.end.is_none());
114    }
115
116    #[test]
117    fn commit_rect_normalizes_both_directions() {
118        let mut m = MarqueeState::new(3);
119        m.on_down(10.0, 20.0);
120        m.on_drag(0.0, 40.0);
121        assert_eq!(m.commit_rect(), Some((0.0, 20.0, 10.0, 40.0)));
122    }
123
124    #[test]
125    fn is_dragging_screen_manhattan() {
126        let m = MarqueeState::new(3);
127        // Manhattan move of 3 -> not dragging (strict threshold).
128        assert!(!m.is_dragging_screen(3, 0, 0, 0));
129        assert!(!m.is_dragging_screen(2, 1, 0, 0));
130        // Manhattan move of 4 -> dragging.
131        assert!(m.is_dragging_screen(4, 0, 0, 0));
132        assert!(m.is_dragging_screen(2, 2, 0, 0));
133    }
134}