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}