ariadne/
draw.rs

1use super::*;
2use yansi::Paint;
3
4pub struct Characters {
5    pub hbar: char,
6    pub vbar: char,
7    pub xbar: char,
8    pub vbar_break: char,
9    pub vbar_gap: char,
10
11    pub uarrow: char,
12    pub rarrow: char,
13
14    pub ltop: char,
15    pub mtop: char,
16    pub rtop: char,
17    pub lbot: char,
18    pub rbot: char,
19    pub mbot: char,
20
21    pub lbox: char,
22    pub rbox: char,
23
24    pub lcross: char,
25    pub rcross: char,
26
27    pub underbar: char,
28    pub underline: char,
29}
30
31impl Characters {
32    pub fn unicode() -> Self {
33        Self {
34            hbar: '─',
35            vbar: '│',
36            xbar: '┼',
37            vbar_break: '┆',
38            vbar_gap: '┆',
39            uarrow: '🭯',
40            rarrow: '▶',
41            ltop: '╭',
42            mtop: '┬',
43            rtop: '╮',
44            lbot: '╰',
45            mbot: '┴',
46            rbot: '╯',
47            lbox: '[',
48            rbox: ']',
49            lcross: '├',
50            rcross: '┤',
51            underbar: '┬',
52            underline: '─',
53        }
54    }
55
56    pub fn ascii() -> Self {
57        Self {
58            hbar: '-',
59            vbar: '|',
60            xbar: '+',
61            vbar_break: '*',
62            vbar_gap: ':',
63            uarrow: '^',
64            rarrow: '>',
65            ltop: ',',
66            mtop: 'v',
67            rtop: '.',
68            lbot: '`',
69            mbot: '^',
70            rbot: '\'',
71            lbox: '[',
72            rbox: ']',
73            lcross: '|',
74            rcross: '|',
75            underbar: '|',
76            underline: '^',
77        }
78    }
79}
80
81/// Output stream to check for whether color is enabled.
82#[derive(Clone, Copy, Debug)]
83pub enum StreamType {
84    /// Standard Output
85    Stdout,
86    /// Standard Error
87    Stderr,
88}
89
90#[cfg(feature = "concolor")]
91impl From<StreamType> for concolor::Stream {
92    fn from(s: StreamType) -> Self {
93        match s {
94            StreamType::Stdout => concolor::Stream::Stdout,
95            StreamType::Stderr => concolor::Stream::Stderr,
96        }
97    }
98}
99
100/// A trait used to add formatting attributes to displayable items intended to be written to a
101/// particular stream (`stdout` or `stderr`).
102///
103/// Attributes specified through this trait are not composable (i.e: the behaviour of two nested attributes each with a
104/// conflicting attribute is left unspecified).
105pub trait StreamAwareFmt: Sized {
106    #[cfg(feature = "concolor")]
107    /// Returns true if color is enabled for the given stream.
108    fn color_enabled_for(s: StreamType) -> bool {
109        concolor::get(s.into()).color()
110    }
111
112    #[cfg(not(feature = "concolor"))]
113    #[doc(hidden)]
114    fn color_enabled_for(_: StreamType) -> bool {
115        true
116    }
117
118    /// Give this value the specified foreground colour, when color is enabled for the specified stream.
119    fn fg<C: Into<Option<Color>>>(self, color: C, stream: StreamType) -> Foreground<Self> {
120        if Self::color_enabled_for(stream) {
121            Foreground(self, color.into())
122        } else {
123            Foreground(self, None)
124        }
125    }
126
127    /// Give this value the specified background colour, when color is enabled for the specified stream.
128    fn bg<C: Into<Option<Color>>>(self, color: C, stream: StreamType) -> Background<Self> {
129        if Self::color_enabled_for(stream) {
130            Background(self, color.into())
131        } else {
132            Background(self, None)
133        }
134    }
135}
136
137impl<T: fmt::Display> StreamAwareFmt for T {}
138
139/// A trait used to add formatting attributes to displayable items.
140///
141/// If using the `concolor` feature, this trait assumes that the items are going to be printed to
142/// `stderr`. If you are printing to `stdout`, `use` the [`StdoutFmt`] trait instead.
143///
144/// Attributes specified through this trait are not composable (i.e: the behaviour of two nested attributes each with a
145/// conflicting attribute is left unspecified).
146pub trait Fmt: Sized {
147    /// Give this value the specified foreground colour.
148    fn fg<C: Into<Option<Color>>>(self, color: C) -> Foreground<Self>
149    where
150        Self: fmt::Display,
151    {
152        if cfg!(feature = "concolor") {
153            StreamAwareFmt::fg(self, color, StreamType::Stderr)
154        } else {
155            Foreground(self, color.into())
156        }
157    }
158
159    /// Give this value the specified background colour.
160    fn bg<C: Into<Option<Color>>>(self, color: C) -> Background<Self>
161    where
162        Self: fmt::Display,
163    {
164        if cfg!(feature = "concolor") {
165            StreamAwareFmt::bg(self, color, StreamType::Stdout)
166        } else {
167            Background(self, color.into())
168        }
169    }
170}
171
172impl<T: fmt::Display> Fmt for T {}
173
174/// A trait used to add formatting attributes to displayable items intended to be written to `stdout`.
175///
176/// Attributes specified through this trait are not composable (i.e: the behaviour of two nested attributes each with a
177/// conflicting attribute is left unspecified).
178#[cfg(any(feature = "concolor", doc))]
179pub trait StdoutFmt: StreamAwareFmt {
180    /// Give this value the specified foreground colour, when color is enabled for `stdout`.
181    fn fg<C: Into<Option<Color>>>(self, color: C) -> Foreground<Self> {
182        StreamAwareFmt::fg(self, color, StreamType::Stdout)
183    }
184
185    /// Give this value the specified background colour, when color is enabled for `stdout`.
186    fn bg<C: Into<Option<Color>>>(self, color: C) -> Background<Self> {
187        StreamAwareFmt::bg(self, color, StreamType::Stdout)
188    }
189}
190
191#[cfg(feature = "concolor")]
192impl<T: fmt::Display> StdoutFmt for T {}
193
194#[derive(Copy, Clone, Debug)]
195pub struct Foreground<T>(T, Option<Color>);
196impl<T: fmt::Display> fmt::Display for Foreground<T> {
197    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
198        if let Some(col) = self.1 {
199            write!(f, "{}", Paint::new(&self.0).fg(col))
200        } else {
201            write!(f, "{}", self.0)
202        }
203    }
204}
205
206#[derive(Copy, Clone, Debug)]
207pub struct Background<T>(T, Option<Color>);
208impl<T: fmt::Display> fmt::Display for Background<T> {
209    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
210        if let Some(col) = self.1 {
211            write!(f, "{}", Paint::new(&self.0).bg(col))
212        } else {
213            write!(f, "{}", self.0)
214        }
215    }
216}
217
218/// A type that can generate distinct 8-bit colors.
219pub struct ColorGenerator {
220    state: [u16; 3],
221    min_brightness: f32,
222}
223
224impl Default for ColorGenerator {
225    fn default() -> Self { Self::from_state([30000, 15000, 35000], 0.5) }
226}
227
228impl ColorGenerator {
229    /// Create a new [`ColorGenerator`] with the given pre-chosen state.
230    ///
231    /// The minimum brightness can be used to control the colour brightness (0.0 - 1.0). The default is 0.5.
232    pub fn from_state(state: [u16; 3], min_brightness: f32) -> Self {
233        Self { state, min_brightness: min_brightness.max(0.0).min(1.0) }
234    }
235
236    /// Create a new [`ColorGenerator`] with the default state.
237    pub fn new() -> Self {
238        Self::default()
239    }
240
241    /// Generate the next colour in the sequence.
242    pub fn next(&mut self) -> Color {
243        for i in 0..3 {
244            // magic constant, one of only two that have this property!
245            self.state[i] = (self.state[i] as usize).wrapping_add(40503 * (i * 4 + 1130)) as u16;
246        }
247        Color::Fixed(16
248            + ((self.state[2] as f32 / 65535.0 * (1.0 - self.min_brightness) + self.min_brightness) * 5.0
249            + (self.state[1] as f32 / 65535.0 * (1.0 - self.min_brightness) + self.min_brightness) * 30.0
250            + (self.state[0] as f32 / 65535.0 * (1.0 - self.min_brightness) + self.min_brightness) * 180.0) as u8)
251    }
252}