1use ratatui::{
2 style::{Color, Modifier, Style},
3 widgets::{Block, Borders},
4};
5
6use crate::config::config::CellSelectionStyle;
7
8#[derive(Debug, Clone)]
10pub enum CellRenderMode {
11 Underline,
13 Block,
15 Border,
17 Corners,
19 Subtle,
21}
22
23impl From<&str> for CellRenderMode {
24 fn from(s: &str) -> Self {
25 match s.to_lowercase().as_str() {
26 "block" => CellRenderMode::Block,
27 "border" => CellRenderMode::Border,
28 "corners" => CellRenderMode::Corners,
29 "subtle" => CellRenderMode::Subtle,
30 _ => CellRenderMode::Underline,
31 }
32 }
33}
34
35pub struct CellRenderer {
37 style_config: CellSelectionStyle,
38}
39
40impl CellRenderer {
41 #[must_use]
42 pub fn new(style_config: CellSelectionStyle) -> Self {
43 Self { style_config }
44 }
45
46 #[must_use]
48 pub fn get_selected_style(&self) -> Style {
49 let mut style = Style::default();
50
51 style = style.fg(self.parse_color(&self.style_config.foreground));
53
54 if self.style_config.use_background {
56 style = style.bg(self.parse_color(&self.style_config.background));
57 }
58
59 let mode = CellRenderMode::from(self.style_config.mode.as_str());
61 match mode {
62 CellRenderMode::Underline => {
63 if self.style_config.underline {
64 style = style.add_modifier(Modifier::UNDERLINED);
65 }
66 }
67 CellRenderMode::Block => {
68 style = style.add_modifier(Modifier::REVERSED);
70 }
71 CellRenderMode::Border | CellRenderMode::Corners => {
72 style = style
75 .bg(Color::DarkGray) .fg(self.parse_color(&self.style_config.foreground)) .add_modifier(Modifier::BOLD);
78 }
79 CellRenderMode::Subtle => {
80 }
82 }
83
84 if self.style_config.bold {
86 style = style.add_modifier(Modifier::BOLD);
87 }
88
89 style
90 }
91
92 #[must_use]
94 pub fn render_cell_value(&self, value: &str, is_selected: bool, width: usize) -> String {
95 if !is_selected {
96 return value.to_string();
97 }
98
99 let mode = CellRenderMode::from(self.style_config.mode.as_str());
100
101 match mode {
102 CellRenderMode::Border => self.render_with_border(value, width),
103 CellRenderMode::Corners => self.render_with_corners(value, width),
104 _ => value.to_string(),
105 }
106 }
107
108 fn render_with_border(&self, value: &str, width: usize) -> String {
110 let chars = match self.style_config.border_style.as_str() {
111 "double" => ('═', '║', '╔', '╗', '╚', '╝'),
112 "rounded" => ('─', '│', '╭', '╮', '╰', '╯'),
113 "thick" => ('━', '┃', '┏', '┓', '┗', '┛'),
114 _ => ('─', '│', '┌', '┐', '└', '┘'), };
116
117 format!("{}{}{}", chars.2, value, chars.3)
120 }
121
122 fn render_with_corners(&self, value: &str, width: usize) -> String {
124 let corners: Vec<char> = self.style_config.corner_chars.chars().collect();
125 if corners.len() >= 4 {
126 format!("{}{}{}", corners[0], value, corners[1])
128 } else {
129 value.to_string()
130 }
131 }
132
133 fn parse_color(&self, color_str: &str) -> Color {
135 match color_str.to_lowercase().as_str() {
136 "black" => Color::Black,
137 "red" => Color::Red,
138 "green" => Color::Green,
139 "yellow" => Color::Yellow,
140 "blue" => Color::Blue,
141 "magenta" => Color::Magenta,
142 "cyan" => Color::Cyan,
143 "gray" | "grey" => Color::Gray,
144 "dark_gray" | "dark_grey" => Color::DarkGray,
145 "light_red" | "bright_red" => Color::LightRed,
146 "light_green" | "bright_green" => Color::LightGreen,
147 "light_yellow" | "bright_yellow" => Color::LightYellow,
148 "light_blue" | "bright_blue" => Color::LightBlue,
149 "light_magenta" | "bright_magenta" => Color::LightMagenta,
150 "light_cyan" | "bright_cyan" => Color::LightCyan,
151 "white" => Color::White,
152 "orange" => Color::Rgb(255, 165, 0),
153 "purple" => Color::Rgb(128, 0, 128),
154 "teal" => Color::Rgb(0, 128, 128),
155 "pink" => Color::Rgb(255, 192, 203),
156 _ => Color::Yellow, }
158 }
159
160 #[must_use]
162 pub fn get_style_previews() -> Vec<(&'static str, &'static str)> {
163 vec![
164 ("underline", "Classic underline style"),
165 ("block", "Inverse/block selection"),
166 ("border", "Full border around cell"),
167 ("corners", "Corner markers only"),
168 ("subtle", "Just color highlight"),
169 ]
170 }
171}
172
173#[must_use]
175pub fn create_bordered_cell<'a>(content: &str, border_style: &str) -> Block<'a> {
176 let block = Block::default().borders(Borders::ALL);
177
178 match border_style {
179 "double" => block.border_type(ratatui::widgets::BorderType::Double),
180 "rounded" => block.border_type(ratatui::widgets::BorderType::Rounded),
181 "thick" => block.border_type(ratatui::widgets::BorderType::Thick),
182 _ => block,
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn test_cell_render_modes() {
192 let config = CellSelectionStyle::default();
193 let renderer = CellRenderer::new(config);
194
195 let style = renderer.get_selected_style();
196 assert!(style.add_modifier.contains(Modifier::UNDERLINED));
197 assert!(style.add_modifier.contains(Modifier::BOLD));
198 }
199
200 #[test]
201 fn test_color_parsing() {
202 let mut config = CellSelectionStyle::default();
203 config.foreground = "orange".to_string();
204 let renderer = CellRenderer::new(config);
205
206 let style = renderer.get_selected_style();
207 assert_eq!(style.fg, Some(Color::Rgb(255, 165, 0)));
209 }
210}