Skip to main content

devpage_design/tiles/
mod.rs

1//! Tiles: squares of a drawing, 256 device pixels a side, painted at one
2//! scale in one theme — where each is in the design, the pyramid's levels,
3//! the cache that keeps them and marks the ones damage makes stale, and the
4//! frame plan a browser canvas carries out.
5
6use kurbo::Rect as KRect;
7
8use crate::display::List;
9use crate::scene::Theme;
10
11mod cache;
12mod plan;
13
14pub use cache::*;
15pub use plan::*;
16
17/// A tile's side, in device pixels.
18pub const TILE: u32 = 256;
19
20/// The design-coordinate square of tile `(col, row)` at `scale`, from the
21/// drawing's top-left.
22pub fn tile_rect(list: &List, scale: f64, col: u32, row: u32) -> KRect {
23    square((list.area.x as f64, list.area.y as f64), scale, col, row)
24}
25
26/// The design-coordinate square of tile `(col, row)` at `scale` of a drawing
27/// whose top-left is `origin`.
28pub fn square((x, y): (f64, f64), scale: f64, col: u32, row: u32) -> KRect {
29    let side = TILE as f64 / scale;
30    KRect::new(
31        x + col as f64 * side,
32        y + row as f64 * side,
33        x + (col + 1) as f64 * side,
34        y + (row + 1) as f64 * side,
35    )
36}
37
38/// A scale tiles are painted at, to a 1/1024th: a level of the pyramid.
39#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
40pub struct Level(pub i64);
41
42impl Level {
43    /// The level a scale is painted at.
44    pub fn of(scale: f64) -> Level {
45        Level((scale * 1024.0).round().max(1.0) as i64)
46    }
47
48    pub fn scale(self) -> f64 {
49        self.0 as f64 / 1024.0
50    }
51}
52
53/// A tile: its theme, its level, and its column and row from the drawing's
54/// top-left.
55#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
56pub struct Key {
57    pub theme: Theme,
58    pub level: Level,
59    pub col: u32,
60    pub row: u32,
61}
62
63impl Key {
64    /// Its square in the design's coordinates, the drawing's top-left at
65    /// `origin`.
66    pub fn rect(&self, origin: (f64, f64)) -> KRect {
67        square(origin, self.level.scale(), self.col, self.row)
68    }
69
70    /// Whether its square, the drawing's top-left at `origin`, overlaps one
71    /// of `rects`, in the design's coordinates.
72    pub fn overlaps(&self, origin: (f64, f64), rects: &[KRect]) -> bool {
73        let r = self.rect(origin);
74        rects
75            .iter()
76            .any(|d| d.x0 < r.x1 && d.x1 > r.x0 && d.y0 < r.y1 && d.y1 > r.y0)
77    }
78}
79
80/// What a cache keys its tiles by: a tile's [`Key`], with whatever else
81/// tells the cache's tiles apart — a server's design, say.
82pub trait Tiled: Copy + Eq + std::hash::Hash {
83    fn key(&self) -> Key;
84}
85
86impl Tiled for Key {
87    fn key(&self) -> Key {
88        *self
89    }
90}
91
92/// A view of a drawing: `width` × `height` device pixels showing the
93/// drawing scaled by `zoom`, its top-left at `origin`, a whole device pixel.
94#[derive(Clone, Copy, Debug)]
95pub struct View {
96    pub width: f64,
97    pub height: f64,
98    pub zoom: f64,
99    pub origin: (f64, f64),
100}
101
102impl View {
103    /// The tiles at `level` the view shows, as columns and rows of the
104    /// drawing `area` × `level`, nearest the view's middle first.
105    pub fn tiles(&self, area: KRect, level: Level) -> Vec<(u32, u32)> {
106        let side = TILE as f64;
107        let scale = level.scale();
108        let (ox, oy) = self.origin;
109        let count = |v: f64| (v * scale / side).ceil() as i64;
110        let (cols, rows) = (count(area.width()), count(area.height()));
111        let span = |o: f64, n: f64, count: i64| {
112            let first = ((-o) / side).floor().max(0.0) as i64;
113            let last = (((n - o) / side).ceil() as i64).min(count);
114            first..last.max(first)
115        };
116        let (cs, rs) = (span(ox, self.width, cols), span(oy, self.height, rows));
117        let mut out: Vec<(u32, u32)> = rs
118            .flat_map(|r| cs.clone().map(move |c| (c as u32, r as u32)))
119            .collect();
120        let (mx, my) = (
121            (self.width / 2.0 - ox) / side,
122            (self.height / 2.0 - oy) / side,
123        );
124        let d = |p: &(u32, u32)| (p.0 as f64 + 0.5 - mx).powi(2) + (p.1 as f64 + 0.5 - my).powi(2);
125        out.sort_by(|a, b| d(a).total_cmp(&d(b)));
126        out
127    }
128
129    /// Where a tile at any level lands in the view, in device pixels —
130    /// `(x0, y0, x1, y1)` — or `None` when the view doesn't show it.
131    pub fn place(&self, area: KRect, key: &Key) -> Option<KRect> {
132        let rect = square((area.x0, area.y0), key.level.scale(), key.col, key.row);
133        let (ox, oy) = self.origin;
134        let to = |x: f64, y: f64| {
135            (
136                ox + (x - area.x0) * self.zoom,
137                oy + (y - area.y0) * self.zoom,
138            )
139        };
140        let (x0, y0) = to(rect.x0, rect.y0);
141        let (x1, y1) = to(rect.x1, rect.y1);
142        (x1 >= 0.0 && y1 >= 0.0 && x0 <= self.width && y0 <= self.height)
143            .then(|| KRect::new(x0, y0, x1, y1))
144    }
145}
146
147#[cfg(test)]
148mod tests;