rat_theme4/
salsa_theme.rs1use crate::{Palette, is_log_style_define};
2use log::info;
3use ratatui::style::Style;
4use std::any::{Any, type_name};
5use std::collections::{HashMap, hash_map};
6use std::fmt::{Debug, Formatter};
7
8#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
11#[non_exhaustive]
12pub enum Category {
13 #[default]
14 Other,
15 Dark,
17 Light,
19 Shell,
22}
23
24trait StyleValue: Any + Debug {}
25impl<T> StyleValue for T where T: Any + Debug {}
26
27type Entry = Box<dyn Fn(&SalsaTheme) -> Box<dyn StyleValue> + 'static>;
28type Modify = Box<dyn Fn(Box<dyn Any>, &SalsaTheme) -> Box<dyn StyleValue> + 'static>;
29
30#[derive(Default)]
39pub struct SalsaTheme {
40 pub name: String,
41 pub cat: Category,
42 pub p: Palette,
43 styles: HashMap<&'static str, Entry>,
44 modify: HashMap<&'static str, Modify>,
45}
46
47impl Debug for SalsaTheme {
48 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
49 f.debug_struct("SalsaTheme")
50 .field("name", &self.name)
51 .field("cat", &self.cat)
52 .field("palette", &self.p)
53 .field("styles", &self.styles.keys().collect::<Vec<_>>())
54 .field("modify", &self.modify.keys().collect::<Vec<_>>())
55 .finish()
56 }
57}
58
59impl SalsaTheme {
60 pub fn new(name: impl Into<String>, cat: Category, p: Palette) -> Self {
62 Self {
63 name: name.into(),
64 cat,
65 p,
66 styles: Default::default(),
67 modify: Default::default(),
68 }
69 }
70
71 pub fn name(&self) -> &str {
73 &self.name
74 }
75
76 pub fn define(&mut self, name: &'static str, style: Style) {
78 let boxed = Box::new(move |_: &SalsaTheme| -> Box<dyn StyleValue> { Box::new(style) });
79 if is_log_style_define() {
80 info!("salsa-style: {:?}->{:?}", name, style);
81 }
82 self.styles.insert(name, boxed);
83 }
84
85 pub fn define_clone(&mut self, name: &'static str, sample: impl Clone + Any + Debug + 'static) {
87 let boxed = Box::new(move |_th: &SalsaTheme| -> Box<dyn StyleValue> {
88 Box::new(sample.clone()) });
90 if is_log_style_define() {
91 info!("salsa-style: {:?}->{:?}", name, boxed(self));
92 }
93 self.styles.insert(name, boxed);
94 }
95
96 pub fn define_fn<O: Any + Debug>(
100 &mut self,
101 name: &'static str,
102 create: impl Fn(&SalsaTheme) -> O + 'static,
103 ) {
104 let boxed = Box::new(move |th: &SalsaTheme| -> Box<dyn StyleValue> {
105 Box::new(create(th)) });
107 if is_log_style_define() {
108 info!("salsa-style: {:?}->{:?}", name, boxed(self));
109 }
110 self.styles.insert(name, boxed);
111 }
112
113 pub fn define_fn0<O: Any + Debug>(
118 &mut self,
119 name: &'static str,
120 create: impl Fn() -> O + 'static,
121 ) {
122 let boxed = Box::new(move |_th: &SalsaTheme| -> Box<dyn StyleValue> {
123 Box::new(create()) });
125 if is_log_style_define() {
126 info!("salsa-style: {:?}->{:?}", name, boxed(self));
127 }
128 self.styles.insert(name, boxed);
129 }
130
131 pub fn modify<O: Any + Debug>(
133 &mut self,
134 name: &'static str,
135 modify: impl Fn(Box<dyn Any>, &SalsaTheme) -> Box<O> + 'static,
136 ) {
137 let boxed = Box::new(
138 move |v: Box<dyn Any>, th: &SalsaTheme| -> Box<dyn StyleValue> { modify(v, th) },
139 );
140 match self.modify.entry(name) {
141 hash_map::Entry::Occupied(_) => {
142 panic!("salsa-theme: only a single modification is possible");
143 }
144 hash_map::Entry::Vacant(entry) => {
145 entry.insert(boxed);
146 }
147 };
148 }
149
150 pub fn style_style(&self, name: &str) -> Style
164 where
165 Self: Sized,
166 {
167 self.style::<Style>(name)
168 }
169
170 pub fn style<O: Default + Sized + 'static>(&self, name: &str) -> O
180 where
181 Self: Sized,
182 {
183 if cfg!(debug_assertions) {
184 let style = match self.dyn_style(name) {
185 Some(v) => v,
186 None => {
187 panic!("unknown widget {:?}", name)
188 }
189 };
190 let any_style = style as Box<dyn Any>;
191 let style = match any_style.downcast::<O>() {
192 Ok(v) => v,
193 Err(_) => {
194 let style = self.dyn_style(name).expect("style");
195 panic!(
196 "downcast fails for '{}' to {}: {:?}",
197 name,
198 type_name::<O>(),
199 style
200 );
201 }
202 };
203 *style
204 } else {
205 let Some(style) = self.dyn_style(name) else {
206 return O::default();
207 };
208 let any_style = style as Box<dyn Any>;
209 let Ok(style) = any_style.downcast::<O>() else {
210 return O::default();
211 };
212 *style
213 }
214 }
215
216 #[allow(clippy::collapsible_else_if)]
217 fn dyn_style(&self, name: &str) -> Option<Box<dyn StyleValue>> {
218 if let Some(entry_fn) = self.styles.get(name) {
219 let mut style = entry_fn(self);
220 if let Some(modify) = self.modify.get(name) {
221 style = modify(style, self);
222 }
223 Some(style)
224 } else {
225 if cfg!(debug_assertions) {
226 panic!("unknown style {:?}", name)
227 } else {
228 None
229 }
230 }
231 }
232}