1use ratatui::buffer::Buffer;
7use ratatui::style::{Color, Modifier};
8
9#[derive(Debug, Clone)]
11pub struct CellPreview {
12 pub symbol: String,
14 pub fg: Color,
16 pub bg: Color,
18 pub modifier: Modifier,
20}
21
22impl CellPreview {
23 pub fn new(symbol: impl Into<String>, fg: Color, bg: Color, modifier: Modifier) -> Self {
25 Self {
26 symbol: symbol.into(),
27 fg,
28 bg,
29 modifier,
30 }
31 }
32
33 pub fn is_default_style(&self) -> bool {
35 self.fg == Color::Reset && self.bg == Color::Reset && self.modifier.is_empty()
36 }
37}
38
39pub fn inspect_cell(buffer: &Buffer, x: u16, y: u16) -> Option<CellPreview> {
55 let area = buffer.area;
56 if !point_in_rect(area.x, area.y, area.width, area.height, x, y) {
57 return None;
58 }
59
60 let cell = &buffer[(x, y)];
61 Some(CellPreview {
62 symbol: cell.symbol().to_string(),
63 fg: cell.fg,
64 bg: cell.bg,
65 modifier: cell.modifier,
66 })
67}
68
69#[inline]
71pub fn point_in_rect(rect_x: u16, rect_y: u16, width: u16, height: u16, x: u16, y: u16) -> bool {
72 let within_x = x >= rect_x && x < rect_x.saturating_add(width);
73 let within_y = y >= rect_y && y < rect_y.saturating_add(height);
74 within_x && within_y
75}
76
77pub fn format_color_compact(color: Color) -> String {
90 match color {
91 Color::Rgb(r, g, b) => format!("({r},{g},{b})"),
92 Color::Indexed(i) => format!("#{i}"),
93 other => format!("{other:?}"),
94 }
95}
96
97pub fn format_modifier_compact(modifier: Modifier) -> String {
101 if modifier.is_empty() {
102 return String::new();
103 }
104
105 let mut parts = Vec::new();
106 if modifier.contains(Modifier::BOLD) {
107 parts.push("B");
108 }
109 if modifier.contains(Modifier::DIM) {
110 parts.push("D");
111 }
112 if modifier.contains(Modifier::ITALIC) {
113 parts.push("I");
114 }
115 if modifier.contains(Modifier::UNDERLINED) {
116 parts.push("U");
117 }
118 if modifier.contains(Modifier::SLOW_BLINK) || modifier.contains(Modifier::RAPID_BLINK) {
119 parts.push("*");
120 }
121 if modifier.contains(Modifier::REVERSED) {
122 parts.push("R");
123 }
124 if modifier.contains(Modifier::CROSSED_OUT) {
125 parts.push("X");
126 }
127
128 parts.join("")
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use ratatui::layout::Rect;
135
136 #[test]
137 fn test_point_in_rect() {
138 assert!(point_in_rect(0, 0, 10, 10, 5, 5));
139 assert!(point_in_rect(0, 0, 10, 10, 0, 0));
140 assert!(point_in_rect(0, 0, 10, 10, 9, 9));
141 assert!(!point_in_rect(0, 0, 10, 10, 10, 10));
142 assert!(!point_in_rect(5, 5, 10, 10, 4, 5));
143 }
144
145 #[test]
146 fn test_format_color_compact() {
147 assert_eq!(format_color_compact(Color::Rgb(255, 0, 128)), "(255,0,128)");
148 assert_eq!(format_color_compact(Color::Red), "Red");
149 assert_eq!(format_color_compact(Color::Reset), "Reset");
150 assert_eq!(format_color_compact(Color::Indexed(42)), "#42");
151 }
152
153 #[test]
154 fn test_format_modifier_compact() {
155 assert_eq!(format_modifier_compact(Modifier::empty()), "");
156 assert_eq!(format_modifier_compact(Modifier::BOLD), "B");
157 assert_eq!(
158 format_modifier_compact(Modifier::BOLD | Modifier::ITALIC),
159 "BI"
160 );
161 }
162
163 #[test]
164 fn test_inspect_cell() {
165 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
166 buffer[(5, 5)].set_char('X');
167 buffer[(5, 5)].set_fg(Color::Red);
168
169 let preview = inspect_cell(&buffer, 5, 5).unwrap();
170 assert_eq!(preview.symbol, "X");
171 assert_eq!(preview.fg, Color::Red);
172
173 assert!(inspect_cell(&buffer, 20, 20).is_none());
175 }
176
177 #[test]
178 fn test_cell_preview_is_default() {
179 let default = CellPreview::new(" ", Color::Reset, Color::Reset, Modifier::empty());
180 assert!(default.is_default_style());
181
182 let styled = CellPreview::new("X", Color::Red, Color::Reset, Modifier::empty());
183 assert!(!styled.is_default_style());
184 }
185}