From ce969b172b26ac02b518b32ee0d976b9101f0e96 Mon Sep 17 00:00:00 2001 From: hypercube <0hypercube@gmail.com> Date: Thu, 7 Aug 2025 21:53:00 +0100 Subject: [PATCH] Extract drag state to new file --- .../utility_types/document_metadata.rs | 13 +++ .../utility_types/network_interface.rs | 26 +++--- .../tool/tool_messages/select_tool.rs | 89 ++----------------- .../tool_messages/select_tool/drag_state.rs | 84 +++++++++++++++++ .../tool_messages/select_tool/duplicate.rs | 2 +- 5 files changed, 120 insertions(+), 94 deletions(-) create mode 100644 editor/src/messages/tool/tool_messages/select_tool/drag_state.rs diff --git a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs index cc7073e8a2..b907a42a19 100644 --- a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs +++ b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs @@ -451,6 +451,19 @@ impl LayerNodeIdentifier { } } +impl PartialEq for LayerNodeIdentifier { + fn eq(&self, other: &NodeId) -> bool { + self.to_node() == *other + } +} + +// Implement == comparisons +impl PartialEq for NodeId { + fn eq(&self, other: &LayerNodeIdentifier) -> bool { + other.to_node() == *self + } +} + // ======== // AxisIter // ======== diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 4c659544b0..a4aebec174 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -1317,15 +1317,11 @@ impl NodeNetworkInterface { /// Layers excluding ones that are children of other layers in the list in layer tree order. // TODO: Cache this // Now allocation free! - pub fn shallowest_unique_layers(&self, network_path: &[NodeId]) -> ShallowestSelectionIter<'_> { + pub fn shallowest_unique_layers(&self, network_path: &[NodeId]) -> ShallowestSelectionIter<'_, NodeId> { // Avoids the clone and filtering from from the selected_nodes_in_nested_network. let metadata = self.network_metadata(network_path); let selection = metadata.and_then(|metadata| metadata.persistent_metadata.selection_undo_history.back()); - ShallowestSelectionIter { - selection: selection.map_or([].as_slice(), |selection| selection.0.as_slice()), - next: Some(LayerNodeIdentifier::ROOT_PARENT), - metadata: self.document_metadata(), - } + ShallowestSelectionIter::new(self.document_metadata(), selection.map_or([].as_slice(), |selection| selection.0.as_slice())) } /// Ancestor that is shared by all layers and that is deepest (more nested). Default may be the root. Skips selected non-folder, non-artboard layers @@ -6997,13 +6993,23 @@ pub enum TransactionStatus { /// Iterate through the shallowest selected layers without allocating #[derive(Clone)] -pub struct ShallowestSelectionIter<'a> { +pub struct ShallowestSelectionIter<'a, T: PartialEq> { next: Option, - selection: &'a [NodeId], // TODO: should be HashSet to avoid duplicates. + selection: &'a [T], // TODO: should be HashSet to avoid duplicates. metadata: &'a DocumentMetadata, } -impl Iterator for ShallowestSelectionIter<'_> { +impl<'a, T: PartialEq> ShallowestSelectionIter<'a, T> { + pub fn new(metadata: &'a DocumentMetadata, selection: &'a [T]) -> Self { + ShallowestSelectionIter { + selection, + next: Some(LayerNodeIdentifier::ROOT_PARENT), + metadata, + } + } +} + +impl> Iterator for ShallowestSelectionIter<'_, T> { type Item = LayerNodeIdentifier; fn next(&mut self) -> Option { @@ -7012,7 +7018,7 @@ impl Iterator for ShallowestSelectionIter<'_> { let below_in_tree = || layer_node.ancestors(self.metadata).find_map(|ancestor| ancestor.next_sibling(self.metadata)); // If the current layer is selected, return it. - if layer_node != LayerNodeIdentifier::ROOT_PARENT && self.selection.contains(&layer_node.to_node()) { + if layer_node != LayerNodeIdentifier::ROOT_PARENT && self.selection.iter().any(|selection| *selection == layer_node) { self.next = below_in_tree(); // Go straight to below and don't look at children return Some(layer_node); } diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 2336ab4037..2e337116b3 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -6,7 +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::NodeNetworkInterface; +use crate::messages::portfolio::document::utility_types::network_interface::{NodeNetworkInterface, ShallowestSelectionIter}; use crate::messages::preferences::SelectionMode; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose}; @@ -25,8 +25,10 @@ use graphene_std::renderer::Quad; use graphene_std::renderer::Rect; use graphene_std::transform::ReferencePoint; +mod drag_state; mod duplicate; pub mod options; +use drag_state::*; #[derive(Default, ExtractField)] pub struct SelectTool { @@ -162,85 +164,6 @@ 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, -} - -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: DragState, @@ -879,6 +802,7 @@ impl Fsm for SelectToolFsmState { let snap_data = SnapData::ignore(document, input, ignore); let [start, current] = tool_data.drag.start_current_viewport(document.metadata()); + let e0 = tool_data .bounding_box_manager .as_ref() @@ -893,7 +817,7 @@ impl Fsm for SelectToolFsmState { }; // TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481 - for layer in document.network_interface.shallowest_unique_layers(&[]) { + for layer in ShallowestSelectionIter::new(document.metadata(), &tool_data.layers_dragging) { responses.add_front(GraphOperationMessage::TransformChange { layer, transform: DAffine2::from_translation(mouse_delta), @@ -901,9 +825,8 @@ impl Fsm for SelectToolFsmState { skip_rerender: false, }); } - 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); + tool_data.drag.offset_viewport(mouse_delta, document.metadata()); // Auto-panning let messages = [ diff --git a/editor/src/messages/tool/tool_messages/select_tool/drag_state.rs b/editor/src/messages/tool/tool_messages/select_tool/drag_state.rs new file mode 100644 index 0000000000..e99b5565b8 --- /dev/null +++ b/editor/src/messages/tool/tool_messages/select_tool/drag_state.rs @@ -0,0 +1,84 @@ +use crate::consts::{DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, SELECTION_TOLERANCE}; +use crate::messages::{portfolio::document::utility_types::document_metadata::DocumentMetadata, preferences::SelectionMode, tool::tool_messages::tool_prelude::*}; +/// Represents the current drag in progress +#[derive(Clone, Debug, Default)] +pub 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, +} + +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 offset_viewport(&mut self, offset: DVec2, metadata: &DocumentMetadata) { + self.current_document = self.current_document + metadata.document_to_viewport.inverse().transform_vector2(offset); + } + + 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) + } + + pub 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] + } + } +} diff --git a/editor/src/messages/tool/tool_messages/select_tool/duplicate.rs b/editor/src/messages/tool/tool_messages/select_tool/duplicate.rs index 0babc4d02a..f6b89af80f 100644 --- a/editor/src/messages/tool/tool_messages/select_tool/duplicate.rs +++ b/editor/src/messages/tool/tool_messages/select_tool/duplicate.rs @@ -1,5 +1,5 @@ use super::super::tool_prelude::*; -use super::DragState; +use super::drag_state::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;