anathema_widgets/widget/
style.rs1use std::str::FromStr;
2
3use anathema_state::{Color, Hex};
4use anathema_value_resolver::ValueKind;
5
6#[derive(Debug, PartialEq, Eq, Copy, Clone)]
31pub struct Style {
32 pub fg: Option<Color>,
34 pub bg: Option<Color>,
36 pub attributes: Attributes,
38}
39
40impl Style {
41 pub const fn new() -> Self {
43 Self {
44 fg: None,
45 bg: None,
46 attributes: Attributes::empty(),
47 }
48 }
49
50 pub fn from_cell_attribs(attributes: &anathema_value_resolver::Attributes<'_>) -> Self {
52 let mut style = Self::new();
53
54 if let Some(color) = attributes.get("foreground") {
55 let color = match color {
56 ValueKind::Hex(Hex { r, g, b }) => Color::from((*r, *g, *b)),
57 ValueKind::Str(cow) => Color::from_str(cow.as_ref()).unwrap_or(Color::Reset),
58 ValueKind::Int(ansi) => Color::AnsiVal(*ansi as u8),
59 ValueKind::Color(c) => *c,
60 _ => Color::Reset,
61 };
62
63 style.fg = Some(color)
64 }
65
66 if let Some(color) = attributes.get("background") {
67 let color = match color {
68 ValueKind::Color(color) => *color,
69 ValueKind::Hex(Hex { r, g, b }) => Color::from((*r, *g, *b)),
70 ValueKind::Str(cow) => Color::from_str(cow.as_ref()).unwrap_or(Color::Reset),
71 ValueKind::Int(ansi) => Color::AnsiVal(*ansi as u8),
72 _ => Color::Reset,
73 };
74
75 style.bg = Some(color)
76 }
77
78 if let Some(true) = attributes.get_as::<bool>("bold") {
79 style.attributes |= Attributes::BOLD;
80 } else if let Some(false) = attributes.get_as::<bool>("bold") {
81 style.attributes |= Attributes::NORMAL;
82 }
83
84 if let Some(true) = attributes.get_as::<bool>("dim") {
85 style.attributes |= Attributes::DIM;
86 } else if let Some(false) = attributes.get_as::<bool>("dim") {
87 style.attributes |= Attributes::NOT_DIM;
88 }
89
90 if let Some(true) = attributes.get_as::<bool>("italic") {
91 style.attributes |= Attributes::ITALIC;
92 } else if let Some(false) = attributes.get_as::<bool>("italic") {
93 style.attributes |= Attributes::NOT_ITALIC;
94 }
95
96 if let Some(true) = attributes.get_as::<bool>("underline") {
97 style.attributes |= Attributes::UNDERLINED;
98 } else if let Some(false) = attributes.get_as::<bool>("underline") {
99 style.attributes |= Attributes::NOT_UNDERLINED;
100 }
101
102 if let Some(true) = attributes.get_as::<bool>("crossed_out") {
103 style.attributes |= Attributes::CROSSED_OUT;
104 } else if let Some(false) = attributes.get_as::<bool>("crossed_out") {
105 style.attributes |= Attributes::NOT_CROSSED_OUT;
106 }
107
108 if let Some(true) = attributes.get_as::<bool>("overline") {
109 style.attributes |= Attributes::OVERLINED;
110 } else if let Some(false) = attributes.get_as::<bool>("overline") {
111 style.attributes |= Attributes::NOT_OVERLINED;
112 }
113
114 if let Some(true) = attributes.get_as::<bool>("inverse") {
115 style.attributes |= Attributes::REVERSED;
116 } else if let Some(false) = attributes.get_as::<bool>("inverse") {
117 style.attributes |= Attributes::NOT_REVERSED;
118 }
119
120 style
121 }
122
123 pub fn set_fg(&mut self, fg: Color) {
125 self.fg = Some(fg);
126 }
127
128 pub fn set_bg(&mut self, bg: Color) {
130 self.bg = Some(bg);
131 }
132
133 pub fn set_bold(&mut self, bold: bool) {
135 if bold {
136 self.attributes |= Attributes::BOLD;
137 } else {
138 self.attributes &= !Attributes::BOLD;
139 }
140 }
141
142 pub fn set_italic(&mut self, italic: bool) {
144 if italic {
145 self.attributes |= Attributes::ITALIC;
146 } else {
147 self.attributes &= !Attributes::ITALIC;
148 }
149 }
150
151 pub fn set_dim(&mut self, dim: bool) {
153 if dim {
154 self.attributes |= Attributes::DIM;
155 } else {
156 self.attributes &= !Attributes::DIM;
157 }
158 }
159
160 pub fn set_underlined(&mut self, underlined: bool) {
162 if underlined {
163 self.attributes |= Attributes::UNDERLINED;
164 } else {
165 self.attributes &= !Attributes::UNDERLINED;
166 }
167 }
168
169 pub fn set_overlined(&mut self, overlined: bool) {
171 if overlined {
172 self.attributes |= Attributes::OVERLINED;
173 } else {
174 self.attributes &= !Attributes::OVERLINED;
175 }
176 }
177
178 pub fn set_crossed_out(&mut self, crossed_out: bool) {
180 if crossed_out {
181 self.attributes |= Attributes::CROSSED_OUT;
182 } else {
183 self.attributes &= !Attributes::CROSSED_OUT;
184 }
185 }
186
187 pub fn set_reversed(&mut self, inverse: bool) {
189 if inverse {
190 self.attributes |= Attributes::REVERSED;
191 } else {
192 self.attributes &= !Attributes::REVERSED;
193 }
194 }
195
196 pub fn reset() -> Self {
198 let mut style = Self::new();
199 style.fg = Some(Color::Reset);
200 style.bg = Some(Color::Reset);
201 style
202 }
203
204 pub fn merge(&mut self, other: Style) {
208 if let (None, Some(fg)) = (self.fg, other.fg) {
209 self.fg = Some(fg);
210 }
211
212 if let (None, Some(bg)) = (self.bg, other.bg) {
213 self.bg = Some(bg);
214 }
215
216 self.attributes |= other.attributes;
217 }
218}
219
220bitflags::bitflags! {
221 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
223 pub struct Attributes: u16 {
224 const BOLD = 0b0000_0000_0000_0001;
227 const DIM = 0b0000_0000_0000_0010;
229 const ITALIC = 0b0000_0000_0000_0100;
231 const UNDERLINED = 0b0000_0000_0000_1000;
233 const CROSSED_OUT = 0b0000_0000_0001_0000;
235 const OVERLINED = 0b0000_0000_0010_0000;
237 const REVERSED = 0b0000_0000_0100_0000;
239
240 const NORMAL = 0b0000_0000_1000_0000;
243 const NOT_DIM = 0b0000_0001_0000_0000;
245 const NOT_ITALIC = 0b0000_0010_0000_0000;
247 const NOT_UNDERLINED = 0b0000_0100_0000_0000;
249 const NOT_CROSSED_OUT = 0b0000_1000_0000_0000;
251 const NOT_OVERLINED = 0b0001_0000_0000_0000;
253 const NOT_REVERSED = 0b0010_0000_0000_0000;
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 #[test]
263 fn merging_styles() {
264 let mut right = Style::new();
265 right.set_fg(Color::Green);
266 right.set_bg(Color::Blue);
267
268 let mut left = Style::new();
269 left.set_fg(Color::Red);
270
271 left.merge(right);
272
273 assert_eq!(left.fg.unwrap(), Color::Red);
274 assert_eq!(left.bg.unwrap(), Color::Blue);
275 }
276}