1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
/*!
Enums and structs used for stylizing text and the cursor.
```
use yacll::stylize::{Styler, Style, Color, Attr, reset};
use yacll::Yogurt;

// my very cool (and not at all unreadable) style
fn cool_style() -> Style {
    Styler::new()
        .foreground(Color::Red)
        .background(Color::Blue)
        .underline(Color::Green)
        .attr(Attr::Underlined)
        .attr(Attr::Overlined)
        .attr(Attr::Strikethrough)
        .attr(Attr::Bold)
        .attr(Attr::Italic)
        .style()
}

fn main() -> yacll::Result<()> {
    let mut y = Yogurt::new();
    y.style(cool_style());
    y.print("Hello, world!\n")?;
    y.style(reset());
    y.flush()?;
    Ok(())
}
// try running `$ cargo run --example=style` if you have the repository cloned!
```
*/

/// Border to use with [`draw_box`](Yogurt::draw_box).
pub const BORDER: [char; 8] = ['┌', '─', '┐', '│', '│', '└', '─', '┘'];
/// Border to use with [`draw_box`](Yogurt::draw_box).
pub const ROUND_BORDER: [char; 8] = ['╭', '─', '╮', '│', '│', '╰', '─', '╯'];

use crate::{Result, Yogurt};
pub use crossterm::style::Color;
pub use crossterm::style::ContentStyle as Style;
use crossterm::{
    cursor::{CursorShape, DisableBlinking, EnableBlinking, Hide, SetCursorShape, Show},
    queue,
    style::{Attribute, Attributes, SetStyle},
};

/// Style builder.
pub struct Styler {
    foreground: Option<Color>,
    background: Option<Color>,
    underline: Option<Color>,
    attributes: Vec<Attr>,
}

impl Default for Styler {
    fn default() -> Self {
        Styler::new()
    }
}

impl Styler {
    /// Construct a new `Styler`.
    pub fn new() -> Self {
        Styler {
            foreground: None,
            background: None,
            underline: None,
            attributes: Vec::new(),
        }
    }
    /// Set the foreground color.
    pub fn foreground(&mut self, color: Color) -> &mut Self {
        self.foreground = Some(color);
        self
    }
    /// Set the background color.
    pub fn background(&mut self, color: Color) -> &mut Self {
        self.background = Some(color);
        self
    }
    /// Set the underline color.
    pub fn underline(&mut self, color: Color) -> &mut Self {
        self.underline = Some(color);
        self
    }
    /// Add an attribute to the list.
    pub fn attr(&mut self, attr: Attr) -> &mut Self {
        self.attributes.push(attr);
        self
    }
    /// Convert into a `Style`.
    pub fn style(&mut self) -> Style {
        Style {
            foreground_color: self.foreground,
            background_color: self.background,
            underline_color: self.underline,
            attributes: Attributes::from(
                self.attributes
                    .iter()
                    .map(|a| <Attr as Into<Attribute>>::into(*a))
                    .collect::<Vec<_>>()
                    .as_slice(),
            ),
        }
    }
}

/// Returns a style that resets all colors and attributes.
pub fn reset() -> Style {
    Styler::new()
        .foreground(Color::Reset)
        .background(Color::Reset)
        .underline(Color::Reset)
        .attr(Attr::Reset)
        .style()
}

/// Possible cursor attributes. See also [`cursor_off`](Yogurt::cursor_off) and
/// [`cursor_on`](Yogurt::cursor_on).
pub enum CursorAttr {
    /// Sets the cursor shape to an underscore.
    UnderScore,
    /// Sets the cursor shape to a line.
    Line,
    /// Sets the cursor shape to a block.
    Block,
    /// Makes cursor blink.
    Blinking,
    /// Makes text hidden.
    Hidden,
}

/// Styling of text and cursor.
impl Yogurt {
    /// Turn on a cursor attribute.
    pub fn cursor_on(&mut self, cursor: CursorAttr) -> Result<()> {
        match cursor {
            CursorAttr::UnderScore => {
                queue! { self.stdout, SetCursorShape(CursorShape::UnderScore) }?
            }
            CursorAttr::Line => queue! { self.stdout, SetCursorShape(CursorShape::Line) }?,
            CursorAttr::Block => queue! { self.stdout, SetCursorShape(CursorShape::Block) }?,
            CursorAttr::Blinking => queue! { self.stdout, EnableBlinking }?,
            CursorAttr::Hidden => queue! { self.stdout, Hide }?,
        }
        Ok(())
    }
    /// Turn off a cursor attribute. Turning off a cursor shape resets it to [`Block`](CursorAttr::Block).
    pub fn cursor_off(&mut self, cursor: CursorAttr) -> Result<()> {
        match cursor {
            CursorAttr::UnderScore | CursorAttr::Line | CursorAttr::Block => {
                queue! { self.stdout, SetCursorShape(CursorShape::Block) }?
            }
            CursorAttr::Blinking => queue! { self.stdout, DisableBlinking }?,
            CursorAttr::Hidden => queue! { self.stdout, Show }?,
        }
        Ok(())
    }
    /// Enable a style.
    pub fn style(&mut self, style: Style) -> Result<()> {
        queue! {
            self.stdout,
            SetStyle(style),
        }?;
        Ok(())
    }
}

// copypasted from crossterm https://docs.rs/crossterm/0.25.0/src/crossterm/style/types/attribute.rs.html#8
// why make a custom attribute type? cuz there's too much extra stuff in the crossterm one
// (like for example how is fraktur even converted to monospace? i doubt any terminals actually support it)
macro_rules! Attribute {
    (
        $(
            $(#[$inner:ident $($args:tt)*])*
            $name:ident = $sgr:expr,
        )*
    ) => {
        /// Represents an attribute.
        ///
        /// | Attribute | Windows | UNIX | Notes |
        /// | :-- | :--: | :--: | :-- |
        /// | `Reset` | ✓ | ✓ | |
        /// | `Bold` | ✓ | ✓ | |
        /// | `Dim` | ✓ | ✓ | |
        /// | `Italic` | ? | ? | Not widely supported, sometimes treated as inverse. |
        /// | `Underlined` | ✓ | ✓ | |
        /// | `Blink` | ? | ? | Not widely supported, sometimes treated as inverse. |
        /// | `Reverse` | ✓ | ✓ | |
        /// | `Hidden` | ✓ | ✓ | Also known as Conceal. |
        #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
        #[non_exhaustive]
        pub enum Attr {
            $(
                $(#[$inner $($args)*])*
                $name,
            )*
        }

        // there used to be some SGR thingy here but i removed that because it wasn't used anywhere

        impl Attr {
            /// Iterates over all the variants of the Attr enum.
            pub fn iter() -> impl Iterator<Item = Attr> {
                use self::Attr::*;
                [ $($name,)* ].iter().copied()
            }
        }
    }
}

Attribute! {
    /// Resets all the attributes.
    Reset = 0,
    /// Increases the text intensity.
    Bold = 1,
    /// Decreases the text intensity.
    Dim = 2,
    /// Emphasises the text.
    Italic = 3,
    /// Underlines the text.
    Underlined = 4,
    /// Makes the text blink.
    Blink = 5,
    /// Swaps foreground and background colors.
    Inverse = 7,
    /// Hides the text (also known as Conceal).
    Hidden = 8,
    /// Crosses out the text.
    Strikethrough = 9,
    /// Draws a line over the top of the text.
    Overlined = 53,
}

impl From<Attr> for Attribute {
    fn from(attr: Attr) -> Self {
        use Attr::*;
        match attr {
            Reset => Attribute::Reset,
            Bold => Attribute::Bold,
            Dim => Attribute::Dim,
            Italic => Attribute::Italic,
            Underlined => Attribute::Underlined,
            Blink => Attribute::SlowBlink,
            Inverse => Attribute::Reverse,
            Hidden => Attribute::Hidden,
            Strikethrough => Attribute::CrossedOut,
            Overlined => Attribute::OverLined,
        }
    }
}