From 989f4bac7083c046ad8e290ae9f357f6c8e89a29 Mon Sep 17 00:00:00 2001 From: krVatsal Date: Sun, 2 Nov 2025 17:21:15 +0530 Subject: [PATCH 1/4] add arrow shape feature in editor Signed-off-by: krVatsal --- .../shapes/arrow_shape.rs | 102 ++++++++++++++++++ .../tool/common_functionality/shapes/mod.rs | 2 + .../shapes/shape_utility.rs | 5 + .../messages/tool/tool_messages/shape_tool.rs | 33 +++++- 4 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs diff --git a/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs b/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs new file mode 100644 index 0000000000..4d30698e81 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs @@ -0,0 +1,102 @@ +use super::shape_utility::ShapeToolModifierKey; +use super::*; +use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; +use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; +use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; +use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate; +use crate::messages::prelude::*; +use glam::DVec2; +use graphene_std::vector::{PointId, SegmentId, VectorModificationType}; +use std::collections::VecDeque; + +#[derive(Default)] +pub struct Arrow; + +impl Arrow { + pub fn create_node() -> NodeTemplate { + let node_type = resolve_document_node_type("Path").expect("Path node does not exist"); + node_type.default_node_template() + } + + pub fn update_shape( + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + layer: LayerNodeIdentifier, + tool_data: &mut ShapeToolData, + modifier: ShapeToolModifierKey, + responses: &mut VecDeque, + ) { + let [center, lock_ratio, _] = modifier; + + // Work in viewport space like Line does + let start_viewport = tool_data.data.viewport_drag_start(document); + let end_viewport = input.mouse.position; + + let delta = end_viewport - start_viewport; + let length = delta.length(); + if length < 1e-6 { + return; + } + + let direction = delta.normalize(); + let perpendicular = DVec2::new(-direction.y, direction.x); + + let shaft_thickness = length * 0.05; + let head_width = length * 0.15; + let head_length = length * 0.2; + + // Build arrow in viewport space + let viewport_anchors = vec![ + start_viewport, + start_viewport + direction * head_length - perpendicular * (head_width * 0.5), + start_viewport + direction * head_length - perpendicular * (shaft_thickness * 0.5), + end_viewport - perpendicular * (shaft_thickness * 0.5), + end_viewport + perpendicular * (shaft_thickness * 0.5), + start_viewport + direction * head_length + perpendicular * (shaft_thickness * 0.5), + start_viewport + direction * head_length + perpendicular * (head_width * 0.5), + ]; + + let vector = document.network_interface.compute_modified_vector(layer); + let existing_point_ids: Vec = vector.as_ref().map(|v| v.point_domain.ids().to_vec()).unwrap_or_default(); + let existing_segment_ids: Vec = vector.as_ref().map(|v| v.segment_domain.ids().to_vec()).unwrap_or_default(); + + for point_id in existing_point_ids { + responses.add(GraphOperationMessage::Vector { + layer, + modification_type: VectorModificationType::RemovePoint { id: point_id }, + }); + } + + for segment_id in existing_segment_ids { + responses.add(GraphOperationMessage::Vector { + layer, + modification_type: VectorModificationType::RemoveSegment { id: segment_id }, + }); + } + + let point_ids: Vec = viewport_anchors + .iter() + .map(|&pos| { + let id = PointId::generate(); + responses.add(GraphOperationMessage::Vector { + layer, + modification_type: VectorModificationType::InsertPoint { id, position: pos }, + }); + id + }) + .collect(); + + for i in 0..point_ids.len() { + let id = SegmentId::generate(); + let points = [point_ids[i], point_ids[(i + 1) % point_ids.len()]]; + responses.add(GraphOperationMessage::Vector { + layer, + modification_type: VectorModificationType::InsertSegment { id, points, handles: [None, None] }, + }); + } + + responses.add(NodeGraphMessage::RunDocumentGraph); + } + + pub fn overlays(_document: &DocumentMessageHandler, _tool_data: &ShapeToolData, _overlay_context: &mut OverlayContext) {} +} diff --git a/editor/src/messages/tool/common_functionality/shapes/mod.rs b/editor/src/messages/tool/common_functionality/shapes/mod.rs index 5031a6224e..b005f61a19 100644 --- a/editor/src/messages/tool/common_functionality/shapes/mod.rs +++ b/editor/src/messages/tool/common_functionality/shapes/mod.rs @@ -1,4 +1,5 @@ pub mod arc_shape; +pub mod arrow_shape; pub mod circle_shape; pub mod ellipse_shape; pub mod grid_shape; @@ -9,6 +10,7 @@ pub mod shape_utility; pub mod spiral_shape; pub mod star_shape; +pub use super::shapes::arrow_shape::Arrow; pub use super::shapes::ellipse_shape::Ellipse; pub use super::shapes::line_shape::{Line, LineEnd}; pub use super::shapes::rectangle_shape::Rectangle; diff --git a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs index fb7d913faa..07bcc9e211 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -33,6 +33,7 @@ pub enum ShapeType { Grid, Rectangle, Ellipse, + Arrow, Line, } @@ -47,6 +48,7 @@ impl ShapeType { Self::Spiral => "Spiral", Self::Rectangle => "Rectangle", Self::Ellipse => "Ellipse", + Self::Arrow => "Arrow", Self::Line => "Line", }) .into() @@ -57,6 +59,7 @@ impl ShapeType { Self::Line => "Line Tool", Self::Rectangle => "Rectangle Tool", Self::Ellipse => "Ellipse Tool", + Self::Arrow => "Arrow Tool", _ => "", }) .into() @@ -67,6 +70,7 @@ impl ShapeType { Self::Line => "VectorLineTool", Self::Rectangle => "VectorRectangleTool", Self::Ellipse => "VectorEllipseTool", + Self::Arrow => "VectorArrowTool", _ => "", }) .into() @@ -77,6 +81,7 @@ impl ShapeType { Self::Line => ToolType::Line, Self::Rectangle => ToolType::Rectangle, Self::Ellipse => ToolType::Ellipse, + Self::Arrow => ToolType::Shape, _ => ToolType::Shape, } } diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 76a123cb3a..e556525bb0 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -9,6 +9,7 @@ use crate::messages::tool::common_functionality::gizmos::gizmo_manager::GizmoMan use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::common_functionality::shapes::arc_shape::Arc; +use crate::messages::tool::common_functionality::shapes::arrow_shape::Arrow; use crate::messages::tool::common_functionality::shapes::circle_shape::Circle; use crate::messages::tool::common_functionality::shapes::grid_shape::Grid; use crate::messages::tool::common_functionality::shapes::line_shape::{LineToolData, clicked_on_line_endpoints}; @@ -168,6 +169,30 @@ fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder { } .into() }), + MenuListEntry::new("Rectangle").label("Rectangle").on_commit(move |_| { + ShapeToolMessage::UpdateOptions { + options: ShapeOptionsUpdate::ShapeType(ShapeType::Rectangle), + } + .into() + }), + MenuListEntry::new("Ellipse").label("Ellipse").on_commit(move |_| { + ShapeToolMessage::UpdateOptions { + options: ShapeOptionsUpdate::ShapeType(ShapeType::Ellipse), + } + .into() + }), + MenuListEntry::new("Arrow").label("Arrow").on_commit(move |_| { + ShapeToolMessage::UpdateOptions { + options: ShapeOptionsUpdate::ShapeType(ShapeType::Arrow), + } + .into() + }), + MenuListEntry::new("Line").label("Line").on_commit(move |_| { + ShapeToolMessage::UpdateOptions { + options: ShapeOptionsUpdate::ShapeType(ShapeType::Line), + } + .into() + }), ]]; DropdownInput::new(entries).selected_index(Some(shape_type as u32)).widget_holder() } @@ -805,7 +830,7 @@ impl Fsm for ShapeToolFsmState { }; match tool_data.current_shape { - ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Spiral | ShapeType::Grid | ShapeType::Rectangle | ShapeType::Ellipse => { + ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Spiral | ShapeType::Grid | ShapeType::Rectangle | ShapeType::Ellipse | ShapeType::Arrow => { tool_data.data.start(document, input) } ShapeType::Line => { @@ -822,6 +847,7 @@ impl Fsm for ShapeToolFsmState { ShapeType::Star => Star::create_node(tool_options.vertices), ShapeType::Circle => Circle::create_node(), ShapeType::Arc => Arc::create_node(tool_options.arc_type), + ShapeType::Arrow => Arrow::create_node(), ShapeType::Spiral => Spiral::create_node(tool_options.spiral_type, tool_options.turns), ShapeType::Grid => Grid::create_node(tool_options.grid_type), ShapeType::Rectangle => Rectangle::create_node(), @@ -835,7 +861,7 @@ impl Fsm for ShapeToolFsmState { let defered_responses = &mut VecDeque::new(); match tool_data.current_shape { - ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Spiral | ShapeType::Grid | ShapeType::Rectangle | ShapeType::Ellipse => { + ShapeType::Arrow | ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Spiral | ShapeType::Grid | ShapeType::Rectangle | ShapeType::Ellipse => { defered_responses.add(GraphOperationMessage::TransformSet { layer, transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), @@ -872,6 +898,7 @@ impl Fsm for ShapeToolFsmState { ShapeType::Star => Star::update_shape(document, input, layer, tool_data, modifier, responses), ShapeType::Circle => Circle::update_shape(document, input, layer, tool_data, modifier, responses), ShapeType::Arc => Arc::update_shape(document, input, layer, tool_data, modifier, responses), + ShapeType::Arrow => Arrow::update_shape(document, input, layer, tool_data, modifier, responses), ShapeType::Spiral => Spiral::update_shape(document, input, layer, tool_data, responses), ShapeType::Grid => Grid::update_shape(document, input, layer, tool_options.grid_type, tool_data, modifier, responses), ShapeType::Rectangle => Rectangle::update_shape(document, input, layer, tool_data, modifier, responses), @@ -1127,6 +1154,7 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque vec![HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Arrow")])], }; HintData(hint_groups) } @@ -1142,6 +1170,7 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Angle")]), ShapeType::Circle => HintGroup(vec![HintInfo::keys([Key::Alt], "From Center")]), ShapeType::Spiral => HintGroup(vec![]), }; From f3b1179524ce7fe2d498cf930e8625dff939ddd8 Mon Sep 17 00:00:00 2001 From: krVatsal Date: Mon, 10 Nov 2025 19:25:10 +0530 Subject: [PATCH 2/4] fix the arrow tool to show arrow in viewport space Signed-off-by: krVatsal --- .../common_functionality/graph_modification_utils.rs | 4 ++++ .../tool/common_functionality/shapes/arrow_shape.rs | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs index 05bbaf1a39..56e548d3cb 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -363,6 +363,10 @@ pub fn get_arc_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInt NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Arc") } +pub fn get_arrow_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { + NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Arrow") +} + pub fn get_spiral_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Spiral") } diff --git a/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs b/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs index 4d30698e81..ee48e6f7f5 100644 --- a/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs @@ -56,6 +56,12 @@ impl Arrow { start_viewport + direction * head_length + perpendicular * (head_width * 0.5), ]; + // Get the layer's transform to convert viewport coordinates to layer-local coordinates + let viewport_to_layer = document.metadata().transform_to_viewport(layer).inverse(); + + // Convert viewport coordinates to layer-local coordinates + let local_anchors: Vec = viewport_anchors.iter().map(|&viewport_pos| viewport_to_layer.transform_point2(viewport_pos)).collect(); + let vector = document.network_interface.compute_modified_vector(layer); let existing_point_ids: Vec = vector.as_ref().map(|v| v.point_domain.ids().to_vec()).unwrap_or_default(); let existing_segment_ids: Vec = vector.as_ref().map(|v| v.segment_domain.ids().to_vec()).unwrap_or_default(); @@ -74,7 +80,8 @@ impl Arrow { }); } - let point_ids: Vec = viewport_anchors + // Insert points in layer-local coordinates + let point_ids: Vec = local_anchors .iter() .map(|&pos| { let id = PointId::generate(); From 7316f2e23282c5f5ffa50894dfb3536a0c661242 Mon Sep 17 00:00:00 2001 From: krVatsal Date: Tue, 11 Nov 2025 10:51:57 +0530 Subject: [PATCH 3/4] fix the direction of arrow and make the new arrow node Signed-off-by: krVatsal --- .../shapes/arrow_shape.rs | 121 ++++++++---------- node-graph/gcore/src/subpath/core.rs | 19 +++ .../gcore/src/vector/generator_nodes.rs | 20 +++ 3 files changed, 89 insertions(+), 71 deletions(-) diff --git a/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs b/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs index ee48e6f7f5..8fcb982fb7 100644 --- a/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs @@ -1,12 +1,15 @@ use super::shape_utility::ShapeToolModifierKey; use super::*; +use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate; +use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate}; use crate::messages::prelude::*; -use glam::DVec2; -use graphene_std::vector::{PointId, SegmentId, VectorModificationType}; +use crate::messages::tool::common_functionality::graph_modification_utils; +use glam::{DAffine2, DVec2}; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; use std::collections::VecDeque; #[derive(Default)] @@ -14,8 +17,14 @@ pub struct Arrow; impl Arrow { pub fn create_node() -> NodeTemplate { - let node_type = resolve_document_node_type("Path").expect("Path node does not exist"); - node_type.default_node_template() + let node_type = resolve_document_node_type("Arrow").expect("Arrow node does not exist"); + node_type.node_template_input_override([ + None, + Some(NodeInput::value(TaggedValue::F64(100.), false)), // length + Some(NodeInput::value(TaggedValue::F64(10.), false)), // shaft_width + Some(NodeInput::value(TaggedValue::F64(30.), false)), // head_width + Some(NodeInput::value(TaggedValue::F64(20.), false)), // head_length + ]) } pub fn update_shape( @@ -28,81 +37,51 @@ impl Arrow { ) { let [center, lock_ratio, _] = modifier; - // Work in viewport space like Line does - let start_viewport = tool_data.data.viewport_drag_start(document); - let end_viewport = input.mouse.position; + let Some([start, end]) = tool_data.data.calculate_points(document, input, center, lock_ratio) else { + return; + }; - let delta = end_viewport - start_viewport; + let delta = end - start; let length = delta.length(); if length < 1e-6 { return; } - let direction = delta.normalize(); - let perpendicular = DVec2::new(-direction.y, direction.x); + let Some(node_id) = graph_modification_utils::get_arrow_id(layer, &document.network_interface) else { + return; + }; - let shaft_thickness = length * 0.05; - let head_width = length * 0.15; + // Calculate proportional dimensions + let shaft_width = length * 0.1; + let head_width = length * 0.3; let head_length = length * 0.2; - // Build arrow in viewport space - let viewport_anchors = vec![ - start_viewport, - start_viewport + direction * head_length - perpendicular * (head_width * 0.5), - start_viewport + direction * head_length - perpendicular * (shaft_thickness * 0.5), - end_viewport - perpendicular * (shaft_thickness * 0.5), - end_viewport + perpendicular * (shaft_thickness * 0.5), - start_viewport + direction * head_length + perpendicular * (shaft_thickness * 0.5), - start_viewport + direction * head_length + perpendicular * (head_width * 0.5), - ]; - - // Get the layer's transform to convert viewport coordinates to layer-local coordinates - let viewport_to_layer = document.metadata().transform_to_viewport(layer).inverse(); - - // Convert viewport coordinates to layer-local coordinates - let local_anchors: Vec = viewport_anchors.iter().map(|&viewport_pos| viewport_to_layer.transform_point2(viewport_pos)).collect(); - - let vector = document.network_interface.compute_modified_vector(layer); - let existing_point_ids: Vec = vector.as_ref().map(|v| v.point_domain.ids().to_vec()).unwrap_or_default(); - let existing_segment_ids: Vec = vector.as_ref().map(|v| v.segment_domain.ids().to_vec()).unwrap_or_default(); - - for point_id in existing_point_ids { - responses.add(GraphOperationMessage::Vector { - layer, - modification_type: VectorModificationType::RemovePoint { id: point_id }, - }); - } - - for segment_id in existing_segment_ids { - responses.add(GraphOperationMessage::Vector { - layer, - modification_type: VectorModificationType::RemoveSegment { id: segment_id }, - }); - } - - // Insert points in layer-local coordinates - let point_ids: Vec = local_anchors - .iter() - .map(|&pos| { - let id = PointId::generate(); - responses.add(GraphOperationMessage::Vector { - layer, - modification_type: VectorModificationType::InsertPoint { id, position: pos }, - }); - id - }) - .collect(); - - for i in 0..point_ids.len() { - let id = SegmentId::generate(); - let points = [point_ids[i], point_ids[(i + 1) % point_ids.len()]]; - responses.add(GraphOperationMessage::Vector { - layer, - modification_type: VectorModificationType::InsertSegment { id, points, handles: [None, None] }, - }); - } - - responses.add(NodeGraphMessage::RunDocumentGraph); + // Update Arrow node parameters + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 1), + input: NodeInput::value(TaggedValue::F64(length), false), + }); + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 2), + input: NodeInput::value(TaggedValue::F64(shaft_width), false), + }); + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 3), + input: NodeInput::value(TaggedValue::F64(head_width), false), + }); + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 4), + input: NodeInput::value(TaggedValue::F64(head_length), false), + }); + + // Set transform to position and rotate the arrow + let angle = delta.y.atan2(delta.x); + responses.add(GraphOperationMessage::TransformSet { + layer, + transform: DAffine2::from_scale_angle_translation(DVec2::ONE, angle, start), + transform_in: TransformIn::Viewport, + skip_rerender: false, + }); } pub fn overlays(_document: &DocumentMessageHandler, _tool_data: &ShapeToolData, _overlay_context: &mut OverlayContext) {} diff --git a/node-graph/gcore/src/subpath/core.rs b/node-graph/gcore/src/subpath/core.rs index a8329d99cf..2b41d4e057 100644 --- a/node-graph/gcore/src/subpath/core.rs +++ b/node-graph/gcore/src/subpath/core.rs @@ -317,6 +317,25 @@ impl Subpath { Self::from_anchors([p1, p2], false) } + /// Constructs an arrow shape with parametric control over dimensions + pub fn new_arrow(length: f64, shaft_width: f64, head_width: f64, head_length: f64) -> Self { + let half_shaft = shaft_width * 0.5; + let half_head = head_width * 0.5; + let head_base = length - head_length; + + let anchors = [ + DVec2::new(0., -half_shaft), // Tail bottom + DVec2::new(head_base, -half_shaft), // Head base bottom (shaft) + DVec2::new(head_base, -half_head), // Head base bottom (wide) + DVec2::new(length, 0.), // Tip + DVec2::new(head_base, half_head), // Head base top (wide) + DVec2::new(head_base, half_shaft), // Head base top (shaft) + DVec2::new(0., half_shaft), // Tail top + ]; + + Self::from_anchors(anchors, true) + } + pub fn new_spiral(a: f64, outer_radius: f64, turns: f64, start_angle: f64, delta_theta: f64, spiral_type: SpiralType) -> Self { let mut manipulator_groups = Vec::new(); let mut prev_in_handle = None; diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index 05ad236a50..4bd63366f6 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -179,6 +179,26 @@ fn star( Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter))) } +#[node_macro::node(category("Vector: Shape"))] +fn arrow( + _: impl Ctx, + _primary: (), + #[unit(" px")] + #[default(100)] + length: f64, + #[unit(" px")] + #[default(10)] + shaft_width: f64, + #[unit(" px")] + #[default(30)] + head_width: f64, + #[unit(" px")] + #[default(20)] + head_length: f64, +) -> Table { + Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_arrow(length, shaft_width, head_width, head_length))) +} + #[node_macro::node(category("Vector: Shape"))] fn line(_: impl Ctx, _primary: (), #[default(0., 0.)] start: PixelSize, #[default(100., 100.)] end: PixelSize) -> Table { Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_line(start, end))) From b21a73dd7fc98fd759a5c85a1a94d5f43877dc75 Mon Sep 17 00:00:00 2001 From: krVatsal Date: Wed, 19 Nov 2025 10:50:44 +0530 Subject: [PATCH 4/4] updated arrow tool to hae start and end points Signed-off-by: krVatsal --- .../shapes/arrow_shape.rs | 30 ++++++++--------- node-graph/gcore/src/subpath/core.rs | 32 +++++++++++++------ .../gcore/src/vector/generator_nodes.rs | 7 ++-- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs b/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs index 8fcb982fb7..b2e5589599 100644 --- a/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs @@ -20,10 +20,11 @@ impl Arrow { let node_type = resolve_document_node_type("Arrow").expect("Arrow node does not exist"); node_type.node_template_input_override([ None, - Some(NodeInput::value(TaggedValue::F64(100.), false)), // length - Some(NodeInput::value(TaggedValue::F64(10.), false)), // shaft_width - Some(NodeInput::value(TaggedValue::F64(30.), false)), // head_width - Some(NodeInput::value(TaggedValue::F64(20.), false)), // head_length + Some(NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false)), // start + Some(NodeInput::value(TaggedValue::DVec2(DVec2::new(100., 0.)), false)), // end + Some(NodeInput::value(TaggedValue::F64(10.), false)), // shaft_width + Some(NodeInput::value(TaggedValue::F64(30.), false)), // head_width + Some(NodeInput::value(TaggedValue::F64(20.), false)), // head_length ]) } @@ -56,31 +57,26 @@ impl Arrow { let head_width = length * 0.3; let head_length = length * 0.2; - // Update Arrow node parameters + // Update Arrow node parameters - now using start/end points instead of transform responses.add(NodeGraphMessage::SetInput { input_connector: InputConnector::node(node_id, 1), - input: NodeInput::value(TaggedValue::F64(length), false), + input: NodeInput::value(TaggedValue::DVec2(start), false), }); responses.add(NodeGraphMessage::SetInput { input_connector: InputConnector::node(node_id, 2), - input: NodeInput::value(TaggedValue::F64(shaft_width), false), + input: NodeInput::value(TaggedValue::DVec2(end), false), }); responses.add(NodeGraphMessage::SetInput { input_connector: InputConnector::node(node_id, 3), - input: NodeInput::value(TaggedValue::F64(head_width), false), + input: NodeInput::value(TaggedValue::F64(shaft_width), false), }); responses.add(NodeGraphMessage::SetInput { input_connector: InputConnector::node(node_id, 4), - input: NodeInput::value(TaggedValue::F64(head_length), false), + input: NodeInput::value(TaggedValue::F64(head_width), false), }); - - // Set transform to position and rotate the arrow - let angle = delta.y.atan2(delta.x); - responses.add(GraphOperationMessage::TransformSet { - layer, - transform: DAffine2::from_scale_angle_translation(DVec2::ONE, angle, start), - transform_in: TransformIn::Viewport, - skip_rerender: false, + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 5), + input: NodeInput::value(TaggedValue::F64(head_length), false), }); } diff --git a/node-graph/gcore/src/subpath/core.rs b/node-graph/gcore/src/subpath/core.rs index 2b41d4e057..eec78d8cde 100644 --- a/node-graph/gcore/src/subpath/core.rs +++ b/node-graph/gcore/src/subpath/core.rs @@ -317,20 +317,32 @@ impl Subpath { Self::from_anchors([p1, p2], false) } - /// Constructs an arrow shape with parametric control over dimensions - pub fn new_arrow(length: f64, shaft_width: f64, head_width: f64, head_length: f64) -> Self { + /// Constructs an arrow shape from start and end points with parametric control over dimensions + pub fn new_arrow(start: DVec2, end: DVec2, shaft_width: f64, head_width: f64, head_length: f64) -> Self { + let delta = end - start; + let length = delta.length(); + + if length < 1e-10 { + // Degenerate case: return a point + return Self::from_anchors([start], true); + } + + let direction = delta / length; + let perpendicular = DVec2::new(-direction.y, direction.x); + let half_shaft = shaft_width * 0.5; let half_head = head_width * 0.5; - let head_base = length - head_length; + let head_base_distance = (length - head_length).max(0.); + let head_base = start + direction * head_base_distance; let anchors = [ - DVec2::new(0., -half_shaft), // Tail bottom - DVec2::new(head_base, -half_shaft), // Head base bottom (shaft) - DVec2::new(head_base, -half_head), // Head base bottom (wide) - DVec2::new(length, 0.), // Tip - DVec2::new(head_base, half_head), // Head base top (wide) - DVec2::new(head_base, half_shaft), // Head base top (shaft) - DVec2::new(0., half_shaft), // Tail top + start - perpendicular * half_shaft, // Tail bottom + head_base - perpendicular * half_shaft, // Head base bottom (shaft) + head_base - perpendicular * half_head, // Head base bottom (wide) + end, // Tip + head_base + perpendicular * half_head, // Head base top (wide) + head_base + perpendicular * half_shaft, // Head base top (shaft) + start + perpendicular * half_shaft, // Tail top ]; Self::from_anchors(anchors, true) diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index 4bd63366f6..43d1e94896 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -183,9 +183,8 @@ fn star( fn arrow( _: impl Ctx, _primary: (), - #[unit(" px")] - #[default(100)] - length: f64, + #[default(0., 0.)] start: PixelSize, + #[default(100., 0.)] end: PixelSize, #[unit(" px")] #[default(10)] shaft_width: f64, @@ -196,7 +195,7 @@ fn arrow( #[default(20)] head_length: f64, ) -> Table { - Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_arrow(length, shaft_width, head_width, head_length))) + Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_arrow(start, end, shaft_width, head_width, head_length))) } #[node_macro::node(category("Vector: Shape"))]