Seperate duplication logic

This commit is contained in:
hypercube
2025-08-05 17:02:27 +01:00
parent 6b0c04d4d3
commit eb255073e6
5 changed files with 230 additions and 202 deletions

View File

@@ -52,5 +52,6 @@
"files.insertFinalNewline": true,
"files.associations": {
"*.graphite": "json"
}
},
"rust-analyzer.checkOnSave": false
}

View File

@@ -78,7 +78,8 @@ impl ScrollDelta {
}
}
// TODO: Document the difference between this and EditorMouseState
/// This is similar to [`EditorMouseState`] except that the position is stored relative to the viewport rather than relative to the UI.
/// Convert using [`EditorMouseState::to_mouse_state`]
#[derive(Debug, Copy, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct MouseState {
pub position: ViewportPosition,
@@ -94,7 +95,8 @@ impl MouseState {
}
}
// TODO: Document the difference between this and MouseState
/// This is similar to [`MouseState`] except that the position is stored relative to the UI rather than relative to the viewport.
/// Convert using [`EditorMouseState::to_mouse_state`]
#[derive(Debug, Copy, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct EditorMouseState {
pub editor_position: EditorPosition,

View File

@@ -15,7 +15,6 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions;
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
use crate::messages::portfolio::document::utility_types::network_interface::OutputConnector;
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
use crate::messages::portfolio::document_migration::*;
use crate::messages::preferences::SelectionMode;
use crate::messages::prelude::*;
@@ -259,12 +258,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
};
let mut copy_val = |buffer: &mut Vec<CopyBufferEntry>| {
let mut ordered_last_elements = active_document.network_interface.shallowest_unique_layers(&[]).collect::<Vec<_>>();
ordered_last_elements.sort_by_key(|layer| {
let Some(parent) = layer.parent(active_document.metadata()) else { return usize::MAX };
DocumentMessageHandler::get_calculated_insert_index(active_document.metadata(), &SelectedNodes(vec![layer.to_node()]), parent)
});
let ordered_last_elements = active_document.network_interface.shallowest_unique_layers(&[]).collect::<Vec<_>>();
for layer in ordered_last_elements.into_iter() {
let layer_node_id = layer.to_node();

View File

@@ -6,8 +6,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::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface, NodeTemplate};
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::messages::preferences::SelectionMode;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose};
@@ -22,11 +21,11 @@ use crate::messages::tool::common_functionality::utility_functions::{resize_boun
use crate::messages::tool::tool_messages::select_tool::options::NestedSelectionBehavior;
use bezier_rs::Subpath;
use glam::DMat2;
use graph_craft::document::NodeId;
use graphene_std::renderer::Quad;
use graphene_std::renderer::Rect;
use graphene_std::transform::ReferencePoint;
mod duplicate;
pub mod options;
#[derive(Default, ExtractField)]
@@ -163,23 +162,104 @@ impl Default for SelectToolFsmState {
}
}
/// Represents the current drag in progress
#[derive(Clone, Debug, Default)]
struct DragState {
pub start_document: DVec2,
pub current_document: DVec2,
/// Selection mode is set when the drag exceeds a certain distance. Once resolved, the selection mode cannot change.
resolved_selection_mode: Option<SelectionMode>,
}
impl DragState {
pub fn new(input: &InputPreprocessorMessageHandler, metadata: &DocumentMetadata) -> Self {
let document_mouse = metadata.document_to_viewport.inverse().transform_point2(input.mouse.position);
Self {
start_document: document_mouse,
current_document: document_mouse,
resolved_selection_mode: None,
}
}
pub fn set_current(&mut self, input: &InputPreprocessorMessageHandler, metadata: &DocumentMetadata) {
self.current_document = metadata.document_to_viewport.inverse().transform_point2(input.mouse.position);
}
pub fn start_viewport(&self, metadata: &DocumentMetadata) -> DVec2 {
metadata.document_to_viewport.transform_point2(self.start_document)
}
pub fn current_viewport(&self, metadata: &DocumentMetadata) -> DVec2 {
metadata.document_to_viewport.transform_point2(self.current_document)
}
pub fn start_current_viewport(&self, metadata: &DocumentMetadata) -> [DVec2; 2] {
[self.start_viewport(metadata), self.current_viewport(metadata)]
}
pub fn total_drag_delta_document(&self) -> DVec2 {
self.current_document - self.start_document
}
pub fn total_drag_delta_viewport(&self, metadata: &DocumentMetadata) -> DVec2 {
metadata.document_to_viewport.transform_vector2(self.total_drag_delta_document())
}
pub fn inverse_drag_delta_viewport(&self, metadata: &DocumentMetadata) -> DVec2 {
-self.total_drag_delta_viewport(metadata)
}
fn update_selection_mode(&mut self, metadata: &DocumentMetadata, preferences: &PreferencesMessageHandler) -> SelectionMode {
if let Some(resolved_selection_mode) = self.resolved_selection_mode {
return resolved_selection_mode;
}
if preferences.get_selection_mode() != SelectionMode::Directional {
self.resolved_selection_mode = Some(preferences.get_selection_mode());
return preferences.get_selection_mode();
}
let [start, current] = self.start_current_viewport(metadata);
// Drag direction cannot be resolved TODO: why not consider only X distance?
if start.distance_squared(current) >= DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD.powi(2) {
let selection_mode = if current.x < start.x { SelectionMode::Touched } else { SelectionMode::Enclosed };
self.resolved_selection_mode = Some(selection_mode);
return selection_mode;
}
SelectionMode::default()
}
/// A viewport quad representing the drag bounds. Expanded if the start == end
pub fn expanded_selection_box_viewport(&self, metadata: &DocumentMetadata) -> [DVec2; 2] {
let [start, current] = self.start_current_viewport(metadata);
if start == current {
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
[current - tolerance, current + tolerance]
} else {
[start, current]
}
}
}
#[derive(Clone, Debug, Default)]
struct SelectToolData {
drag_start: ViewportPosition,
drag_current: ViewportPosition,
drag: DragState,
duplicates: duplicate::DuplcateState,
// select
lasso_polygon: Vec<ViewportPosition>,
selection_mode: Option<SelectionMode>,
layers_dragging: Vec<LayerNodeIdentifier>, // Unordered, often used as temporary buffer
ordered_layers: Vec<LayerNodeIdentifier>, // Ordered list of layers
layer_selected_on_start: Option<LayerNodeIdentifier>,
select_single_layer: Option<LayerNodeIdentifier>,
layers_dragging: Vec<LayerNodeIdentifier>, // Unordered, often used as temporary buffer
ordered_layers: Vec<LayerNodeIdentifier>, // Ordered list of layers
axis_align: bool,
non_duplicated_layers: Option<Vec<LayerNodeIdentifier>>,
bounding_box_manager: Option<BoundingBoxManager>,
snap_manager: SnapManager,
cursor: MouseCursorIcon,
pivot_gizmo: PivotGizmo,
pivot_gizmo_start: Option<DVec2>,
pivot_gizmo_shift: Option<DVec2>,
compass_rose: CompassRose,
line_center: DVec2,
@@ -205,37 +285,6 @@ impl SelectToolData {
}
}
pub fn selection_quad(&self) -> Quad {
let bbox = self.selection_box();
Quad::from_box(bbox)
}
pub fn calculate_selection_mode_from_direction(&mut self) -> SelectionMode {
let bbox: [DVec2; 2] = self.selection_box();
let above_threshold = bbox[1].distance_squared(bbox[0]) > DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD.powi(2);
if self.selection_mode.is_none() && above_threshold {
let mode = if bbox[1].x < bbox[0].x {
SelectionMode::Touched
} else {
// This also covers the case where they're equal: the area is zero, so we use `Enclosed` to ensure the selection ends up empty, as nothing will be enclosed by an empty area
SelectionMode::Enclosed
};
self.selection_mode = Some(mode);
}
self.selection_mode.unwrap_or(SelectionMode::Touched)
}
pub fn selection_box(&self) -> [DVec2; 2] {
if self.drag_current == self.drag_start {
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
[self.drag_start - tolerance, self.drag_start + tolerance]
} else {
[self.drag_start, self.drag_current]
}
}
pub fn intersect_lasso_no_artboards(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) -> Vec<LayerNodeIdentifier> {
if self.lasso_polygon.len() < 2 {
return Vec::new();
@@ -252,101 +301,6 @@ impl SelectToolData {
document.is_layer_fully_inside_polygon(layer, input, polygon)
}
/// Duplicates the currently dragging layers. Called when Alt is pressed and the layers have not yet been duplicated.
fn start_duplicates(&mut self, document: &mut DocumentMessageHandler, responses: &mut VecDeque<Message>) {
self.non_duplicated_layers = Some(self.layers_dragging.clone());
let mut new_dragging = Vec::new();
// Get the shallowest unique layers and sort by their index relative to parent for ordered processing
let mut layers = document.network_interface.shallowest_unique_layers(&[]).collect::<Vec<_>>();
layers.sort_by_key(|layer| {
let Some(parent) = layer.parent(document.metadata()) else { return usize::MAX };
DocumentMessageHandler::get_calculated_insert_index(document.metadata(), &SelectedNodes(vec![layer.to_node()]), parent)
});
for layer in layers.into_iter().rev() {
let Some(parent) = layer.parent(document.metadata()) else { continue };
// Moves the layer back to its starting position.
responses.add(GraphOperationMessage::TransformChange {
layer,
transform: DAffine2::from_translation(self.drag_start - self.drag_current),
transform_in: TransformIn::Viewport,
skip_rerender: true,
});
// Copy the layer
let mut copy_ids = HashMap::new();
let node_id = layer.to_node();
copy_ids.insert(node_id, NodeId(0));
document
.network_interface
.upstream_flow_back_from_nodes(vec![layer.to_node()], &[], FlowType::LayerChildrenUpstreamFlow)
.enumerate()
.for_each(|(index, node_id)| {
copy_ids.insert(node_id, NodeId((index + 1) as u64));
});
let nodes = document.network_interface.copy_nodes(&copy_ids, &[]).collect::<Vec<(NodeId, NodeTemplate)>>();
let insert_index = DocumentMessageHandler::get_calculated_insert_index(document.metadata(), &SelectedNodes(vec![layer.to_node()]), parent);
let new_ids: HashMap<_, _> = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect();
let layer_id = *new_ids.get(&NodeId(0)).expect("Node Id 0 should be a layer");
let layer = LayerNodeIdentifier::new_unchecked(layer_id);
new_dragging.push(layer);
responses.add(NodeGraphMessage::AddNodes { nodes, new_ids });
responses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index });
}
let nodes = new_dragging.iter().map(|layer| layer.to_node()).collect();
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
responses.add(NodeGraphMessage::RunDocumentGraph);
self.layers_dragging = new_dragging;
}
/// Removes the duplicated layers. Called when Alt is released and the layers have previously been duplicated.
fn stop_duplicates(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
let Some(original) = self.non_duplicated_layers.take() else {
return;
};
// Delete the duplicated layers
for layer in document.network_interface.shallowest_unique_layers(&[]) {
responses.add(NodeGraphMessage::DeleteNodes {
node_ids: vec![layer.to_node()],
delete_children: true,
});
}
for &layer in &original {
responses.add(GraphOperationMessage::TransformChange {
layer,
transform: DAffine2::from_translation(self.drag_current - self.drag_start),
transform_in: TransformIn::Viewport,
skip_rerender: true,
});
}
let nodes = original
.iter()
.filter_map(|layer| {
if *layer != LayerNodeIdentifier::ROOT_PARENT {
Some(layer.to_node())
} else {
log::error!("ROOT_PARENT cannot be part of non_duplicated_layers");
None
}
})
.collect();
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph);
self.layers_dragging = original;
}
fn state_from_pivot_gizmo(&self, mouse: DVec2) -> Option<SelectToolFsmState> {
match self.pivot_gizmo.state.gizmo_type {
PivotGizmoType::Pivot if self.pivot_gizmo.state.is_pivot() => self.pivot_gizmo.pivot.is_over(mouse).then_some(SelectToolFsmState::DraggingPivot),
@@ -627,10 +581,7 @@ impl Fsm for SelectToolFsmState {
tool_data.pivot_gizmo.pivot.recalculate_pivot(document);
let pivot = draw_pivot.then_some(tool_data.pivot_gizmo.pivot.pivot).flatten();
if let Some(pivot) = pivot {
let offset = tool_data
.pivot_gizmo_start
.map(|offset| tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - offset).unwrap_or_default())
.unwrap_or_default();
let offset = tool_data.drag.total_drag_delta_viewport(document.metadata());
let shift = tool_data.pivot_gizmo_shift.unwrap_or_default();
overlay_context.pivot(pivot + offset + shift, angle);
}
@@ -681,12 +632,11 @@ impl Fsm for SelectToolFsmState {
}
if axis_state.is_none_or(|(axis, _)| !axis.is_constraint()) && tool_data.axis_align {
let mouse_position = mouse_position - tool_data.drag_start;
let snap_resolution = SELECTION_DRAG_ANGLE.to_radians();
let angle = -mouse_position.angle_to(DVec2::X);
let angle = -tool_data.drag.total_drag_delta_viewport(document.metadata()).angle_to(DVec2::X);
let snapped_angle = (angle / snap_resolution).round() * snap_resolution;
let extension = tool_data.drag_current - tool_data.drag_start;
let extension = tool_data.drag.total_drag_delta_viewport(document.metadata());
let origin = compass_center - extension;
let viewport_diagonal = input.viewport_bounds.size().length();
@@ -709,13 +659,9 @@ impl Fsm for SelectToolFsmState {
// Check if the tool is in selection mode
if let Self::Drawing { selection_shape, .. } = self {
// Get the updated selection box bounds
let quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]);
let quad = Quad::from_box(tool_data.drag.start_current_viewport(document.metadata()));
let current_selection_mode = match tool_action_data.preferences.get_selection_mode() {
SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(),
SelectionMode::Touched => SelectionMode::Touched,
SelectionMode::Enclosed => SelectionMode::Enclosed,
};
let current_selection_mode = tool_data.drag.update_selection_mode(document.metadata(), tool_action_data.preferences);
// Draw outline visualizations on the layers to be selected
let intersected_layers = match selection_shape {
@@ -761,11 +707,9 @@ impl Fsm for SelectToolFsmState {
}
if let Self::Dragging { .. } = self {
let quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]);
let document_start = document.metadata().document_to_viewport.inverse().transform_point2(quad.top_left());
let document_current = document.metadata().document_to_viewport.inverse().transform_point2(quad.bottom_right());
let quad = Quad::from_box(tool_data.drag.start_current_viewport(document.metadata()));
overlay_context.translation_box(document_current - document_start, quad, None);
overlay_context.translation_box(tool_data.drag.total_drag_delta_document(), quad, None);
}
self
@@ -791,9 +735,8 @@ impl Fsm for SelectToolFsmState {
..
},
) => {
tool_data.drag_start = input.mouse.position;
tool_data.drag_current = input.mouse.position;
tool_data.selection_mode = None;
tool_data.drag = DragState::new(input, document.metadata());
tool_data.duplicates.reset();
let mut selected: Vec<_> = document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface).collect();
let intersection_list = document.click_list(input).collect::<Vec<_>>();
@@ -852,8 +795,6 @@ impl Fsm for SelectToolFsmState {
(axis_state.unwrap_or_default(), axis_state.is_some())
};
tool_data.pivot_gizmo_start = Some(tool_data.drag_current);
SelectToolFsmState::Dragging {
axis,
using_compass,
@@ -893,8 +834,6 @@ impl Fsm for SelectToolFsmState {
responses.add(DocumentMessage::StartTransaction);
tool_data.pivot_gizmo_start = Some(tool_data.drag_current);
SelectToolFsmState::Dragging {
axis: Axis::None,
using_compass: false,
@@ -907,7 +846,6 @@ impl Fsm for SelectToolFsmState {
SelectToolFsmState::Drawing { selection_shape, has_drawn: false }
}
};
tool_data.non_duplicated_layers = None;
state
}
@@ -929,20 +867,18 @@ impl Fsm for SelectToolFsmState {
if !has_dragged {
responses.add(ToolMessage::UpdateHints);
}
if input.keyboard.key(modifier_keys.duplicate) && tool_data.non_duplicated_layers.is_none() {
tool_data.start_duplicates(document, responses);
} else if !input.keyboard.key(modifier_keys.duplicate) && tool_data.non_duplicated_layers.is_some() {
tool_data.stop_duplicates(document, responses);
}
tool_data
.duplicates
.set_duplicating(input.keyboard.key(modifier_keys.duplicate), &mut tool_data.layers_dragging, document, &tool_data.drag, responses);
tool_data.axis_align = input.keyboard.key(modifier_keys.axis_align);
// Ignore the non duplicated layers if the current layers have not spawned yet.
let layers_exist = tool_data.layers_dragging.iter().all(|&layer| document.metadata().click_targets(layer).is_some());
let ignore = tool_data.non_duplicated_layers.as_ref().filter(|_| !layers_exist).unwrap_or(&tool_data.layers_dragging);
let ignore = tool_data.duplicates.non_duplicated_layers.as_ref().filter(|_| !layers_exist).unwrap_or(&tool_data.layers_dragging);
let snap_data = SnapData::ignore(document, input, ignore);
let (start, current) = (tool_data.drag_start, tool_data.drag_current);
let [start, current] = tool_data.drag.start_current_viewport(document.metadata());
let e0 = tool_data
.bounding_box_manager
.as_ref()
@@ -965,7 +901,9 @@ impl Fsm for SelectToolFsmState {
skip_rerender: false,
});
}
tool_data.drag_current += mouse_delta;
tool_data.drag.current_document += document.metadata().document_to_viewport.inverse().transform_vector2(mouse_delta);
info!("current {} mouse {}", tool_data.drag.current_viewport(document.metadata()), input.mouse.position);
// Auto-panning
let messages = [
@@ -1025,7 +963,7 @@ impl Fsm for SelectToolFsmState {
responses,
bounds,
&mut tool_data.layers_dragging,
tool_data.drag_start,
tool_data.drag.start_viewport(document.metadata()),
input.mouse.position,
input.keyboard.key(Key::Shift),
ToolType::Select,
@@ -1056,11 +994,11 @@ impl Fsm for SelectToolFsmState {
responses.add(ToolMessage::UpdateHints);
}
tool_data.drag_current = input.mouse.position;
tool_data.drag.set_current(input, document.metadata());
responses.add(OverlaysMessage::Draw);
if selection_shape == SelectionShapeType::Lasso {
extend_lasso(&mut tool_data.lasso_polygon, tool_data.drag_current);
extend_lasso(&mut tool_data.lasso_polygon, tool_data.drag.current_viewport(document.metadata()));
}
// Auto-panning
@@ -1110,10 +1048,7 @@ impl Fsm for SelectToolFsmState {
SelectToolMessage::PointerOutsideViewport(_),
) => {
// Auto-panning
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
tool_data.drag_current += shift;
tool_data.drag_start += shift;
}
tool_data.auto_panning.shift_viewport(input, responses);
SelectToolFsmState::Dragging {
axis,
@@ -1142,9 +1077,7 @@ impl Fsm for SelectToolFsmState {
}
(SelectToolFsmState::Drawing { .. }, SelectToolMessage::PointerOutsideViewport(_)) => {
// Auto-panning
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
tool_data.drag_start += shift;
}
tool_data.auto_panning.shift_viewport(input, responses);
self
}
@@ -1165,7 +1098,7 @@ impl Fsm for SelectToolFsmState {
if !has_dragged && input.keyboard.key(remove_from_selection) && tool_data.layer_selected_on_start.is_none() {
// When you click on the layer with remove from selection key (shift) pressed, we deselect all nodes that are children.
let quad = tool_data.selection_quad();
let quad = Quad::from_box(tool_data.drag.expanded_selection_box_viewport(document.metadata()));
let intersection = document.intersect_quad_no_artboards(quad, input);
if let Some(path) = intersection.last() {
@@ -1224,13 +1157,12 @@ impl Fsm for SelectToolFsmState {
tool_data.snap_manager.cleanup(responses);
tool_data.select_single_layer = None;
if let Some(start) = tool_data.pivot_gizmo_start {
let offset = tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - start).unwrap_or_default();
if tool_data.pivot_gizmo.pivot_disconnected() {
let offset = tool_data.drag.total_drag_delta_viewport(document.metadata());
if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() {
*v += offset;
}
}
tool_data.pivot_gizmo_start = None;
let pivot_gizmo = tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
@@ -1241,13 +1173,10 @@ impl Fsm for SelectToolFsmState {
SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds | SelectToolFsmState::DraggingPivot,
SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter,
) => {
let drag_too_small = input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON;
let response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction };
let pivot_gizmo = tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
responses.add(response);
input.mouse.finish_transaction(tool_data.drag.start_viewport(document.metadata()), responses);
tool_data.axis_align = false;
tool_data.snap_manager.cleanup(responses);
@@ -1261,12 +1190,9 @@ impl Fsm for SelectToolFsmState {
SelectToolFsmState::Ready
}
(SelectToolFsmState::Drawing { selection_shape, .. }, SelectToolMessage::DragStop { remove_from_selection }) => {
let quad = tool_data.selection_quad();
let quad = Quad::from_box(tool_data.drag.start_current_viewport(document.metadata()));
let selection_mode = match tool_action_data.preferences.get_selection_mode() {
SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(),
selection_mode => selection_mode,
};
let selection_mode = tool_data.drag.update_selection_mode(document.metadata(), tool_action_data.preferences);
let intersection: Vec<LayerNodeIdentifier> = match selection_shape {
SelectionShapeType::Box => document.intersect_quad_no_artboards(quad, input).collect(),

View File

@@ -0,0 +1,105 @@
use super::super::tool_prelude::*;
use super::DragState;
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeTemplate};
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
use crate::messages::tool::tool_messages::select_tool::LayerNodeIdentifier;
use crate::messages::tool::tool_messages::select_tool::TransformIn;
use graphene_std::uuid::NodeId;
#[derive(Clone, Debug, Default)]
pub struct DuplcateState {
pub non_duplicated_layers: Option<Vec<LayerNodeIdentifier>>,
}
impl DuplcateState {
pub fn reset(&mut self) {
self.non_duplicated_layers = None;
}
pub fn set_duplicating(&mut self, state: bool, layers_dragging: &mut Vec<LayerNodeIdentifier>, document: &mut DocumentMessageHandler, dragging: &DragState, responses: &mut VecDeque<Message>) {
if !state && self.non_duplicated_layers.is_some() {
self.stop_duplicates(layers_dragging, document, dragging, responses);
} else if state && self.non_duplicated_layers.is_none() {
self.start_duplicates(layers_dragging, document, dragging, responses);
}
}
/// Duplicates the currently dragging layers. Called when Alt is pressed and the layers have not yet been duplicated.
fn start_duplicates(&mut self, layers_dragging: &mut Vec<LayerNodeIdentifier>, document: &mut DocumentMessageHandler, dragging: &DragState, responses: &mut VecDeque<Message>) {
let mut new_dragging = Vec::new();
// Get the shallowest unique layers and sort by their index relative to parent for ordered processing
let layers = document.network_interface.shallowest_unique_layers(&[]).collect::<Vec<_>>();
for layer in layers.into_iter().rev() {
let Some(parent) = layer.parent(document.metadata()) else { continue };
// Moves the layer back to its starting position.
responses.add(GraphOperationMessage::TransformChange {
layer,
transform: DAffine2::from_translation(dragging.inverse_drag_delta_viewport(document.metadata())),
transform_in: TransformIn::Viewport,
skip_rerender: true,
});
// Copy the layer
let mut copy_ids = HashMap::new();
let node_id = layer.to_node();
copy_ids.insert(node_id, NodeId(0));
document
.network_interface
.upstream_flow_back_from_nodes(vec![layer.to_node()], &[], FlowType::LayerChildrenUpstreamFlow)
.enumerate()
.for_each(|(index, node_id)| {
copy_ids.insert(node_id, NodeId((index + 1) as u64));
});
let nodes = document.network_interface.copy_nodes(&copy_ids, &[]).collect::<Vec<(NodeId, NodeTemplate)>>();
let insert_index = DocumentMessageHandler::get_calculated_insert_index(document.metadata(), &SelectedNodes(vec![layer.to_node()]), parent);
let new_ids: HashMap<_, _> = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect();
let layer_id = *new_ids.get(&NodeId(0)).expect("Node Id 0 should be a layer");
let layer = LayerNodeIdentifier::new_unchecked(layer_id);
new_dragging.push(layer);
responses.add(NodeGraphMessage::AddNodes { nodes, new_ids });
responses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index });
}
let nodes = new_dragging.iter().filter(|&&layer| layer != LayerNodeIdentifier::ROOT_PARENT).map(|layer| layer.to_node()).collect();
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
responses.add(NodeGraphMessage::RunDocumentGraph);
self.non_duplicated_layers = Some(core::mem::replace(layers_dragging, new_dragging));
}
/// Removes the duplicated layers. Called when Alt is released and the layers have previously been duplicated.
fn stop_duplicates(&mut self, layers_dragging: &mut Vec<LayerNodeIdentifier>, document: &DocumentMessageHandler, dragging: &DragState, responses: &mut VecDeque<Message>) {
let Some(original) = self.non_duplicated_layers.take() else {
return;
};
// Delete the duplicated layers
for layer in document.network_interface.shallowest_unique_layers(&[]) {
responses.add(NodeGraphMessage::DeleteNodes {
node_ids: vec![layer.to_node()],
delete_children: true,
});
}
for &layer in &original {
responses.add(GraphOperationMessage::TransformChange {
layer,
transform: DAffine2::from_translation(dragging.total_drag_delta_viewport(document.metadata())),
transform_in: TransformIn::Viewport,
skip_rerender: true,
});
}
let nodes = original.iter().filter(|&&layer| layer != LayerNodeIdentifier::ROOT_PARENT).map(|layer| layer.to_node()).collect();
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph);
*layers_dragging = original;
}
}