sql_cli/ui/rendering/
cell_renderer.rs

1use ratatui::{
2    style::{Color, Modifier, Style},
3    widgets::{Block, Borders},
4};
5
6use crate::config::config::CellSelectionStyle;
7
8/// Different visual styles for rendering selected cells
9#[derive(Debug, Clone)]
10pub enum CellRenderMode {
11    /// Traditional underline style
12    Underline,
13    /// Full block/inverse video style
14    Block,
15    /// Border around the cell
16    Border,
17    /// Just corners of the cell
18    Corners,
19    /// Subtle highlight (just color change)
20    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
35/// Renders a cell with the configured selection style
36pub 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    /// Create a style for a selected cell based on configuration
46    pub fn get_selected_style(&self) -> Style {
47        let mut style = Style::default();
48
49        // Parse foreground color
50        style = style.fg(self.parse_color(&self.style_config.foreground));
51
52        // Apply background if configured
53        if self.style_config.use_background {
54            style = style.bg(self.parse_color(&self.style_config.background));
55        }
56
57        // Apply modifiers based on mode
58        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                // Inverse video effect - swap foreground and background
67                style = style.add_modifier(Modifier::REVERSED);
68            }
69            CellRenderMode::Border | CellRenderMode::Corners => {
70                // Use dark gray background (like column highlight) with bright foreground
71                // This is easier on the eyes than cyan background
72                style = style
73                    .bg(Color::DarkGray) // Same as column mode background
74                    .fg(self.parse_color(&self.style_config.foreground)) // Use configured color
75                    .add_modifier(Modifier::BOLD);
76            }
77            CellRenderMode::Subtle => {
78                // Just use the color, no additional modifiers
79            }
80        }
81
82        // Apply bold if configured
83        if self.style_config.bold {
84            style = style.add_modifier(Modifier::BOLD);
85        }
86
87        style
88    }
89
90    /// Render a cell value with optional border/corner decorations
91    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    /// Render value with full border
106    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            _ => ('─', '│', '┌', '┐', '└', '┘'), // single
112        };
113
114        // For inline cell rendering, we can't really do multi-line borders
115        // So we'll just add subtle markers
116        format!("{}{}{}", chars.2, value, chars.3)
117    }
118
119    /// Render value with just corner markers
120    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            // Add subtle corner markers inline
124            format!("{}{}{}", corners[0], value, corners[1])
125        } else {
126            value.to_string()
127        }
128    }
129
130    /// Parse color string to Ratatui Color
131    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, // Default
154        }
155    }
156
157    /// Get a preview of all available styles for configuration UI
158    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
169/// Helper to create bordered cell for special rendering
170pub 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        // Orange should be RGB(255, 165, 0)
203        assert_eq!(style.fg, Some(Color::Rgb(255, 165, 0)));
204    }
205}