Skip to main content

standard_plugin/draw/
mod.rs

1//! Vector shapes drawn into a plugin's own surface buffer, in either model.
2//!
3//! [`Shapes`] is implemented by [`Surface<Cells>`](crate::Surface) (ink: a
4//! [`Style`](crate::Style); box-drawing and block characters) and by
5//! [`Surface<Pixels>`](crate::Surface) (ink: an [`Rgba`](crate::Rgba);
6//! antialiased edges). Coordinates are cells or pixels and may lie outside
7//! the surface: everything is clipped, and only what is drawn is marked
8//! dirty.
9//!
10//! ```rust,ignore
11#![doc = include_str!("../../examples/shapes_stage.rs")]
12//! ```
13
14mod cells;
15pub mod font;
16mod pixels;
17
18pub use font::{GLYPH_ADVANCE, GLYPH_HEIGHT, GLYPH_WIDTH};
19pub use pixels::Image;
20
21/// A rectangle whose origin may lie outside the surface.
22#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
23pub struct Area {
24    pub x: i32,
25    pub y: i32,
26    pub w: u32,
27    pub h: u32,
28}
29
30impl Area {
31    pub const fn new(x: i32, y: i32, w: u32, h: u32) -> Self {
32        Self { x, y, w, h }
33    }
34
35    pub(crate) fn left(&self) -> i64 {
36        i64::from(self.x)
37    }
38
39    pub(crate) fn top(&self) -> i64 {
40        i64::from(self.y)
41    }
42
43    pub(crate) fn right(&self) -> i64 {
44        i64::from(self.x) + i64::from(self.w)
45    }
46
47    pub(crate) fn bottom(&self) -> i64 {
48        i64::from(self.y) + i64::from(self.h)
49    }
50}
51
52impl From<crate::Rect> for Area {
53    fn from(rect: crate::Rect) -> Self {
54        Self {
55            x: rect.x.min(i32::MAX as u32) as i32,
56            y: rect.y.min(i32::MAX as u32) as i32,
57            w: rect.w,
58            h: rect.h,
59        }
60    }
61}
62
63/// Shapes both surface models draw. `Ink` is what paints: a style for
64/// cells, a colour for pixels.
65pub trait Shapes {
66    type Ink: Copy;
67
68    /// A one-unit outline of `area`.
69    fn stroke_rect(&mut self, area: Area, ink: Self::Ink);
70
71    /// `area`, filled.
72    fn fill_rect(&mut self, area: Area, ink: Self::Ink);
73
74    /// A line from `from` to `to`, both ends included.
75    fn line(&mut self, from: (i32, i32), to: (i32, i32), ink: Self::Ink);
76
77    /// A horizontal bar across `area`: the left `fraction` (0 to 1) in
78    /// `fill`, the rest in `track`. Partial units are shown with eighth
79    /// blocks (cells) or coverage (pixels).
80    fn bar(&mut self, area: Area, fraction: f32, fill: Self::Ink, track: Self::Ink);
81
82    /// The outline of `area` with corners of `radius` (cells round their
83    /// corners with arcs whatever the radius; a zero radius is square).
84    fn rounded_rect(&mut self, area: Area, radius: u32, ink: Self::Ink);
85
86    /// `area` filled, with corners of `radius`.
87    fn fill_rounded_rect(&mut self, area: Area, radius: u32, ink: Self::Ink);
88
89    /// A circle's outline around `centre`, in continuous coordinates: the
90    /// unit at `(x, y)` spans `x..x + 1`, so `(x + 0.5, y + 0.5)` is its
91    /// centre, and a moving shape can sit between units (pixels antialias
92    /// it; cells round to the nearest cell). Cells are about twice as tall
93    /// as wide, so in cells the radius counts rows and spans twice as many
94    /// columns.
95    fn circle(&mut self, centre: (f32, f32), radius: f32, ink: Self::Ink);
96
97    /// A filled circle (see [`Shapes::circle`]).
98    fn fill_circle(&mut self, centre: (f32, f32), radius: f32, ink: Self::Ink);
99
100    /// Text from `(x, y)`, clipped: cells write graphemes; pixels draw the
101    /// built-in 5x7 font ([`GLYPH_ADVANCE`] pixels per character, ASCII;
102    /// other characters show as a box). Returns the units advanced.
103    fn label(&mut self, x: i32, y: i32, text: &str, ink: Self::Ink) -> u32;
104}
105
106/// `fraction` clamped to 0..=1 (NaN is 0).
107pub(crate) fn unit(fraction: f32) -> f32 {
108    if fraction.is_nan() {
109        0.0
110    } else {
111        fraction.clamp(0.0, 1.0)
112    }
113}
114
115/// The part of the segment inside `[-1, width] x [-1, height]` (a margin
116/// of one unit so antialiased edges stay whole), or `None` when it misses.
117/// Liang-Barsky; the result is rounded to whole units.
118pub(crate) fn clip_segment(
119    from: (i64, i64),
120    to: (i64, i64),
121    width: u32,
122    height: u32,
123) -> Option<((i64, i64), (i64, i64))> {
124    let (x0, y0) = (from.0 as f64, from.1 as f64);
125    let (dx, dy) = ((to.0 - from.0) as f64, (to.1 - from.1) as f64);
126    let (min_x, max_x) = (-1.0, f64::from(width));
127    let (min_y, max_y) = (-1.0, f64::from(height));
128    let (mut enter, mut leave) = (0.0f64, 1.0f64);
129    for (p, q) in [
130        (-dx, x0 - min_x),
131        (dx, max_x - x0),
132        (-dy, y0 - min_y),
133        (dy, max_y - y0),
134    ] {
135        if p == 0.0 {
136            if q < 0.0 {
137                return None;
138            }
139            continue;
140        }
141        let t = q / p;
142        if p < 0.0 {
143            enter = enter.max(t);
144        } else {
145            leave = leave.min(t);
146        }
147        if enter > leave {
148            return None;
149        }
150    }
151    let point = |t: f64| {
152        (
153            libm::round(x0 + t * dx) as i64,
154            libm::round(y0 + t * dy) as i64,
155        )
156    };
157    Some((point(enter), point(leave)))
158}