use std::{collections::HashMap, str::FromStr};
use ratatui::style::{Color, Modifier, Style};
use serde::Deserialize;
use crate::{iterator_ext::IteratorExtensions, ratatui_theme::ResolvedPalette};
#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum StyleType {
Normal(Style),
Raw(RawStyle),
FgOnlyNormal(Color),
FgOnlyPalette(String),
}
#[derive(Debug, Deserialize, Clone)]
struct RawStyle {
#[serde(skip_serializing_if = "Option::is_none")]
pub fg: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bg: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub underline_color: Option<String>,
#[serde(
default,
skip_serializing_if = "Modifier::is_empty",
deserialize_with = "deserialize_modifier"
)]
pub add_modifier: Modifier,
#[serde(
default,
skip_serializing_if = "Modifier::is_empty",
deserialize_with = "deserialize_modifier"
)]
pub sub_modifier: Modifier,
}
impl RawStyle {
fn into_style(self, palette: &HashMap<String, Color>) -> Style {
Style {
fg: self.fg.and_then(|fg| palette.get(&fg).copied()),
bg: self.bg.and_then(|bg| palette.get(&bg).copied()),
underline_color: self
.underline_color
.and_then(|uc| palette.get(&uc).copied()),
add_modifier: self.add_modifier,
sub_modifier: self.sub_modifier,
}
}
}
impl FromStr for RawStyle {
type Err = toml::de::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
toml::from_str(s)
}
}
fn deserialize_modifier<'de, D>(deserializer: D) -> Result<Modifier, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::Deserialize;
Option::<Modifier>::deserialize(deserializer)
.map(|modifier| modifier.unwrap_or_else(Modifier::empty))
}
#[derive(Deserialize)]
pub(super) struct RawTheme {
#[serde(default)]
styles: HashMap<String, StyleType>,
#[serde(default)]
palette: HashMap<String, Color>,
}
impl RawTheme {
pub fn resolve(self) -> ResolvedPalette {
let mut resolved_styles = HashMap::with_capacity(self.styles.len());
for key in self.styles.keys() {
let mut final_style = Style::default();
let parts = key.split('.').to_vec();
for i in 1..=parts.len() {
let parent_key = parts[..i].join(".");
if let Some(style_type) = self.styles.get(&parent_key) {
let style = match style_type {
StyleType::Normal(style) => *style,
StyleType::Raw(raw_style) => raw_style.clone().into_style(&self.palette),
StyleType::FgOnlyNormal(color) => {
resolved_styles.insert(key.clone(), Style::default().fg(*color));
continue;
}
StyleType::FgOnlyPalette(key) => {
if let Some(copied) = self.palette.get(key) {
final_style.fg = Some(*copied)
};
resolved_styles.insert(key.clone(), final_style);
continue;
}
};
if let Some(fg) = style.fg {
final_style.fg = Some(fg);
}
if let Some(bg) = style.bg {
final_style.bg = Some(bg);
}
if let Some(uc) = style.underline_color {
final_style.underline_color = Some(uc)
}
final_style.add_modifier |= style.add_modifier & !Modifier::UNDERLINED;
final_style.sub_modifier |= style.sub_modifier & !Modifier::UNDERLINED;
}
}
resolved_styles.insert(key.clone(), final_style);
}
ResolvedPalette {
styles: resolved_styles,
palette: self.palette,
}
}
}