1use 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
53pub 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
75pub 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 assert!(!m.is_dragging_screen(3, 0, 0, 0));
129 assert!(!m.is_dragging_screen(2, 1, 0, 0));
130 assert!(m.is_dragging_screen(4, 0, 0, 0));
132 assert!(m.is_dragging_screen(2, 2, 0, 0));
133 }
134}