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 pub fn new(style_config: CellSelectionStyle) -> Self {
42 Self { style_config }
43 }
44
45 pub fn get_selected_style(&self) -> Style {
47 let mut style = Style::default();
48
49 style = style.fg(self.parse_color(&self.style_config.foreground));
51
52 if self.style_config.use_background {
54 style = style.bg(self.parse_color(&self.style_config.background));
55 }
56
57 let mode = CellRenderMode::from(self.style_config.mode.as_str());
59 match mode {
60 CellRenderMode::Underline => {
61 if self.style_config.underline {
62 style = style.add_modifier(Modifier::UNDERLINED);
63 }
64 }
65 CellRenderMode::Block => {
66 style = style.add_modifier(Modifier::REVERSED);
68 }
69 CellRenderMode::Border | CellRenderMode::Corners => {
70 style = style
73 .bg(Color::DarkGray) .fg(self.parse_color(&self.style_config.foreground)) .add_modifier(Modifier::BOLD);
76 }
77 CellRenderMode::Subtle => {
78 }
80 }
81
82 if self.style_config.bold {
84 style = style.add_modifier(Modifier::BOLD);
85 }
86
87 style
88 }
89
90 pub fn render_cell_value(&self, value: &str, is_selected: bool, width: usize) -> String {
92 if !is_selected {
93 return value.to_string();
94 }
95
96 let mode = CellRenderMode::from(self.style_config.mode.as_str());
97
98 match mode {
99 CellRenderMode::Border => self.render_with_border(value, width),
100 CellRenderMode::Corners => self.render_with_corners(value, width),
101 _ => value.to_string(),
102 }
103 }
104
105 fn render_with_border(&self, value: &str, width: usize) -> String {
107 let chars = match self.style_config.border_style.as_str() {
108 "double" => ('═', '║', '╔', '╗', '╚', '╝'),
109 "rounded" => ('─', '│', '╭', '╮', '╰', '╯'),
110 "thick" => ('━', '┃', '┏', '┓', '┗', '┛'),
111 _ => ('─', '│', '┌', '┐', '└', '┘'), };
113
114 format!("{}{}{}", chars.2, value, chars.3)
117 }
118
119 fn render_with_corners(&self, value: &str, width: usize) -> String {
121 let corners: Vec<char> = self.style_config.corner_chars.chars().collect();
122 if corners.len() >= 4 {
123 format!("{}{}{}", corners[0], value, corners[1])
125 } else {
126 value.to_string()
127 }
128 }
129
130 fn parse_color(&self, color_str: &str) -> Color {
132 match color_str.to_lowercase().as_str() {
133 "black" => Color::Black,
134 "red" => Color::Red,
135 "green" => Color::Green,
136 "yellow" => Color::Yellow,
137 "blue" => Color::Blue,
138 "magenta" => Color::Magenta,
139 "cyan" => Color::Cyan,
140 "gray" | "grey" => Color::Gray,
141 "dark_gray" | "dark_grey" => Color::DarkGray,
142 "light_red" | "bright_red" => Color::LightRed,
143 "light_green" | "bright_green" => Color::LightGreen,
144 "light_yellow" | "bright_yellow" => Color::LightYellow,
145 "light_blue" | "bright_blue" => Color::LightBlue,
146 "light_magenta" | "bright_magenta" => Color::LightMagenta,
147 "light_cyan" | "bright_cyan" => Color::LightCyan,
148 "white" => Color::White,
149 "orange" => Color::Rgb(255, 165, 0),
150 "purple" => Color::Rgb(128, 0, 128),
151 "teal" => Color::Rgb(0, 128, 128),
152 "pink" => Color::Rgb(255, 192, 203),
153 _ => Color::Yellow, }
155 }
156
157 pub fn get_style_previews() -> Vec<(&'static str, &'static str)> {
159 vec![
160 ("underline", "Classic underline style"),
161 ("block", "Inverse/block selection"),
162 ("border", "Full border around cell"),
163 ("corners", "Corner markers only"),
164 ("subtle", "Just color highlight"),
165 ]
166 }
167}
168
169pub fn create_bordered_cell<'a>(content: &str, border_style: &str) -> Block<'a> {
171 let block = Block::default().borders(Borders::ALL);
172
173 match border_style {
174 "double" => block.border_type(ratatui::widgets::BorderType::Double),
175 "rounded" => block.border_type(ratatui::widgets::BorderType::Rounded),
176 "thick" => block.border_type(ratatui::widgets::BorderType::Thick),
177 _ => block,
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn test_cell_render_modes() {
187 let config = CellSelectionStyle::default();
188 let renderer = CellRenderer::new(config);
189
190 let style = renderer.get_selected_style();
191 assert!(style.add_modifier.contains(Modifier::UNDERLINED));
192 assert!(style.add_modifier.contains(Modifier::BOLD));
193 }
194
195 #[test]
196 fn test_color_parsing() {
197 let mut config = CellSelectionStyle::default();
198 config.foreground = "orange".to_string();
199 let renderer = CellRenderer::new(config);
200
201 let style = renderer.get_selected_style();
202 assert_eq!(style.fg, Some(Color::Rgb(255, 165, 0)));
204 }
205}