Skip to content

Commit 8acfa93

Browse files
committed
Support moving single layers
1 parent 1b37e78 commit 8acfa93

File tree

9 files changed

+115
-1
lines changed

9 files changed

+115
-1
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ const menuEntries: MenuListEntries = [
126126
[
127127
{ label: "Select All", shortcut: ["Ctrl", "A"], action: async () => (await wasm).select_all_layers() },
128128
{ label: "Deselect All", shortcut: ["Ctrl", "Alt", "A"], action: async () => (await wasm).deselect_all_layers() },
129+
{ label: "Move Layer Up", shortcut: ["Ctrl", "]"], action: async () => (await wasm).move_selected_layer(1) },
130+
{ label: "Move Layer Down", shortcut: ["Ctrl", "["], action: async () => (await wasm).move_selected_layer(-1) },
131+
{ label: "Move Layer To Top", shortcut: ["Ctrl", "Shift", "]"], action: async () => (await wasm).move_selected_layer(999999) },
132+
{ label: "Move Layer To Bottom", shortcut: ["Ctrl", "Shift", "["], action: async () => (await wasm).move_selected_layer(-999999) },
129133
],
130134
],
131135
},

client/web/wasm/src/document.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,14 +163,22 @@ pub fn select_all_layers() -> Result<(), JsValue> {
163163
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::SelectAllLayers)).map_err(convert_error)
164164
}
165165

166-
/// Select all layers
166+
/// Deselect all layers
167167
#[wasm_bindgen]
168168
pub fn deselect_all_layers() -> Result<(), JsValue> {
169169
EDITOR_STATE
170170
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::DeselectAllLayers))
171171
.map_err(convert_error)
172172
}
173173

174+
/// Reorder selected layer
175+
#[wasm_bindgen]
176+
pub fn move_selected_layer(delta: i32) -> Result<(), JsValue> {
177+
EDITOR_STATE
178+
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::MoveSelectedLayer(delta)))
179+
.map_err(convert_error)
180+
}
181+
174182
/// Export the document
175183
#[wasm_bindgen]
176184
pub fn export_document() -> Result<(), JsValue> {

client/web/wasm/src/wrappers.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ pub fn translate_key(name: &str) -> Key {
131131
"arrowdown" => KeyArrowDown,
132132
"arrowleft" => KeyArrowLeft,
133133
"arrowright" => KeyArrowRight,
134+
"[" => KeyLeftBracket,
135+
"]" => KeyRightBracket,
134136
_ => UnknownKey,
135137
}
136138
}

core/document/src/document.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,14 @@ impl Document {
227227
Ok(())
228228
}
229229

230+
pub fn reorder_layer(&mut self, source_path: &[LayerId], target_path: &[LayerId]) -> Result<(), DocumentError> {
231+
// TODO: Detect when moving between folders and handle properly
232+
233+
self.root.as_folder_mut()?.reorder_layer(*source_path.last().unwrap(), *target_path.last().unwrap())?;
234+
235+
Ok(())
236+
}
237+
230238
fn mark_as_dirty(&mut self, path: &[LayerId]) -> Result<(), DocumentError> {
231239
let mut root = &mut self.root;
232240
root.cache_dirty = true;
@@ -377,6 +385,15 @@ impl Document {
377385
self.mark_as_dirty(path)?;
378386
Some(vec![DocumentResponse::DocumentChanged])
379387
}
388+
Operation::ReorderLayers { source_path, target_path } => {
389+
self.reorder_layer(source_path, target_path)?;
390+
391+
Some(vec![
392+
DocumentResponse::DocumentChanged,
393+
DocumentResponse::FolderChanged { path: source_path.to_vec() },
394+
DocumentResponse::SelectLayer { path: source_path.to_vec() },
395+
])
396+
}
380397
};
381398
if !matches!(
382399
operation,

core/document/src/layers/folder.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,24 @@ 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)?;
70+
let target_pos = self.layer_ids.iter().position(|x| *x == target_id).ok_or(DocumentError::LayerNotFound)?;
71+
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);
76+
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;
81+
}
82+
83+
Ok(())
84+
}
85+
6886
/// Returns a list of layers in the folder
6987
pub fn list_layers(&self) -> &[LayerId] {
7088
self.layer_ids.as_slice()

core/document/src/operation.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,8 @@ pub enum Operation {
7676
path: Vec<LayerId>,
7777
color: Color,
7878
},
79+
ReorderLayers {
80+
source_path: Vec<LayerId>,
81+
target_path: Vec<LayerId>,
82+
},
7983
}

core/editor/src/document/document_message_handler.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ pub enum DocumentMessage {
5454
WheelCanvasZoom,
5555
SetCanvasRotation(f64),
5656
NudgeSelectedLayers(f64, f64),
57+
MoveSelectedLayer(i32),
5758
}
5859

5960
impl From<DocumentOperation> for DocumentMessage {
@@ -161,6 +162,32 @@ impl DocumentMessageHandler {
161162
layers_with_indices.sort_by_key(|(_, indices)| indices.clone());
162163
return layers_with_indices.into_iter().map(|(path, _)| path).collect();
163164
}
165+
166+
/// Returns the paths to all layers in order
167+
fn all_layers_sorted(&self) -> Vec<Vec<LayerId>> {
168+
// Compute the indices for each layer to be able to sort them
169+
let mut layers_with_indices: Vec<(Vec<LayerId>, Vec<usize>)> = self
170+
.active_document()
171+
.layer_data
172+
.iter()
173+
.filter_map(|(path, _)| (path.len() > 0).then(|| path.clone())) // Ignore root layer data
174+
.filter_map(|path| {
175+
// Currently it is possible that layer_data contains layers that are don't actually exist
176+
// and thus indices_for_path can return an error. We currently skip these layers and log a warning.
177+
// Once this problem is solved this code can be simplified
178+
match self.active_document().document.indices_for_path(&path) {
179+
Err(err) => {
180+
warn!("all_layers_sorted: Could not get indices for the layer {:?}: {:?}", path, err);
181+
None
182+
}
183+
Ok(indices) => Some((path, indices)),
184+
}
185+
})
186+
.collect();
187+
188+
layers_with_indices.sort_by_key(|(_, indices)| indices.clone());
189+
return layers_with_indices.into_iter().map(|(path, _)| path).collect();
190+
}
164191
}
165192

166193
impl Default for DocumentMessageHandler {
@@ -554,6 +581,33 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
554581
responses.push_back(operation.into());
555582
}
556583
}
584+
MoveSelectedLayer(delta) => {
585+
let paths: Vec<Vec<LayerId>> = self.selected_layers_sorted();
586+
// TODO: Support moving more than one layer
587+
if paths.len() == 1 {
588+
let all_layer_indices = self.all_layers_sorted();
589+
590+
let max_index = all_layer_indices.len() as i32 - 1;
591+
592+
let mut selected_layer_index = -1;
593+
let mut next_layer_index = -1;
594+
for (i, path) in all_layer_indices.iter().enumerate() {
595+
if *path == paths[0] {
596+
selected_layer_index = i as i32;
597+
next_layer_index = (selected_layer_index + delta).clamp(0, max_index);
598+
break;
599+
}
600+
}
601+
602+
if next_layer_index != -1 && next_layer_index != selected_layer_index {
603+
let operation = DocumentOperation::ReorderLayers {
604+
source_path: paths[0].clone(),
605+
target_path: all_layer_indices[next_layer_index as usize].to_vec(),
606+
};
607+
responses.push_back(operation.into());
608+
}
609+
}
610+
}
557611
message => todo!("document_action_handler does not implement: {}", message.to_discriminant().global_name()),
558612
}
559613
}
@@ -589,6 +643,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
589643
DuplicateSelectedLayers,
590644
CopySelectedLayers,
591645
NudgeSelectedLayers,
646+
MoveSelectedLayer,
592647
);
593648
common.extend(select);
594649
}

core/editor/src/input/input_mapper.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +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::MoveSelectedLayer(-1), key_down=KeyLeftBracket, modifiers=[KeyControl]},
239+
entry! {action=DocumentMessage::MoveSelectedLayer(1), key_down=KeyRightBracket, modifiers=[KeyControl]},
240+
entry! {action=DocumentMessage::MoveSelectedLayer(-999999), key_down=KeyLeftBracket, modifiers=[KeyControl, KeyShift]},
241+
entry! {action=DocumentMessage::MoveSelectedLayer(999999), key_down=KeyRightBracket, modifiers=[KeyControl, KeyShift]},
238242
// Global Actions
239243
entry! {action=GlobalMessage::LogInfo, key_down=Key1},
240244
entry! {action=GlobalMessage::LogDebug, key_down=Key2},

core/editor/src/input/keyboard.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ pub enum Key {
6969
KeyArrowDown,
7070
KeyArrowLeft,
7171
KeyArrowRight,
72+
KeyLeftBracket,
73+
KeyRightBracket,
7274

7375
// This has to be the last element in the enum.
7476
NumKeys,

0 commit comments

Comments
 (0)