ratatui_toolkit/primitives/termtui/
attrs.rs1use ratatui::style::{Color as RatatuiColor, Modifier, Style};
4use termwiz::color::ColorSpec;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
8pub enum Color {
9 #[default]
11 Default,
12 Indexed(u8),
14 Rgb(u8, u8, u8),
16}
17
18impl Color {
19 pub fn to_ratatui(self) -> Option<RatatuiColor> {
21 match self {
22 Color::Default => None,
23 Color::Indexed(idx) => Some(match idx {
24 0 => RatatuiColor::Black,
25 1 => RatatuiColor::Red,
26 2 => RatatuiColor::Green,
27 3 => RatatuiColor::Yellow,
28 4 => RatatuiColor::Blue,
29 5 => RatatuiColor::Magenta,
30 6 => RatatuiColor::Cyan,
31 7 => RatatuiColor::Gray,
32 8 => RatatuiColor::DarkGray,
33 9 => RatatuiColor::LightRed,
34 10 => RatatuiColor::LightGreen,
35 11 => RatatuiColor::LightYellow,
36 12 => RatatuiColor::LightBlue,
37 13 => RatatuiColor::LightMagenta,
38 14 => RatatuiColor::LightCyan,
39 15 => RatatuiColor::White,
40 _ => RatatuiColor::Indexed(idx),
41 }),
42 Color::Rgb(r, g, b) => Some(RatatuiColor::Rgb(r, g, b)),
43 }
44 }
45}
46
47impl From<ColorSpec> for Color {
48 fn from(spec: ColorSpec) -> Self {
49 match spec {
50 ColorSpec::Default => Color::Default,
51 ColorSpec::PaletteIndex(idx) => Color::Indexed(idx),
52 ColorSpec::TrueColor(srgba) => Color::Rgb(
53 (srgba.0 * 255.0) as u8,
54 (srgba.1 * 255.0) as u8,
55 (srgba.2 * 255.0) as u8,
56 ),
57 }
58 }
59}
60
61const TEXT_MODE_BOLD: u8 = 1 << 0;
63const TEXT_MODE_ITALIC: u8 = 1 << 1;
64const TEXT_MODE_UNDERLINE: u8 = 1 << 2;
65const TEXT_MODE_INVERSE: u8 = 1 << 3;
66const TEXT_MODE_STRIKETHROUGH: u8 = 1 << 4;
67
68#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
70pub struct Attrs {
71 pub fg: Color,
73 pub bg: Color,
75 mode: u8,
77}
78
79impl Attrs {
80 pub fn new() -> Self {
82 Self::default()
83 }
84
85 pub fn bold(&self) -> bool {
87 self.mode & TEXT_MODE_BOLD != 0
88 }
89
90 pub fn set_bold(&mut self, on: bool) {
92 if on {
93 self.mode |= TEXT_MODE_BOLD;
94 } else {
95 self.mode &= !TEXT_MODE_BOLD;
96 }
97 }
98
99 pub fn italic(&self) -> bool {
101 self.mode & TEXT_MODE_ITALIC != 0
102 }
103
104 pub fn set_italic(&mut self, on: bool) {
106 if on {
107 self.mode |= TEXT_MODE_ITALIC;
108 } else {
109 self.mode &= !TEXT_MODE_ITALIC;
110 }
111 }
112
113 pub fn underline(&self) -> bool {
115 self.mode & TEXT_MODE_UNDERLINE != 0
116 }
117
118 pub fn set_underline(&mut self, on: bool) {
120 if on {
121 self.mode |= TEXT_MODE_UNDERLINE;
122 } else {
123 self.mode &= !TEXT_MODE_UNDERLINE;
124 }
125 }
126
127 pub fn inverse(&self) -> bool {
129 self.mode & TEXT_MODE_INVERSE != 0
130 }
131
132 pub fn set_inverse(&mut self, on: bool) {
134 if on {
135 self.mode |= TEXT_MODE_INVERSE;
136 } else {
137 self.mode &= !TEXT_MODE_INVERSE;
138 }
139 }
140
141 pub fn strikethrough(&self) -> bool {
143 self.mode & TEXT_MODE_STRIKETHROUGH != 0
144 }
145
146 pub fn set_strikethrough(&mut self, on: bool) {
148 if on {
149 self.mode |= TEXT_MODE_STRIKETHROUGH;
150 } else {
151 self.mode &= !TEXT_MODE_STRIKETHROUGH;
152 }
153 }
154
155 pub fn reset(&mut self) {
157 *self = Self::default();
158 }
159
160 pub fn to_ratatui(&self) -> Style {
162 let mut style = Style::default();
163
164 let (fg, bg) = if self.inverse() {
166 (self.bg, self.fg)
167 } else {
168 (self.fg, self.bg)
169 };
170
171 if let Some(color) = fg.to_ratatui() {
172 style = style.fg(color);
173 }
174 if let Some(color) = bg.to_ratatui() {
175 style = style.bg(color);
176 }
177
178 let mut modifiers = Modifier::empty();
180 if self.bold() {
181 modifiers |= Modifier::BOLD;
182 }
183 if self.italic() {
184 modifiers |= Modifier::ITALIC;
185 }
186 if self.underline() {
187 modifiers |= Modifier::UNDERLINED;
188 }
189 if self.strikethrough() {
190 modifiers |= Modifier::CROSSED_OUT;
191 }
192
193 if !modifiers.is_empty() {
194 style = style.add_modifier(modifiers);
195 }
196
197 style
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn test_color_default() {
207 let color = Color::Default;
208 assert_eq!(color.to_ratatui(), None);
209 }
210
211 #[test]
212 fn test_color_indexed() {
213 assert_eq!(Color::Indexed(0).to_ratatui(), Some(RatatuiColor::Black));
214 assert_eq!(Color::Indexed(1).to_ratatui(), Some(RatatuiColor::Red));
215 assert_eq!(
216 Color::Indexed(100).to_ratatui(),
217 Some(RatatuiColor::Indexed(100))
218 );
219 }
220
221 #[test]
222 fn test_color_rgb() {
223 assert_eq!(
224 Color::Rgb(255, 128, 64).to_ratatui(),
225 Some(RatatuiColor::Rgb(255, 128, 64))
226 );
227 }
228
229 #[test]
230 fn test_attrs_default() {
231 let attrs = Attrs::default();
232 assert!(!attrs.bold());
233 assert!(!attrs.italic());
234 assert!(!attrs.underline());
235 assert!(!attrs.inverse());
236 }
237
238 #[test]
239 fn test_attrs_set_modes() {
240 let mut attrs = Attrs::new();
241
242 attrs.set_bold(true);
243 assert!(attrs.bold());
244
245 attrs.set_italic(true);
246 assert!(attrs.italic());
247
248 attrs.set_underline(true);
249 assert!(attrs.underline());
250
251 attrs.set_inverse(true);
252 assert!(attrs.inverse());
253
254 attrs.reset();
255 assert!(!attrs.bold());
256 assert!(!attrs.italic());
257 }
258}