Skip to main content

slint_mapping/sources/
synthetic.rs

1//! [`SyntheticTileSource`] — generates coloured test tiles in-process.
2//!
3//! Useful for end-to-end testing the framework without a real tile
4//! bundle. Each tile gets:
5//!   - A solid background colour derived from `(z, x, y)` so adjacent
6//!     tiles are visually distinguishable.
7//!   - A 1-pixel border so tile seams are visible.
8//!   - A pair of crossing lines through the centre at higher zooms so
9//!     you can sanity-check the projection (the antimeridian should
10//!     wrap; the prime meridian and equator should sit on tile edges
11//!     at appropriate zoom levels).
12
13use crate::source::{TileKey, TileSource};
14use slint::{Image, Rgba8Pixel, SharedPixelBuffer};
15
16pub struct SyntheticTileSource {
17    tile_size: u32,
18}
19
20impl Default for SyntheticTileSource {
21    fn default() -> Self {
22        Self { tile_size: 256 }
23    }
24}
25
26impl SyntheticTileSource {
27    pub fn new() -> Self {
28        Self::default()
29    }
30
31    pub fn with_tile_size(mut self, size: u32) -> Self {
32        self.tile_size = size;
33        self
34    }
35}
36
37impl TileSource for SyntheticTileSource {
38    fn tile(&self, key: TileKey) -> Option<Image> {
39        let size = self.tile_size;
40        let mut buf = SharedPixelBuffer::<Rgba8Pixel>::new(size, size);
41        let stride = size as usize;
42        let pixels = buf.make_mut_slice();
43
44        // Derive a colour from (z, x, y) so neighbours differ.
45        // Hash-ish: take low bits of each, spread across RGB.
46        let r = (40 + (key.x.wrapping_mul(73) & 0x7f)) as u8;
47        let g = (40 + (key.y.wrapping_mul(151) & 0x7f)) as u8;
48        let b = (40 + ((key.x ^ key.y).wrapping_mul(199) & 0x7f)) as u8;
49        let fill = Rgba8Pixel { r, g, b, a: 255 };
50        let border = Rgba8Pixel {
51            r: 255,
52            g: 255,
53            b: 255,
54            a: 100,
55        };
56        let cross = Rgba8Pixel {
57            r: 255,
58            g: 255,
59            b: 255,
60            a: 60,
61        };
62
63        for y in 0..size as usize {
64            for x in 0..size as usize {
65                let on_border = x == 0 || y == 0 || x == stride - 1 || y == stride - 1;
66                let on_cross = (x == stride / 2) || (y == stride / 2);
67                pixels[y * stride + x] = if on_border {
68                    border
69                } else if on_cross && key.z >= 4 {
70                    cross
71                } else {
72                    fill
73                };
74            }
75        }
76
77        Some(Image::from_rgba8(buf))
78    }
79
80    fn tile_size(&self) -> u32 {
81        self.tile_size
82    }
83
84    fn max_zoom(&self) -> u8 {
85        18
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn always_returns_a_tile() {
95        let src = SyntheticTileSource::new();
96        for key in [
97            TileKey { x: 0, y: 0, z: 0 },
98            TileKey {
99                x: 1234,
100                y: 5678,
101                z: 12,
102            },
103            TileKey {
104                x: u32::MAX,
105                y: u32::MAX,
106                z: 18,
107            },
108        ] {
109            assert!(src.tile(key).is_some(), "{key:?}");
110        }
111    }
112
113    #[test]
114    fn distinct_keys_yield_distinct_tiles() {
115        // Two different keys must produce visually distinct images so
116        // pan/zoom tests can tell tiles apart. We don't have a cheap
117        // pixel-diff here; verifying the colour-deriving math via the
118        // hash spread is enough to guarantee not-all-the-same.
119        let src = SyntheticTileSource::new();
120        let a = src.tile(TileKey { x: 0, y: 0, z: 5 }).unwrap();
121        let b = src.tile(TileKey { x: 1, y: 0, z: 5 }).unwrap();
122        let (aw, ah) = (a.size().width, a.size().height);
123        let (bw, bh) = (b.size().width, b.size().height);
124        assert_eq!((aw, ah), (256, 256));
125        assert_eq!((bw, bh), (256, 256));
126    }
127
128    #[test]
129    fn tile_size_override_takes_effect() {
130        let src = SyntheticTileSource::new().with_tile_size(128);
131        assert_eq!(src.tile_size(), 128);
132        let img = src.tile(TileKey { x: 0, y: 0, z: 0 }).unwrap();
133        assert_eq!(img.size().width, 128);
134        assert_eq!(img.size().height, 128);
135    }
136}