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;