diff --git a/editor/src/messages/portfolio/document/overlays/utility_functions.rs b/editor/src/messages/portfolio/document/overlays/utility_functions.rs index b84a932a..be00c45f 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_functions.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_functions.rs @@ -52,3 +52,28 @@ pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut Shape } } } + +pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) { + for layer in document.selected_nodes.selected_layers(document.metadata()) { + let Some(subpaths) = get_subpaths(layer, &document.network) else { continue }; + let transform = document.metadata().transform_to_viewport(layer); + let selected = shape_editor.selected_shape_state.get(&layer); + let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point)); + + let mut manipulator_groups = get_manipulator_groups(subpaths); + + if let Some(first_manipulator) = manipulator_groups.next() { + let anchor = first_manipulator.anchor; + let anchor_position = transform.transform_point2(anchor); + + overlay_context.square(anchor_position, is_selected(selected, ManipulatorPointId::new(first_manipulator.id, SelectedType::Anchor))); + }; + + if let Some(last_manipulator) = manipulator_groups.last() { + let anchor = last_manipulator.anchor; + let anchor_position = transform.transform_point2(anchor); + + overlay_context.square(anchor_position, is_selected(selected, ManipulatorPointId::new(last_manipulator.id, SelectedType::Anchor))); + }; + } +} diff --git a/editor/src/messages/tool/common_functionality/mod.rs b/editor/src/messages/tool/common_functionality/mod.rs index 253a4c57..02c20780 100644 --- a/editor/src/messages/tool/common_functionality/mod.rs +++ b/editor/src/messages/tool/common_functionality/mod.rs @@ -5,3 +5,4 @@ pub mod resize; pub mod shape_editor; pub mod snapping; pub mod transformation_cage; +pub mod utility_funcitons; diff --git a/editor/src/messages/tool/common_functionality/utility_funcitons.rs b/editor/src/messages/tool/common_functionality/utility_funcitons.rs new file mode 100644 index 00000000..c1e333a4 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/utility_funcitons.rs @@ -0,0 +1,34 @@ +use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; +use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::graph_modification_utils::get_subpaths; +use glam::DVec2; + +/// Determines if a path should be extended. Returns the path and if it is extending from the start, if applicable. +pub fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64) -> Option<(LayerNodeIdentifier, usize, bool)> { + let mut best = None; + let mut best_distance_squared = tolerance * tolerance; + + for layer in document.selected_nodes.selected_layers(document.metadata()) { + let viewspace = document.metadata().transform_to_viewport(layer); + + let subpaths = get_subpaths(layer, &document.network)?; + for (subpath_index, subpath) in subpaths.iter().enumerate() { + if subpath.closed() { + continue; + } + + for (manipulator_group, from_start) in [(subpath.manipulator_groups().first(), true), (subpath.manipulator_groups().last(), false)] { + let Some(manipulator_group) = manipulator_group else { break }; + + let distance_squared = viewspace.transform_point2(manipulator_group.anchor).distance_squared(pos); + + if distance_squared < best_distance_squared { + best = Some((layer, subpath_index, from_start)); + best_distance_squared = distance_squared; + } + } + } + } + + best +} diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs index 9d6a4c86..a8093da4 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -1,8 +1,11 @@ use super::tool_prelude::*; use crate::messages::portfolio::document::node_graph::VectorDataModification; +use crate::messages::portfolio::document::overlays::utility_functions::path_endpoint_overlays; +use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::graph_modification_utils; +use crate::messages::tool::common_functionality::utility_funcitons::should_extend; use graph_craft::document::NodeId; use graphene_core::uuid::generate_uuid; @@ -42,6 +45,8 @@ impl Default for FreehandOptions { pub enum FreehandToolMessage { // Standard messages #[remain::unsorted] + Overlays(OverlayContext), + #[remain::unsorted] Abort, #[remain::unsorted] WorkingColorChanged, @@ -169,6 +174,7 @@ impl<'a> MessageHandler> for Freehan impl ToolTransition for FreehandTool { fn event_to_message_map(&self) -> EventToMessageMap { EventToMessageMap { + overlay_provider: Some(|overlay_context: OverlayContext| FreehandToolMessage::Overlays(overlay_context).into()), tool_abort: Some(FreehandToolMessage::Abort.into()), working_color_changed: Some(FreehandToolMessage::WorkingColorChanged.into()), ..Default::default() @@ -178,6 +184,7 @@ impl ToolTransition for FreehandTool { #[derive(Clone, Debug, Default)] struct FreehandToolData { + extend_from_start: bool, last_point: DVec2, dragged: bool, weight: f64, @@ -190,40 +197,64 @@ impl Fsm for FreehandToolFsmState { fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { let ToolActionHandlerData { - document, global_tool_data, input, .. + document, + global_tool_data, + input, + shape_editor, + .. } = tool_action_data; let ToolMessage::Freehand(event) = event else { return self; }; match (self, event) { + (_, FreehandToolMessage::Overlays(mut overlay_context)) => { + path_endpoint_overlays(document, shape_editor, &mut overlay_context); + + self + } (FreehandToolFsmState::Ready, FreehandToolMessage::DragStart) => { responses.add(DocumentMessage::StartTransaction); - responses.add(DocumentMessage::DeselectAllLayers); let parent = document.new_layer_parent(); let transform = document.metadata().transform_to_viewport(parent); let pos = transform.inverse().transform_point2(input.mouse.position); tool_data.dragged = false; + tool_data.extend_from_start = false; tool_data.last_point = pos; tool_data.weight = tool_options.line_weight; - let subpath = bezier_rs::Subpath::from_anchors([pos], false); + if let Some((layer, subpath_index, from_start)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) { + let manipulator_group = ManipulatorGroup::new_anchor(pos); + let modification = if from_start { + tool_data.extend_from_start = true; + VectorDataModification::AddStartManipulatorGroup { subpath_index, manipulator_group } + } else { + VectorDataModification::AddEndManipulatorGroup { subpath_index, manipulator_group } + }; + responses.add(GraphOperationMessage::Vector { layer, modification }); + tool_data.dragged = true; + tool_data.last_point = pos; + tool_data.layer = Some(layer); + } else { + responses.add(DocumentMessage::DeselectAllLayers); + let subpath = bezier_rs::Subpath::from_anchors([pos], false); - let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), parent, responses); - tool_data.layer = Some(layer); + let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), parent, responses); + tool_data.layer = Some(layer); - responses.add(GraphOperationMessage::FillSet { - layer, - fill: if let Some(color) = tool_options.fill.active_color() { Fill::Solid(color) } else { Fill::None }, - }); + responses.add(GraphOperationMessage::FillSet { + layer, + fill: if let Some(color) = tool_options.fill.active_color() { Fill::Solid(color) } else { Fill::None }, + }); - responses.add(GraphOperationMessage::StrokeSet { - layer, - stroke: Stroke::new(tool_options.stroke.active_color(), tool_data.weight), - }); + responses.add(GraphOperationMessage::StrokeSet { + layer, + stroke: Stroke::new(tool_options.stroke.active_color(), tool_data.weight), + }); + } FreehandToolFsmState::Drawing } @@ -234,7 +265,11 @@ impl Fsm for FreehandToolFsmState { if tool_data.last_point != pos { let manipulator_group = ManipulatorGroup::new_anchor(pos); - let modification = VectorDataModification::AddEndManipulatorGroup { subpath_index: 0, manipulator_group }; + let modification = if tool_data.extend_from_start { + VectorDataModification::AddStartManipulatorGroup { subpath_index: 0, manipulator_group } + } else { + VectorDataModification::AddEndManipulatorGroup { subpath_index: 0, manipulator_group } + }; responses.add(GraphOperationMessage::Vector { layer, modification }); tool_data.dragged = true; tool_data.last_point = pos; diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 2578285b..dae011e9 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -8,6 +8,7 @@ use crate::messages::tool::common_functionality::color_selector::{ToolColorOptio use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils::get_subpaths; use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager}; +use crate::messages::tool::common_functionality::utility_funcitons::should_extend; use graph_craft::document::NodeId; use graphene_core::uuid::{generate_uuid, ManipulatorGroupId}; @@ -767,33 +768,3 @@ fn add_manipulator_group(layer: Option, from_start: bool, m }; GraphOperationMessage::Vector { layer, modification }.into() } - -/// Determines if a path should be extended. Returns the path and if it is extending from the start, if applicable. -fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64) -> Option<(LayerNodeIdentifier, usize, bool)> { - let mut best = None; - let mut best_distance_squared = tolerance * tolerance; - - for layer in document.selected_nodes.selected_layers(document.metadata()) { - let viewspace = document.metadata().transform_to_viewport(layer); - - let subpaths = get_subpaths(layer, &document.network)?; - for (subpath_index, subpath) in subpaths.iter().enumerate() { - if subpath.closed() { - continue; - } - - for (manipulator_group, from_start) in [(subpath.manipulator_groups().first(), true), (subpath.manipulator_groups().last(), false)] { - let Some(manipulator_group) = manipulator_group else { break }; - - let distance_squared = viewspace.transform_point2(manipulator_group.anchor).distance_squared(pos); - - if distance_squared < best_distance_squared { - best = Some((layer, subpath_index, from_start)); - best_distance_squared = distance_squared; - } - } - } - } - - best -}