mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 23:28:12 +08:00
Seperate duplication logic
This commit is contained in:
Vendored
+2
-1
@@ -52,5 +52,6 @@
|
|||||||
"files.insertFinalNewline": true,
|
"files.insertFinalNewline": true,
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"*.graphite": "json"
|
"*.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)]
|
#[derive(Debug, Copy, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct MouseState {
|
pub struct MouseState {
|
||||||
pub position: ViewportPosition,
|
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)]
|
#[derive(Debug, Copy, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct EditorMouseState {
|
pub struct EditorMouseState {
|
||||||
pub editor_position: EditorPosition,
|
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::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::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::OutputConnector;
|
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::portfolio::document_migration::*;
|
||||||
use crate::messages::preferences::SelectionMode;
|
use crate::messages::preferences::SelectionMode;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
@@ -259,12 +258,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut copy_val = |buffer: &mut Vec<CopyBufferEntry>| {
|
let mut copy_val = |buffer: &mut Vec<CopyBufferEntry>| {
|
||||||
let mut ordered_last_elements = active_document.network_interface.shallowest_unique_layers(&[]).collect::<Vec<_>>();
|
let 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)
|
|
||||||
});
|
|
||||||
|
|
||||||
for layer in ordered_last_elements.into_iter() {
|
for layer in ordered_last_elements.into_iter() {
|
||||||
let layer_node_id = layer.to_node();
|
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::graph_operation::utility_types::TransformIn;
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
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::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface, NodeTemplate};
|
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||||
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
|
|
||||||
use crate::messages::preferences::SelectionMode;
|
use crate::messages::preferences::SelectionMode;
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose};
|
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 crate::messages::tool::tool_messages::select_tool::options::NestedSelectionBehavior;
|
||||||
use bezier_rs::Subpath;
|
use bezier_rs::Subpath;
|
||||||
use glam::DMat2;
|
use glam::DMat2;
|
||||||
use graph_craft::document::NodeId;
|
|
||||||
use graphene_std::renderer::Quad;
|
use graphene_std::renderer::Quad;
|
||||||
use graphene_std::renderer::Rect;
|
use graphene_std::renderer::Rect;
|
||||||
use graphene_std::transform::ReferencePoint;
|
use graphene_std::transform::ReferencePoint;
|
||||||
|
|
||||||
|
mod duplicate;
|
||||||
pub mod options;
|
pub mod options;
|
||||||
|
|
||||||
#[derive(Default, ExtractField)]
|
#[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)]
|
#[derive(Clone, Debug, Default)]
|
||||||
struct SelectToolData {
|
struct SelectToolData {
|
||||||
drag_start: ViewportPosition,
|
drag: DragState,
|
||||||
drag_current: ViewportPosition,
|
duplicates: duplicate::DuplcateState,
|
||||||
|
|
||||||
|
// select
|
||||||
lasso_polygon: Vec<ViewportPosition>,
|
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>,
|
layer_selected_on_start: Option<LayerNodeIdentifier>,
|
||||||
select_single_layer: 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,
|
axis_align: bool,
|
||||||
non_duplicated_layers: Option<Vec<LayerNodeIdentifier>>,
|
|
||||||
bounding_box_manager: Option<BoundingBoxManager>,
|
bounding_box_manager: Option<BoundingBoxManager>,
|
||||||
snap_manager: SnapManager,
|
snap_manager: SnapManager,
|
||||||
cursor: MouseCursorIcon,
|
cursor: MouseCursorIcon,
|
||||||
pivot_gizmo: PivotGizmo,
|
pivot_gizmo: PivotGizmo,
|
||||||
pivot_gizmo_start: Option<DVec2>,
|
|
||||||
pivot_gizmo_shift: Option<DVec2>,
|
pivot_gizmo_shift: Option<DVec2>,
|
||||||
compass_rose: CompassRose,
|
compass_rose: CompassRose,
|
||||||
line_center: DVec2,
|
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> {
|
pub fn intersect_lasso_no_artboards(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) -> Vec<LayerNodeIdentifier> {
|
||||||
if self.lasso_polygon.len() < 2 {
|
if self.lasso_polygon.len() < 2 {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
@@ -252,101 +301,6 @@ impl SelectToolData {
|
|||||||
document.is_layer_fully_inside_polygon(layer, input, polygon)
|
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> {
|
fn state_from_pivot_gizmo(&self, mouse: DVec2) -> Option<SelectToolFsmState> {
|
||||||
match self.pivot_gizmo.state.gizmo_type {
|
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),
|
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);
|
tool_data.pivot_gizmo.pivot.recalculate_pivot(document);
|
||||||
let pivot = draw_pivot.then_some(tool_data.pivot_gizmo.pivot.pivot).flatten();
|
let pivot = draw_pivot.then_some(tool_data.pivot_gizmo.pivot.pivot).flatten();
|
||||||
if let Some(pivot) = pivot {
|
if let Some(pivot) = pivot {
|
||||||
let offset = tool_data
|
let offset = tool_data.drag.total_drag_delta_viewport(document.metadata());
|
||||||
.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 shift = tool_data.pivot_gizmo_shift.unwrap_or_default();
|
let shift = tool_data.pivot_gizmo_shift.unwrap_or_default();
|
||||||
overlay_context.pivot(pivot + offset + shift, angle);
|
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 {
|
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 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 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 origin = compass_center - extension;
|
||||||
let viewport_diagonal = input.viewport_bounds.size().length();
|
let viewport_diagonal = input.viewport_bounds.size().length();
|
||||||
|
|
||||||
@@ -709,13 +659,9 @@ impl Fsm for SelectToolFsmState {
|
|||||||
// Check if the tool is in selection mode
|
// Check if the tool is in selection mode
|
||||||
if let Self::Drawing { selection_shape, .. } = self {
|
if let Self::Drawing { selection_shape, .. } = self {
|
||||||
// Get the updated selection box bounds
|
// 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() {
|
let current_selection_mode = tool_data.drag.update_selection_mode(document.metadata(), tool_action_data.preferences);
|
||||||
SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(),
|
|
||||||
SelectionMode::Touched => SelectionMode::Touched,
|
|
||||||
SelectionMode::Enclosed => SelectionMode::Enclosed,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Draw outline visualizations on the layers to be selected
|
// Draw outline visualizations on the layers to be selected
|
||||||
let intersected_layers = match selection_shape {
|
let intersected_layers = match selection_shape {
|
||||||
@@ -761,11 +707,9 @@ impl Fsm for SelectToolFsmState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Self::Dragging { .. } = self {
|
if let Self::Dragging { .. } = self {
|
||||||
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 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());
|
|
||||||
|
|
||||||
overlay_context.translation_box(document_current - document_start, quad, None);
|
overlay_context.translation_box(tool_data.drag.total_drag_delta_document(), quad, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
@@ -791,9 +735,8 @@ impl Fsm for SelectToolFsmState {
|
|||||||
..
|
..
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
tool_data.drag_start = input.mouse.position;
|
tool_data.drag = DragState::new(input, document.metadata());
|
||||||
tool_data.drag_current = input.mouse.position;
|
tool_data.duplicates.reset();
|
||||||
tool_data.selection_mode = None;
|
|
||||||
|
|
||||||
let mut selected: Vec<_> = document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface).collect();
|
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<_>>();
|
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())
|
(axis_state.unwrap_or_default(), axis_state.is_some())
|
||||||
};
|
};
|
||||||
|
|
||||||
tool_data.pivot_gizmo_start = Some(tool_data.drag_current);
|
|
||||||
|
|
||||||
SelectToolFsmState::Dragging {
|
SelectToolFsmState::Dragging {
|
||||||
axis,
|
axis,
|
||||||
using_compass,
|
using_compass,
|
||||||
@@ -893,8 +834,6 @@ impl Fsm for SelectToolFsmState {
|
|||||||
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
||||||
tool_data.pivot_gizmo_start = Some(tool_data.drag_current);
|
|
||||||
|
|
||||||
SelectToolFsmState::Dragging {
|
SelectToolFsmState::Dragging {
|
||||||
axis: Axis::None,
|
axis: Axis::None,
|
||||||
using_compass: false,
|
using_compass: false,
|
||||||
@@ -907,7 +846,6 @@ impl Fsm for SelectToolFsmState {
|
|||||||
SelectToolFsmState::Drawing { selection_shape, has_drawn: false }
|
SelectToolFsmState::Drawing { selection_shape, has_drawn: false }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
tool_data.non_duplicated_layers = None;
|
|
||||||
|
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
@@ -929,20 +867,18 @@ impl Fsm for SelectToolFsmState {
|
|||||||
if !has_dragged {
|
if !has_dragged {
|
||||||
responses.add(ToolMessage::UpdateHints);
|
responses.add(ToolMessage::UpdateHints);
|
||||||
}
|
}
|
||||||
if input.keyboard.key(modifier_keys.duplicate) && tool_data.non_duplicated_layers.is_none() {
|
tool_data
|
||||||
tool_data.start_duplicates(document, responses);
|
.duplicates
|
||||||
} else if !input.keyboard.key(modifier_keys.duplicate) && tool_data.non_duplicated_layers.is_some() {
|
.set_duplicating(input.keyboard.key(modifier_keys.duplicate), &mut tool_data.layers_dragging, document, &tool_data.drag, responses);
|
||||||
tool_data.stop_duplicates(document, responses);
|
|
||||||
}
|
|
||||||
|
|
||||||
tool_data.axis_align = input.keyboard.key(modifier_keys.axis_align);
|
tool_data.axis_align = input.keyboard.key(modifier_keys.axis_align);
|
||||||
|
|
||||||
// Ignore the non duplicated layers if the current layers have not spawned yet.
|
// 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 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 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
|
let e0 = tool_data
|
||||||
.bounding_box_manager
|
.bounding_box_manager
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -965,7 +901,9 @@ impl Fsm for SelectToolFsmState {
|
|||||||
skip_rerender: false,
|
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
|
// Auto-panning
|
||||||
let messages = [
|
let messages = [
|
||||||
@@ -1025,7 +963,7 @@ impl Fsm for SelectToolFsmState {
|
|||||||
responses,
|
responses,
|
||||||
bounds,
|
bounds,
|
||||||
&mut tool_data.layers_dragging,
|
&mut tool_data.layers_dragging,
|
||||||
tool_data.drag_start,
|
tool_data.drag.start_viewport(document.metadata()),
|
||||||
input.mouse.position,
|
input.mouse.position,
|
||||||
input.keyboard.key(Key::Shift),
|
input.keyboard.key(Key::Shift),
|
||||||
ToolType::Select,
|
ToolType::Select,
|
||||||
@@ -1056,11 +994,11 @@ impl Fsm for SelectToolFsmState {
|
|||||||
responses.add(ToolMessage::UpdateHints);
|
responses.add(ToolMessage::UpdateHints);
|
||||||
}
|
}
|
||||||
|
|
||||||
tool_data.drag_current = input.mouse.position;
|
tool_data.drag.set_current(input, document.metadata());
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
if selection_shape == SelectionShapeType::Lasso {
|
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
|
// Auto-panning
|
||||||
@@ -1110,10 +1048,7 @@ impl Fsm for SelectToolFsmState {
|
|||||||
SelectToolMessage::PointerOutsideViewport(_),
|
SelectToolMessage::PointerOutsideViewport(_),
|
||||||
) => {
|
) => {
|
||||||
// Auto-panning
|
// Auto-panning
|
||||||
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
|
tool_data.auto_panning.shift_viewport(input, responses);
|
||||||
tool_data.drag_current += shift;
|
|
||||||
tool_data.drag_start += shift;
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectToolFsmState::Dragging {
|
SelectToolFsmState::Dragging {
|
||||||
axis,
|
axis,
|
||||||
@@ -1142,9 +1077,7 @@ impl Fsm for SelectToolFsmState {
|
|||||||
}
|
}
|
||||||
(SelectToolFsmState::Drawing { .. }, SelectToolMessage::PointerOutsideViewport(_)) => {
|
(SelectToolFsmState::Drawing { .. }, SelectToolMessage::PointerOutsideViewport(_)) => {
|
||||||
// Auto-panning
|
// Auto-panning
|
||||||
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
|
tool_data.auto_panning.shift_viewport(input, responses);
|
||||||
tool_data.drag_start += shift;
|
|
||||||
}
|
|
||||||
|
|
||||||
self
|
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() {
|
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.
|
// 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);
|
let intersection = document.intersect_quad_no_artboards(quad, input);
|
||||||
|
|
||||||
if let Some(path) = intersection.last() {
|
if let Some(path) = intersection.last() {
|
||||||
@@ -1224,13 +1157,12 @@ impl Fsm for SelectToolFsmState {
|
|||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
tool_data.select_single_layer = None;
|
tool_data.select_single_layer = None;
|
||||||
|
|
||||||
if let Some(start) = tool_data.pivot_gizmo_start {
|
if tool_data.pivot_gizmo.pivot_disconnected() {
|
||||||
let offset = tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - start).unwrap_or_default();
|
let offset = tool_data.drag.total_drag_delta_viewport(document.metadata());
|
||||||
if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() {
|
if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() {
|
||||||
*v += offset;
|
*v += offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tool_data.pivot_gizmo_start = None;
|
|
||||||
|
|
||||||
let pivot_gizmo = tool_data.pivot_gizmo();
|
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||||
@@ -1241,13 +1173,10 @@ impl Fsm for SelectToolFsmState {
|
|||||||
SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds | SelectToolFsmState::DraggingPivot,
|
SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds | SelectToolFsmState::DraggingPivot,
|
||||||
SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter,
|
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();
|
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||||
responses.add(TransformLayerMessage::SetPivotGizmo { 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.axis_align = false;
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
@@ -1261,12 +1190,9 @@ impl Fsm for SelectToolFsmState {
|
|||||||
SelectToolFsmState::Ready
|
SelectToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::Drawing { selection_shape, .. }, SelectToolMessage::DragStop { remove_from_selection }) => {
|
(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() {
|
let selection_mode = tool_data.drag.update_selection_mode(document.metadata(), tool_action_data.preferences);
|
||||||
SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(),
|
|
||||||
selection_mode => selection_mode,
|
|
||||||
};
|
|
||||||
|
|
||||||
let intersection: Vec<LayerNodeIdentifier> = match selection_shape {
|
let intersection: Vec<LayerNodeIdentifier> = match selection_shape {
|
||||||
SelectionShapeType::Box => document.intersect_quad_no_artboards(quad, input).collect(),
|
SelectionShapeType::Box => document.intersect_quad_no_artboards(quad, input).collect(),
|
||||||
|
|||||||
@@ -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