Skip to main content

open_gpui_canvas/
layer.rs

1use crate::document::CanvasRecordId;
2use serde::{Deserialize, Serialize};
3
4#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
5pub enum CanvasZOrderCommand {
6    BringToFront,
7    BringForward,
8    SendBackward,
9    SendToBack,
10}
11
12#[derive(Clone, Debug, Eq, PartialEq)]
13pub(crate) struct CanvasLayerRecord {
14    pub id: CanvasRecordId,
15    pub z_index: i32,
16    pub ordinal: usize,
17    pub selected: bool,
18}
19
20pub(crate) fn sort_layer_records(records: &mut [CanvasLayerRecord]) {
21    records.sort_by(|left, right| {
22        left.z_index
23            .cmp(&right.z_index)
24            .then_with(|| left.ordinal.cmp(&right.ordinal))
25    });
26}
27
28pub(crate) fn reorder_layer_z_indices(
29    records: &mut Vec<CanvasLayerRecord>,
30    command: CanvasZOrderCommand,
31) -> Vec<(CanvasRecordId, i32)> {
32    if !records.iter().any(|record| record.selected) {
33        return Vec::new();
34    }
35
36    let original_order = records
37        .iter()
38        .map(|record| record.id.clone())
39        .collect::<Vec<_>>();
40    reorder_layer_records(records, command);
41    if records
42        .iter()
43        .map(|record| &record.id)
44        .eq(original_order.iter())
45    {
46        return Vec::new();
47    }
48
49    layer_z_index_updates(records)
50}
51
52fn reorder_layer_records(records: &mut Vec<CanvasLayerRecord>, command: CanvasZOrderCommand) {
53    match command {
54        CanvasZOrderCommand::BringToFront => {
55            let mut selected = Vec::new();
56            records.retain(|record| {
57                if record.selected {
58                    selected.push(record.clone());
59                    false
60                } else {
61                    true
62                }
63            });
64            records.extend(selected);
65        }
66        CanvasZOrderCommand::SendToBack => {
67            let mut selected = Vec::new();
68            records.retain(|record| {
69                if record.selected {
70                    selected.push(record.clone());
71                    false
72                } else {
73                    true
74                }
75            });
76            selected.extend(records.iter().cloned());
77            *records = selected;
78        }
79        CanvasZOrderCommand::BringForward => {
80            let mut index = records.len();
81            while index > 0 {
82                index -= 1;
83                if !records[index].selected {
84                    continue;
85                }
86                let next = index + 1;
87                if next < records.len() && !records[next].selected {
88                    records.swap(index, next);
89                }
90            }
91        }
92        CanvasZOrderCommand::SendBackward => {
93            for index in 0..records.len() {
94                if !records[index].selected {
95                    continue;
96                }
97                if index > 0 && !records[index - 1].selected {
98                    records.swap(index - 1, index);
99                }
100            }
101        }
102    }
103}
104
105fn layer_z_index_updates(records: &[CanvasLayerRecord]) -> Vec<(CanvasRecordId, i32)> {
106    let min_z = records
107        .iter()
108        .map(|record| record.z_index)
109        .min()
110        .unwrap_or_default();
111    let base_z = normalized_layer_z_order_base(min_z, records.len());
112
113    records
114        .iter()
115        .enumerate()
116        .filter_map(|(index, record)| {
117            let z_index = normalized_layer_z_index(base_z, index);
118            (z_index != record.z_index).then(|| (record.id.clone(), z_index))
119        })
120        .collect()
121}
122
123fn normalized_layer_z_order_base(min_z: i32, record_count: usize) -> i32 {
124    let max_offset = record_count.saturating_sub(1).min(i32::MAX as usize) as i32;
125    if min_z > i32::MAX.saturating_sub(max_offset) {
126        i32::MAX - max_offset
127    } else {
128        min_z
129    }
130}
131
132fn normalized_layer_z_index(base_z: i32, index: usize) -> i32 {
133    let offset = index.min(i32::MAX as usize) as i32;
134    base_z.saturating_add(offset)
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use crate::{EdgeId, NodeId, ShapeId};
141
142    #[test]
143    fn bring_forward_crosses_sparse_adjacent_layer() {
144        let mut records = vec![node("back", 1, 0, true), shape("front", 10, 1, false)];
145        sort_layer_records(&mut records);
146
147        let updates = reorder_layer_z_indices(&mut records, CanvasZOrderCommand::BringForward);
148
149        assert_order(&records, ["shape:front", "node:back"]);
150        assert_eq!(
151            updates,
152            vec![
153                (CanvasRecordId::Shape(ShapeId::from("front")), 1),
154                (CanvasRecordId::Node(NodeId::from("back")), 2),
155            ]
156        );
157    }
158
159    #[test]
160    fn send_backward_crosses_duplicate_adjacent_layer() {
161        let mut records = vec![node("back", 0, 0, false), shape("front", 0, 1, true)];
162        sort_layer_records(&mut records);
163
164        let updates = reorder_layer_z_indices(&mut records, CanvasZOrderCommand::SendBackward);
165
166        assert_order(&records, ["shape:front", "node:back"]);
167        assert_eq!(
168            updates,
169            vec![(CanvasRecordId::Node(NodeId::from("back")), 1)]
170        );
171    }
172
173    #[test]
174    fn bring_to_front_preserves_selected_relative_order() {
175        let mut records = vec![
176            node("node", 0, 0, true),
177            shape("shape", 1, 1, false),
178            edge("edge", 2, 2, true),
179            shape("top", 3, 3, false),
180        ];
181        sort_layer_records(&mut records);
182
183        let updates = reorder_layer_z_indices(&mut records, CanvasZOrderCommand::BringToFront);
184
185        assert_order(
186            &records,
187            ["shape:shape", "shape:top", "node:node", "edge:edge"],
188        );
189        assert_eq!(
190            updates,
191            vec![
192                (CanvasRecordId::Shape(ShapeId::from("shape")), 0),
193                (CanvasRecordId::Shape(ShapeId::from("top")), 1),
194                (CanvasRecordId::Node(NodeId::from("node")), 2),
195                (CanvasRecordId::Edge(EdgeId::from("edge")), 3),
196            ]
197        );
198    }
199
200    fn node(id: &str, z_index: i32, ordinal: usize, selected: bool) -> CanvasLayerRecord {
201        CanvasLayerRecord {
202            id: CanvasRecordId::Node(NodeId::from(id)),
203            z_index,
204            ordinal,
205            selected,
206        }
207    }
208
209    fn edge(id: &str, z_index: i32, ordinal: usize, selected: bool) -> CanvasLayerRecord {
210        CanvasLayerRecord {
211            id: CanvasRecordId::Edge(EdgeId::from(id)),
212            z_index,
213            ordinal,
214            selected,
215        }
216    }
217
218    fn shape(id: &str, z_index: i32, ordinal: usize, selected: bool) -> CanvasLayerRecord {
219        CanvasLayerRecord {
220            id: CanvasRecordId::Shape(ShapeId::from(id)),
221            z_index,
222            ordinal,
223            selected,
224        }
225    }
226
227    fn assert_order<const N: usize>(records: &[CanvasLayerRecord], expected: [&str; N]) {
228        let actual = records
229            .iter()
230            .map(|record| record.id.to_string())
231            .collect::<Vec<_>>();
232        assert_eq!(actual, expected);
233    }
234}