cod/
lib.rs

1//! A small library for command-line drawing.
2#![warn(clippy::pedantic)]
3#![warn(missing_docs)]
4
5use std::io::{stdout, Write};
6
7#[cfg(feature = "crossterm")]
8pub use crossterm;
9
10pub mod clear;
11pub mod color;
12pub mod goto;
13pub mod guard;
14pub mod prelude;
15pub mod rect;
16pub mod style;
17pub mod term;
18
19mod line;
20mod println;
21
22#[cfg(feature = "crossterm")]
23pub mod read;
24
25/// The user attempted to draw a non-orthogonal line through an orthogonal
26/// function, such as [`orth_line`] or [`rect::line`].
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct NonOrthogonal;
29
30/// Print an escape sequence.
31fn escape<T: std::fmt::Display>(code: T) {
32    print!("{}[{}", 27 as char, code);
33}
34
35/// Disable all style and color attributes.
36pub fn normal() {
37    escape("0m");
38}
39
40/// Draw a single character onto the screen.
41pub fn pixel(c: char, x: u32, y: u32) {
42    escape(format!("{};{}H{}", y + 1, x + 1, c));
43}
44
45/// Draw an orthogonal line to the screen.
46///
47/// # Errors
48///
49/// If the given line is non-orthogonal, returns an error.
50pub fn orth_line(c: char, x1: u32, y1: u32, x2: u32, y2: u32) -> Result<(), NonOrthogonal> {
51    if x1 != x2 && y1 != y2 {
52        return Err(NonOrthogonal);
53    }
54
55    if x1 == x2 {
56        let mut y = y1.min(y2);
57
58        while y != y1.max(y2) + 1 {
59            pixel(c, x1, y);
60
61            y += 1;
62        }
63    } else {
64        let mut x = x1.min(x2);
65
66        while x != x1.max(x2) + 1 {
67            pixel(c, x, y1);
68
69            x += 1;
70        }
71    }
72
73    Ok(())
74}
75
76/// Draw a line onto the screen.
77#[allow(clippy::missing_panics_doc)]
78pub fn line(c: char, x1: u32, y1: u32, x2: u32, y2: u32) {
79    if x1 == x2 || y1 == y2 {
80        orth_line(c, x1, x2, y1, y2).unwrap();
81        return;
82    }
83
84    for (x, y) in line::Iter::new(x1, y1, x2, y2) {
85        pixel(c, x, y);
86    }
87}
88
89/// Draw a "texture" onto the screen.
90pub fn blit<S: AsRef<str>>(src: S, mut x: u32, mut y: u32) {
91    let src = src.as_ref();
92    let rows = src.split('\n').map(|s| s.chars());
93
94    let ox = x;
95    for row in rows {
96        for c in row {
97            pixel(c, x, y);
98            x += 1;
99        }
100        x = ox;
101        y += 1;
102    }
103}
104
105/// Draw a "texture" onto the screen, skipping over spaces.
106/// Replaces all `blank`s with actual spaces.
107///
108/// Example:
109///
110/// ```rust
111/// # use cod::prelude::*;
112///
113/// // prints `foobar`
114/// cod::blit("foobar", 0, 0);
115/// // updates to `to ban`
116/// cod::blit_transparent("t _  n", '_', 0, 0);
117/// ```
118pub fn blit_transparent<S: AsRef<str>>(src: S, blank: char, mut x: u32, mut y: u32) {
119    let src = src.as_ref();
120    let rows = src.split('\n').map(|s| s.chars());
121
122    let ox = x;
123    for row in rows {
124        for c in row {
125            match c {
126                ' ' => goto::right(1),
127                ch if ch == blank => pixel(' ', x, y),
128                _ => pixel(c, x, y),
129            }
130            x += 1;
131        }
132        x = ox;
133        y += 1;
134    }
135}
136
137/// Draw a triangle onto the screen.
138pub fn triangle(c: char, x1: u32, y1: u32, x2: u32, y2: u32, x3: u32, y3: u32) {
139    line(c, x1, y1, x2, y2);
140    line(c, x2, y2, x3, y3);
141    line(c, x1, y1, x3, y3);
142}
143
144// TODO: do this ever
145// /// Draw a filled triangle onto the screen.
146// pub fn triangle_fill(c: char, x1: u32, y1: u32, x2: u32, y2: u32, x3: u32, y3: u32) {
147//     todo!()
148// }
149
150/// Draw text onto the screen (non-wrapping, but respects linebreaks).
151pub fn text<S: AsRef<str>>(s: S, x: u32, mut y: u32) {
152    let chars = s.as_ref().chars();
153    let mut nx = x;
154    for ch in chars {
155        if ch == '\n' {
156            nx = x;
157            y += 1;
158        }
159
160        pixel(ch, nx, y);
161        nx += 1;
162    }
163}
164
165/// Flush to stdout.
166///
167/// # Panics
168///
169/// If flushing fails, panics with `Failed to flush to stdout`.
170pub fn flush() {
171    stdout().flush().expect("Failed to flush stdout");
172}