use crate::{
color::get_hex,
color::{AnsiColor, Color8},
theme::ColorTable,
};
#[derive(Debug, Clone, Copy)]
pub enum CssType {
Svg,
Html,
}
#[cfg_attr(feature = "wasm", wasm_bindgen::prelude::wasm_bindgen)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[derive(Debug, Clone, Copy)]
pub enum Mode {
Dark,
Light,
}
#[derive(Debug, Clone)]
pub enum NodeStyle {
Bold,
Blink,
Dim,
Italic,
Underline,
Hide,
Row,
Text,
Main,
Strike,
}
#[derive(Debug, Clone, Default)]
pub struct Style {
pub colors: Vec<AnsiColor>,
pub bg_colors: Vec<AnsiColor>,
pub bold: bool,
pub blink: bool,
pub dim: bool,
pub italic: bool,
pub underline: bool,
pub hide: bool,
pub strike: bool,
}
impl NodeStyle {
pub fn class_name(&self) -> &'static str {
match self {
NodeStyle::Bold => "b",
NodeStyle::Blink => "B",
NodeStyle::Dim => "d",
NodeStyle::Italic => "i",
NodeStyle::Underline => "u",
NodeStyle::Hide => "h",
NodeStyle::Row => "r",
NodeStyle::Text => "t",
NodeStyle::Main => "m",
NodeStyle::Strike => "s",
}
}
}
impl Style {
pub fn add_color(&mut self, c: AnsiColor) {
if !self.colors.contains(&c) {
self.colors.push(c);
}
}
pub fn add_bg_color(&mut self, c: AnsiColor) {
if !self.bg_colors.contains(&c) {
self.bg_colors.push(c);
}
}
#[allow(clippy::too_many_arguments)]
pub fn to_css<T: ColorTable>(
&self,
theme: T,
ty: CssType,
mode: Option<Mode>,
light_bg: Option<String>,
dark_bg: Option<String>,
font_family: String,
font_size: usize,
) -> String {
let mut css = String::new();
match ty {
CssType::Svg => {
let text_style = format!(
r#".{}{{dominant-baseline:central;font-variant-ligatures:none;white-space: pre;font-family:{font_family};font-size:{font_size}px;}}"#,
NodeStyle::Text.class_name()
);
css.push_str(&text_style);
}
CssType::Html => {
let main_style = format!(
".{}{{display:flex;flex-direction:column;}}",
NodeStyle::Main.class_name()
);
css.push_str(&main_style);
let row_style = format!(".{}{{display:flex;}}", NodeStyle::Row.class_name());
css.push_str(&row_style);
let text_style = format!(
".{}{{margin:0;padding:0;font-family:{font_family};white-space:pre;display:inline-block;font-size:{font_size}px}}",
NodeStyle::Text.class_name()
);
css.push_str(&text_style);
}
}
if self.bold {
css.push_str(&format!(
".{}{{font-weight:bold}}",
NodeStyle::Bold.class_name()
));
}
if self.hide {
css.push_str(&format!(".{}{{opacity:0}}", NodeStyle::Hide.class_name()));
}
if self.dim {
css.push_str(&format!(
".{}{{font-weight:lighter;opacity:0.5}}",
NodeStyle::Dim.class_name()
));
}
if self.italic {
css.push_str(&format!(
".{}{{font-style:italic}}",
NodeStyle::Italic.class_name()
));
}
if self.underline {
css.push_str(&format!(
".{}{{text-decoration:underline}}",
NodeStyle::Underline.class_name()
));
}
if self.strike {
css.push_str(&format!(
".{}{{text-decoration:line-through}}",
NodeStyle::Strike.class_name()
));
}
if self.blink {
css.push_str(&format!(
".{}{{animation:bk 1s steps(1, end) infinite;}} @keyframes bk{{50% {{opacity: 0}}}}",
NodeStyle::Blink.class_name()
));
}
let dark_bg_color = dark_bg.unwrap_or("#181818".to_string());
let light_bg_color = light_bg.unwrap_or("#FFFFFF".to_string());
let (color_field, bg_field) = match ty {
CssType::Html => ("color", "background"),
CssType::Svg => ("fill", "fill"),
};
let light_color_css: String = self.colors.iter().fold(String::new(), |mut acc, c| {
acc.push_str(&format!(
".{}{{{color_field}:{}}}",
c.class_name(),
c.get_hex(theme)
));
acc
});
let bg_light_color_css: String = self.bg_colors.iter().fold(String::new(), |mut acc, c| {
acc.push_str(&format!(
".{}{{{bg_field}:{}}}",
c.bg_class_name(),
c.get_hex(theme)
));
acc
});
let dark_color_css: String = self.colors.iter().fold(String::new(), |mut acc, c| {
let hex = match c {
AnsiColor::Color8(color8) => match color8 {
Color8::Black => Color8::White.get_hex(theme),
Color8::White => Color8::Black.get_hex(theme),
Color8::BrightBlack => Color8::BrightWhite.get_hex(theme),
Color8::BrightWhite => Color8::BrightBlack.get_hex(theme),
_ => color8.get_hex(theme),
},
_ => c.get_hex(theme),
};
acc.push_str(&format!(".{}{{{color_field}:{}}}", c.class_name(), hex));
acc
});
let bg_dark_color_css: String = self.bg_colors.iter().fold(String::new(), |mut acc, c| {
let hex = match c {
AnsiColor::Color8(color8) => match color8 {
Color8::Black => Color8::White.get_hex(theme),
Color8::White => Color8::Black.get_hex(theme),
Color8::BrightBlack => Color8::BrightWhite.get_hex(theme),
Color8::BrightWhite => Color8::BrightBlack.get_hex(theme),
_ => color8.get_hex(theme),
},
_ => c.get_hex(theme),
};
acc.push_str(&format!(".{}{{{bg_field}:{}}}", c.bg_class_name(), hex));
acc
});
if let Some(mode) = mode {
let default_text_style = match (mode, ty) {
(Mode::Dark, CssType::Html) => {
format!("div{{color: {} }}", get_hex(theme.white()))
}
(Mode::Dark, CssType::Svg) => {
format!("svg > text{{fill:{}}}", get_hex(theme.white()))
}
(Mode::Light, CssType::Html) => {
format!("div{{color:{}}}", get_hex(theme.black()))
}
(Mode::Light, CssType::Svg) => {
format!("svg > text{{fill:{}}}", get_hex(theme.black()))
}
};
css.push_str(&default_text_style);
let (color_css, bg_color_css) = match mode {
Mode::Dark => (dark_color_css, bg_dark_color_css),
Mode::Light => (light_color_css, bg_light_color_css),
};
let root_style = match mode {
Mode::Dark => format!(":root{{background:{dark_bg_color}}}"),
Mode::Light => format!(":root{{background:{light_bg_color}}}"),
};
css.push_str(&root_style);
css.push_str(&color_css);
css.push_str(&bg_color_css);
return css;
}
let default_light_text_style = match ty {
CssType::Svg => format!("svg > text{{fill:{}}}", get_hex(theme.black())),
CssType::Html => format!("div{{color:{}}}", get_hex(theme.black())),
};
let default_dark_text_style = match ty {
CssType::Svg => format!("svg > text{{fill:{}}}", get_hex(theme.white())),
CssType::Html => format!("div{{color:{}}}", get_hex(theme.white())),
};
let root_css = format!(
r#":root {{color-scheme: light dark; background: {light_bg_color}}}{light_color_css}{bg_light_color_css}{default_light_text_style}"#
);
let dark_css = format!(
r#"@media (prefers-color-scheme: dark) {{:root {{background: {dark_bg_color}}}{dark_color_css}{bg_dark_color_css}{default_dark_text_style}}}"#)
.trim()
.to_string();
css.push_str(&root_css);
css.push_str(&dark_css);
css
}
}