1use super::*;
2use yansi::Paint;
3
4pub struct Characters {
5 pub hbar: char,
6 pub vbar: char,
7 pub xbar: char,
8 #[allow(unused)]
9 pub vbar_break: char,
10 pub vbar_gap: char,
11
12 pub uarrow: char,
13 pub rarrow: char,
14
15 pub ltop: char,
16 #[allow(unused)]
17 pub mtop: char,
18 pub rtop: char,
19 pub lbot: char,
20 pub rbot: char,
21 pub mbot: char,
22
23 pub lbox: char,
24 pub rbox: char,
25
26 pub lcross: char,
27 #[allow(unused)]
28 pub rcross: char,
29
30 pub underbar: char,
31 pub underline: char,
32}
33
34impl Characters {
35 pub fn unicode() -> Self {
36 Self {
37 hbar: '─',
38 vbar: '│',
39 xbar: '┼',
40 vbar_break: '┆',
41 vbar_gap: '┆',
42 uarrow: '▲',
43 rarrow: '▶',
44 ltop: '╭',
45 mtop: '┬',
46 rtop: '╮',
47 lbot: '╰',
48 mbot: '┴',
49 rbot: '╯',
50 lbox: '[',
51 rbox: ']',
52 lcross: '├',
53 rcross: '┤',
54 underbar: '┬',
55 underline: '─',
56 }
57 }
58
59 pub fn ascii() -> Self {
60 Self {
61 hbar: '-',
62 vbar: '|',
63 xbar: '+',
64 vbar_break: '*',
65 vbar_gap: ':',
66 uarrow: '^',
67 rarrow: '>',
68 ltop: ',',
69 mtop: 'v',
70 rtop: '.',
71 lbot: '`',
72 mbot: '^',
73 rbot: '\'',
74 lbox: '[',
75 rbox: ']',
76 lcross: '|',
77 rcross: '|',
78 underbar: '|',
79 underline: '^',
80 }
81 }
82}
83
84#[derive(Clone, Copy, Debug)]
86pub enum StreamType {
87 Stdout,
89 Stderr,
91}
92
93#[cfg(feature = "concolor")]
94impl From<StreamType> for concolor::Stream {
95 fn from(s: StreamType) -> Self {
96 match s {
97 StreamType::Stdout => concolor::Stream::Stdout,
98 StreamType::Stderr => concolor::Stream::Stderr,
99 }
100 }
101}
102
103pub trait StreamAwareFmt: Sized {
109 #[cfg(feature = "concolor")]
110 fn color_enabled_for(s: StreamType) -> bool {
112 concolor::get(s.into()).color()
113 }
114
115 #[cfg(not(feature = "concolor"))]
116 #[doc(hidden)]
117 fn color_enabled_for(_: StreamType) -> bool {
118 true
119 }
120
121 fn fg<C: Into<Option<Color>>>(self, color: C, stream: StreamType) -> Foreground<Self> {
123 if Self::color_enabled_for(stream) {
124 Foreground(self, color.into())
125 } else {
126 Foreground(self, None)
127 }
128 }
129
130 fn bg<C: Into<Option<Color>>>(self, color: C, stream: StreamType) -> Background<Self> {
132 if Self::color_enabled_for(stream) {
133 Background(self, color.into())
134 } else {
135 Background(self, None)
136 }
137 }
138}
139
140impl<T: fmt::Display> StreamAwareFmt for T {}
141
142pub trait Fmt: Sized {
150 fn fg<C: Into<Option<Color>>>(self, color: C) -> Foreground<Self>
152 where
153 Self: fmt::Display,
154 {
155 if cfg!(feature = "concolor") {
156 StreamAwareFmt::fg(self, color, StreamType::Stderr)
157 } else {
158 Foreground(self, color.into())
159 }
160 }
161
162 fn bg<C: Into<Option<Color>>>(self, color: C) -> Background<Self>
164 where
165 Self: fmt::Display,
166 {
167 if cfg!(feature = "concolor") {
168 StreamAwareFmt::bg(self, color, StreamType::Stdout)
169 } else {
170 Background(self, color.into())
171 }
172 }
173}
174
175impl<T: fmt::Display> Fmt for T {}
176
177#[cfg(any(feature = "concolor", doc))]
182pub trait StdoutFmt: StreamAwareFmt {
183 fn fg<C: Into<Option<Color>>>(self, color: C) -> Foreground<Self> {
185 StreamAwareFmt::fg(self, color, StreamType::Stdout)
186 }
187
188 fn bg<C: Into<Option<Color>>>(self, color: C) -> Background<Self> {
190 StreamAwareFmt::bg(self, color, StreamType::Stdout)
191 }
192}
193
194#[cfg(feature = "concolor")]
195impl<T: fmt::Display> StdoutFmt for T {}
196
197#[derive(Copy, Clone, Debug)]
198pub struct Foreground<T>(T, Option<Color>);
199impl<T: fmt::Display> fmt::Display for Foreground<T> {
200 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
201 if let Some(col) = self.1 {
202 write!(f, "{}", Paint::new(&self.0).fg(col))
203 } else {
204 write!(f, "{}", self.0)
205 }
206 }
207}
208
209#[derive(Copy, Clone, Debug)]
210pub struct Background<T>(T, Option<Color>);
211impl<T: fmt::Display> fmt::Display for Background<T> {
212 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
213 if let Some(col) = self.1 {
214 write!(f, "{}", Paint::new(&self.0).bg(col))
215 } else {
216 write!(f, "{}", self.0)
217 }
218 }
219}
220
221pub struct ColorGenerator {
223 state: [u16; 3],
224 min_brightness: f32,
225}
226
227impl Default for ColorGenerator {
228 fn default() -> Self {
229 Self::new()
230 }
231}
232
233impl ColorGenerator {
234 pub const fn from_state(state: [u16; 3], min_brightness: f32) -> Self {
238 Self {
239 state,
240 min_brightness: min_brightness.clamp(0.0, 1.0),
241 }
242 }
243
244 pub const fn new() -> Self {
246 Self::from_state([30000, 15000, 35000], 0.5)
247 }
248
249 #[allow(clippy::should_implement_trait)]
251 pub const fn next(&mut self) -> Color {
252 let mut i = 0;
254 while i < 3 {
255 self.state[i] = (self.state[i] as usize).wrapping_add(40503 * (i * 4 + 1130)) as u16;
257 i += 1;
258 }
259 Color::Fixed(
260 16 + ((self.state[2] as f32 / 65535.0 * (1.0 - self.min_brightness)
261 + self.min_brightness)
262 * 5.0
263 + (self.state[1] as f32 / 65535.0 * (1.0 - self.min_brightness)
264 + self.min_brightness)
265 * 30.0
266 + (self.state[0] as f32 / 65535.0 * (1.0 - self.min_brightness)
267 + self.min_brightness)
268 * 180.0) as u8,
269 )
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 #[test]
278 fn const_colors() {
279 const COLORS: [Color; 3] = {
280 let mut gen = ColorGenerator::new();
281 [gen.next(), gen.next(), gen.next()]
282 };
283 assert_ne!(COLORS[0], COLORS[1]);
284 assert_ne!(COLORS[1], COLORS[2]);
285 assert_ne!(COLORS[2], COLORS[0]);
286 }
287}