1use ratatui::buffer::Buffer;
2use ratatui::layout::Rect;
3use ratatui::style::{Color, Modifier, Style};
4use ratatui::widgets::{Padding, Scrollbar, ScrollbarOrientation, ScrollbarState};
5
6use crate::popup::PopupSize;
7
8pub const BORDER_GRAY: Color = Color::Rgb(100, 100, 100);
11pub const OUTPUT_GRAY: Color = Color::Rgb(120, 120, 120);
12pub const FOOTER_GRAY: Color = Color::Rgb(140, 140, 140);
13
14pub const CHECK: &str = "✓";
17pub const CROSS: &str = "✗";
18
19pub const HIGHLIGHT_SYMBOL: &str = "> ";
20
21pub const BACKGROUND_DOT_SYMBOL: &str = "+";
22
23pub const POPUP_WIDTH: PopupSize = PopupSize::Fixed(44);
26
27pub const DEFAULT_HIGHLIGHT: Style = Style::new()
30 .fg(Color::Blue)
31 .bg(Color::Black)
32 .add_modifier(Modifier::REVERSED);
33
34pub const TEXT_PADDING: Padding = Padding::new(1, 1, 0, 0);
37
38pub const DEFAULT_FRAMES: &[&str] = &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
46pub enum DotPattern {
47 Diagonal,
49 #[default]
51 Grid,
52}
53
54pub fn decorate_with_dots(buf: &mut Buffer, area: Rect, color: Color, symbol: &str, density: u16) {
80 decorate_with_dots_with_pattern(buf, area, color, symbol, density, density, DotPattern::default());
81}
82
83pub fn decorate_with_dots_with_pattern(
85 buf: &mut Buffer,
86 area: Rect,
87 color: Color,
88 symbol: &str,
89 density_x: u16,
90 density_y: u16,
91 pattern: DotPattern,
92) {
93 if density_x == 0 || density_y == 0 {
94 return;
95 }
96 for y in area.y..area.bottom() {
97 for x in area.x..area.right() {
98 let hit = match pattern {
99 DotPattern::Diagonal => (x + y) % density_x == 0,
100 DotPattern::Grid => x % density_x == 0 && y % density_y == 0,
101 };
102 if !hit {
103 continue;
104 }
105 let cell = &mut buf[(x, y)];
106 if cell.symbol() == " " {
107 cell.set_symbol(symbol);
108 cell.set_fg(color);
109 }
110 }
111 }
112}
113
114pub fn default_scrollbar() -> Scrollbar<'static> {
115 Scrollbar::new(ScrollbarOrientation::VerticalRight)
116 .begin_symbol(None)
117 .end_symbol(None)
118}
119
120pub fn default_scrollbar_state(total: usize, position: usize) -> ScrollbarState {
121 ScrollbarState::new(total).position(position)
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn decorate_with_dots_paints_empty_cells() {
130 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 5));
131 decorate_with_dots(&mut buf, Rect::new(0, 0, 10, 5), Color::Red, "·", 2);
132 assert_eq!(buf[(0, 0)].symbol(), "·");
134 assert_eq!(buf[(0, 0)].fg, Color::Red);
135 assert_eq!(buf[(2, 0)].symbol(), "·");
136 assert_eq!(buf[(0, 2)].symbol(), "·");
137 assert_eq!(buf[(2, 2)].symbol(), "·");
138 assert_eq!(buf[(1, 0)].symbol(), " ");
140 assert_eq!(buf[(0, 1)].symbol(), " ");
141 assert_eq!(buf[(1, 1)].symbol(), " ");
142 }
143
144 #[test]
145 fn decorate_with_dots_skips_non_empty_cells() {
146 let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
147 buf[(2, 0)].set_symbol("X");
148 decorate_with_dots(&mut buf, Rect::new(0, 0, 5, 1), Color::Blue, "·", 1);
149 assert_eq!(buf[(2, 0)].symbol(), "X");
150 assert_eq!(buf[(0, 0)].symbol(), "·");
151 assert_eq!(buf[(3, 0)].symbol(), "·");
152 }
153
154 #[test]
155 fn decorate_with_dots_density_5() {
156 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 1));
157 decorate_with_dots(&mut buf, Rect::new(0, 0, 10, 1), Color::White, "·", 5);
158 assert_eq!(buf[(0, 0)].symbol(), "·");
159 assert_eq!(buf[(5, 0)].symbol(), "·");
160 assert_eq!(buf[(1, 0)].symbol(), " ");
161 assert_eq!(buf[(3, 0)].symbol(), " ");
162 }
163
164 #[test]
165 fn decorate_with_dots_respects_area_bounds() {
166 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 10));
167 decorate_with_dots(&mut buf, Rect::new(5, 5, 3, 3), Color::Green, "·", 1);
168 assert_eq!(buf[(5, 5)].symbol(), "·");
169 assert_eq!(buf[(7, 7)].symbol(), "·");
170 assert_eq!(buf[(0, 0)].symbol(), " ");
171 assert_eq!(buf[(8, 5)].symbol(), " ");
172 }
173
174 #[test]
175 fn decorate_with_dots_density_zero_is_noop() {
176 let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
177 decorate_with_dots(&mut buf, Rect::new(0, 0, 5, 1), Color::Red, "·", 0);
178 assert_eq!(buf[(0, 0)].symbol(), " ");
179 }
180
181 #[test]
182 fn decorate_with_dots_custom_symbol() {
183 let mut buf = Buffer::empty(Rect::new(0, 0, 3, 1));
184 decorate_with_dots(&mut buf, Rect::new(0, 0, 3, 1), Color::Cyan, "•", 1);
185 assert_eq!(buf[(0, 0)].symbol(), "•");
186 assert_eq!(buf[(0, 0)].fg, Color::Cyan);
187 }
188
189 #[test]
192 fn decorate_with_dots_grid_pattern() {
193 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 5));
194 decorate_with_dots_with_pattern(
195 &mut buf, Rect::new(0, 0, 10, 5), Color::Red, "·", 4, 2, DotPattern::Grid,
196 );
197 assert_eq!(buf[(0, 0)].symbol(), "·");
198 assert_eq!(buf[(4, 0)].symbol(), "·");
199 assert_eq!(buf[(0, 4)].symbol(), "·");
200 assert_eq!(buf[(4, 4)].symbol(), "·");
201 assert_eq!(buf[(1, 1)].symbol(), " ");
202 assert_eq!(buf[(2, 2)].symbol(), " ");
203 }
204
205 #[test]
206 fn decorate_with_dots_default_is_grid() {
207 let mut buf = Buffer::empty(Rect::new(0, 0, 4, 1));
208 decorate_with_dots(&mut buf, Rect::new(0, 0, 4, 1), Color::White, "·", 2);
209 assert_eq!(buf[(0, 0)].symbol(), "·");
211 assert_eq!(buf[(2, 0)].symbol(), "·");
212 assert_eq!(buf[(1, 0)].symbol(), " ");
213 assert_eq!(buf[(3, 0)].symbol(), " ");
214 }
215
216 #[test]
217 fn dot_pattern_default_is_grid() {
218 assert_eq!(DotPattern::default(), DotPattern::Grid);
219 }
220
221 #[test]
222 fn decorate_with_dots_color_persists() {
223 let mut buf = Buffer::empty(Rect::new(0, 0, 4, 1));
224 decorate_with_dots(&mut buf, Rect::new(0, 0, 4, 1), Color::Cyan, "·", 2);
225 assert_eq!(buf[(0, 0)].fg, Color::Cyan);
226 }
227}