too/renderer/
rasterizer.rs

1use std::{borrow::Cow, ops::RangeInclusive};
2
3use crate::{
4    layout::Axis,
5    math::{pos2, Pos2, Rect},
6    renderer::{Attribute, Cell, Color, Grapheme, Pixel, Rgba},
7    view::ViewId,
8    Str,
9};
10
11/// Shapes that a [`Rasterizer`] can produce
12#[derive(Clone, PartialEq)]
13pub enum Shape {
14    /// Fill the region with a color
15    FillBg {
16        /// The region to fill
17        rect: Rect,
18        /// The color to use
19        color: Rgba,
20    },
21    /// Fill the region with a pixel
22    FillWith {
23        /// The region to fill
24        rect: Rect,
25        /// The [`Pixel`] to use
26        pixel: Pixel,
27    },
28    /// Draw a line between 2 positions, wtih a pixel
29    Line {
30        /// The start position
31        start: Pos2,
32        /// The end position
33        end: Pos2,
34        /// The pixel to use
35        pixel: Pixel,
36    },
37    /// Draws some text into a region
38    Text {
39        /// The region to use
40        rect: Rect,
41        /// The Text to use
42        shape: TextShape<'static>,
43    },
44    /// Set a specific cell at a position with a [`Cell`]
45    Set {
46        /// The position
47        pos: Pos2,
48        /// The cell to use
49        cell: Cell,
50    },
51}
52
53impl std::fmt::Debug for Shape {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        struct CompactRect<'a>(&'a Rect);
56        impl<'a> std::fmt::Debug for CompactRect<'a> {
57            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58                write!(
59                    f,
60                    "{{ x: {}, y: {}, w: {}, h: {} }}",
61                    self.0.min.x,
62                    self.0.min.y,
63                    self.0.width(),
64                    self.0.height()
65                )
66            }
67        }
68
69        match self {
70            Self::FillBg { rect, color } => f
71                .debug_struct("FillBg")
72                .field("rect", &CompactRect(rect))
73                .field("color", color)
74                .finish(),
75            Self::FillWith { rect, pixel } => f
76                .debug_struct("FillWith")
77                .field("rect", &CompactRect(rect))
78                .field("pixel", pixel)
79                .finish(),
80            Self::Line { start, end, pixel } => f
81                .debug_struct("Line")
82                .field("start", start)
83                .field("end", end)
84                .field("pixel", pixel)
85                .finish(),
86            Self::Text { rect, shape } => f
87                .debug_struct("Text")
88                .field("rect", &CompactRect(rect))
89                .field("shape", shape)
90                .finish(),
91            Self::Set { pos, cell } => f
92                .debug_struct("Set")
93                .field("pos", pos)
94                .field("cell", cell)
95                .finish(),
96        }
97    }
98}
99
100/// A rasterizer turns abstract shapes into _draw_ calls
101pub trait Rasterizer {
102    /// Start a new shape with an provided id
103    fn begin(&mut self, _id: ViewId) {}
104    /// End the current shape with the provided id
105    fn end(&mut self, _id: ViewId) {}
106
107    /// Sets the region for this rasterizer to use
108    fn set_rect(&mut self, rect: Rect);
109    /// The region that this rasterizer is using
110    fn rect(&self) -> Rect;
111
112    /// Clear the entire region with a color
113    fn clear(&mut self, color: Rgba) {
114        self.fill_bg(color);
115    }
116
117    /// Fill the background of the region with a color
118    fn fill_bg(&mut self, color: Rgba);
119    /// Fill the background of the region with a pixel
120    fn fill_with(&mut self, pixel: Pixel);
121
122    /// Draw a horizontal line at the `y` offset between `x0..=x1` using the provided pixel
123    fn horizontal_line(&mut self, y: i32, range: RangeInclusive<i32>, pixel: Pixel) {
124        self.line(Axis::Horizontal, pos2(0, y), range, pixel)
125    }
126    /// Draw a vertical line at the `x` offset between `y0..=y1` using the provided pixel
127    fn vertical_line(&mut self, x: i32, range: RangeInclusive<i32>, pixel: Pixel) {
128        self.line(Axis::Vertical, pos2(x, 0), range, pixel)
129    }
130
131    /// Draws a line in a specific orientation starting an offset `x0,x1..=y0,y1` using the provided pixel
132    fn line(&mut self, axis: Axis, offset: Pos2, range: RangeInclusive<i32>, pixel: Pixel);
133
134    /// Draws a [`TextShape`] into the region
135    fn text(&mut self, shape: TextShape<'_>);
136
137    /// Sets a pixel as a specific position
138    fn pixel(&mut self, pos: Pos2, pixel: Pixel);
139    /// Sets a grapheme as a specific position
140    fn grapheme(&mut self, pos: Pos2, grapheme: Grapheme);
141    /// Tries to get a cell at a specific position
142    fn get_mut(&mut self, pos: Pos2) -> Option<&mut Cell>;
143}
144
145/// A shape for drawing text
146#[derive(Clone, Debug, PartialEq)]
147pub struct TextShape<'a> {
148    pub(crate) label: Cow<'a, str>,
149    pub(crate) fg: Color,
150    pub(crate) bg: Color,
151    pub(crate) attribute: Option<Attribute>,
152}
153
154impl<'a> From<&'a str> for TextShape<'a> {
155    fn from(value: &'a str) -> Self {
156        Self::new(value)
157    }
158}
159
160impl<'a> From<&'a Str> for TextShape<'a> {
161    fn from(value: &'a Str) -> Self {
162        Self::new(value)
163    }
164}
165
166impl<'a> TextShape<'a> {
167    /// Create a new text shape from a label.
168    ///
169    /// By default, when drawn ontop of another shape, this will:
170    /// - reset the foreground
171    /// - reuse the background
172    pub const fn new(label: &'a str) -> Self {
173        Self {
174            label: Cow::Borrowed(label),
175            fg: Color::Reset,
176            bg: Color::Reuse,
177            attribute: None,
178        }
179    }
180
181    /// Sets the foreground to use for this label
182    pub fn fg(mut self, fg: impl Into<Rgba>) -> Self {
183        self.fg = Color::Set(fg.into());
184        self
185    }
186
187    /// Sets the background to use for this label
188    pub fn bg(mut self, bg: impl Into<Rgba>) -> Self {
189        self.bg = Color::Set(bg.into());
190        self
191    }
192
193    /// Use this attribute for the label
194    pub fn attribute(mut self, attribute: Attribute) -> Self {
195        match &mut self.attribute {
196            Some(attr) => *attr |= attribute,
197            None => self.attribute = Some(attribute),
198        }
199        self
200    }
201
202    /// Use this attribute for the label, maybe
203    pub fn maybe_attribute(mut self, attribute: Option<Attribute>) -> Self {
204        match attribute {
205            Some(attr) => self.attribute(attr),
206            None => {
207                self.attribute.take();
208                self
209            }
210        }
211    }
212}