Add an arrow to the Shape tool (#3343)

* add arrow shape feature in editor

Signed-off-by: krVatsal <kumarvatsal34@gmail.com>

* fix the arrow tool to show arrow in viewport space

Signed-off-by: krVatsal <kumarvatsal34@gmail.com>

* fix the direction of arrow and make the new arrow node

Signed-off-by: krVatsal <kumarvatsal34@gmail.com>

* updated arrow tool to hae start and end points

Signed-off-by: krVatsal <kumarvatsal34@gmail.com>

* fixed calculate point bug

Signed-off-by: krVatsal <kumarvatsal34@gmail.com>

* fixed some bugs of arrow positioning

Signed-off-by: krVatsal <kumarvatsal34@gmail.com>

* fixed formatting in whole codebase and added fill to arrow

Signed-off-by: krVatsal <kumarvatsal34@gmail.com>

* fix

---------

Signed-off-by: krVatsal <kumarvatsal34@gmail.com>
Co-authored-by: Timon <me@timon.zip>
This commit is contained in:
Vatsal Kumar
2026-01-12 00:58:28 +00:00
committed by GitHub
co-authored by Timon
parent 479688d86b
commit 4fea2b0fe7
7 changed files with 186 additions and 8 deletions
@@ -364,6 +364,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<NodeId> {
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Arrow")
}
pub fn get_spiral_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Spiral")
}
@@ -0,0 +1,90 @@
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::{InputConnector, NodeTemplate};
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils;
use glam::DVec2;
use graph_craft::document::NodeInput;
use graph_craft::document::value::TaggedValue;
use std::collections::VecDeque;
#[derive(Default)]
pub struct Arrow;
impl Arrow {
pub fn create_node(document: &DocumentMessageHandler, drag_start: DVec2) -> NodeTemplate {
let node_type = resolve_document_node_type("Arrow").expect("Arrow node does not exist");
let viewport_pos = document.metadata().document_to_viewport.transform_point2(drag_start);
node_type.node_template_input_override([
None,
Some(NodeInput::value(TaggedValue::DVec2(viewport_pos), false)), // start
Some(NodeInput::value(TaggedValue::DVec2(viewport_pos), 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
])
}
pub fn update_shape(
document: &DocumentMessageHandler,
input: &InputPreprocessorMessageHandler,
_viewport: &ViewportMessageHandler,
layer: LayerNodeIdentifier,
tool_data: &mut ShapeToolData,
_modifier: ShapeToolModifierKey,
responses: &mut VecDeque<Message>,
) {
// Track current mouse position in viewport space
tool_data.line_data.drag_current = input.mouse.position;
// Convert both points to document space (matching Line tool pattern)
let document_to_viewport = document.metadata().document_to_viewport;
let start_document = tool_data.data.drag_start;
let end_document = document_to_viewport.inverse().transform_point2(tool_data.line_data.drag_current);
// Calculate length in document space for validation
let delta = end_document - start_document;
let length_document = delta.length();
if length_document < 1e-6 {
return;
}
let Some(node_id) = graph_modification_utils::get_arrow_id(layer, &document.network_interface) else {
return;
};
// Calculate proportional dimensions based on arrow length
let shaft_width = length_document * 0.1;
let head_width = length_document * 0.3;
let head_length = length_document * 0.2;
// Update Arrow node parameters with document space coordinates (like Line tool)
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 1),
input: NodeInput::value(TaggedValue::DVec2(start_document), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 2),
input: NodeInput::value(TaggedValue::DVec2(end_document), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 3),
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_width), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 5),
input: NodeInput::value(TaggedValue::F64(head_length), false),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
}
pub fn overlays(_document: &DocumentMessageHandler, _tool_data: &ShapeToolData, _overlay_context: &mut OverlayContext) {}
}
@@ -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;
@@ -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()
@@ -75,6 +78,7 @@ impl ShapeType {
Self::Line => "VectorLineTool",
Self::Rectangle => "VectorRectangleTool",
Self::Ellipse => "VectorEllipseTool",
Self::Arrow => "VectorArrowTool",
_ => "",
})
.into()
@@ -85,6 +89,7 @@ impl ShapeType {
Self::Line => ToolType::Line,
Self::Rectangle => ToolType::Rectangle,
Self::Ellipse => ToolType::Ellipse,
Self::Arrow => ToolType::Shape,
_ => ToolType::Shape,
}
}
@@ -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) -> WidgetInstance {
}
.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_instance()
}
@@ -807,7 +832,7 @@ impl Fsm for ShapeToolFsmState {
ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Spiral | ShapeType::Grid | ShapeType::Rectangle | ShapeType::Ellipse => {
tool_data.data.start(document, input, viewport);
}
ShapeType::Line => {
ShapeType::Arrow | ShapeType::Line => {
let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
let snapped = tool_data
.data
@@ -828,6 +853,7 @@ impl Fsm for ShapeToolFsmState {
ShapeType::Grid => Grid::create_node(tool_options.grid_type),
ShapeType::Rectangle => Rectangle::create_node(),
ShapeType::Ellipse => Ellipse::create_node(),
ShapeType::Arrow => Arrow::create_node(document, tool_data.data.drag_start),
ShapeType::Line => Line::create_node(document, tool_data.data.drag_start),
};
@@ -847,6 +873,11 @@ impl Fsm for ShapeToolFsmState {
tool_options.fill.apply_fill(layer, defered_responses);
}
ShapeType::Arrow => {
tool_data.line_data.weight = tool_options.line_weight;
tool_data.line_data.editing_layer = Some(layer);
tool_options.fill.apply_fill(layer, defered_responses);
}
ShapeType::Line => {
tool_data.line_data.weight = tool_options.line_weight;
tool_data.line_data.editing_layer = Some(layer);
@@ -874,6 +905,7 @@ impl Fsm for ShapeToolFsmState {
ShapeType::Star => Star::update_shape(document, input, viewport, layer, tool_data, modifier, responses),
ShapeType::Circle => Circle::update_shape(document, input, viewport, layer, tool_data, modifier, responses),
ShapeType::Arc => Arc::update_shape(document, input, viewport, layer, tool_data, modifier, responses),
ShapeType::Arrow => Arrow::update_shape(document, input, viewport, layer, tool_data, modifier, responses),
ShapeType::Spiral => Spiral::update_shape(document, input, viewport, 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, viewport, layer, tool_data, modifier, responses),
@@ -1018,6 +1050,7 @@ impl Fsm for ShapeToolFsmState {
}
tool_data.line_data.dragging_endpoint = None;
tool_data.line_data.editing_layer = None;
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair });
@@ -1035,6 +1068,7 @@ impl Fsm for ShapeToolFsmState {
responses.add(DocumentMessage::AbortTransaction);
tool_data.data.cleanup(responses);
tool_data.line_data.dragging_endpoint = None;
tool_data.line_data.editing_layer = None;
tool_data.gizmo_manager.handle_cleanup();
@@ -1130,6 +1164,7 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque<Mess
HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(),
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
])],
ShapeType::Arrow => vec![HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Arrow")])],
};
HintData(hint_groups)
}
@@ -1145,6 +1180,7 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque<Mess
HintInfo::keys([Key::Alt], "From Center"),
HintInfo::keys([Key::Control], "Lock Angle"),
]),
ShapeType::Arrow => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Angle")]),
ShapeType::Circle => HintGroup(vec![HintInfo::keys([Key::Alt], "From Center")]),
ShapeType::Spiral => HintGroup(vec![]),
};