Skip to main content

devpage_design/design/
mod.rs

1//! A design as it is held while it is shown: its scene, how many nodes use
2//! each of its files, and its drawing in each theme a caller asks for —
3//! every one brought along by each change to the scene, the damage it does
4//! to their tiles kept until it is taken. It answers what a canvas or a PNG
5//! asks of a drawing: its size, the interaction at a point, the tiles a
6//! view needs, and an area to paint — rounded first where an update left a
7//! run's children a fraction of a pixel off, so what is painted there is a
8//! fresh drawing's.
9//!
10//! **One rule for a scene that can't be drawn** — its images too many for
11//! the GPU's atlas. The design keeps showing its last good drawing in that
12//! theme: the drawing of the last scene that could be drawn, made afresh the
13//! moment the scene stopped being drawable, which every tile, size and
14//! click is answered from meanwhile — a tile no one painted before, after a
15//! pan or a zoom, too — and which no damage reaches; why is
16//! [`Design::problem`]. The drawing that follows the scene is kept beside
17//! it, so the next change updates it at the change's cost, and the change
18//! that makes the scene drawable again damages every tile of that theme.
19//!
20//! It holds no renderer — a server shares one between its designs, a canvas
21//! has its own — and has no mode: the browser draws the theme it shows when
22//! it is shown, the server every theme the design has, at once.
23
24use kurbo::Rect as KRect;
25use peniko::ImageData;
26
27use crate::display::{Frame, List};
28use crate::proto;
29use crate::render::{Damage, Drawing, Renderer, Undrawable};
30use crate::scene::{self, Applied, Edits, Scene, Theme, Uses};
31use crate::tiles::{self, Level, View};
32
33/// A design's scene — its one copy — the files it uses, and its drawing in
34/// each theme it was asked to draw in.
35pub struct Design {
36    scene: Scene,
37    uses: Uses,
38    /// Light, then dark.
39    themes: [Shown; 2],
40    /// What changed since it was last taken, per theme.
41    damage: Vec<(Theme, Damage)>,
42}
43
44/// One theme of a design.
45#[derive(Default)]
46struct Shown {
47    /// The scene laid out and made into its display list in the theme;
48    /// `None` before it is first asked for, or when it was let go.
49    drawing: Option<Drawing>,
50    /// A whole scene arrived since `drawing` was made: it is made afresh
51    /// when next asked for, sharing its images' blobs where it paints the
52    /// same ones.
53    stale: bool,
54    /// Why the scene can't be drawn in the theme. Nothing is painted from
55    /// `drawing` meanwhile.
56    broken: Option<Undrawable>,
57    /// While the scene can't be drawn: the drawing of the last scene that
58    /// could be, in the theme — what is shown meanwhile. `None` otherwise,
59    /// or when no scene could be drawn in the theme yet.
60    good: Option<Drawing>,
61}
62
63const THEMES: [Theme; 2] = [Theme::Light, Theme::Dark];
64
65fn index(theme: Theme) -> usize {
66    match theme {
67        Theme::Light => 0,
68        Theme::Dark => 1,
69    }
70}
71
72impl Design {
73    /// `scene`, drawn in no theme yet: every tile of both is stale.
74    pub fn new(scene: Scene) -> Design {
75        Design {
76            uses: Uses::of(&scene.root),
77            scene,
78            themes: Default::default(),
79            damage: THEMES.iter().map(|&t| (t, Damage::All)).collect(),
80        }
81    }
82
83    /// The scene.
84    pub fn scene(&self) -> &Scene {
85        &self.scene
86    }
87
88    /// The scene replaced whole — a watch stream sent all of it again. Every
89    /// tile of both themes is stale, and each drawing is made afresh when
90    /// next asked for; until one can be, the last good drawing in its theme
91    /// is what is shown — rounded first where updates left it unrounded,
92    /// while the scene it shows is still at hand.
93    pub fn replace(&mut self, renderer: &mut Renderer, scene: Scene) {
94        for shown in &mut self.themes {
95            if shown.broken.is_some() {
96                // The last good drawing stays; the one that followed the
97                // scene before goes with it.
98                shown.drawing = None;
99            } else if let Some(drawing) = shown.drawing.as_mut().filter(|_| !shown.stale) {
100                drawing.prepare(renderer, &self.scene, everywhere());
101            }
102            shown.stale = true;
103            shown.broken = None;
104        }
105        self.uses = Uses::of(&scene.root);
106        self.scene = scene;
107        self.damage.extend(THEMES.iter().map(|&t| (t, Damage::All)));
108    }
109
110    /// Applies `edits` to the scene and brings every drawing along by what
111    /// they did — at the cost of the edits, never the scene's — or says why
112    /// they don't apply, leaving everything as it was. A theme whose scene
113    /// they leave undrawable has its last good drawing made, from the scene
114    /// before them: what it shows until a change makes the scene drawable
115    /// again.
116    pub fn apply(&mut self, renderer: &mut Renderer, edits: &Edits) -> Result<Applied, String> {
117        let (applied, undo) = scene::applied(&mut self.scene, &mut self.uses, edits)?;
118        let mut breaking = Vec::new();
119        for theme in THEMES {
120            let shown = &mut self.themes[index(theme)];
121            let Some(drawing) = shown.drawing.as_mut().filter(|_| !shown.stale) else {
122                // Made afresh, from this scene, when next asked for.
123                shown.broken = None;
124                continue;
125            };
126            match drawing.update(renderer, &self.scene, &applied) {
127                Ok(damage) => {
128                    // Back from a scene that couldn't be drawn: its tiles
129                    // show the last one that could.
130                    let damage = if shown.broken.take().is_some() {
131                        shown.good = None;
132                        Damage::All
133                    } else {
134                        damage
135                    };
136                    self.damage.push((theme, damage));
137                }
138                Err(undrawable) => {
139                    if shown.broken.replace(undrawable).is_none() {
140                        breaking.push(theme);
141                    }
142                }
143            }
144        }
145        if !breaking.is_empty() {
146            // The scene before the edits, which could be drawn, drawn afresh
147            // in each theme they broke — the tiles painted already show it,
148            // so none is damaged — and the edits applied to it again.
149            undo.undo(&mut self.scene, &mut self.uses);
150            for theme in breaking {
151                self.themes[index(theme)].good = renderer.drawing(&self.scene, theme, None).ok();
152            }
153            scene::apply(&mut self.scene, &mut self.uses, edits)
154                .expect("edits apply again to the scene they were just undone from");
155        }
156        Ok(applied)
157    }
158
159    /// Applies a watch stream's patch: [`Design::apply`] with the edits it
160    /// carries, each image and font file the scene holds already taken from
161    /// it.
162    pub fn patch(&mut self, renderer: &mut Renderer, patch: &proto::Patch) -> Result<(), String> {
163        let edits = crate::wire::edits(patch, &self.scene)?;
164        self.apply(renderer, &edits).map(|_| ())
165    }
166
167    /// Draws the scene in `theme`, if it isn't: made afresh the first time,
168    /// or after a whole scene — painting the last drawing's images, in
169    /// either theme, when it paints the same ones, so the GPU keeps its
170    /// atlas — or why it can't be drawn. A scene that couldn't be drawn
171    /// isn't tried again until it changes.
172    pub fn draw(&mut self, renderer: &mut Renderer, theme: Theme) -> Result<(), &Undrawable> {
173        let i = index(theme);
174        let shown = &self.themes[i];
175        if shown.drawing.is_some() && !shown.stale || shown.broken.is_some() {
176            return match &self.themes[i].broken {
177                Some(undrawable) => Err(undrawable),
178                None => Ok(()),
179            };
180        }
181        let last = self.themes[i]
182            .drawing
183            .take()
184            .or_else(|| self.themes[1 - i].drawing.take());
185        let made = {
186            let after = last.as_ref().or(self.themes[i].good.as_ref());
187            renderer.drawing(&self.scene, theme, after)
188        };
189        // The other theme's drawing, taken to share its images' blobs, goes
190        // back.
191        let last = match last {
192            Some(other) if other.theme() != theme => {
193                self.themes[1 - i].drawing = Some(other);
194                None
195            }
196            last => last,
197        };
198        let shown = &mut self.themes[i];
199        match made {
200            // Its tiles went stale when its scene arrived; any painted since
201            // were the last good drawing's.
202            Ok(drawing) => {
203                shown.drawing = Some(drawing);
204                shown.stale = false;
205                if shown.good.take().is_some() {
206                    self.damage.push((theme, Damage::All));
207                }
208                Ok(())
209            }
210            // The last drawing in the theme, of the scene before this one —
211            // which could be drawn, or the last good one would be kept
212            // already — is what is shown until a scene can be drawn again.
213            Err(undrawable) => {
214                if shown.good.is_none() {
215                    shown.good = last;
216                }
217                Err(shown.broken.insert(undrawable))
218            }
219        }
220    }
221
222    /// Lets the drawing in `theme` go: the design isn't shown in it.
223    pub fn forget(&mut self, theme: Theme) {
224        self.themes[index(theme)] = Shown::default();
225        self.damage.retain(|(t, _)| *t != theme);
226    }
227
228    /// Why the scene can't be drawn in `theme`, when it can't.
229    pub fn problem(&self, theme: Theme) -> Option<&Undrawable> {
230        self.themes[index(theme)].broken.as_ref()
231    }
232
233    /// The damage since it was last taken, per theme, in the order it was
234    /// done.
235    pub fn take_damage(&mut self) -> Vec<(Theme, Damage)> {
236        std::mem::take(&mut self.damage)
237    }
238
239    /// The drawing shown in `theme`, drawn if it isn't — the last good one
240    /// while the scene can't be drawn — or `None` when there is none to
241    /// show: no scene could be drawn in it yet.
242    pub fn drawing(&mut self, renderer: &mut Renderer, theme: Theme) -> Option<&Drawing> {
243        let drawn = self.draw(renderer, theme).is_ok();
244        let shown = &self.themes[index(theme)];
245        if drawn {
246            shown.drawing.as_ref()
247        } else {
248            shown.good.as_ref()
249        }
250    }
251
252    /// The shown drawing's area in `theme`: its top-left and size, in the
253    /// design's coordinates.
254    pub fn area(&mut self, renderer: &mut Renderer, theme: Theme) -> Option<Frame> {
255        self.drawing(renderer, theme).map(Drawing::area)
256    }
257
258    /// The interaction of the topmost node at `(x, y)` — in the design's
259    /// coordinates — that has one, where its clipping frames show it.
260    pub fn action_at(
261        &mut self,
262        renderer: &mut Renderer,
263        theme: Theme,
264        x: f64,
265        y: f64,
266    ) -> Option<&str> {
267        let drawing = self.prepared(renderer, theme, KRect::new(x, y, x, y))?;
268        drawing.action_at(x as f32, y as f32)
269    }
270
271    /// The tiles `view` needs at its own zoom's level, nearest its middle
272    /// first — of the last good drawing while the scene can't be drawn.
273    pub fn view_tiles(
274        &mut self,
275        renderer: &mut Renderer,
276        theme: Theme,
277        view: &View,
278    ) -> Vec<(u32, u32)> {
279        let Some(a) = self.area(renderer, theme) else {
280            return Vec::new();
281        };
282        let area = KRect::new(
283            a.x as f64,
284            a.y as f64,
285            (a.x + a.width) as f64,
286            (a.y + a.height) as f64,
287        );
288        view.tiles(area, Level::of(view.zoom))
289    }
290
291    /// The images the shown drawing in `theme` paints, in the order it first
292    /// paints them: what the GPU's atlas holds for it.
293    pub fn images(&mut self, renderer: &mut Renderer, theme: Theme) -> Option<&[ImageData]> {
294        self.drawing(renderer, theme).map(|d| d.images.as_slice())
295    }
296
297    /// The display list to paint tile `(col, row)` at `scale` from, in
298    /// `theme` — the last good drawing's while the scene can't be drawn.
299    pub fn tile(
300        &mut self,
301        renderer: &mut Renderer,
302        theme: Theme,
303        scale: f64,
304        col: u32,
305        row: u32,
306    ) -> Option<&List> {
307        self.painted(renderer, theme, |list| {
308            tiles::tile_rect(list, scale, col, row)
309        })
310    }
311
312    /// The display list to paint a strip of the drawing at 1× from, in
313    /// `theme`: [`tiles::TILE`] pixels wide at tile column `col`, `rows`
314    /// tall from pixel row `y` — a piece of a PNG band too wide for whole
315    /// tiles.
316    pub fn strip(
317        &mut self,
318        renderer: &mut Renderer,
319        theme: Theme,
320        col: u32,
321        y: u32,
322        rows: u32,
323    ) -> Option<&List> {
324        self.painted(renderer, theme, |list| {
325            let (x0, y0) = (list.area.x as f64, list.area.y as f64);
326            KRect::new(
327                x0 + (col * tiles::TILE) as f64,
328                y0 + y as f64,
329                x0 + ((col + 1) * tiles::TILE) as f64,
330                y0 + (y + rows) as f64,
331            )
332        })
333    }
334
335    /// The display list to paint the whole drawing from, in `theme`.
336    pub fn whole(&mut self, renderer: &mut Renderer, theme: Theme) -> Option<&List> {
337        self.painted(renderer, theme, |_| everywhere())
338    }
339
340    /// The shown drawing's display list in `theme`, rounded where `area` —
341    /// in the design's coordinates, from the list — is painted.
342    fn painted(
343        &mut self,
344        renderer: &mut Renderer,
345        theme: Theme,
346        area: impl FnOnce(&List) -> KRect,
347    ) -> Option<&List> {
348        let rect = area(&self.drawing(renderer, theme)?.list);
349        self.prepared(renderer, theme, rect).map(|d| &d.list)
350    }
351
352    /// The shown drawing in `theme` with every run child in `rect` — in the
353    /// design's coordinates — rounded where the runs now place it: what is
354    /// read there is a fresh drawing's. A last good drawing needs none: it
355    /// was made afresh, or rounded whole when its scene was replaced.
356    fn prepared(&mut self, renderer: &mut Renderer, theme: Theme, rect: KRect) -> Option<&Drawing> {
357        if self.draw(renderer, theme).is_err() {
358            return self.themes[index(theme)].good.as_ref();
359        }
360        let drawing = self.themes[index(theme)].drawing.as_mut()?;
361        drawing.prepare(renderer, &self.scene, rect);
362        Some(drawing)
363    }
364}
365
366/// An area holding all of any drawing.
367fn everywhere() -> KRect {
368    KRect::new(-1e12, -1e12, 1e12, 1e12)
369}
370
371#[cfg(test)]
372impl Design {
373    /// The drawing in `theme` as it is held — none made, nothing rounded —
374    /// for a test to look inside.
375    pub(crate) fn held(&mut self, theme: Theme) -> &mut Drawing {
376        self.themes[index(theme)].drawing.as_mut().expect("drawn")
377    }
378}
379
380#[cfg(test)]
381mod tests;