1pub mod blink;
2pub mod bold;
3pub mod border_bg;
4pub mod border_fg;
5pub mod fill_bg;
6pub mod fill_invert;
7pub mod halign;
8pub mod header;
9pub mod italic;
10pub mod max_width;
11pub mod min_width;
12pub mod palette_16;
13pub mod separator;
14pub mod strikethrough;
15pub mod text_bg;
16pub mod text_fg;
17pub mod text_invert;
18pub mod underline;
19
20use alloc::borrow::Cow;
21use alloc::boxed::Box;
22use alloc::collections::btree_map::Iter;
23use alloc::collections::BTreeMap;
24use alloc::string::String;
25pub use blink::Blink;
26pub use bold::Bold;
27pub use border_bg::BorderBg;
28pub use border_fg::BorderFg;
29use core::any;
30use core::any::Any;
31pub use fill_bg::FillBg;
32pub use fill_invert::FillInvert;
33pub use halign::HAlign;
34pub use header::Header;
35pub use italic::Italic;
36pub use max_width::MaxWidth;
37pub use min_width::MinWidth;
38pub use palette_16::Palette16;
39pub use separator::Separator;
40pub use strikethrough::Strikethrough;
41pub use text_bg::TextBg;
42pub use text_fg::TextFg;
43pub use text_invert::TextInvert;
44pub use underline::Underline;
45
46mod private {
47 use alloc::boxed::Box;
48 use super::Style;
49 use core::any::Any;
50
51 pub trait Replica {
53 fn replicate(&self) -> Box<dyn Style>;
54 }
55
56 impl<C: Clone + Style> Replica for C {
58 fn replicate(&self) -> Box<dyn Style> {
59 Box::new(self.clone())
60 }
61 }
62
63 pub trait Upcast {
66 fn as_any_ref(&self) -> &dyn Any;
68 }
69
70 impl<U: Style> Upcast for U {
72 fn as_any_ref(&self) -> &dyn Any {
73 self
74 }
75 }
76}
77
78pub trait Style: Any + private::Replica + private::Upcast {
79 fn assignability(&self) -> Assignability;
80
81 fn id() -> Cow<'static, str>
82 where
83 Self: Sized,
84 {
85 Cow::Borrowed(any::type_name::<Self>())
86 }
87
88 fn resolve(styles: &Styles) -> Option<&Self>
89 where
90 Self: Sized,
91 {
92 let style_for_id = styles.get(&Self::id());
93 match style_for_id {
94 None => None,
95 Some(style) => style.as_any_ref().downcast_ref(),
96 }
97 }
98
99 fn resolve_or_default(styles: &Styles) -> Cow<Self>
100 where
101 Self: Default + Sized + Clone,
102 {
103 let style = Self::resolve(styles);
104 match style {
105 None => Cow::Owned(Self::default()),
106 Some(style) => Cow::Borrowed(style),
107 }
108 }
109}
110
111#[derive(Default)]
112pub struct Styles(BTreeMap<String, Box<dyn Style>>);
113
114impl Styles {
115 #[must_use]
116 pub fn with(mut self, style: impl Style) -> Self {
117 self.insert(style);
118 self
119 }
120
121 pub fn insert<S: Style>(&mut self, style: S) -> Option<Box<dyn Style>> {
122 self.0.insert(S::id().into(), Box::new(style))
123 }
124
125 #[must_use]
126 pub fn with_all(mut self, styles: &Styles) -> Self {
127 self.insert_all(styles);
128 self
129 }
130
131 pub fn insert_all(&mut self, styles: &Styles) {
132 for (key, style) in &styles.0 {
133 self.0.insert(key.into(), style.replicate());
134 }
135 }
136
137 pub fn get(&self, key: &str) -> Option<&dyn Style> {
138 self.0.get(key).map(|style| &**style)
139 }
140
141 pub fn take(&mut self, key: &str) -> Option<Box<dyn Style>> {
142 self.0.remove(key)
143 }
144
145 pub fn assert_assignability<S>(&self, mut check: impl FnMut(Assignability) -> bool) {
152 for entry in self {
153 assert!(
154 check(entry.1.assignability()),
155 "cannot assign style {} to a {}",
156 entry.0,
157 any::type_name::<S>()
158 );
159 }
160 }
161}
162
163impl Clone for Styles {
164 fn clone(&self) -> Self {
165 let mut clone = BTreeMap::new();
166 for (key, style) in &self.0 {
167 clone.insert(key.clone(), style.replicate());
168 }
169
170 Self(clone)
171 }
172}
173
174impl<'a> IntoIterator for &'a Styles {
175 type Item = (&'a String, &'a Box<dyn Style>);
176 type IntoIter = Iter<'a, String, Box<dyn Style>>;
177
178 fn into_iter(self) -> Self::IntoIter {
179 self.0.iter()
180 }
181}
182
183pub trait Styled {
184 fn styles(&self) -> &Styles;
185}
186
187pub enum Assignability {
189 TableOnly,
191
192 ColTable,
194
195 RowTable,
197
198 RowColTable,
200
201 CellRowColTable,
203}
204
205impl Assignability {
206 pub fn at_col(&self) -> bool {
207 matches!(
208 self,
209 Assignability::ColTable | Assignability::RowColTable | Assignability::CellRowColTable
210 )
211 }
212
213 pub fn at_row(&self) -> bool {
214 matches!(
215 self,
216 Assignability::RowTable | Assignability::RowColTable | Assignability::CellRowColTable
217 )
218 }
219
220 pub fn at_cell(&self) -> bool {
221 matches!(self, Assignability::CellRowColTable)
222 }
223}
224
225#[cfg(test)]
226mod tests;