git_iris/theme/adapters/
ratatui.rs

1//! Ratatui adapter for theme types.
2//!
3//! Provides conversion from theme types to Ratatui types for TUI rendering.
4
5use ratatui::style::{Color, Modifier, Style};
6
7use crate::theme::{Gradient, ThemeColor, ThemeStyle};
8
9/// Convert a `ThemeColor` to a Ratatui `Color`.
10pub trait ToRatatuiColor {
11    /// Convert to Ratatui Color.
12    fn to_ratatui(&self) -> Color;
13}
14
15impl ToRatatuiColor for ThemeColor {
16    fn to_ratatui(&self) -> Color {
17        Color::Rgb(self.r, self.g, self.b)
18    }
19}
20
21/// Convert a `ThemeStyle` to a Ratatui `Style`.
22pub trait ToRatatuiStyle {
23    /// Convert to Ratatui Style.
24    fn to_ratatui(&self) -> Style;
25}
26
27impl ToRatatuiStyle for ThemeStyle {
28    fn to_ratatui(&self) -> Style {
29        let mut style = Style::default();
30
31        if let Some(fg) = self.fg {
32            style = style.fg(fg.to_ratatui());
33        }
34
35        if let Some(bg) = self.bg {
36            style = style.bg(bg.to_ratatui());
37        }
38
39        let mut modifiers = Modifier::empty();
40        if self.bold {
41            modifiers |= Modifier::BOLD;
42        }
43        if self.italic {
44            modifiers |= Modifier::ITALIC;
45        }
46        if self.underline {
47            modifiers |= Modifier::UNDERLINED;
48        }
49        if self.dim {
50            modifiers |= Modifier::DIM;
51        }
52
53        if !modifiers.is_empty() {
54            style = style.add_modifier(modifiers);
55        }
56
57        style
58    }
59}
60
61/// Extension trait for easy access to theme colors as Ratatui Colors.
62pub trait ThemeColorExt {
63    /// Get a token color as Ratatui Color.
64    fn ratatui_color(&self, token: &str) -> Color;
65
66    /// Get a token style as Ratatui Style.
67    fn ratatui_style(&self, name: &str) -> Style;
68
69    /// Get a gradient color as Ratatui Color.
70    fn ratatui_gradient(&self, name: &str, t: f32) -> Color;
71}
72
73impl ThemeColorExt for crate::theme::Theme {
74    fn ratatui_color(&self, token: &str) -> Color {
75        self.color(token).to_ratatui()
76    }
77
78    fn ratatui_style(&self, name: &str) -> Style {
79        self.style(name).to_ratatui()
80    }
81
82    fn ratatui_gradient(&self, name: &str, t: f32) -> Color {
83        self.gradient(name, t).to_ratatui()
84    }
85}
86
87/// Generate styled spans for gradient text.
88///
89/// Creates a vector of single-character spans, each colored according to
90/// its position in the gradient.
91#[allow(clippy::cast_precision_loss, clippy::as_conversions)]
92pub fn gradient_spans(text: &str, gradient: &Gradient) -> Vec<ratatui::text::Span<'static>> {
93    let len = text.chars().count().max(1);
94    text.chars()
95        .enumerate()
96        .map(|(i, c)| {
97            let t = if len == 1 {
98                0.0
99            } else {
100                i as f32 / (len - 1) as f32
101            };
102            let color = gradient.at(t).to_ratatui();
103            ratatui::text::Span::styled(c.to_string(), Style::default().fg(color))
104        })
105        .collect()
106}
107
108/// Generate a gradient line of a given character.
109#[allow(clippy::cast_precision_loss, clippy::as_conversions)]
110pub fn gradient_line(
111    width: usize,
112    ch: char,
113    gradient: &Gradient,
114) -> Vec<ratatui::text::Span<'static>> {
115    (0..width)
116        .map(|i| {
117            let t = if width <= 1 {
118                0.0
119            } else {
120                i as f32 / (width - 1) as f32
121            };
122            let color = gradient.at(t).to_ratatui();
123            ratatui::text::Span::styled(ch.to_string(), Style::default().fg(color))
124        })
125        .collect()
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use crate::theme::ThemeColor;
132
133    #[test]
134    fn test_color_conversion() {
135        let theme_color = ThemeColor::new(225, 53, 255);
136        let ratatui_color = theme_color.to_ratatui();
137        assert_eq!(ratatui_color, Color::Rgb(225, 53, 255));
138    }
139
140    #[test]
141    fn test_style_conversion() {
142        let theme_style = ThemeStyle::fg(ThemeColor::new(255, 0, 0))
143            .with_bg(ThemeColor::new(0, 0, 0))
144            .bold()
145            .italic();
146
147        let ratatui_style = theme_style.to_ratatui();
148
149        assert_eq!(ratatui_style.fg, Some(Color::Rgb(255, 0, 0)));
150        assert_eq!(ratatui_style.bg, Some(Color::Rgb(0, 0, 0)));
151        assert!(ratatui_style.add_modifier.contains(Modifier::BOLD));
152        assert!(ratatui_style.add_modifier.contains(Modifier::ITALIC));
153    }
154}