Skip to content

Commit 83e932c

Browse files
committed
Support moving multiple layers at a time
1 parent d130df8 commit 83e932c

File tree

7 files changed

+113
-55
lines changed

7 files changed

+113
-55
lines changed

client/web/src/components/widgets/inputs/MenuBarInput.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,10 @@ const menuEntries: MenuListEntries = [
130130
label: "Order",
131131
children: [
132132
[
133-
{ label: "Raise To Front", shortcut: ["Ctrl", "Shift", "]"], action: async () => (await wasm).reorder_selected_layer(2147483647) },
134-
{ label: "Raise", shortcut: ["Ctrl", "]"], action: async () => (await wasm).reorder_selected_layer(1) },
135-
{ label: "Lower", shortcut: ["Ctrl", "["], action: async () => (await wasm).reorder_selected_layer(-1) },
136-
{ label: "Lower to Back", shortcut: ["Ctrl", "Shift", "["], action: async () => (await wasm).reorder_selected_layer(-2147483648) },
133+
{ label: "Raise To Front", shortcut: ["Ctrl", "Shift", "]"], action: async () => (await wasm).reorder_selected_layers(2147483647) },
134+
{ label: "Raise", shortcut: ["Ctrl", "]"], action: async () => (await wasm).reorder_selected_layers(1) },
135+
{ label: "Lower", shortcut: ["Ctrl", "["], action: async () => (await wasm).reorder_selected_layers(-1) },
136+
{ label: "Lower to Back", shortcut: ["Ctrl", "Shift", "["], action: async () => (await wasm).reorder_selected_layers(-2147483648) },
137137
],
138138
],
139139
},

client/web/wasm/src/document.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,9 @@ pub fn deselect_all_layers() -> Result<(), JsValue> {
173173

174174
/// Reorder selected layer
175175
#[wasm_bindgen]
176-
pub fn reorder_selected_layer(delta: i32) -> Result<(), JsValue> {
176+
pub fn reorder_selected_layers(delta: i32) -> Result<(), JsValue> {
177177
EDITOR_STATE
178-
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::ReorderSelectedLayer(delta)))
178+
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::ReorderSelectedLayers(delta)))
179179
.map_err(convert_error)
180180
}
181181

core/document/src/document.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -227,10 +227,11 @@ impl Document {
227227
Ok(())
228228
}
229229

230-
pub fn reorder_layer(&mut self, source_path: &[LayerId], target_path: &[LayerId]) -> Result<(), DocumentError> {
230+
pub fn reorder_layers(&mut self, source_paths: &[Vec<LayerId>], target_path: &[LayerId]) -> Result<(), DocumentError> {
231231
// TODO: Detect when moving between folders and handle properly
232232

233-
self.root.as_folder_mut()?.reorder_layer(*source_path.last().unwrap(), *target_path.last().unwrap())?;
233+
let source_layer_ids = source_paths.iter().map(|x| *x.last().unwrap()).collect();
234+
self.root.as_folder_mut()?.reorder_layers(source_layer_ids, *target_path.last().unwrap())?;
234235

235236
Ok(())
236237
}
@@ -402,14 +403,10 @@ impl Document {
402403
self.mark_as_dirty(path)?;
403404
Some(vec![DocumentResponse::DocumentChanged])
404405
}
405-
Operation::ReorderLayers { source_path, target_path } => {
406-
self.reorder_layer(source_path, target_path)?;
407-
408-
Some(vec![
409-
DocumentResponse::DocumentChanged,
410-
DocumentResponse::FolderChanged { path: source_path.to_vec() },
411-
DocumentResponse::SelectLayer { path: source_path.to_vec() },
412-
])
406+
Operation::ReorderLayers { source_paths, target_path } => {
407+
self.reorder_layers(source_paths, target_path)?;
408+
409+
Some(vec![DocumentResponse::DocumentChanged])
413410
}
414411
};
415412
if !matches!(

core/document/src/layers/folder.rs

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,19 +65,79 @@ impl Folder {
6565
Ok(())
6666
}
6767

68-
pub fn reorder_layer(&mut self, source_id: LayerId, target_id: LayerId) -> Result<(), DocumentError> {
69-
let source_pos = self.layer_ids.iter().position(|x| *x == source_id).ok_or(DocumentError::LayerNotFound)?;
68+
pub fn reorder_layers(&mut self, source_ids: Vec<LayerId>, target_id: LayerId) -> Result<(), DocumentError> {
69+
let source_pos = self.layer_ids.iter().position(|x| *x == source_ids[0]).ok_or(DocumentError::LayerNotFound)?;
70+
let source_pos_end = source_pos + source_ids.len() - 1;
7071
let target_pos = self.layer_ids.iter().position(|x| *x == target_id).ok_or(DocumentError::LayerNotFound)?;
7172

72-
let layer_to_move = self.layers.remove(source_pos);
73-
self.layers.insert(target_pos, layer_to_move);
74-
let layer_id_to_move = self.layer_ids.remove(source_pos);
75-
self.layer_ids.insert(target_pos, layer_id_to_move);
73+
let mut last_pos = source_pos;
74+
for layer_id in &source_ids[1..source_ids.len()] {
75+
let layer_pos = self.layer_ids.iter().position(|x| *x == *layer_id).ok_or(DocumentError::LayerNotFound)?;
76+
if (layer_pos as i32 - last_pos as i32).abs() > 1 {
77+
// Selection is not contiguous
78+
return Err(DocumentError::InvalidPath);
79+
}
80+
last_pos = layer_pos;
81+
}
82+
83+
if source_pos < target_pos {
84+
// Dragging up
85+
86+
// Prevent shifting past end
87+
if source_pos_end + 1 >= self.layers.len() {
88+
return Err(DocumentError::InvalidPath);
89+
}
7690

77-
let min_index = source_pos.min(target_pos);
78-
let max_index = source_pos.max(target_pos);
79-
for layer_index in min_index..max_index {
80-
self.layers[layer_index].cache_dirty = true;
91+
fn rearrange<T>(arr: &mut Vec<T>, source_pos: usize, source_pos_end: usize, target_pos: usize)
92+
where
93+
T: Clone,
94+
{
95+
*arr = [
96+
&arr[0..source_pos], // Elements before selection
97+
&arr[source_pos_end + 1..=target_pos], // Elements between selection end and target
98+
&arr[source_pos..=source_pos_end], // Selection itself
99+
&arr[target_pos + 1..], // Elements before target
100+
]
101+
.concat();
102+
}
103+
104+
rearrange(&mut self.layers, source_pos, source_pos_end, target_pos);
105+
rearrange(&mut self.layer_ids, source_pos, source_pos_end, target_pos);
106+
107+
let min_index = source_pos_end.min(target_pos);
108+
let max_index = source_pos_end.max(target_pos);
109+
for layer_index in min_index..max_index {
110+
self.layers[layer_index].cache_dirty = true;
111+
}
112+
} else {
113+
// Dragging down
114+
115+
// Prevent shifting past end
116+
if source_pos == 0 {
117+
return Err(DocumentError::InvalidPath);
118+
}
119+
120+
fn rearrange<T>(arr: &mut Vec<T>, source_pos: usize, source_pos_end: usize, target_pos: usize)
121+
where
122+
T: Clone,
123+
{
124+
*arr = [
125+
&arr[0..target_pos], // Elements before target
126+
&arr[source_pos..=source_pos_end], // Selection itself
127+
&arr[target_pos..source_pos], // Elements between selection and target
128+
&arr[source_pos_end + 1..], // Elements before selection
129+
]
130+
.concat();
131+
}
132+
133+
rearrange(&mut self.layers, source_pos, source_pos_end, target_pos);
134+
rearrange(&mut self.layer_ids, source_pos, source_pos_end, target_pos);
135+
136+
let min_index = source_pos.min(target_pos);
137+
let max_index = source_pos.max(target_pos);
138+
for layer_index in min_index..max_index {
139+
self.layers[layer_index].cache_dirty = true;
140+
}
81141
}
82142

83143
Ok(())

core/document/src/operation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ pub enum Operation {
7777
color: Color,
7878
},
7979
ReorderLayers {
80-
source_path: Vec<LayerId>,
80+
source_paths: Vec<Vec<LayerId>>,
8181
target_path: Vec<LayerId>,
8282
},
8383
}

core/editor/src/document/document_message_handler.rs

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pub enum DocumentMessage {
5454
WheelCanvasZoom,
5555
SetCanvasRotation(f64),
5656
NudgeSelectedLayers(f64, f64),
57-
ReorderSelectedLayer(i32),
57+
ReorderSelectedLayers(i32),
5858
}
5959

6060
impl From<DocumentOperation> for DocumentMessage {
@@ -551,7 +551,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
551551
responses.push_back(FrontendMessage::SetCanvasRotation { new_radians: new }.into());
552552
}
553553
NudgeSelectedLayers(x, y) => {
554-
let paths: Vec<Vec<LayerId>> = self.selected_layers_sorted();
554+
let paths = self.selected_layers_sorted();
555555

556556
let delta = {
557557
let root_layer_rotation = self.layerdata_mut(&[]).rotation;
@@ -566,31 +566,32 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
566566
responses.push_back(operation.into());
567567
}
568568
}
569-
ReorderSelectedLayer(delta) => {
570-
let mut paths: Vec<Vec<LayerId>> = self.selected_layers_sorted();
571-
// TODO: Support moving more than one layer
572-
if paths.len() == 1 {
573-
let all_layer_paths = self.all_layers_sorted();
569+
ReorderSelectedLayers(delta) => {
570+
let selected_layer_paths: Vec<Vec<LayerId>> = self.selected_layers_sorted();
571+
let all_layer_paths = self.all_layers_sorted();
574572

575-
let max_index = all_layer_paths.len() as i64 - 1;
573+
let max_index = all_layer_paths.len() as i64 - 1;
574+
let num_layers_selected = selected_layer_paths.len() as i64;
576575

577-
let mut selected_layer_index = -1;
578-
let mut next_layer_index = -1;
579-
for (i, path) in all_layer_paths.iter().enumerate() {
580-
if *path == paths[0] {
581-
selected_layer_index = i as i32;
582-
next_layer_index = (selected_layer_index as i64 + delta as i64).clamp(0, max_index) as i32;
583-
break;
584-
}
576+
let mut selected_layer_index = -1;
577+
let mut next_layer_index = -1;
578+
for (i, path) in all_layer_paths.iter().enumerate() {
579+
if *path == selected_layer_paths[0] {
580+
selected_layer_index = i as i32;
581+
// Skip past selection length when moving up
582+
let offset = if delta > 0 { num_layers_selected - 1 } else { 0 };
583+
next_layer_index = (selected_layer_index as i64 + delta as i64 + offset).clamp(0, max_index) as i32;
584+
break;
585585
}
586+
}
586587

587-
if next_layer_index != -1 && next_layer_index != selected_layer_index {
588-
let operation = DocumentOperation::ReorderLayers {
589-
source_path: paths.drain(0..1).next().unwrap(),
590-
target_path: all_layer_paths[next_layer_index as usize].to_vec(),
591-
};
592-
responses.push_back(operation.into());
593-
}
588+
if next_layer_index != -1 && next_layer_index != selected_layer_index {
589+
let operation = DocumentOperation::ReorderLayers {
590+
source_paths: selected_layer_paths.clone(),
591+
target_path: all_layer_paths[next_layer_index as usize].to_vec(),
592+
};
593+
responses.push_back(operation.into());
594+
responses.push_back(DocumentMessage::SelectLayers(selected_layer_paths).into());
594595
}
595596
}
596597
message => todo!("document_action_handler does not implement: {}", message.to_discriminant().global_name()),
@@ -628,7 +629,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
628629
DuplicateSelectedLayers,
629630
CopySelectedLayers,
630631
NudgeSelectedLayers,
631-
ReorderSelectedLayer,
632+
ReorderSelectedLayers,
632633
);
633634
common.extend(select);
634635
}

core/editor/src/input/input_mapper.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,10 +235,10 @@ impl Default for Mapping {
235235
entry! {action=DocumentMessage::NudgeSelectedLayers(NUDGE_AMOUNT, -NUDGE_AMOUNT), key_down=KeyArrowRight, modifiers=[KeyArrowUp]},
236236
entry! {action=DocumentMessage::NudgeSelectedLayers(NUDGE_AMOUNT, NUDGE_AMOUNT), key_down=KeyArrowRight, modifiers=[KeyArrowDown]},
237237
entry! {action=DocumentMessage::NudgeSelectedLayers(NUDGE_AMOUNT, 0.), key_down=KeyArrowRight},
238-
entry! {action=DocumentMessage::ReorderSelectedLayer(i32::MAX), key_down=KeyRightCurlyBracket, modifiers=[KeyControl]},
239-
entry! {action=DocumentMessage::ReorderSelectedLayer(1), key_down=KeyRightBracket, modifiers=[KeyControl]},
240-
entry! {action=DocumentMessage::ReorderSelectedLayer(-1), key_down=KeyLeftBracket, modifiers=[KeyControl]},
241-
entry! {action=DocumentMessage::ReorderSelectedLayer(i32::MIN), key_down=KeyLeftCurlyBracket, modifiers=[KeyControl]},
238+
entry! {action=DocumentMessage::ReorderSelectedLayers(i32::MAX), key_down=KeyRightCurlyBracket, modifiers=[KeyControl]},
239+
entry! {action=DocumentMessage::ReorderSelectedLayers(1), key_down=KeyRightBracket, modifiers=[KeyControl]},
240+
entry! {action=DocumentMessage::ReorderSelectedLayers(-1), key_down=KeyLeftBracket, modifiers=[KeyControl]},
241+
entry! {action=DocumentMessage::ReorderSelectedLayers(i32::MIN), key_down=KeyLeftCurlyBracket, modifiers=[KeyControl]},
242242
// Global Actions
243243
entry! {action=GlobalMessage::LogInfo, key_down=Key1},
244244
entry! {action=GlobalMessage::LogDebug, key_down=Key2},

0 commit comments

Comments
 (0)