use crate::is_log_style_define;
use crate::palette::Palette;
use crate::themes::create_fallback;
use log::info;
use ratatui_core::style::Style;
use std::any::{Any, type_name};
use std::collections::{HashMap, hash_map};
use std::fmt::{Debug, Formatter};
trait StyleValue: Any + Debug {}
impl<T> StyleValue for T where T: Any + Debug {}
type Entry = Box<dyn Fn(&SalsaTheme) -> Box<dyn StyleValue> + 'static>;
type Modify = Box<dyn Fn(Box<dyn Any>, &SalsaTheme) -> Box<dyn StyleValue> + 'static>;
pub struct SalsaTheme {
pub name: String,
pub theme: String,
pub p: Palette,
styles: HashMap<&'static str, Entry>,
modify: HashMap<&'static str, Modify>,
}
impl Default for SalsaTheme {
fn default() -> Self {
create_fallback(Palette::default())
}
}
impl Debug for SalsaTheme {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Theme")
.field("name", &self.name)
.field("theme", &self.theme)
.field("palette", &self.p)
.field("styles", &self.styles.keys().collect::<Vec<_>>())
.field("modify", &self.modify.keys().collect::<Vec<_>>())
.finish()
}
}
impl SalsaTheme {
pub fn new(p: Palette) -> Self {
Self {
name: p.theme_name.as_ref().into(),
theme: p.theme.as_ref().into(),
p,
styles: Default::default(),
modify: Default::default(),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn define_style(&mut self, name: &'static str, style: Style) {
let boxed = Box::new(move |_: &SalsaTheme| -> Box<dyn StyleValue> { Box::new(style) });
self.define(name, boxed);
}
pub fn define_clone(&mut self, name: &'static str, sample: impl Clone + Any + Debug + 'static) {
let boxed = Box::new(move |_th: &SalsaTheme| -> Box<dyn StyleValue> {
Box::new(sample.clone()) });
self.define(name, boxed);
}
pub fn define_fn<O: Any + Debug>(
&mut self,
name: &'static str,
create: impl Fn(&SalsaTheme) -> O + 'static,
) {
let boxed = Box::new(move |th: &SalsaTheme| -> Box<dyn StyleValue> {
Box::new(create(th)) });
self.define(name, boxed);
}
pub fn define_fn0<O: Any + Debug>(
&mut self,
name: &'static str,
create: impl Fn() -> O + 'static,
) {
let boxed = Box::new(move |_th: &SalsaTheme| -> Box<dyn StyleValue> {
Box::new(create()) });
self.define(name, boxed);
}
fn define(&mut self, name: &'static str, boxed: Entry) {
if is_log_style_define() {
info!("salsa-style: {:?}", name);
}
match self.styles.insert(name, boxed) {
None => {}
Some(_) => {
if is_log_style_define() {
info!("salsa-style: OVERWRITE {:?}", name);
}
}
};
}
pub fn modify<O: Any + Default + Debug + Sized + 'static>(
&mut self,
name: &'static str,
modify: impl Fn(O, &SalsaTheme) -> O + 'static,
) {
let boxed = Box::new(
move |v: Box<dyn Any>, th: &SalsaTheme| -> Box<dyn StyleValue> {
if cfg!(debug_assertions) {
let v = match v.downcast::<O>() {
Ok(v) => *v,
Err(e) => {
panic!(
"downcast fails for '{}' to {}. Is {:?}",
name,
type_name::<O>(),
e
);
}
};
let v = modify(v, th);
Box::new(v)
} else {
let v = match v.downcast::<O>() {
Ok(v) => *v,
Err(_) => O::default(),
};
let v = modify(v, th);
Box::new(v)
}
},
);
match self.modify.entry(name) {
hash_map::Entry::Occupied(mut entry) => {
if is_log_style_define() {
info!("salsa-style: overwrite modifier for {:?}", name);
}
_ = entry.insert(boxed);
}
hash_map::Entry::Vacant(entry) => {
if is_log_style_define() {
info!("salsa-style: set modifier for {:?}", name);
}
entry.insert(boxed);
}
};
}
pub fn style_style(&self, name: &str) -> Style
where
Self: Sized,
{
self.style::<Style>(name)
}
pub fn style<O: Default + Sized + 'static>(&self, name: &str) -> O
where
Self: Sized,
{
if cfg!(debug_assertions) {
let style = match self.dyn_style(name) {
Some(v) => v,
None => {
panic!("unknown widget {:?}", name)
}
};
let any_style = style as Box<dyn Any>;
let style = match any_style.downcast::<O>() {
Ok(v) => v,
Err(_) => {
let style = self.dyn_style(name).expect("style");
panic!(
"downcast fails for '{}' to {}: {:?}",
name,
type_name::<O>(),
style
);
}
};
*style
} else {
let Some(style) = self.dyn_style(name) else {
return O::default();
};
let any_style = style as Box<dyn Any>;
let Ok(style) = any_style.downcast::<O>() else {
return O::default();
};
*style
}
}
#[allow(clippy::collapsible_else_if)]
fn dyn_style(&self, name: &str) -> Option<Box<dyn StyleValue>> {
if let Some(entry_fn) = self.styles.get(name) {
let mut style = entry_fn(self);
if let Some(modify) = self.modify.get(name) {
style = modify(style, self);
}
Some(style)
} else {
if cfg!(debug_assertions) {
panic!("unknown style {:?}", name)
} else {
None
}
}
}
}