Support rearranging layers with hotkeys (#271)

* Support moving single layers

* Fix "Move layer to top/bottom" keybinds

* Rename things named "move" to "reorder"

Fix formatting

* Combine sorted layer helper functions

* Use integer consts for moving layers to front/back

* Fix merge mistake

* Fix some clippy lints

* Fix panic

* Remove "get" prefix from functions

* Bring layer menu items out to sub-menu

* Support moving multiple layers at a time

* Add comment explaining odd keybinding

* Add reordering tests

* Add negative test

* Add new error type

* Add layer position helper, clean up tests

* Make position helper return Result

* Clean up slice iteration

* Simplify source_layer_ids computation

Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
AJ Weeks
2021-07-21 11:36:51 +02:00
committed by GitHub
co-authored by Dennis Kobert
parent 255cdead28
commit a448b36d9e
10 changed files with 319 additions and 13 deletions
@@ -54,6 +54,7 @@ pub enum DocumentMessage {
WheelCanvasZoom,
SetCanvasRotation(f64),
NudgeSelectedLayers(f64, f64),
ReorderSelectedLayers(i32),
}
impl From<DocumentOperation> for DocumentMessage {
@@ -136,21 +137,23 @@ impl DocumentMessageHandler {
);
}
/// Returns the paths to the selected layers in order
fn selected_layers_sorted(&self) -> Vec<Vec<LayerId>> {
/// Returns the paths to all layers in order, optionally including only selected layers
fn layers_sorted(&self, only_selected: bool) -> Vec<Vec<LayerId>> {
// Compute the indices for each layer to be able to sort them
// TODO: Replace with drain_filter https://github.com/rust-lang/rust/issues/59618
let mut layers_with_indices: Vec<(Vec<LayerId>, Vec<usize>)> = self
.active_document()
.layer_data
.iter()
.filter_map(|(path, data)| data.selected.then(|| path.clone()))
// 'path.len() > 0' filters out root layer since it has no indices
.filter_map(|(path, data)| (!path.is_empty() && !only_selected || data.selected).then(|| path.clone()))
.filter_map(|path| {
// Currently it is possible that layer_data contains layers that are don't actually exist
// and thus indices_for_path can return an error. We currently skip these layers and log a warning.
// Once this problem is solved this code can be simplified
match self.active_document().document.indices_for_path(&path) {
Err(err) => {
warn!("selected_layers_sorted: Could not get indices for the layer {:?}: {:?}", path, err);
warn!("layers_sorted: Could not get indices for the layer {:?}: {:?}", path, err);
None
}
Ok(indices) => Some((path, indices)),
@@ -161,6 +164,16 @@ impl DocumentMessageHandler {
layers_with_indices.sort_by_key(|(_, indices)| indices.clone());
layers_with_indices.into_iter().map(|(path, _)| path).collect()
}
/// Returns the paths to all layers in order
fn all_layers_sorted(&self) -> Vec<Vec<LayerId>> {
self.layers_sorted(false)
}
/// Returns the paths to all selected layers in order
fn selected_layers_sorted(&self) -> Vec<Vec<LayerId>> {
self.layers_sorted(true)
}
}
impl Default for DocumentMessageHandler {
@@ -336,20 +349,19 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
responses.extend(self.handle_folder_changed(path));
}
DeleteSelectedLayers => {
// TODO: Replace with drain_filter https://github.com/rust-lang/rust/issues/59618
let paths: Vec<Vec<LayerId>> = self.active_document().layer_data.iter().filter_map(|(path, data)| data.selected.then(|| path.clone())).collect();
let paths = self.selected_layers_sorted();
for path in paths {
self.active_document_mut().layer_data.remove(&path);
responses.push_back(DocumentOperation::DeleteLayer { path }.into())
}
}
DuplicateSelectedLayers => {
for path in self.active_document().layer_data.iter().filter_map(|(path, data)| data.selected.then(|| path.clone())) {
for path in self.selected_layers_sorted() {
responses.push_back(DocumentOperation::DuplicateLayer { path }.into())
}
}
CopySelectedLayers => {
let paths: Vec<Vec<LayerId>> = self.selected_layers_sorted();
let paths = self.selected_layers_sorted();
self.copy_buffer.clear();
for path in paths {
match self.active_document().document.layer(&path).map(|t| t.clone()) {
@@ -539,7 +551,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
responses.push_back(FrontendMessage::SetCanvasRotation { new_radians: new }.into());
}
NudgeSelectedLayers(x, y) => {
let paths: Vec<Vec<LayerId>> = self.selected_layers_sorted();
let paths = self.selected_layers_sorted();
let delta = {
let root_layer_rotation = self.layerdata_mut(&[]).rotation;
@@ -554,6 +566,34 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
responses.push_back(operation.into());
}
}
ReorderSelectedLayers(delta) => {
let selected_layer_paths: Vec<Vec<LayerId>> = self.selected_layers_sorted();
let all_layer_paths = self.all_layers_sorted();
let max_index = all_layer_paths.len() as i64 - 1;
let num_layers_selected = selected_layer_paths.len() as i64;
let mut selected_layer_index = -1;
let mut next_layer_index = -1;
for (i, path) in all_layer_paths.iter().enumerate() {
if *path == selected_layer_paths[0] {
selected_layer_index = i as i32;
// Skip past selection length when moving up
let offset = if delta > 0 { num_layers_selected - 1 } else { 0 };
next_layer_index = (selected_layer_index as i64 + delta as i64 + offset).clamp(0, max_index) as i32;
break;
}
}
if next_layer_index != -1 && next_layer_index != selected_layer_index {
let operation = DocumentOperation::ReorderLayers {
source_paths: selected_layer_paths.clone(),
target_path: all_layer_paths[next_layer_index as usize].to_vec(),
};
responses.push_back(operation.into());
responses.push_back(DocumentMessage::SelectLayers(selected_layer_paths).into());
}
}
message => todo!("document_action_handler does not implement: {}", message.to_discriminant().global_name()),
}
}
@@ -589,6 +629,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
DuplicateSelectedLayers,
CopySelectedLayers,
NudgeSelectedLayers,
ReorderSelectedLayers,
);
common.extend(select);
}
+4
View File
@@ -235,6 +235,10 @@ impl Default for Mapping {
entry! {action=DocumentMessage::NudgeSelectedLayers(NUDGE_AMOUNT, -NUDGE_AMOUNT), key_down=KeyArrowRight, modifiers=[KeyArrowUp]},
entry! {action=DocumentMessage::NudgeSelectedLayers(NUDGE_AMOUNT, NUDGE_AMOUNT), key_down=KeyArrowRight, modifiers=[KeyArrowDown]},
entry! {action=DocumentMessage::NudgeSelectedLayers(NUDGE_AMOUNT, 0.), key_down=KeyArrowRight},
entry! {action=DocumentMessage::ReorderSelectedLayers(i32::MAX), key_down=KeyRightCurlyBracket, modifiers=[KeyControl]}, // TODO: Use KeyRightBracket with ctrl+shift modifiers once input system is fixed
entry! {action=DocumentMessage::ReorderSelectedLayers(1), key_down=KeyRightBracket, modifiers=[KeyControl]},
entry! {action=DocumentMessage::ReorderSelectedLayers(-1), key_down=KeyLeftBracket, modifiers=[KeyControl]},
entry! {action=DocumentMessage::ReorderSelectedLayers(i32::MIN), key_down=KeyLeftCurlyBracket, modifiers=[KeyControl]}, // TODO: Use KeyLeftBracket with ctrl+shift modifiers once input system is fixed
// Global Actions
entry! {action=GlobalMessage::LogInfo, key_down=Key1},
entry! {action=GlobalMessage::LogDebug, key_down=Key2},
+4
View File
@@ -69,6 +69,10 @@ pub enum Key {
KeyArrowDown,
KeyArrowLeft,
KeyArrowRight,
KeyLeftBracket,
KeyRightBracket,
KeyLeftCurlyBracket,
KeyRightCurlyBracket,
// This has to be the last element in the enum.
NumKeys,