use ratatui_core::buffer::Cell;
use ratatui_core::style::{Color, Modifier};
use crate::color::{ColorDepth, Rgb};
use crate::geometry::Padding;
use crate::theme::{Paint, PropValue, StyleProps};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct CellStyle {
pub fg: Option<Rgb>,
pub bg: Option<Rgb>,
pub bold: bool,
pub italic: bool,
pub underline: bool,
pub dim: bool,
}
impl CellStyle {
#[must_use]
pub fn fg(color: Rgb) -> Self {
Self { fg: Some(color), ..Self::default() }
}
#[must_use]
pub fn on(mut self, color: Rgb) -> Self {
self.bg = Some(color);
self
}
#[must_use]
pub fn with_bold(mut self, bold: bool) -> Self {
self.bold = bold;
self
}
#[cfg(test)]
pub(crate) fn apply(self, cell: &mut Cell, depth: ColorDepth) {
self.for_depth(depth).apply(cell);
}
pub(crate) fn for_depth(self, depth: ColorDepth) -> CellPaint {
let mut modifier = Modifier::empty();
modifier.set(Modifier::BOLD, self.bold);
modifier.set(Modifier::ITALIC, self.italic);
modifier.set(Modifier::UNDERLINED, self.underline);
modifier.set(Modifier::DIM, self.dim);
CellPaint { fg: self.fg.map(|fg| to_color(fg, depth)), bg: self.bg.map(|bg| to_color(bg, depth)), modifier }
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct CellPaint {
fg: Option<Color>,
bg: Option<Color>,
modifier: Modifier,
}
impl CellPaint {
pub(crate) fn apply(self, cell: &mut Cell) {
if let Some(fg) = self.fg {
cell.fg = fg;
}
if let Some(bg) = self.bg {
cell.bg = bg;
}
cell.modifier = self.modifier;
}
}
pub(crate) fn to_color(color: Rgb, depth: ColorDepth) -> Color {
match depth {
ColorDepth::TrueColor => Color::Rgb(color.r, color.g, color.b),
ColorDepth::Ansi256 => Color::Indexed(color.to_ansi256()),
ColorDepth::Ansi16 => Color::Indexed(color.to_ansi16()),
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct WidgetStyle {
props: StyleProps,
phase: f32,
}
impl WidgetStyle {
pub(crate) fn new(props: StyleProps, phase: f32) -> Self {
Self { props, phase }
}
#[must_use]
pub(crate) fn without(mut self, key: &str) -> Self {
self.props.remove(key);
self
}
#[must_use]
pub fn color(&self, key: &str) -> Option<Rgb> {
self.props.paint(key).map(|paint: Paint| paint.at(self.phase))
}
#[must_use]
pub fn flag(&self, key: &str) -> bool {
self.props.flag(key)
}
#[must_use]
pub fn cells(&self, key: &str) -> Option<u16> {
self.props.cells(key)
}
#[must_use]
pub fn word(&self, key: &str) -> Option<&'static str> {
self.props.word(key)
}
#[must_use]
pub fn padding(&self) -> Padding {
match self.props.get("padding") {
Some(PropValue::Pair(v, h)) => Padding::symmetric(v, h),
Some(PropValue::Cells(n)) => Padding::all(n),
_ => Padding::default(),
}
}
#[must_use]
pub fn text(&self) -> CellStyle {
CellStyle {
fg: self.color("fg"),
bg: self.color("bg"),
bold: self.flag("bold"),
italic: self.flag("italic"),
underline: self.flag("underline"),
dim: self.flag("dim"),
}
}
#[must_use]
pub fn is_animated(&self) -> bool {
self.props.is_animated()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::theme::ThemeRegistry;
#[test]
fn applies_colours_for_each_depth() {
let mut cell = Cell::default();
CellStyle::fg(Rgb::new(255, 0, 0))
.on(Rgb::new(0, 0, 0))
.with_bold(true)
.apply(&mut cell, ColorDepth::TrueColor);
assert_eq!(cell.fg, Color::Rgb(255, 0, 0));
assert!(cell.modifier.contains(Modifier::BOLD));
CellStyle::fg(Rgb::new(255, 0, 0)).apply(&mut cell, ColorDepth::Ansi256);
assert_eq!(cell.fg, Color::Indexed(196));
assert_eq!(cell.bg, Color::Rgb(0, 0, 0));
assert!(!cell.modifier.contains(Modifier::BOLD));
}
#[test]
fn resolves_theme_properties() {
let (theme, _) = ThemeRegistry::builtin().resolve_or_default("monochrome");
let style = WidgetStyle::new(theme.style("button", Some("primary"), &[]), 0.0);
assert_ne!(style.text().bg, theme.color("accent"));
assert_eq!(style.text().fg, theme.color("accent"));
assert!(style.text().bold);
assert_eq!(style.padding(), Padding::symmetric(0, 2));
}
}