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