mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 15:28:04 +08:00
Seperate duplication logic
This commit is contained in:
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -52,5 +52,6 @@
|
||||
"files.insertFinalNewline": true,
|
||||
"files.associations": {
|
||||
"*.graphite": "json"
|
||||
}
|
||||
},
|
||||
"rust-analyzer.checkOnSave": false
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(©_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(),
|
||||
|
||||
105
editor/src/messages/tool/tool_messages/select_tool/duplicate.rs
Normal file
105
editor/src/messages/tool/tool_messages/select_tool/duplicate.rs
Normal 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(©_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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user