Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Group layers with Ctrl+G into independent groups if they're spread across artboards #1992

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion editor/src/messages/input_mapper/input_mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::messages::input_mapper::utility_types::misc::MappingEntry;
use crate::messages::input_mapper::utility_types::misc::{KeyMappingEntries, Mapping};
use crate::messages::portfolio::document::node_graph::utility_types::Direction;
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
use crate::messages::portfolio::document::utility_types::misc::GroupFolderType;
use crate::messages::prelude::*;
use crate::messages::tool::tool_messages::brush_tool::BrushToolMessageOptionsUpdate;
use crate::messages::tool::tool_messages::select_tool::SelectToolPointerKeys;
Expand Down Expand Up @@ -320,7 +321,7 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(KeyS); modifiers=[Accel], action_dispatch=DocumentMessage::SaveDocument),
entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
entry!(KeyDown(KeyJ); modifiers=[Accel], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
entry!(KeyDown(KeyG); modifiers=[Accel], action_dispatch=DocumentMessage::GroupSelectedLayers),
entry!(KeyDown(KeyG); modifiers=[Accel], action_dispatch=DocumentMessage::GroupSelectedLayers {group_folder_type: GroupFolderType::Layer}),
entry!(KeyDown(KeyG); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::UngroupSelectedLayers),
entry!(KeyDown(KeyN); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::CreateEmptyFolder),
entry!(KeyDown(BracketLeft); modifiers=[Alt], action_dispatch=DocumentMessage::SelectionStepBack),
Expand Down
9 changes: 4 additions & 5 deletions editor/src/messages/portfolio/document/document_message.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::utility_types::misc::SnappingState;
use super::utility_types::misc::{GroupFolderType, SnappingState};
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
Expand Down Expand Up @@ -40,9 +40,6 @@ pub enum DocumentMessage {
},
ClearArtboards,
ClearLayersPanel,
InsertBooleanOperation {
operation: graphene_core::vector::misc::BooleanOperation,
},
CreateEmptyFolder,
DebugPrintDocument,
DeleteSelectedLayers,
Expand All @@ -69,7 +66,9 @@ pub enum DocumentMessage {
GridOptions(GridSnapping),
GridOverlays(OverlayContext),
GridVisibility(bool),
GroupSelectedLayers,
GroupSelectedLayers {
group_folder_type: GroupFolderType,
},
ImaginateGenerate {
imaginate_node: Vec<NodeId>,
},
Expand Down
132 changes: 73 additions & 59 deletions editor/src/messages/portfolio/document/document_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use super::node_graph::utility_types::Transform;
use super::overlays::utility_types::Pivot;
use super::utility_types::clipboards::Clipboard;
use super::utility_types::error::EditorError;
use super::utility_types::misc::{SnappingOptions, SnappingState, GET_SNAP_BOX_FUNCTIONS, GET_SNAP_GEOMETRY_FUNCTIONS};
use super::utility_types::misc::{GroupFolderType, SnappingOptions, SnappingState, GET_SNAP_BOX_FUNCTIONS, GET_SNAP_GEOMETRY_FUNCTIONS};
use super::utility_types::network_interface::{self, NodeNetworkInterface, TransactionStatus};
use super::utility_types::nodes::{CollapsedLayers, SelectedNodes};
use crate::application::{generate_uuid, GRAPHITE_GIT_COMMIT_HASH};
Expand Down Expand Up @@ -310,41 +310,16 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
layout_target: LayoutTarget::LayersPanelOptions,
});
}
DocumentMessage::InsertBooleanOperation { operation } => {
responses.add(DocumentMessage::AddTransaction);

let Some(parent) = self.network_interface.deepest_common_ancestor(&[], false) else {
// Cancel grouping layers across different artboards
// TODO: Group each set of layers for each artboard separately
return;
};
let insert_index = DocumentMessageHandler::get_calculated_insert_index(self.metadata(), self.network_interface.selected_nodes(&[]).unwrap(), parent);

let folder_id = NodeId(generate_uuid());
let boolean_operation_layer = LayerNodeIdentifier::new_unchecked(folder_id);
responses.add(GraphOperationMessage::NewBooleanOperationLayer {
id: folder_id,
operation,
parent,
insert_index,
});
responses.add(NodeGraphMessage::SetDisplayNameImpl {
node_id: folder_id,
alias: "Boolean Operation".to_string(),
});
// Move all shallowest selected layers as children
responses.add(DocumentMessage::MoveSelectedLayersToGroup { parent: boolean_operation_layer });
}
DocumentMessage::CreateEmptyFolder => {
let selected_nodes = self.network_interface.selected_nodes(&[]).unwrap();
let id = NodeId(generate_uuid());

let parent = self
.network_interface
.deepest_common_ancestor(&self.selection_network_path, true)
.deepest_common_ancestor(&selected_nodes, &self.selection_network_path, true)
.unwrap_or(LayerNodeIdentifier::ROOT_PARENT);

let insert_index = DocumentMessageHandler::get_calculated_insert_index(self.metadata(), self.network_interface.selected_nodes(&[]).unwrap(), parent);

let insert_index = DocumentMessageHandler::get_calculated_insert_index(self.metadata(), &self.network_interface.selected_nodes(&[]).unwrap(), parent);
responses.add(DocumentMessage::AddTransaction);
responses.add(GraphOperationMessage::NewCustomLayer {
id,
Expand Down Expand Up @@ -392,8 +367,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
DocumentMessage::DuplicateSelectedLayers => {
let parent = self.new_layer_parent(false);
let calculated_insert_index =
DocumentMessageHandler::get_calculated_insert_index(self.network_interface.document_metadata(), self.network_interface.selected_nodes(&[]).unwrap(), parent);

DocumentMessageHandler::get_calculated_insert_index(self.network_interface.document_metadata(), &self.network_interface.selected_nodes(&[]).unwrap(), parent);
responses.add(DocumentMessage::AddTransaction);
responses.add(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
responses.add(PortfolioMessage::PasteIntoFolder {
Expand Down Expand Up @@ -492,33 +466,38 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
self.snapping_state.grid_snapping = enabled;
responses.add(OverlaysMessage::Draw);
}
DocumentMessage::GroupSelectedLayers => {
DocumentMessage::GroupSelectedLayers { group_folder_type } => {
responses.add(DocumentMessage::AddTransaction);

let Some(parent) = self.network_interface.deepest_common_ancestor(&self.selection_network_path, false) else {
// Cancel grouping layers across different artboards
// TODO: Group each set of layers for each artboard separately
return;
};
let insert_index = DocumentMessageHandler::get_calculated_insert_index(self.metadata(), self.network_interface.selected_nodes(&[]).unwrap(), parent);

let node_id = NodeId(generate_uuid());
let new_group_node = document_node_definitions::resolve_document_node_type("Merge")
.expect("Failed to create merge node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
node_id,
node_template: new_group_node,
});
let new_group_folder = LayerNodeIdentifier::new_unchecked(node_id);
// Move the new folder to the correct position
responses.add(NodeGraphMessage::MoveLayerToStack {
layer: new_group_folder,
parent,
insert_index,
});

responses.add(DocumentMessage::MoveSelectedLayersToGroup { parent: new_group_folder });
let mut parent_per_selected_nodes: HashMap<LayerNodeIdentifier, Vec<NodeId>> = HashMap::new();
let artboards = LayerNodeIdentifier::ROOT_PARENT
.children(self.metadata())
.filter(|x| self.network_interface.is_artboard(&x.to_node(), &self.selection_network_path))
.collect::<Vec<_>>();
let selected_nodes: SelectedNodes = self.network_interface.selected_nodes(&[]).unwrap();
if artboards.len() > 0 {
// Artboard workflow
for artboard in artboards {
let selected_descendants = artboard.descendants(self.metadata()).filter(|x| selected_nodes.selected_layers_contains(*x, self.metadata()));
for selected_descendant in selected_descendants {
parent_per_selected_nodes.entry(artboard).or_insert_with(Vec::new).push(selected_descendant.to_node());
}
}
let mut new_folders: Vec<NodeId> = Vec::new();
for children in parent_per_selected_nodes.into_values() {
let selected_nodes = SelectedNodes(children);
let parent: LayerNodeIdentifier = self.network_interface.deepest_common_ancestor(&selected_nodes, &self.selection_network_path, false).unwrap();
let insert_index = DocumentMessageHandler::get_calculated_insert_index(self.metadata(), &selected_nodes, parent);
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: selected_nodes.0 });
new_folders.push(DocumentMessageHandler::group_layers(responses, insert_index, parent, group_folder_type));
}
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: new_folders });
} else {
// Non-artboard workflow
let parent: LayerNodeIdentifier = self.network_interface.deepest_common_ancestor(&selected_nodes, &self.selection_network_path, false).unwrap();
let insert_index = DocumentMessageHandler::get_calculated_insert_index(self.metadata(), &self.network_interface.selected_nodes(&self.selection_network_path).unwrap(), parent);
DocumentMessageHandler::group_layers(responses, insert_index, parent, group_folder_type);
}
}
DocumentMessage::ImaginateGenerate { imaginate_node } => {
let random_value = generate_uuid();
Expand Down Expand Up @@ -1618,12 +1597,13 @@ impl DocumentMessageHandler {

/// Finds the parent folder which, based on the current selections, should be the container of any newly added layers.
pub fn new_layer_parent(&self, include_self: bool) -> LayerNodeIdentifier {
let selected_nodes = self.network_interface.selected_nodes(&self.selection_network_path).unwrap();
self.network_interface
.deepest_common_ancestor(&self.selection_network_path, include_self)
.deepest_common_ancestor(&selected_nodes, &self.selection_network_path, include_self)
.unwrap_or_else(|| self.network_interface.all_artboards().iter().next().copied().unwrap_or(LayerNodeIdentifier::ROOT_PARENT))
}

pub fn get_calculated_insert_index(metadata: &DocumentMetadata, selected_nodes: SelectedNodes, parent: LayerNodeIdentifier) -> usize {
pub fn get_calculated_insert_index(metadata: &DocumentMetadata, selected_nodes: &SelectedNodes, parent: LayerNodeIdentifier) -> usize {
parent
.children(metadata)
.enumerate()
Expand All @@ -1643,6 +1623,35 @@ impl DocumentMessageHandler {
.unwrap_or(0)
}

pub fn group_layers(responses: &mut VecDeque<Message>, insert_index: usize, parent: LayerNodeIdentifier, group_folder_type: GroupFolderType) -> NodeId {
let folder_id = NodeId(generate_uuid());
match group_folder_type {
GroupFolderType::Layer => responses.add(GraphOperationMessage::NewCustomLayer {
id: folder_id,
nodes: Vec::new(),
parent,
insert_index,
}),
GroupFolderType::BooleanOperation(operation) => {
responses.add(GraphOperationMessage::NewBooleanOperationLayer {
id: folder_id,
operation,
parent,
insert_index,
});
}
};
let new_group_folder = LayerNodeIdentifier::new_unchecked(folder_id);
// Move the new folder to the correct position
responses.add(NodeGraphMessage::MoveLayerToStack {
layer: new_group_folder,
parent,
insert_index,
});
responses.add(DocumentMessage::MoveSelectedLayersToGroup { parent: new_group_folder });
folder_id
}

/// Loads layer resources such as creating the blob URLs for the images and loading all of the fonts in the document.
pub fn load_layer_resources(&self, responses: &mut VecDeque<Message>) {
let mut fonts = HashSet::new();
Expand Down Expand Up @@ -2022,7 +2031,12 @@ impl DocumentMessageHandler {
IconButton::new("Folder", 24)
.tooltip("Group Selected")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GroupSelectedLayers))
.on_update(|_| DocumentMessage::GroupSelectedLayers.into())
.on_update(|_| {
DocumentMessage::GroupSelectedLayers {
group_folder_type: GroupFolderType::Layer,
}
.into()
})
.disabled(!has_selection)
.widget_holder(),
IconButton::new("Trash", 24)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
let layer = modify_inputs.create_layer(id);
modify_inputs.insert_boolean_data(operation, layer);
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
responses.add(NodeGraphMessage::SetDisplayNameImpl {
node_id: id,
alias: "Boolean Operation".to_string(),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
}
GraphOperationMessage::NewCustomLayer { id, nodes, parent, insert_index } => {
Expand Down
6 changes: 6 additions & 0 deletions editor/src/messages/portfolio/document/utility_types/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,3 +464,9 @@ impl PTZ {
self.zoom = zoom.clamp(crate::consts::VIEWPORT_ZOOM_SCALE_MIN, crate::consts::VIEWPORT_ZOOM_SCALE_MAX)
}
}

#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum GroupFolderType {
Layer,
BooleanOperation(graphene_std::vector::misc::BooleanOperation),
}
Original file line number Diff line number Diff line change
Expand Up @@ -1248,15 +1248,11 @@ impl NodeNetworkInterface {
}

/// Ancestor that is shared by all layers and that is deepest (more nested). Default may be the root. Skips selected non-folder, non-artboard layers
pub fn deepest_common_ancestor(&self, network_path: &[NodeId], include_self: bool) -> Option<LayerNodeIdentifier> {
pub fn deepest_common_ancestor(&self, selected_nodes: &SelectedNodes, network_path: &[NodeId], include_self: bool) -> Option<LayerNodeIdentifier> {
if !network_path.is_empty() {
log::error!("Currently can only get deepest common ancestor in the document network");
return None;
}
let Some(selected_nodes) = self.selected_nodes(network_path) else {
log::error!("Could not get selected nodes in deepest_common_ancestor");
return None;
};
selected_nodes
.selected_layers(&self.document_metadata)
.map(|layer| {
Expand Down
12 changes: 8 additions & 4 deletions editor/src/messages/tool/tool_messages/select_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis};
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GroupFolderType};
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface, NodeTemplate};
use crate::messages::portfolio::document::utility_types::transformation::Selected;
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
Expand Down Expand Up @@ -156,7 +156,12 @@ impl SelectTool {
IconButton::new(icon, 24)
.tooltip(operation.to_string())
.disabled(selected_count == 0)
.on_update(move |_| DocumentMessage::InsertBooleanOperation { operation }.into())
.on_update(move |_| {
DocumentMessage::GroupSelectedLayers {
group_folder_type: GroupFolderType::BooleanOperation(operation),
}
.into()
})
.widget_holder()
})
}
Expand Down Expand Up @@ -341,8 +346,7 @@ impl SelectToolData {

let nodes = document.network_interface.copy_nodes(&copy_ids, &[]).collect::<Vec<(NodeId, NodeTemplate)>>();

let insert_index = DocumentMessageHandler::get_calculated_insert_index(document.metadata(), document.network_interface.selected_nodes(&[]).unwrap(), parent);

let insert_index = DocumentMessageHandler::get_calculated_insert_index(document.metadata(), &document.network_interface.selected_nodes(&[]).unwrap(), parent);
let new_ids: HashMap<_, _> = nodes.iter().map(|(id, _)| (*id, NodeId(generate_uuid()))).collect();

let layer_id = *new_ids.get(&NodeId(0)).expect("Node Id 0 should be a layer");
Expand Down
Loading