1use ratatui_core::buffer::Cell;
4use ratatui_core::style::{Color, Modifier};
5
6use crate::color::{ColorDepth, Rgb};
7use crate::geometry::Padding;
8use crate::theme::{Paint, PropValue, StyleProps};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12pub struct CellStyle {
13 pub fg: Option<Rgb>,
15 pub bg: Option<Rgb>,
17 pub bold: bool,
19 pub italic: bool,
21 pub underline: bool,
23 pub dim: bool,
25}
26
27impl CellStyle {
28 #[must_use]
30 pub fn fg(color: Rgb) -> Self {
31 Self { fg: Some(color), ..Self::default() }
32 }
33
34 #[must_use]
36 pub fn on(mut self, color: Rgb) -> Self {
37 self.bg = Some(color);
38 self
39 }
40
41 #[must_use]
43 pub fn with_bold(mut self, bold: bool) -> Self {
44 self.bold = bold;
45 self
46 }
47
48 #[cfg(test)]
50 pub(crate) fn apply(self, cell: &mut Cell, depth: ColorDepth) {
51 self.for_depth(depth).apply(cell);
52 }
53
54 pub(crate) fn for_depth(self, depth: ColorDepth) -> CellPaint {
56 let mut modifier = Modifier::empty();
57 modifier.set(Modifier::BOLD, self.bold);
58 modifier.set(Modifier::ITALIC, self.italic);
59 modifier.set(Modifier::UNDERLINED, self.underline);
60 modifier.set(Modifier::DIM, self.dim);
61 CellPaint { fg: self.fg.map(|fg| to_color(fg, depth)), bg: self.bg.map(|bg| to_color(bg, depth)), modifier }
62 }
63}
64
65#[derive(Debug, Clone, Copy)]
69pub(crate) struct CellPaint {
70 fg: Option<Color>,
71 bg: Option<Color>,
72 modifier: Modifier,
73}
74
75impl CellPaint {
76 pub(crate) fn apply(self, cell: &mut Cell) {
78 if let Some(fg) = self.fg {
79 cell.fg = fg;
80 }
81 if let Some(bg) = self.bg {
82 cell.bg = bg;
83 }
84 cell.modifier = self.modifier;
85 }
86}
87
88pub(crate) fn to_color(color: Rgb, depth: ColorDepth) -> Color {
90 match depth {
91 ColorDepth::TrueColor => Color::Rgb(color.r, color.g, color.b),
92 ColorDepth::Ansi256 => Color::Indexed(color.to_ansi256()),
93 ColorDepth::Ansi16 => Color::Indexed(color.to_ansi16()),
94 }
95}
96
97#[derive(Debug, Clone, PartialEq)]
99pub struct WidgetStyle {
100 props: StyleProps,
101 phase: f32,
102}
103
104impl WidgetStyle {
105 pub(crate) fn new(props: StyleProps, phase: f32) -> Self {
106 Self { props, phase }
107 }
108
109 #[must_use]
111 pub fn color(&self, key: &str) -> Option<Rgb> {
112 self.props.paint(key).map(|paint: Paint| paint.at(self.phase))
113 }
114
115 #[must_use]
117 pub fn flag(&self, key: &str) -> bool {
118 self.props.flag(key)
119 }
120
121 #[must_use]
123 pub fn cells(&self, key: &str) -> Option<u16> {
124 self.props.cells(key)
125 }
126
127 #[must_use]
129 pub fn word(&self, key: &str) -> Option<&'static str> {
130 self.props.word(key)
131 }
132
133 #[must_use]
135 pub fn padding(&self) -> Padding {
136 match self.props.get("padding") {
137 Some(PropValue::Pair(v, h)) => Padding::symmetric(v, h),
138 Some(PropValue::Cells(n)) => Padding::all(n),
139 _ => Padding::default(),
140 }
141 }
142
143 #[must_use]
145 pub fn text(&self) -> CellStyle {
146 CellStyle {
147 fg: self.color("fg"),
148 bg: self.color("bg"),
149 bold: self.flag("bold"),
150 italic: self.flag("italic"),
151 underline: self.flag("underline"),
152 dim: self.flag("dim"),
153 }
154 }
155
156 #[must_use]
158 pub fn is_animated(&self) -> bool {
159 self.props.is_animated()
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166 use crate::theme::ThemeRegistry;
167
168 #[test]
169 fn applies_colours_for_each_depth() {
170 let mut cell = Cell::default();
171 CellStyle::fg(Rgb::new(255, 0, 0))
172 .on(Rgb::new(0, 0, 0))
173 .with_bold(true)
174 .apply(&mut cell, ColorDepth::TrueColor);
175 assert_eq!(cell.fg, Color::Rgb(255, 0, 0));
176 assert!(cell.modifier.contains(Modifier::BOLD));
177 CellStyle::fg(Rgb::new(255, 0, 0)).apply(&mut cell, ColorDepth::Ansi256);
178 assert_eq!(cell.fg, Color::Indexed(196));
179 assert_eq!(cell.bg, Color::Rgb(0, 0, 0));
180 assert!(!cell.modifier.contains(Modifier::BOLD));
181 }
182
183 #[test]
184 fn resolves_theme_properties() {
185 let (theme, _) = ThemeRegistry::builtin().resolve_or_default("monochrome");
186 let style = WidgetStyle::new(theme.style("button", Some("primary"), &[]), 0.0);
187 assert_ne!(style.text().bg, theme.color("accent"));
189 assert_eq!(style.text().fg, theme.color("accent"));
190 assert!(style.text().bold);
191 assert_eq!(style.padding(), Padding::symmetric(0, 2));
192 }
193}