Skip to main content

devpage_design/display/
chunk.rs

1//! A chunk: one scene node's own commands, in its own coordinates, under
2//! property-tree nodes, with an R-tree over them; and the builder that makes
3//! them, opening a node's layers as property-tree nodes.
4
5use kurbo::{BezPath, Rect as KRect, Shape, Vec2};
6use rstar::{AABB, RTree, RTreeObject};
7
8use crate::render::Draw;
9
10use super::*;
11
12/// A drawing command and the property state it is drawn under: its clip and
13/// its effect, if any. Its transform is its chunk's.
14pub struct Command {
15    pub draw: Draw,
16    pub clip: Id,
17    pub effect: Option<Id>,
18    /// Every pixel it can touch, in its chunk's coordinates.
19    pub bounds: KRect,
20}
21
22/// Builds one chunk's commands, turning the layers they are drawn in into
23/// property-tree nodes under the chunk's transform.
24pub struct Builder<'a> {
25    pub(super) trees: &'a mut Trees,
26    pub(super) transform: Id,
27    pub(super) stack: Vec<(Id, Option<Id>)>,
28    pub(super) commands: Vec<Command>,
29    /// The property-tree nodes made for layers, to be freed with the chunk.
30    pub(super) made: Made,
31}
32
33/// The property-tree nodes a chunk's own layers made.
34#[derive(Default, Clone)]
35pub struct Made {
36    pub clips: Vec<Id>,
37    pub effects: Vec<Id>,
38}
39
40impl<'a> Builder<'a> {
41    /// Commands drawn under `transform`, inside `clip` and `effect`.
42    pub fn new(trees: &'a mut Trees, transform: Id, clip: Id, effect: Option<Id>) -> Builder<'a> {
43        Builder {
44            trees,
45            transform,
46            stack: vec![(clip, effect)],
47            commands: Vec::new(),
48            made: Made::default(),
49        }
50    }
51
52    /// A fill or a stroke, covering what its path covers.
53    pub fn draw(&mut self, draw: Draw) {
54        let bounds = match &draw {
55            Draw::Fill(path, _) => path.bounding_box(),
56            Draw::Stroke(path, width, _) => {
57                let half = *width as f64 / 2.0;
58                path.bounding_box().inflate(half, half)
59            }
60            Draw::Glyphs(_, size, _, _, glyphs) => glyph_bounds(*size, glyphs),
61        };
62        let (clip, effect) = *self.stack.last().expect("balanced layers");
63        // Anti-aliasing reaches the pixel an edge passes through.
64        self.commands.push(Command {
65            draw,
66            clip,
67            effect,
68            bounds: bounds.inflate(1.0, 1.0),
69        });
70    }
71
72    /// Opens a clip to `path`, in the chunk's coordinates.
73    pub fn push_clip(&mut self, path: BezPath) {
74        let (clip, effect) = *self.stack.last().expect("balanced layers");
75        let id = self.trees.clips.add(Clip {
76            parent: Some(clip),
77            transform: self.transform,
78            path,
79        });
80        self.made.clips.push(id);
81        self.stack.push((id, effect));
82    }
83
84    /// Opens a layer with `blend`, bounded by `bounds`.
85    pub fn push_layer(&mut self, blend: Blend, bounds: Option<BezPath>) {
86        let (clip, effect) = *self.stack.last().expect("balanced layers");
87        let id = self.trees.effects.add(Effect {
88            parent: effect,
89            clip,
90            transform: self.transform,
91            blend,
92            bounds,
93        });
94        self.made.effects.push(id);
95        self.stack.push((clip, Some(id)));
96    }
97
98    /// Closes the last layer or clip opened.
99    pub fn pop(&mut self) {
100        if self.stack.len() > 1 {
101            self.stack.pop();
102        }
103    }
104
105    pub fn finish(self) -> (Vec<Command>, Made) {
106        (self.commands, self.made)
107    }
108}
109
110/// What a glyph run can touch: its glyphs' origins widened by an em each
111/// way, which holds any glyph's outline — accents, descenders and italic
112/// overhang included — at that size.
113pub(super) fn glyph_bounds(size: f32, glyphs: &[crate::render::Glyph]) -> KRect {
114    let mut r: Option<KRect> = None;
115    for g in glyphs {
116        let p = KRect::new(g.x as f64, g.y as f64, g.x as f64, g.y as f64);
117        r = Some(r.map_or(p, |r| r.union(p)));
118    }
119    let em = size as f64;
120    r.unwrap_or_default().inflate(em, em)
121}
122
123/// A command's entry in its chunk's index.
124pub(super) struct Local {
125    pub(super) at: usize,
126    pub(super) bounds: KRect,
127}
128
129impl RTreeObject for Local {
130    type Envelope = AABB<[f64; 2]>;
131    fn envelope(&self) -> Self::Envelope {
132        aabb(self.bounds)
133    }
134}
135
136pub(super) fn aabb(r: KRect) -> AABB<[f64; 2]> {
137    AABB::from_corners([r.x0, r.y0], [r.x1, r.y1])
138}
139
140/// A node's paint order: the order keys from the root down to it. A node
141/// paints before its children, and children in order.
142pub use crate::tree::Order;
143
144/// What a click on a node does, and where it lands: its box in the design's
145/// coordinates, cut by its clipping frames.
146#[derive(Clone, Debug, PartialEq)]
147pub struct Hit {
148    pub action: String,
149    pub area: KRect,
150}
151
152/// One scene node's own commands, in its own coordinates, and their index.
153pub struct Chunk {
154    pub transform: Id,
155    /// The space its coordinates are in: the design's, or a run child's.
156    pub space: Space,
157    /// Where its coordinates' origin is in its space's.
158    pub origin: Vec2,
159    pub commands: Vec<Command>,
160    pub(super) index: RTree<Local>,
161    /// What cuts it in its space's coordinates: its clipping frames' boxes,
162    /// those inside its space.
163    pub cut: Option<KRect>,
164    /// What it covers for the drawing's area, in its own coordinates, uncut:
165    /// each shape, line and blur by itself.
166    pub covers: Vec<KRect>,
167    /// What its commands can touch in the design's coordinates, cut —
168    /// `None` when that is nothing.
169    pub bounds: Option<KRect>,
170    /// Its node's interaction, if it has one, in its own coordinates: the
171    /// box `(0, 0, width, height)`.
172    pub action: Option<(String, KRect)>,
173    /// The property-tree nodes its own layers made.
174    pub made: Made,
175}
176
177impl Chunk {
178    #[allow(clippy::too_many_arguments)]
179    pub fn new(
180        transform: Id,
181        space: Space,
182        origin: Vec2,
183        (commands, made): (Vec<Command>, Made),
184        covers: Vec<KRect>,
185        action: Option<(String, KRect)>,
186        cut: Option<KRect>,
187    ) -> Chunk {
188        let index = RTree::bulk_load(
189            commands
190                .iter()
191                .enumerate()
192                .map(|(at, c)| Local {
193                    at,
194                    bounds: c.bounds,
195                })
196                .collect(),
197        );
198        let mut chunk = Chunk {
199            transform,
200            space,
201            origin,
202            commands,
203            index,
204            cut,
205            covers,
206            bounds: None,
207            action,
208            made,
209        };
210        chunk.place(origin, cut);
211        chunk
212    }
213
214    /// The chunk with its origin at `origin` and cut by `cut`.
215    pub fn moved(mut self, origin: Vec2, cut: Option<KRect>) -> Chunk {
216        self.place(origin, cut);
217        self
218    }
219
220    pub(super) fn place(&mut self, origin: Vec2, cut: Option<KRect>) {
221        let local = self
222            .commands
223            .iter()
224            .map(|c| c.bounds)
225            .reduce(|a, b| a.union(b));
226        self.bounds = local.and_then(|r| cut_to(r + origin, cut));
227        self.origin = origin;
228        self.cut = cut;
229    }
230
231    /// What it covers in the design's coordinates for the drawing's area:
232    /// each cover cut by itself, then joined.
233    pub fn cover(&self) -> Option<KRect> {
234        self.covers
235            .iter()
236            .filter_map(|&r| cut_to(r + self.origin, self.cut))
237            .reduce(|a, b| a.union(b))
238    }
239
240    /// Where its entry in the top-level index reaches: what it paints and
241    /// where it can be clicked.
242    pub(super) fn reach(&self) -> Option<KRect> {
243        let hit = self
244            .hit()
245            .map(|h| h.area)
246            .filter(|r| r.width() > 0.0 && r.height() > 0.0);
247        match (self.bounds, hit) {
248            (Some(a), Some(b)) => Some(a.union(b)),
249            (a, b) => a.or(b),
250        }
251    }
252}
253
254/// `r` cut by `cut`, or nothing when nothing is left.
255pub(super) fn cut_to(r: KRect, cut: Option<KRect>) -> Option<KRect> {
256    let r = cut.map_or(r, |c| r.intersect(c));
257    (r.width() > 0.0 && r.height() > 0.0).then_some(r)
258}