Skip to main content

devpage_design/render/
drawing.rs

1//! A drawing: a scene laid out and made into its display list in one
2//! theme, kept so later scenes update it; its area and the images it paints.
3
4use std::collections::{BTreeMap, BTreeSet, HashMap};
5
6use kurbo::{Rect as KRect, Vec2};
7use peniko::ImageData;
8
9use crate::display::{
10    Blend, Builder, Chunk, Clip, Effect, Id, List, Order, Placed, RunId, Space, Transform,
11};
12use crate::scene::{self, ImageId, Node, Origin, Scene, Theme};
13use crate::tree::{NodeId as SceneId, OrderKey};
14
15use super::*;
16
17/// An image a drawing paints, and what it shows: an image file, known by
18/// its id, or a shadow's pixels.
19#[derive(Clone)]
20pub(super) struct Painted {
21    pub(super) data: ImageData,
22    pub(super) file: Option<ImageId>,
23}
24
25impl Painted {
26    /// Whether the two show the same pixels.
27    pub(super) fn same(&self, other: &Painted) -> bool {
28        match (self.file, other.file) {
29            (Some(a), Some(b)) => a == b,
30            (None, None) => {
31                (self.data.width, self.data.height) == (other.data.width, other.data.height)
32                    && self.data.data.data() == other.data.data.data()
33            }
34            _ => false,
35        }
36    }
37}
38
39/// A scene laid out, in one theme, and made into its display list: kept, so
40/// a later scene updates it by what changed. Its layout tree, every node's
41/// box by its id, the property-tree nodes each node made, the list, and the
42/// images the list paints.
43pub struct Drawing {
44    /// The layout: the drawing's own tree and each run's.
45    pub(super) laid: Laid,
46    pub(super) theme: Theme,
47    /// Every node's box, in its space's coordinates.
48    pub(super) frames: BTreeMap<Order, Frame>,
49    pub(super) recs: BTreeMap<Order, Rec>,
50    /// Each run's place in the list, by its layout's slot.
51    pub(super) shown: HashMap<u32, RunId>,
52    /// For each run, the least start along it, in the design's coordinates,
53    /// of a child an update took out: where what follows moved from.
54    pub(super) shifts: HashMap<RunId, f64>,
55    /// Each run as the last walk through it found it.
56    pub(super) contexts: HashMap<RunId, RunContext>,
57    /// Each node's order, by its id: where a patch's nodes are in it.
58    pub(super) orders: HashMap<SceneId, Order>,
59    /// The display list: chunks under property trees, and their index.
60    pub(crate) list: List,
61    /// The blob each image file is painted from in this drawing.
62    pub(super) blobs: HashMap<ImageId, ImageData>,
63    /// Every image the list paints, each under a blob of its own, in the
64    /// order the list first paints it: what the GPU's atlas holds for this
65    /// drawing, placed as [`crate::atlas::place`] places them.
66    pub(crate) images: Vec<ImageData>,
67    /// What each of `images` shows.
68    pub(super) shows: Vec<Painted>,
69    /// The nodes whose own commands paint an image, in paint order.
70    pub(super) painters: BTreeSet<Order>,
71    /// Whether `images` fit the GPU's atlas together.
72    pub(super) fits: Result<(), Undrawable>,
73    /// How many nodes the last update visited after laying out.
74    pub(super) visited: usize,
75}
76
77/// What an update did to a drawing's tiles: every one, or those over the
78/// rects it touched.
79#[derive(Debug, Clone, PartialEq)]
80pub enum Damage {
81    /// Every tile: a new scene, or the drawing's top-left moved, which moves
82    /// every tile.
83    All,
84    /// The tiles over `rects`, in the design's coordinates — each chunk the
85    /// update changed, where it was and where it is, and the strips the
86    /// drawing's area gained or lost — of a drawing whose top-left is now
87    /// `origin`.
88    Rects {
89        origin: (f64, f64),
90        rects: Vec<KRect>,
91    },
92}
93
94/// The property-tree nodes a scene node made — its transform, its opacity's
95/// effect and its clip — and the images its own commands paint, in order.
96#[derive(Clone)]
97pub(super) struct Rec {
98    pub(super) node: SceneId,
99    /// The space its box and chunk are in.
100    pub(super) space: Space,
101    pub(super) transform: Id,
102    pub(super) effect: Option<Id>,
103    pub(super) clip: Option<Id>,
104    pub(super) painted: Vec<(Painted, Origin)>,
105}
106
107impl Drawing {
108    /// The design's drawing: its root's box plus anything painted outside
109    /// it, cut by any clipping frame, in whole pixels.
110    pub fn area(&self) -> Frame {
111        self.list.area
112    }
113
114    /// Every node's box, in tree order, in the design's coordinates.
115    pub fn frames(&self) -> Vec<Frame> {
116        self.frames
117            .iter()
118            .map(|(o, f)| {
119                let at = self.list.space_at(&self.recs[o].space);
120                Frame {
121                    x: f.x + at.x as f32,
122                    y: f.y + at.y as f32,
123                    ..*f
124                }
125            })
126            .collect()
127    }
128
129    pub fn theme(&self) -> Theme {
130        self.theme
131    }
132
133    /// The interaction of the topmost node at `(x, y)` — in the design's
134    /// coordinates — that has one, where its clipping frames show it.
135    pub fn action_at(&self, x: f32, y: f32) -> Option<&str> {
136        self.list.action_at(x as f64, y as f64)
137    }
138
139    /// Updates the drawing to `new` by the patch that made it: `applied`,
140    /// what [`crate::scene::apply`] did to the scene the drawing showed — the
141    /// subtrees removed and placed and the nodes changed, by id. Only those
142    /// are laid out and painted again, and nodes that only moved keep their
143    /// chunks — the work is the patch's, never the scene's. The damage it
144    /// returns is what changed; or why `new` can't be painted — its images
145    /// too many for the GPU's atlas — in which case the drawing is `new`'s
146    /// all the same.
147    pub(crate) fn update(
148        &mut self,
149        renderer: &mut Renderer,
150        new: &Scene,
151        applied: &crate::scene::Applied,
152    ) -> Result<Damage, Undrawable> {
153        renderer.update(self, new, applied)
154    }
155
156    /// Rounds anew, before `area` — in the design's coordinates — is painted,
157    /// hit or read, the children of runs there that an update moved by a
158    /// fraction of a pixel: an update moves them by the sums alone and leaves
159    /// their boxes and chunks as they were, so an edit costs what it changed
160    /// and the rounding costs what is read. `scene` is the scene the drawing
161    /// shows. After it, the drawing there is a fresh one's, box for box.
162    pub(crate) fn prepare(&mut self, renderer: &mut Renderer, scene: &Scene, area: KRect) {
163        let mut rounded = false;
164        loop {
165            let stale = self.list.unrounded(area);
166            if stale.is_empty() {
167                break;
168            }
169            for (drid, key) in stale {
170                renderer.reround(self, scene, drid, &key);
171            }
172            self.list.flush();
173            rounded = true;
174        }
175        if rounded {
176            renderer.settle_area(self);
177        }
178    }
179
180    /// [`Drawing::prepare`] over the whole drawing.
181    #[cfg(test)]
182    pub(crate) fn prepare_all(&mut self, renderer: &mut Renderer, scene: &Scene) {
183        let everything = KRect::new(-1e12, -1e12, 1e12, 1e12);
184        self.prepare(renderer, scene, everything);
185    }
186}
187
188impl Renderer {
189    /// The scene laid out once and made into its display list in `theme` —
190    /// every color its value there — or why it can't be drawn: images and
191    /// shadows that don't fit the GPU's atlas together. What the PNG, the
192    /// canvas and `devpage check` all start from.
193    ///
194    /// Each image is painted under a blob of its own, made in the order the
195    /// drawing first paints it — except that a drawing made `after` the one
196    /// a canvas last showed, painting the same images in the same order,
197    /// paints that drawing's: the GPU then keeps the atlas it has, which
198    /// holds exactly them, placed as a fresh one would.
199    pub fn drawing(
200        &mut self,
201        scene: &Scene,
202        theme: Theme,
203        after: Option<&Drawing>,
204    ) -> Result<Drawing, Undrawable> {
205        self.register(&scene.fonts);
206        let mut laid = Laid::build(self, scene);
207        laid.settle_top(self);
208        let root = laid.root;
209        let mut d = Drawing {
210            laid,
211            theme,
212            frames: BTreeMap::new(),
213            recs: BTreeMap::new(),
214            shown: HashMap::new(),
215            shifts: HashMap::new(),
216            contexts: HashMap::new(),
217            orders: HashMap::new(),
218            list: List::new(),
219            blobs: HashMap::new(),
220            images: Vec::new(),
221            shows: Vec::new(),
222            painters: BTreeSet::new(),
223            fits: Ok(()),
224            visited: 0,
225        };
226        self.blobs.clear();
227        let mut made = Vec::new();
228        let (t, c) = (d.list.root_transform, d.list.root_clip);
229        let mut walk = Walk {
230            scene,
231            dirty: &|_| true,
232            relaid: &|_| true,
233            path: None,
234            damage: None,
235            fresh: Some(&mut made),
236            images: true,
237        };
238        self.sync(
239            &mut d,
240            &mut walk,
241            &scene.root,
242            Vec::new(),
243            Owner::Top,
244            root,
245            t,
246            Place::default(),
247            (&Space::Root, Vec2::ZERO),
248            c,
249            None,
250            None,
251            (false, true),
252            None,
253        );
254        d.list.load(made);
255        d.blobs = std::mem::take(&mut self.blobs);
256        self.settle_area(&mut d);
257        self.settle_images(&mut d)?;
258        if let Some(last) = after
259            && last.shows.len() == d.shows.len()
260            && last.shows.iter().zip(&d.shows).all(|(a, b)| a.same(b))
261        {
262            let last_of: HashMap<u64, ImageData> = d
263                .shows
264                .iter()
265                .zip(&last.shows)
266                .map(|(new, old)| (new.data.data.id(), old.data.clone()))
267                .collect();
268            d.list.swap_images(&last_of);
269            for rec in d.recs.values_mut() {
270                for (p, _) in &mut rec.painted {
271                    if let Some(old) = last_of.get(&p.data.data.id()) {
272                        p.data = old.clone();
273                    }
274                }
275            }
276            for data in d.blobs.values_mut() {
277                if let Some(old) = last_of.get(&data.data.id()) {
278                    *data = old.clone();
279                }
280            }
281            d.shows = last.shows.clone();
282            d.images = d.shows.iter().map(|p| p.data.clone()).collect();
283        }
284        Ok(d)
285    }
286
287    /// The drawing's area from its root's box and what its chunks cover,
288    /// rounded out to whole pixels, ignoring the slivers floating point
289    /// leaves on a curve's bounds.
290    pub(super) fn settle_area(&mut self, d: &mut Drawing) {
291        let root = d.frames[&Vec::new()];
292        let mut area = frame_rect(root);
293        if let Some(covered) = d.list.covered() {
294            area = area.union(covered);
295        }
296        const SLIVER: f64 = 0.01;
297        let area = KRect::new(
298            (area.x0 + SLIVER).floor(),
299            (area.y0 + SLIVER).floor(),
300            (area.x1 - SLIVER).ceil(),
301            (area.y1 - SLIVER).ceil(),
302        );
303        d.list.set_area(Frame {
304            x: area.x0 as f32,
305            y: area.y0 as f32,
306            width: area.width() as f32,
307            height: area.height() as f32,
308        });
309    }
310
311    /// The images the drawing paints, in the order it first paints them —
312    /// each image file once — and whether they fit the GPU's atlas together.
313    pub(super) fn settle_images(&mut self, d: &mut Drawing) -> Result<(), Undrawable> {
314        d.fits = self.fit_images(d);
315        d.fits.clone()
316    }
317
318    pub(super) fn fit_images(&mut self, d: &mut Drawing) -> Result<(), Undrawable> {
319        let mut files = BTreeSet::new();
320        let mut painted: Vec<(Painted, Origin)> = Vec::new();
321        for order in &d.painters {
322            for (p, at) in &d.recs[order].painted {
323                if p.file.is_none_or(|id| files.insert(id)) {
324                    painted.push((p.clone(), at.clone()));
325                }
326            }
327        }
328        d.blobs.retain(|id, _| files.contains(id));
329        d.shows = painted.iter().map(|(p, _)| p.clone()).collect();
330        d.images = d.shows.iter().map(|p| p.data.clone()).collect();
331        let sizes: Vec<(u32, u32)> = painted
332            .iter()
333            .map(|(p, _)| (p.data.width, p.data.height))
334            .collect();
335        if let Err(n) = crate::atlas::place(&sizes) {
336            let (Painted { data, .. }, at) = &painted[n];
337            let what = if at.file.is_empty() {
338                "an image"
339            } else {
340                "this"
341            };
342            return Err(Undrawable {
343                message: format!(
344                    "{what} doesn't fit: the images a drawing paints — its image files, \
345                     at most {side} pixels a side, and its shadows — share the GPU's one \
346                     {atlas}×{atlas} atlas, and the {n} before this {w}×{h} fill it",
347                    side = scene::MAX_IMAGE_SIDE,
348                    atlas = crate::atlas::SIDE,
349                    w = data.width,
350                    h = data.height,
351                ),
352                at: Some(at.clone()),
353            });
354        }
355        Ok(())
356    }
357
358    /// Makes `node`'s chunk again when it is dirty, new or another size, or
359    /// moves it when only its place changed; then its children's. `remade`
360    /// says its parent's clip or effect was made anew, which its own
361    /// commands are under; `all` that every node under it is to be visited.
362    /// `space` is the space it is in and where that space's origin is in the
363    /// design's coordinates; `item`, the run and key when it is a run's
364    /// child, whose place the run's sums keep.
365    #[allow(clippy::too_many_arguments)]
366    pub(super) fn sync(
367        &mut self,
368        d: &mut Drawing,
369        w: &mut Walk,
370        node: &Node,
371        order: Order,
372        owner: Owner,
373        id: NodeId,
374        parent_t: Id,
375        parent: Place,
376        (space, space_at): (&Space, Vec2),
377        clip: Id,
378        cut: Option<KRect>,
379        effect: Option<Id>,
380        (remade, all_below): (bool, bool),
381        item: Option<(RunId, OrderKey)>,
382    ) -> bool {
383        let (abs, place) = placed(d.laid.tree(owner), id, parent, order.is_empty());
384        let f = Frame {
385            x: abs.x - space_at.x as f32,
386            y: abs.y - space_at.y as f32,
387            ..abs
388        };
389        let parent_origin = Vec2::new(
390            parent.at.0 as f64 - space_at.x,
391            parent.at.1 as f64 - space_at.y,
392        );
393        let origin = Vec2::new(f.x as f64, f.y as f64);
394        d.visited += 1;
395        let old = d.recs.remove(&order);
396        let is_new = old.is_none();
397        let was = d.frames.insert(order.clone(), f);
398        let resized = was.is_none_or(|was| (was.width, was.height) != (f.width, f.height));
399        let moved = was.is_none_or(|was| (was.x, was.y) != (f.x, f.y));
400        let cut_changed = d.list.chunk(&order).is_none_or(|c| c.cut != cut);
401        let dirty = (w.dirty)(&order);
402        let remake = old.is_none() || remade || dirty || resized;
403        let trees = &mut d.list.trees;
404        let offset = if item.is_some() {
405            Vec2::ZERO
406        } else {
407            origin - parent_origin
408        };
409        // Its transform is kept, and moved: its children's are under it.
410        let transform = match &old {
411            Some(rec) => {
412                let t = trees.transforms.get_mut(rec.transform);
413                t.offset = offset;
414                t.item = item.clone();
415                rec.transform
416            }
417            None => trees.transforms.add(Transform {
418                parent: Some(parent_t),
419                offset,
420                item: item.clone(),
421            }),
422        };
423        let mut children_remade = remade;
424        let (own_effect, own_clip) = if remake {
425            // Its opacity and its clip are changed in place where they stay,
426            // so what its children paint under them still names them.
427            // A frame with children always has its own effect and clip, at
428            // no opacity and no shape when it has neither, so its look
429            // changing — opacity or clip coming or going — changes them in
430            // place and reaches none of its children.
431            let parent_node = !node.children.is_empty();
432            let opacity = (node.opacity < 1.0 || parent_node).then_some(node.opacity);
433            let had = old.as_ref().and_then(|r| r.effect);
434            let new_effect = match (had, opacity) {
435                (Some(e), Some(o)) if !remade => {
436                    trees.effects.get_mut(e).blend = Blend::Opacity(o);
437                    Some(e)
438                }
439                (had, o) => {
440                    if let Some(e) = had {
441                        trees.effects.remove(e);
442                    }
443                    children_remade |= had.is_some() || o.is_some();
444                    o.map(|o| {
445                        trees.effects.add(Effect {
446                            parent: effect,
447                            clip,
448                            transform,
449                            blend: Blend::Opacity(o),
450                            bounds: None,
451                        })
452                    })
453                }
454            };
455            let shape = if node.clip {
456                Some(node_shape(
457                    node,
458                    KRect::new(0.0, 0.0, f.width as f64, f.height as f64),
459                    0.0,
460                ))
461            } else {
462                parent_node.then(kurbo::BezPath::new)
463            };
464            let had = old.as_ref().and_then(|r| r.clip);
465            let new_clip = match (had, shape) {
466                (Some(c), Some(path)) if !remade => {
467                    trees.clips.get_mut(c).path = path;
468                    Some(c)
469                }
470                (had, shape) => {
471                    if let Some(c) = had {
472                        trees.clips.remove(c);
473                    }
474                    children_remade |= had.is_some() || shape.is_some();
475                    shape.map(|path| {
476                        trees.clips.add(Clip {
477                            parent: Some(clip),
478                            transform,
479                            path,
480                        })
481                    })
482                }
483            };
484            (new_effect, new_clip)
485        } else {
486            let rec = old.as_ref().expect("kept");
487            (rec.effect, rec.clip)
488        };
489        let under = own_effect.or(effect);
490        let was = d.list.take(&order);
491        let (chunk, painted) = match was {
492            Some(c) if !remake => {
493                let moved = c.origin != origin || c.cut != cut;
494                let at = d.list.space_at(space);
495                if moved && let Some(damage) = w.damage.as_deref_mut() {
496                    damage.extend(c.bounds.map(|b| b + at));
497                }
498                let c = c.moved(origin, cut);
499                if moved && let Some(damage) = w.damage.as_deref_mut() {
500                    damage.extend(c.bounds.map(|b| b + space_at));
501                }
502                (c, old.expect("kept").painted)
503            }
504            was => {
505                if let Some(c) = &was {
506                    if let Some(damage) = w.damage.as_deref_mut() {
507                        let at = d.list.space_at(&c.space);
508                        damage.extend(c.bounds.map(|b| b + at));
509                    }
510                    d.list.free(&c.made);
511                }
512                let local = Frame {
513                    x: 0.0,
514                    y: 0.0,
515                    width: f.width,
516                    height: f.height,
517                };
518                self.painted.clear();
519                let mut covers = Vec::new();
520                let mut b = Builder::new(&mut d.list.trees, transform, clip, under);
521                self.own(w.scene, node, local, d.theme, &mut b, &mut covers);
522                let mut built = b.finish();
523                let mut painted = std::mem::take(&mut self.painted);
524                // A shadow that paints the very pixels it painted before
525                // keeps its blobs, so the GPU keeps its atlas.
526                if let Some(rec) = &old
527                    && rec.painted.len() == painted.len()
528                    && rec
529                        .painted
530                        .iter()
531                        .zip(&painted)
532                        .all(|(a, b)| a.0.same(&b.0))
533                {
534                    let last_of: HashMap<u64, ImageData> = painted
535                        .iter()
536                        .zip(&rec.painted)
537                        .map(|(new, old)| (new.0.data.data.id(), old.0.data.clone()))
538                        .collect();
539                    swap(&mut built.0, &last_of);
540                    painted = rec.painted.clone();
541                }
542                let action = node
543                    .on_click
544                    .clone()
545                    .map(|a| (a, KRect::new(0.0, 0.0, f.width as f64, f.height as f64)));
546                let c = Chunk::new(transform, space.clone(), origin, built, covers, action, cut);
547                if let Some(damage) = w.damage.as_deref_mut() {
548                    damage.extend(c.bounds.map(|b| b + space_at));
549                }
550                if !painted.is_empty() || old.as_ref().is_some_and(|r| !r.painted.is_empty()) {
551                    w.images = true;
552                }
553                (c, painted)
554            }
555        };
556        if painted.is_empty() {
557            d.painters.remove(&order);
558        } else {
559            d.painters.insert(order.clone());
560        }
561        d.orders.insert(node.id.clone(), order.clone());
562        d.recs.insert(
563            order.clone(),
564            Rec {
565                node: node.id.clone(),
566                space: space.clone(),
567                transform,
568                effect: own_effect,
569                clip: own_clip,
570                painted,
571            },
572        );
573        match w.fresh.as_deref_mut() {
574            Some(fresh) if *space == Space::Root => fresh.push((order.clone(), chunk)),
575            _ => d.list.put(order.clone(), chunk),
576        }
577        let rect = frame_rect(f);
578        let (inner, inner_cut) = match own_clip {
579            Some(c) if node.clip => (c, Some(cut.map_or(rect, |k| rect.intersect(k)))),
580            Some(c) => (c, cut),
581            None => (clip, cut),
582        };
583        // Its children: all of them when what they are placed by or drawn
584        // under changed; otherwise only those on a changed node's path, and
585        // the rest too when one of those changed size, which can move them.
586        let all = w.path.is_none()
587            || all_below
588            || is_new
589            || (w.relaid)(&order)
590            || resized
591            || moved
592            || cut_changed
593            || children_remade;
594        if leaf_of(node, &w.scene.images).is_some() {
595            return resized;
596        }
597        let on_path = match (all, w.path) {
598            (false, Some(path)) => path.get(&order).cloned().unwrap_or_default(),
599            _ => BTreeSet::new(),
600        };
601        let down = Down {
602            transform,
603            place,
604            space,
605            space_at,
606            clip: inner,
607            cut: inner_cut,
608            effect: under,
609            remade: children_remade,
610        };
611        if let Some(&rid) = d.laid.run_of.get(&order) {
612            // A run's children are placed by its sums: its own move or change
613            // of size moves them without a visit — what does need one, a
614            // move by a fraction of a pixel, a new breadth or a new padding,
615            // the run finds itself.
616            let every = w.path.is_none() || all_below || is_new || children_remade;
617            let on_path = match w.path {
618                Some(path) if !every => path.get(&order).cloned().unwrap_or_default(),
619                _ => BTreeSet::new(),
620            };
621            self.sync_run(
622                d, w, node, &order, owner, id, rid, &down, every, &on_path, is_new,
623            );
624            return resized;
625        }
626        let visit = |r: &mut Renderer, d: &mut Drawing, w: &mut Walk, k: &OrderKey, all: bool| {
627            let Some(child) = node.children.get(k) else {
628                return false;
629            };
630            let mut o = order.clone();
631            o.push(k.clone());
632            let (owner, id) = d.laid.nodes[&o];
633            r.sync(
634                d,
635                w,
636                child,
637                o,
638                owner,
639                id,
640                down.transform,
641                down.place,
642                (down.space, down.space_at),
643                down.clip,
644                down.cut,
645                down.effect,
646                (down.remade, all),
647                None,
648            )
649        };
650        // Only in auto layout does one child's size move its siblings.
651        let flows = node.layout != scene::Layout::None;
652        if all {
653            for k in node.children.entries().map(|(k, _)| k) {
654                visit(self, d, w, k, all_below);
655            }
656        } else {
657            let mut moves = false;
658            for k in &on_path {
659                moves |= visit(self, d, w, k, false);
660            }
661            if moves && flows {
662                for k in node.children.entries().map(|(k, _)| k) {
663                    if !on_path.contains(k) {
664                        visit(self, d, w, k, false);
665                    }
666                }
667            }
668        }
669        resized
670    }
671
672    /// A run's children: each placed by the run's sums, those `all` or on
673    /// the path visited, and — when one of them changed size by a fraction
674    /// of a pixel, which moves where every child after it rounds — every
675    /// child after it too. A child that changed size moves the ones after it
676    /// by the sums alone; their damage is one rectangle, from where the
677    /// first change starts to where the run ends.
678    #[allow(clippy::too_many_arguments)]
679    fn sync_run(
680        &mut self,
681        d: &mut Drawing,
682        w: &mut Walk,
683        node: &Node,
684        order: &Order,
685        owner: Owner,
686        id: NodeId,
687        rid: u32,
688        down: &Down,
689        all: bool,
690        on_path: &BTreeSet<OrderKey>,
691        is_new: bool,
692    ) {
693        if d.laid.runs.get(rid).free {
694            return self.sync_free(
695                d, w, node, order, owner, id, rid, down, all, on_path, is_new,
696            );
697        }
698        let size = d.laid.tree(owner).unrounded_layout(id).size;
699        let Some((run, asked, lead, gap)) = d.laid.run_places(rid, size) else {
700            return;
701        };
702        let (pc0, _) = run.padding_across();
703        let (row, line, align) = (run.row, asked.line, run.align);
704        let drid = match d.shown.get(&rid) {
705            Some(&r) => r,
706            None => {
707                let r = d.list.add_run(down.space.clone());
708                d.shown.insert(rid, r);
709                r
710            }
711        };
712        let raw = Vec2::new(
713            down.place.raw.0 as f64 - down.space_at.x,
714            down.place.raw.1 as f64 - down.space_at.y,
715        );
716        let at = Vec2::new(
717            down.place.at.0 as f64 - down.space_at.x,
718            down.place.at.1 as f64 - down.space_at.y,
719        );
720        let old_entry = d.list.run(drid).entry().map(|e| e + down.space_at);
721        let changed = {
722            let dr = d.list.run(drid);
723            // What every child's own layout or record holds: its direction,
724            // the gap each carries, its line's breadth. A move of the run, by
725            // any amount, or of where its first child starts, is only where
726            // the sums place them — rounded again lazily, when read.
727            // A new breadth moves the children across only where they are
728            // centred or set at the end; elsewhere it reaches only the
729            // children it laid out again, which the layout lists.
730            is_new
731                || dr.row != row
732                || (dr.line != line as f64 && run.align != crate::scene::Align::Start)
733        };
734        // Where its children start and how far apart they are are the run's
735        // own: a change of either moves every child by the sums, rounded
736        // anew when read, and damages the run.
737        let spaced = {
738            let dr = d.list.run(drid);
739            !is_new && (dr.gap != gap || dr.start != lead as f64)
740        };
741        // A new `align` moves every child across that doesn't stretch: each
742        // one's box genuinely moves, so each is placed again — its subtree,
743        // in its own coordinates, is not.
744        let realign = !is_new && d.contexts.get(&drid).is_some_and(|c| c.align != align);
745        let relaid = std::mem::take(&mut d.laid.runs.get_mut(rid).relaid);
746        {
747            let dr = d.list.run_mut(drid);
748            dr.raw = raw;
749            dr.at = at;
750            dr.origin = down.space_at;
751            dr.row = row;
752            dr.start = lead as f64;
753            dr.across = pc0 as f64;
754            dr.gap = gap;
755            dr.line = line as f64;
756            dr.cut = down.cut;
757        }
758        d.list.resum(drid);
759        let every = all || changed;
760        let local = |v: (f32, f32)| (v.0 - down.space_at.x as f32, v.1 - down.space_at.y as f32);
761        d.contexts.insert(
762            drid,
763            RunContext {
764                order: order.clone(),
765                owner,
766                id,
767                rid,
768                transform: down.transform,
769                place: Place {
770                    at: local(down.place.at),
771                    raw: local(down.place.raw),
772                },
773                space: down.space.clone(),
774                clip: down.clip,
775                effect: down.effect,
776                align,
777            },
778        );
779        let mut shift: Option<f64> = d.shifts.remove(&drid);
780        let keys: Vec<OrderKey> = if every || realign {
781            node.children.entries().map(|(k, _)| k.clone()).collect()
782        } else {
783            on_path.union(&relaid).cloned().collect()
784        };
785        let mut queue: std::collections::VecDeque<OrderKey> = keys.into();
786        let mut seen = BTreeSet::new();
787        while let Some(k) = queue.pop_front() {
788            if !seen.insert(k.clone()) {
789                continue;
790            }
791            let Some(child) = node.children.get(&k) else {
792                continue;
793            };
794            let (run, asked, _, _) = d.laid.run_places(rid, size).expect("placed");
795            let Some(s) = run.size_of(asked, &k) else {
796                continue;
797            };
798            let stretch = run.children[&k].stretch;
799            let offset = run.across(line, &s, stretch) as f64;
800            let child_node = run.children[&k].node;
801            self.relay(&mut d.laid.runs, rid, &k, line);
802            let (before, old_size) = {
803                let dr = d.list.run(drid);
804                let before = dr.prefix(&k);
805                (before, dr.child(&k).map(|(_, p)| p.size))
806            };
807            let item = child_place(down.place, row, lead as f64 + before, pc0 as f64 + offset);
808            let item_at = Vec2::new(item.at.0 as f64, item.at.1 as f64);
809            let mut o = order.clone();
810            o.push(k.clone());
811            self.sync(
812                d,
813                w,
814                child,
815                o,
816                Owner::Run(rid),
817                child_node,
818                down.transform,
819                item,
820                (&Space::Item(drid, k.clone()), item_at),
821                down.clip,
822                None,
823                down.effect,
824                (down.remade, every),
825                Some((drid, k.clone())),
826            );
827            let delta = {
828                let (raw, at) = d.list.run(drid).place(before, offset);
829                at - raw
830            };
831            d.list.place_child(
832                drid,
833                k.clone(),
834                Some(Placed::new(s.along as f64, offset, delta)),
835            );
836            let grew = old_size.map_or(s.along as f64 + 1.0, |o| s.along as f64 - o);
837            if grew != 0.0 {
838                let start = d.list.space_at(down.space) + d.list.run(drid).place(before, offset).1;
839                let start = if row { start.x } else { start.y };
840                // The children after it move by the sums; any it moves by a
841                // fraction of a pixel round anew when they are next read.
842                shift = Some(shift.map_or(start, |s: f64| s.min(start)));
843            }
844        }
845        d.list.flush();
846        if spaced {
847            let first = d.list.space_at(down.space) + d.list.run(drid).place(0.0, 0.0).1;
848            let first = if row { first.x } else { first.y };
849            shift = Some(shift.map_or(first, |s: f64| s.min(first)));
850        }
851        if let Some(start) = shift
852            && let Some(damage) = w.damage.as_deref_mut()
853        {
854            let new_entry = d.list.run(drid).entry().map(|e| e + down.space_at);
855            if let Some(both) = match (old_entry, new_entry) {
856                (Some(a), Some(b)) => Some(a.union(b)),
857                (a, b) => a.or(b),
858            } {
859                let r = if row {
860                    KRect::new(start.max(both.x0), both.y0, both.x1, both.y1)
861                } else {
862                    KRect::new(both.x0, start.max(both.y0), both.x1, both.y1)
863                };
864                if r.width() > 0.0 && r.height() > 0.0 {
865                    damage.push(r);
866                }
867            }
868        }
869    }
870}
871
872impl Renderer {
873    /// A free run's children: each at its own place in the frame, those on
874    /// the path or laid out again visited — every one when the run is new or
875    /// all are to be — none of them moved by another's change. A move of the
876    /// frame by a fraction of a pixel rounds them anew when read.
877    #[allow(clippy::too_many_arguments)]
878    fn sync_free(
879        &mut self,
880        d: &mut Drawing,
881        w: &mut Walk,
882        node: &Node,
883        order: &Order,
884        owner: Owner,
885        id: NodeId,
886        rid: u32,
887        down: &Down,
888        all: bool,
889        on_path: &BTreeSet<OrderKey>,
890        is_new: bool,
891    ) {
892        let drid = match d.shown.get(&rid) {
893            Some(&r) => r,
894            None => {
895                let r = d.list.add_run(down.space.clone());
896                d.shown.insert(rid, r);
897                r
898            }
899        };
900        let local = |v: (f32, f32)| (v.0 - down.space_at.x as f32, v.1 - down.space_at.y as f32);
901        {
902            let dr = d.list.run_mut(drid);
903            dr.free = true;
904            let (raw, at) = (local(down.place.raw), local(down.place.at));
905            dr.raw = Vec2::new(raw.0 as f64, raw.1 as f64);
906            dr.at = Vec2::new(at.0 as f64, at.1 as f64);
907            dr.origin = down.space_at;
908            dr.cut = down.cut;
909        }
910        d.list.resum(drid);
911        d.contexts.insert(
912            drid,
913            RunContext {
914                order: order.clone(),
915                owner,
916                id,
917                rid,
918                transform: down.transform,
919                place: Place {
920                    at: local(down.place.at),
921                    raw: local(down.place.raw),
922                },
923                space: down.space.clone(),
924                clip: down.clip,
925                effect: down.effect,
926                align: crate::scene::Align::Start,
927            },
928        );
929        let relaid = std::mem::take(&mut d.laid.runs.get_mut(rid).relaid);
930        let keys: Vec<OrderKey> = if all || is_new {
931            node.children.entries().map(|(k, _)| k.clone()).collect()
932        } else {
933            on_path.union(&relaid).cloned().collect()
934        };
935        for k in keys {
936            if node.children.get(&k).is_none() {
937                continue;
938            }
939            self.free_item(d, w, node, order, rid, drid, &k, down, all);
940        }
941        d.list.flush();
942    }
943
944    /// A free run's child `k` synced at its own place: its boxes and chunks
945    /// made where they changed, and its place in the run's index.
946    #[allow(clippy::too_many_arguments)]
947    fn free_item(
948        &mut self,
949        d: &mut Drawing,
950        w: &mut Walk,
951        node: &Node,
952        order: &Order,
953        rid: u32,
954        drid: RunId,
955        k: &OrderKey,
956        down: &Down,
957        all: bool,
958    ) {
959        let child = node.children.get(k).expect("a child");
960        self.relay_free(&mut d.laid.runs, rid, k);
961        let child_node = d.laid.runs.get(rid).children[k].node;
962        let (abs, _) = placed(d.laid.tree(Owner::Run(rid)), child_node, down.place, false);
963        let l = *d.laid.tree(Owner::Run(rid)).unrounded_layout(child_node);
964        let item_at = Vec2::new(abs.x as f64, abs.y as f64);
965        let mut o = order.clone();
966        o.push(k.clone());
967        self.sync(
968            d,
969            w,
970            child,
971            o,
972            Owner::Run(rid),
973            child_node,
974            down.transform,
975            down.place,
976            (&Space::Item(drid, k.clone()), item_at),
977            down.clip,
978            None,
979            down.effect,
980            (down.remade, all),
981            Some((drid, k.clone())),
982        );
983        let at = Vec2::new(l.location.x as f64, l.location.y as f64);
984        let delta = {
985            let (raw, rounded) = d.list.run(drid).place_at(at);
986            rounded - raw
987        };
988        d.list
989            .place_child(drid, k.clone(), Some(Placed::at(at, delta)));
990    }
991
992    /// Run `drid`'s child `key` rounded where the run's sums now place it:
993    /// its subtree's boxes and chunks made again as a walk through its run
994    /// would make them.
995    pub(super) fn reround(&mut self, d: &mut Drawing, scene: &Scene, drid: RunId, key: &OrderKey) {
996        let Some(ctx) = d.contexts.get(&drid).cloned() else {
997            return;
998        };
999        let space_at = d.list.space_at(&ctx.space);
1000        let abs = |v: (f32, f32)| (v.0 + space_at.x as f32, v.1 + space_at.y as f32);
1001        let place = Place {
1002            at: abs(ctx.place.at),
1003            raw: abs(ctx.place.raw),
1004        };
1005        if d.laid.runs.get(ctx.rid).free {
1006            let Some(node) = scene.at(&ctx.order) else {
1007                return;
1008            };
1009            let mut walk = Walk {
1010                scene,
1011                dirty: &|_| false,
1012                relaid: &|_| false,
1013                path: None,
1014                damage: None,
1015                fresh: None,
1016                images: false,
1017            };
1018            let down = Down {
1019                transform: ctx.transform,
1020                place,
1021                space: &ctx.space,
1022                space_at,
1023                clip: ctx.clip,
1024                cut: None,
1025                effect: ctx.effect,
1026                remade: false,
1027            };
1028            self.blobs = std::mem::take(&mut d.blobs);
1029            self.free_item(
1030                d, &mut walk, node, &ctx.order, ctx.rid, drid, key, &down, true,
1031            );
1032            d.blobs = std::mem::take(&mut self.blobs);
1033            if walk.images {
1034                let _ = self.settle_images(d);
1035            }
1036            return;
1037        }
1038        let size = d.laid.tree(ctx.owner).unrounded_layout(ctx.id).size;
1039        let Some((run, asked, lead, _)) = d.laid.run_places(ctx.rid, size) else {
1040            return;
1041        };
1042        let line = asked.line;
1043        let Some(s) = run.size_of(asked, key) else {
1044            return;
1045        };
1046        let offset = run.across(line, &s, run.children[key].stretch) as f64;
1047        let (pc0, _) = run.padding_across();
1048        let (row, child_node) = (run.row, run.children[key].node);
1049        self.relay(&mut d.laid.runs, ctx.rid, key, line);
1050        let before = d.list.run(drid).prefix(key);
1051        let item = child_place(place, row, lead as f64 + before, pc0 as f64 + offset);
1052        let item_at = Vec2::new(item.at.0 as f64, item.at.1 as f64);
1053        let mut order = ctx.order.clone();
1054        order.push(key.clone());
1055        let Some(node) = scene.at(&order) else {
1056            return;
1057        };
1058        let mut walk = Walk {
1059            scene,
1060            dirty: &|_| false,
1061            relaid: &|_| false,
1062            path: None,
1063            damage: None,
1064            fresh: None,
1065            images: false,
1066        };
1067        self.blobs = std::mem::take(&mut d.blobs);
1068        self.sync(
1069            d,
1070            &mut walk,
1071            node,
1072            order,
1073            Owner::Run(ctx.rid),
1074            child_node,
1075            ctx.transform,
1076            item,
1077            (&Space::Item(drid, key.clone()), item_at),
1078            ctx.clip,
1079            None,
1080            ctx.effect,
1081            (false, true),
1082            Some((drid, key.clone())),
1083        );
1084        d.blobs = std::mem::take(&mut self.blobs);
1085        let delta = {
1086            let (raw, at) = d.list.run(drid).place(before, offset);
1087            at - raw
1088        };
1089        d.list.place_child(
1090            drid,
1091            key.clone(),
1092            Some(Placed::new(s.along as f64, offset, delta)),
1093        );
1094        if walk.images {
1095            let _ = self.settle_images(d);
1096        }
1097    }
1098}
1099
1100/// Where a run is and what its children are drawn under, as the last walk
1101/// through it found: what rounding a child of it again, lazily, starts from.
1102/// Its places are in its space's coordinates.
1103#[derive(Clone)]
1104pub(super) struct RunContext {
1105    pub(super) order: Order,
1106    pub(super) owner: Owner,
1107    pub(super) id: NodeId,
1108    pub(super) rid: u32,
1109    pub(super) transform: Id,
1110    pub(super) place: Place,
1111    pub(super) space: Space,
1112    pub(super) clip: Id,
1113    pub(super) effect: Option<Id>,
1114    /// The run's `align` when its children were last placed.
1115    pub(super) align: crate::scene::Align,
1116}
1117
1118/// What a node's children are visited under.
1119pub(super) struct Down<'a> {
1120    pub(super) transform: Id,
1121    pub(super) place: Place,
1122    pub(super) space: &'a Space,
1123    pub(super) space_at: Vec2,
1124    pub(super) clip: Id,
1125    pub(super) cut: Option<KRect>,
1126    pub(super) effect: Option<Id>,
1127    pub(super) remade: bool,
1128}
1129
1130/// What a [`Renderer::sync`] walk reads and adds to.
1131pub(super) struct Walk<'a> {
1132    pub(super) scene: &'a Scene,
1133    /// Whether a node's own attributes changed.
1134    pub(super) dirty: &'a dyn Fn(&Order) -> bool,
1135    /// Whether a node's attributes that place or cut its children changed:
1136    /// what reaches them, where they are in its own space.
1137    pub(super) relaid: &'a dyn Fn(&Order) -> bool,
1138    /// The nodes whose children were added or removed: all their children
1139    /// may have moved.
1140    /// For an update, the children of each node on a changed node's path
1141    /// that lead to one; `None` walks every node.
1142    pub(super) path: Option<&'a HashMap<Order, BTreeSet<OrderKey>>>,
1143    pub(super) damage: Option<&'a mut Vec<KRect>>,
1144    /// For a new drawing, the chunks made, to bulk-load the index with.
1145    pub(super) fresh: Option<&'a mut Vec<(Order, Chunk)>>,
1146    /// Whether a chunk made again paints an image, or painted one.
1147    pub(super) images: bool,
1148}
1149
1150/// The commands' images swapped for the blobs `last_of` names.
1151pub(super) fn swap(commands: &mut [crate::display::Command], last_of: &HashMap<u64, ImageData>) {
1152    for c in commands {
1153        if let Draw::Fill(_, Paint::Image(data, _)) = &mut c.draw
1154            && let Some(old) = last_of.get(&data.data.id())
1155        {
1156            *data = old.clone();
1157        }
1158    }
1159}
1160
1161#[cfg(test)]
1162mod tests;