Skip to main content

facett_core/render/
layer.rs

1//! **The graphs-over-maps seam** — a z-ordered [`LayerStack`].
2//!
3//! The render doctrine's "graphs-overlaid-on-maps = LATER" line is unblocked by
4//! adding the *seam* now (not the feature): a shared camera (see
5//! [`super::camera::Camera`]) plus a z-ordered stack of [`Layer`]s so a graph skin
6//! can later composite over a map skin without a retrofit. This milestone ships
7//! the struct + its z-order contract only — **no consumer is wired**; the skins
8//! still draw themselves directly.
9
10/// What a layer *is* — the domain kind drawn at this z. Opaque to the kernel: the
11/// stack only orders layers; it never interprets the kind. Extend as skins land.
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13pub enum LayerKind {
14    /// A 2D vector basemap (facett-map).
15    Map2d,
16    /// A 3D extruded map (facett-map3d).
17    Map3d,
18    /// A graph drawn over the base (facett-graphview / its skins).
19    Graph,
20    /// A caller-supplied overlay (decorations, ravens, HUD).
21    Overlay,
22}
23
24/// One stackable layer: its paint **z** (lower draws first / underneath) and its
25/// [`LayerKind`]. The renderer composites layers in ascending z.
26#[derive(Clone, Copy, Debug, PartialEq)]
27pub struct Layer {
28    /// Paint order key — ascending z paints back→front (a higher-z graph layer
29    /// composites *over* a lower-z map layer). Ties keep insertion order (stable).
30    pub z: f32,
31    pub kind: LayerKind,
32}
33
34impl Layer {
35    pub fn new(z: f32, kind: LayerKind) -> Self {
36        Self { z, kind }
37    }
38}
39
40/// A z-ordered set of [`Layer`]s — the composition seam for graphs-over-maps. Push
41/// layers in any order; [`LayerStack::ordered`] yields them back→front (ascending
42/// z), with insertion order breaking ties so the result is deterministic.
43#[derive(Clone, Debug, Default)]
44pub struct LayerStack {
45    layers: Vec<Layer>,
46}
47
48impl LayerStack {
49    pub fn new() -> Self {
50        Self::default()
51    }
52
53    /// Push a layer; returns its insertion index (its tie-break key).
54    pub fn push(&mut self, layer: Layer) -> usize {
55        self.layers.push(layer);
56        self.layers.len() - 1
57    }
58
59    /// Convenience: push `kind` at `z`.
60    pub fn add(&mut self, z: f32, kind: LayerKind) -> usize {
61        self.push(Layer::new(z, kind))
62    }
63
64    pub fn len(&self) -> usize {
65        self.layers.len()
66    }
67    pub fn is_empty(&self) -> bool {
68        self.layers.is_empty()
69    }
70
71    /// The layers in **paint order** — ascending z, insertion order breaking ties.
72    /// Back→front: the renderer draws element 0 first (underneath), the last on top.
73    pub fn ordered(&self) -> Vec<Layer> {
74        let mut idx: Vec<usize> = (0..self.layers.len()).collect();
75        // Stable sort on z keeps insertion order for equal z (the tie-break rule).
76        idx.sort_by(|&a, &b| {
77            self.layers[a].z.partial_cmp(&self.layers[b].z).unwrap_or(std::cmp::Ordering::Equal)
78        });
79        idx.into_iter().map(|i| self.layers[i]).collect()
80    }
81
82    /// The topmost layer (largest z; last-inserted wins a tie), or `None` if empty.
83    pub fn top(&self) -> Option<Layer> {
84        self.ordered().last().copied()
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    /// INJECT-ASSERT: layers come back ascending-z regardless of push order, so a
93    /// graph pushed under (higher z) a map composites on top.
94    #[test]
95    fn layer_stack_orders_back_to_front_by_z() {
96        let mut s = LayerStack::new();
97        s.add(10.0, LayerKind::Graph); // pushed first, but highest z
98        s.add(0.0, LayerKind::Map3d); // base
99        s.add(5.0, LayerKind::Map2d);
100        let order = s.ordered();
101        assert_eq!(order.len(), 3);
102        assert_eq!(order[0].kind, LayerKind::Map3d, "lowest z drawn first (underneath)");
103        assert_eq!(order[1].kind, LayerKind::Map2d);
104        assert_eq!(order[2].kind, LayerKind::Graph, "highest z composites on top");
105        assert_eq!(s.top().unwrap().kind, LayerKind::Graph);
106    }
107
108    /// INJECT-ASSERT: equal z keeps insertion order (stable tie-break) — two
109    /// overlays at the same z paint in the order the host added them.
110    #[test]
111    fn layer_stack_breaks_ties_by_insertion_order() {
112        let mut s = LayerStack::new();
113        s.add(1.0, LayerKind::Map2d);
114        let first = s.add(2.0, LayerKind::Overlay);
115        let second = s.add(2.0, LayerKind::Graph);
116        let _ = (first, second);
117        let order = s.ordered();
118        assert_eq!(order[0].kind, LayerKind::Map2d);
119        assert_eq!(order[1].kind, LayerKind::Overlay, "first-inserted of the tie paints first");
120        assert_eq!(order[2].kind, LayerKind::Graph);
121    }
122
123    #[test]
124    fn empty_stack_has_no_top() {
125        let s = LayerStack::new();
126        assert!(s.is_empty());
127        assert!(s.top().is_none());
128    }
129}