1use std::fmt;
6use std::ops::{Add, AddAssign};
7
8use termion::color::{AnsiValue, Bg, Fg};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13pub struct Style {
14 pub foreground: Option<u8>,
16 pub background: Option<u8>,
18 pub bold: Option<bool>,
20 pub italic: Option<bool>,
22 pub invert: Option<bool>,
24}
25
26impl Style {
27 pub const EMPTY: Style = Style {
29 foreground: None,
30 background: None,
31 bold: None,
32 italic: None,
33 invert: None,
34 };
35
36 pub const BOLD: Style = Style {
38 bold: Some(true),
39 ..Style::EMPTY
40 };
41
42 pub const ITALIC: Style = Style {
44 italic: Some(true),
45 ..Style::EMPTY
46 };
47
48 pub const INVERT: Style = Style {
50 invert: Some(true),
51 ..Style::EMPTY
52 };
53
54 pub fn fg(value: u8) -> Style {
56 Style {
57 foreground: Some(value),
58 ..Style::EMPTY
59 }
60 }
61
62 pub fn bg(value: u8) -> Style {
64 Style {
65 background: Some(value),
66 ..Style::EMPTY
67 }
68 }
69
70 pub fn with(self, other: Style) -> Style {
72 other.or(self)
73 }
74
75 pub fn or(self, other: Style) -> Style {
77 Style {
78 foreground: self.foreground.or(other.foreground),
79 background: self.background.or(other.background),
80 bold: self.bold.or(other.bold),
81 italic: self.italic.or(other.italic),
82 invert: self.invert.or(other.invert),
83 }
84 }
85}
86
87impl Default for Style {
88 fn default() -> Self {
89 Self::EMPTY
90 }
91}
92
93impl Add for Style {
94 type Output = Style;
95
96 fn add(self, rhs: Self) -> Self::Output {
97 self.with(rhs)
98 }
99}
100
101impl AddAssign for Style {
102 fn add_assign(&mut self, rhs: Self) {
103 *self = *self + rhs;
104 }
105}
106
107impl fmt::Display for Style {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 if let Some(foreground) = self.foreground {
110 Fg(AnsiValue(foreground)).fmt(f)?;
111 }
112 if let Some(background) = self.background {
113 Bg(AnsiValue(background)).fmt(f)?;
114 }
115 if self.bold == Some(true) {
116 termion::style::Bold.fmt(f)?;
117 }
118 if self.italic == Some(true) {
119 termion::style::Italic.fmt(f)?;
120 }
121 if self.invert == Some(true) {
122 termion::style::Invert.fmt(f)?;
123 }
124 Ok(())
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use std::io::Write;
131
132 use super::*;
133
134 const STYLE_1: Style = Style {
135 foreground: Some(1),
136 bold: Some(true),
137 ..Style::EMPTY
138 };
139
140 const STYLE_2: Style = Style {
141 foreground: Some(2),
142 italic: Some(true),
143 ..Style::EMPTY
144 };
145
146 #[test]
147 fn with() {
148 let style = STYLE_1.with(STYLE_2);
149 assert_eq!(
150 style,
151 Style {
152 foreground: Some(2),
153 background: None,
154 bold: Some(true),
155 italic: Some(true),
156 invert: None
157 },
158 );
159 }
160
161 #[test]
162 fn or() {
163 let style = STYLE_1.or(STYLE_2);
164 assert_eq!(
165 style,
166 Style {
167 foreground: Some(1),
168 background: None,
169 bold: Some(true),
170 italic: Some(true),
171 invert: None
172 },
173 );
174 }
175
176 #[test]
177 fn plus() {
178 assert_eq!(STYLE_1.with(STYLE_2), STYLE_1 + STYLE_2);
179 }
180
181 #[test]
182 fn print_empty() {
183 let mut output = vec![];
184 write!(&mut output, "{}", Style::EMPTY).unwrap();
185 assert_eq!(output, b"");
186 }
187
188 #[test]
189 fn print_full() {
190 let mut output = vec![];
191 write!(
192 &mut output,
193 "{}",
194 Style {
195 foreground: Some(1),
196 background: Some(2),
197 bold: Some(true),
198 italic: Some(true),
199 invert: Some(true),
200 },
201 )
202 .unwrap();
203 assert_eq!(output, b"\x1b[38;5;1m\x1b[48;5;2m\x1b[1m\x1b[3m\x1b[7m");
204 }
205}