mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-22 04:08:12 +08:00
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:
co-authored by
Dennis Kobert
parent
255cdead28
commit
a448b36d9e
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user