easy_sgr/
graphics.rs

1use std::fmt::{Debug, Display};
2
3use crate::{Color, SGRBuilder, SGRWriter, Style};
4
5/// A String encapsulating the usage of SGR codes
6///
7/// SGR codes are applied when the [`Display`] trait is used,
8/// or when the [`SGRString::place_all`] or [`SGRString::clean_all`]
9/// functions are called.
10///
11/// Writing is done through the use of the [`writing`](crate::writing) module
12///
13/// # Examples
14///
15///```rust
16///use easy_sgr::{ColorKind, SGRString, StyleKind};
17///
18///let mut string = SGRString::default();
19///string.text = String::from("This is my text");
20///string.bold = StyleKind::Place;
21///string.foreground = ColorKind::Red;
22///println!("{string}");
23///```
24#[derive(Default, Debug, Clone)]
25pub struct SGRString {
26    /// The actual text
27    pub text: String,
28    /// The type of clean to apply when [`SGRString::clean_all`] is called
29    ///
30    /// By default [`CleanKind::None`], meaning nothing is done
31    pub clean: CleanKind,
32
33    /// Any custom codes added
34    ///
35    /// These codes are written before the string when
36    /// the [`Display`] trait is called
37    pub custom_places: Vec<u8>,
38    /// Any custom codes added
39    ///
40    /// These codes are written after the string when
41    /// the [`Display`] trait is called
42    pub custom_cleans: Vec<u8>,
43
44    /// The color of the foreground
45    ///
46    /// By default [`ColorKind::None`], meaning nothing is applied.
47    /// Not to be confused with [`ColorKind::Default`], where the default SGR
48    /// code for the foreground is applied.
49    pub foreground: ColorKind,
50    /// The color of the background
51    ///
52    /// By default [`ColorKind::None`], meaning nothing is applied.
53    /// Not to be confused with [`ColorKind::Default`], where the default SGR
54    /// code for the background is applied.
55    pub background: ColorKind,
56
57    /// Determines whether the clear code `0` is to be applied to the beginning
58    ///
59    /// Not be confused with [`SGRString.clean`], this effects [`SGRString::place_all`]
60    pub reset: bool,
61    /// Refer to [`StyleKind`]
62    pub bold: StyleKind,
63    /// Refer to [`StyleKind`]
64    pub dim: StyleKind,
65    /// Refer to [`StyleKind`]
66    pub italic: StyleKind,
67    /// Refer to [`StyleKind`]
68    pub underline: StyleKind,
69    /// Refer to [`StyleKind`]
70    pub blinking: StyleKind,
71    /// Refer to [`StyleKind`]
72    pub inverse: StyleKind,
73    /// Refer to [`StyleKind`]
74    pub hidden: StyleKind,
75    /// Refer to [`StyleKind`]
76    pub strikethrough: StyleKind,
77}
78impl SGRString {
79    /// Writes all contained SGR codes to the given [`SGRBuilder`]
80    ///
81    /// Does not perform any IO operations
82    pub fn place_all(&self, builder: &mut SGRBuilder) {
83        if self.reset {
84            builder.write_code(0);
85        }
86        self.place_colors(builder);
87        self.place_styles(builder);
88        self.place_custom(builder);
89    }
90    /// Writes contained SGR color codes to the given [`SGRWriter`]
91    ///
92    /// Does not perform any IO operations
93    pub fn place_colors(&self, builder: &mut SGRBuilder) {
94        use ColorKind::*;
95        match self.foreground {
96            Black => builder.write_code(30),
97            Red => builder.write_code(31),
98            Green => builder.write_code(32),
99            Yellow => builder.write_code(33),
100            Blue => builder.write_code(34),
101            Magenta => builder.write_code(35),
102            Cyan => builder.write_code(36),
103            White => builder.write_code(37),
104            Byte(n) => builder.write_codes(&[38, 5, n]),
105            Rgb(r, g, b) => builder.write_codes(&[38, 2, r, g, b]),
106            Default => builder.write_code(39),
107            ColorKind::None => (),
108        };
109        match self.background {
110            Black => builder.write_code(40),
111            Red => builder.write_code(41),
112            Green => builder.write_code(42),
113            Yellow => builder.write_code(43),
114            Blue => builder.write_code(44),
115            Magenta => builder.write_code(45),
116            Cyan => builder.write_code(46),
117            White => builder.write_code(47),
118            Byte(n) => builder.write_codes(&[48, 5, n]),
119            Rgb(r, g, b) => builder.write_codes(&[48, 2, r, g, b]),
120            Default => builder.write_code(49),
121            ColorKind::None => (),
122        };
123    }
124    /// Writes SGR style codes to the given [`SGRWriter`]
125    ///
126    /// Does not perform any IO operations
127    pub fn place_styles(&self, builder: &mut SGRBuilder) {
128        use StyleKind::*;
129        for (kind, place, not) in [
130            (&self.bold, 1, 22),
131            (&self.dim, 2, 22),
132            (&self.italic, 3, 23),
133            (&self.underline, 4, 24),
134            (&self.blinking, 5, 25),
135            (&self.inverse, 7, 27),
136            (&self.hidden, 8, 28),
137            (&self.strikethrough, 9, 29),
138        ] {
139            match kind {
140                None => (),
141                Place => builder.write_code(place),
142                Clean => builder.write_code(not),
143            }
144        }
145    }
146    /// Writes custom SGR codes to the given [`SGRWriter`]
147    ///
148    /// Does not perform any IO operations
149    pub fn place_custom(&self, builder: &mut SGRBuilder) {
150        builder.write_codes(&self.custom_places);
151    }
152    /// Writes contained SGR codes to the given [`SGRWriter`]
153    ///
154    /// Reverses the effects of [`SGRString::place_all`]
155    ///
156    /// Does not perform any IO operations
157    pub fn clean_all(&self, builder: &mut SGRBuilder) {
158        match self.clean {
159            CleanKind::Reset => builder.write_code(0),
160            CleanKind::Reverse => {
161                self.clean_colors(builder);
162                self.clean_styles(builder);
163            }
164            CleanKind::None => (),
165        }
166        self.clean_custom(builder);
167    }
168    /// Writes SGR color codes to the given [`SGRWriter`]
169    ///
170    /// Reverses the effects of [`SGRString::place_colors`]
171    ///
172    /// Does not perform any IO operations
173    pub fn clean_colors(&self, builder: &mut SGRBuilder) {
174        if self.foreground != ColorKind::None {
175            builder.write_code(39);
176        }
177        if self.background != ColorKind::None {
178            builder.write_code(49);
179        }
180    }
181    /// Writes SGR style codes to the given [`SGRWriter`]
182    ///
183    /// Reverses the effects of [`SGRString::place_styles`]
184    ///
185    /// Does not perform any IO operations
186    pub fn clean_styles(&self, builder: &mut SGRBuilder) {
187        for (kind, place, not) in [
188            (&self.bold, 22, 1),
189            (&self.dim, 22, 2),
190            (&self.italic, 23, 3),
191            (&self.underline, 24, 4),
192            (&self.blinking, 25, 5),
193            (&self.inverse, 27, 7),
194            (&self.hidden, 28, 8),
195            (&self.strikethrough, 29, 9),
196        ] {
197            match kind {
198                StyleKind::None => (),
199                StyleKind::Place => builder.write_code(place),
200                StyleKind::Clean => builder.write_code(not),
201            }
202        }
203    }
204    /// Writes SGR codes to the given [`SGRWriter`]
205    ///
206    /// Reverses the effects of [`SGRString::place_custom`]
207    ///
208    /// Does not perform any IO operations
209    pub fn clean_custom(&self, builder: &mut SGRBuilder) {
210        builder.write_codes(&self.custom_cleans);
211    }
212}
213impl From<Color> for SGRString {
214    fn from(value: Color) -> Self {
215        Self::default().color(value)
216    }
217}
218impl From<Style> for SGRString {
219    fn from(value: Style) -> Self {
220        Self::default().style(value)
221    }
222}
223impl From<&str> for SGRString {
224    fn from(value: &str) -> Self {
225        Self {
226            text: String::from(value),
227            ..Default::default()
228        }
229    }
230}
231impl From<String> for SGRString {
232    fn from(value: String) -> Self {
233        Self {
234            text: value,
235            ..Default::default()
236        }
237    }
238}
239impl From<&String> for SGRString {
240    fn from(value: &String) -> Self {
241        Self {
242            text: String::from(value),
243            ..Default::default()
244        }
245    }
246}
247impl Display for SGRString {
248    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
249        let mut fmt = SGRWriter::from(f);
250        fmt.place_sgr(self)?;
251        fmt.write_inner(&self.text)?;
252        fmt.clean_sgr(self)
253    }
254}
255/// Component of [`SGRString`]; the type of clean
256#[derive(Debug, Clone, Default, PartialEq, Eq)]
257pub enum CleanKind {
258    /// Does nothing
259    #[default]
260    None,
261    /// Resets all by writing `\x1b[0m`
262    Reset,
263    /// Undoes the effects of the [`SGRString::place_all`].
264    Reverse,
265}
266/// Component of [`SGRString`]; the type of a style
267#[derive(Debug, Clone, Default, PartialEq, Eq)]
268pub enum StyleKind {
269    /// Do nothing
270    #[default]
271    None,
272    /// Apply the style
273    Place,
274    /// Apply what undoes the style
275    ///
276    /// The equivalent in [`Style`] are variants prefixed with `Not`
277    Clean,
278}
279/// Component of [`SGRString`]; the type of color
280///
281/// Used for both foreground and background
282#[derive(Debug, Clone, Default, PartialEq, Eq)]
283#[allow(missing_docs)]
284pub enum ColorKind {
285    /// Does nothing
286    #[default]
287    None,
288    Black,
289    Red,
290    Green,
291    Yellow,
292    Blue,
293    Magenta,
294    Cyan,
295    White,
296    Byte(u8),
297    Rgb(u8, u8, u8),
298    /// Applies the default `SGR` color
299    Default,
300}
301impl<I: Into<SGRString>> EasySGR for I {}
302/// Allows for chaining SGR sequence types
303///
304/// Methods return a [`SGRString`]
305pub trait EasySGR: Into<SGRString> {
306    /// Turns self into [`SGRString`]
307    ///
308    /// Equivalent to calling
309    ///```rust
310    /// # use easy_sgr::SGRString;
311    /// # pub trait EasySGR: Into<SGRString> {
312    /// # fn to_sgr(self) -> SGRString {
313    ///Into::<SGRString>::into(self)
314    /// # }
315    /// # }
316    ///```
317    #[must_use]
318    #[inline]
319    fn to_sgr(self) -> SGRString {
320        self.into()
321    }
322    /// Sets the normal text of the returned [`SGRString`]  
323    #[must_use]
324    #[inline]
325    fn text(self, text: impl Into<String>) -> SGRString {
326        SGRString {
327            text: text.into(),
328            ..self.into()
329        }
330    }
331    /// Adds a style to the returned [`SGRString`]
332    #[must_use]
333    #[inline]
334    fn style(self, style: impl Into<Style>) -> SGRString {
335        use Style::*;
336        use StyleKind::*;
337
338        let mut this = self.into();
339        match style.into() {
340            Reset => this.reset = true,
341            Bold => this.bold = Place,
342            Dim => this.dim = Place,
343            Italic => this.italic = Place,
344            Underline => this.underline = Place,
345            Blinking => this.blinking = Place,
346            Inverse => this.inverse = Place,
347            Hidden => this.hidden = Place,
348            Strikethrough => this.strikethrough = Place,
349
350            NotBold => this.bold = Clean,
351            NotDim => this.dim = Clean,
352            NotItalic => this.italic = Clean,
353            NotUnderline => this.underline = Clean,
354            NotBlinking => this.blinking = Clean,
355            NotInverse => this.inverse = Clean,
356            NotHidden => this.hidden = Clean,
357            NotStrikethrough => this.strikethrough = Clean,
358        }
359        this
360    }
361    /// Adds a color(foreground or background) to the returned [`SGRString`]  
362    #[must_use]
363    #[inline]
364    fn color(self, color: impl Into<Color>) -> SGRString {
365        use {Color::*, ColorKind::*};
366
367        let mut this = self.into();
368
369        (this.foreground, this.background) = match color.into() {
370            BlackFg => (Black, this.background),
371            RedFg => (Red, this.background),
372            GreenFg => (Green, this.background),
373            YellowFg => (Yellow, this.background),
374            BlueFg => (Blue, this.background),
375            MagentaFg => (Magenta, this.background),
376            CyanFg => (Cyan, this.background),
377            WhiteFg => (White, this.background),
378            ByteFg(n) => (Byte(n), this.background),
379            RgbFg(r, g, b) => (Rgb(r, g, b), this.background),
380            DefaultFg => (Default, this.background),
381
382            BlackBg => (this.foreground, Black),
383            RedBg => (this.foreground, Red),
384            GreenBg => (this.foreground, Green),
385            YellowBg => (this.foreground, Yellow),
386            BlueBg => (this.foreground, Blue),
387            MagentaBg => (this.foreground, Magenta),
388            CyanBg => (this.foreground, Cyan),
389            WhiteBg => (this.foreground, White),
390            ByteBg(n) => (this.foreground, Byte(n)),
391            RgbBg(r, g, b) => (this.foreground, Rgb(r, g, b)),
392            DefaultBg => (this.foreground, Default),
393        };
394        this
395    }
396    /// Adds a custom code to the returned [`SGRString`]
397    ///
398    /// Code is used in [`SGRString::place_all`]
399    #[must_use]
400    #[inline]
401    fn custom(self, code: impl Into<u8>) -> SGRString {
402        let mut this = self.into();
403        this.custom_places.push(code.into());
404        this
405    }
406    /// Sets the [`CleanKind`] variant of the returned [`SGRString`]
407    #[must_use]
408    #[inline]
409    fn clean(self, clean: impl Into<CleanKind>) -> SGRString {
410        let mut this = self.into();
411        this.clean = clean.into();
412        this
413    }
414    /// Adds a custom code to be written before the returned [`SGRString`]'s text
415    #[must_use]
416    #[inline]
417    fn custom_place(self, code: impl Into<u8>) -> SGRString {
418        let mut this = self.into();
419        this.custom_places.push(code.into());
420        this
421    }
422    /// Adds a custom code to be written after the returned [`SGRString`]'s text
423    #[must_use]
424    #[inline]
425    fn custom_clean(self, code: impl Into<u8>) -> SGRString {
426        let mut this = self.into();
427        this.custom_cleans.push(code.into());
428        this
429    }
430}