Skip to main content

egui_charts/drawings/services/
z_order.rs

1//! Z-Order Service - manages drawing layer ordering
2//!
3//! Controls the rendering order of drawings on the chart.
4//! Higher z_order values are drawn on top of lower values.
5
6use crate::drawings::domain::Drawing;
7
8/// Service for managing drawing z-order (render layering)
9pub struct ZOrderService;
10
11impl ZOrderService {
12    /// Bring a drawing to the front (highest z-order among all drawings)
13    pub fn bring_to_front(drawings: &mut [Drawing], id: usize) {
14        let max_z = drawings.iter().map(|d| d.z_order()).max().unwrap_or(0);
15        if let Some(drawing) = drawings.iter_mut().find(|d| d.id == id) {
16            drawing.set_z_order(max_z.saturating_add(1));
17        }
18    }
19
20    /// Send a drawing to the back (lowest z-order among all drawings)
21    pub fn send_to_back(drawings: &mut [Drawing], id: usize) {
22        let min_z = drawings.iter().map(|d| d.z_order()).min().unwrap_or(0);
23        if let Some(drawing) = drawings.iter_mut().find(|d| d.id == id) {
24            drawing.set_z_order(min_z.saturating_sub(1));
25        }
26    }
27
28    /// Move a drawing one step forward in z-order
29    ///
30    /// Swaps z-order with the next drawing above it.
31    pub fn bring_forward(drawings: &mut [Drawing], id: usize) {
32        let target_idx = match drawings.iter().position(|d| d.id == id) {
33            Some(i) => i,
34            None => return,
35        };
36        let current_z = drawings[target_idx].z_order();
37
38        // Find the drawing with the next higher z-order
39        let swap_idx = drawings
40            .iter()
41            .enumerate()
42            .filter(|(i, d)| *i != target_idx && d.z_order() > current_z)
43            .min_by_key(|(_, d)| d.z_order())
44            .map(|(i, _)| i);
45
46        match swap_idx {
47            Some(si) => {
48                let above_z = drawings[si].z_order();
49                drawings[target_idx].set_z_order(above_z);
50                drawings[si].set_z_order(current_z);
51            }
52            None => {
53                // Already at the top, bump up by 1
54                drawings[target_idx].set_z_order(current_z.saturating_add(1));
55            }
56        }
57    }
58
59    /// Move a drawing one step backward in z-order
60    ///
61    /// Swaps z-order with the next drawing below it.
62    pub fn send_backward(drawings: &mut [Drawing], id: usize) {
63        let target_idx = match drawings.iter().position(|d| d.id == id) {
64            Some(i) => i,
65            None => return,
66        };
67        let current_z = drawings[target_idx].z_order();
68
69        // Find the drawing with the next lower z-order
70        let swap_idx = drawings
71            .iter()
72            .enumerate()
73            .filter(|(i, d)| *i != target_idx && d.z_order() < current_z)
74            .max_by_key(|(_, d)| d.z_order())
75            .map(|(i, _)| i);
76
77        match swap_idx {
78            Some(si) => {
79                let below_z = drawings[si].z_order();
80                drawings[target_idx].set_z_order(below_z);
81                drawings[si].set_z_order(current_z);
82            }
83            None => {
84                // Already at the bottom, push down by 1
85                drawings[target_idx].set_z_order(current_z.saturating_sub(1));
86            }
87        }
88    }
89
90    /// Sort drawings by z-order (ascending: lowest z-order first, drawn first)
91    pub fn sort_by_z_order(drawings: &mut [Drawing]) {
92        drawings.sort_by_key(|d| d.z_order());
93    }
94}
95
96/// `drawings` from the bottom layer to the top: ascending z-order, and in list order where the
97/// z-order is the same.
98pub(crate) fn in_z_order(drawings: &[Drawing]) -> Vec<&Drawing> {
99    let mut ordered: Vec<_> = drawings.iter().collect();
100    ordered.sort_by_key(|d| d.z_order());
101    ordered
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use crate::drawings::domain::DrawingToolType;
108
109    fn make_drawing(id: usize, z: i32) -> Drawing {
110        let mut d = Drawing::new(id, DrawingToolType::TrendLine);
111        d.set_z_order(z);
112        d
113    }
114
115    #[test]
116    fn ordering_operations_preserve_drawing_identity() {
117        let cases = [
118            (
119                "front",
120                ZOrderService::bring_to_front as fn(&mut [Drawing], usize),
121                1,
122                [(1, 6), (2, 5), (3, 3)],
123            ),
124            (
125                "back",
126                ZOrderService::send_to_back,
127                2,
128                [(1, 0), (2, -1), (3, 3)],
129            ),
130            (
131                "forward",
132                ZOrderService::bring_forward,
133                3,
134                [(1, 0), (2, 3), (3, 5)],
135            ),
136            (
137                "backward",
138                ZOrderService::send_backward,
139                3,
140                [(1, 3), (2, 5), (3, 0)],
141            ),
142            (
143                "sort",
144                |drawings: &mut [Drawing], _| ZOrderService::sort_by_z_order(drawings),
145                0,
146                [(1, 0), (3, 3), (2, 5)],
147            ),
148        ];
149        for (name, operation, id, expected) in cases {
150            let mut drawings = [make_drawing(1, 0), make_drawing(2, 5), make_drawing(3, 3)];
151            operation(&mut drawings, id);
152            assert_eq!(drawings.map(|d| (d.id, d.z_order())), expected, "{name}");
153        }
154    }
155}