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}