Improve the Shape tool's arrow drawing controls (#3650)

* fixed the arrow's parameters

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

* shifted the arrow's origin to its tail

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

* modified arrow shapetype fucntion to be like other shapes

* fixed rust formatting

* Remove misleading part of comment referencing the origin

---------

Signed-off-by: krVatsal <kumarvatsal34@gmail.com>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Vatsal Kumar 2026-02-16 03:05:20 +05:30 committed by GitHub
parent 87739ff877
commit 82f7dc7062
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 92 additions and 29 deletions

View File

@ -15,16 +15,16 @@ use std::collections::VecDeque;
pub struct Arrow;
impl Arrow {
pub fn create_node(document: &DocumentMessageHandler, drag_start: DVec2) -> NodeTemplate {
pub fn create_node(document: &DocumentMessageHandler, drag_start: DVec2, shaft_width: f64, head_width: f64, head_length: f64) -> NodeTemplate {
let node_type = resolve_proto_node_type(graphene_std::vector_nodes::arrow::IDENTIFIER).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
Some(NodeInput::value(TaggedValue::F64(shaft_width), false)), // shaft_width
Some(NodeInput::value(TaggedValue::F64(head_width), false)), // head_width
Some(NodeInput::value(TaggedValue::F64(head_length), false)), // head_length
])
}
@ -56,12 +56,7 @@ impl Arrow {
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)
// Update Arrow node start and end points with document space coordinates
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 1),
input: NodeInput::value(TaggedValue::DVec2(start_document), false),
@ -70,18 +65,6 @@ impl Arrow {
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);
}

View File

@ -44,6 +44,9 @@ pub struct ShapeToolOptions {
grid_type: GridType,
spiral_type: SpiralType,
turns: f64,
arrow_shaft_width: f64,
arrow_head_width: f64,
arrow_head_length: f64,
}
impl Default for ShapeToolOptions {
@ -58,6 +61,9 @@ impl Default for ShapeToolOptions {
spiral_type: SpiralType::Archimedean,
turns: 5.,
grid_type: GridType::Rectangular,
arrow_shaft_width: 14.,
arrow_head_width: 32.,
arrow_head_length: 28.,
}
}
}
@ -76,6 +82,9 @@ pub enum ShapeOptionsUpdate {
SpiralType(SpiralType),
Turns(f64),
GridType(GridType),
ArrowShaftWidth(f64),
ArrowHeadWidth(f64),
ArrowHeadLength(f64),
}
#[impl_message(Message, ToolMessage, Shape)]
@ -236,6 +245,51 @@ fn create_weight_widget(line_weight: f64) -> WidgetInstance {
.widget_instance()
}
fn create_arrow_shaft_width_widget(shaft_width: f64) -> WidgetInstance {
NumberInput::new(Some(shaft_width))
.unit(" px")
.label("Shaft")
.min(0.1)
.max(1000.)
.on_update(|number_input: &NumberInput| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ArrowShaftWidth(number_input.value.unwrap()),
}
.into()
})
.widget_instance()
}
fn create_arrow_head_width_widget(head_width: f64) -> WidgetInstance {
NumberInput::new(Some(head_width))
.unit(" px")
.label("Head W")
.min(0.1)
.max(1000.)
.on_update(|number_input: &NumberInput| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ArrowHeadWidth(number_input.value.unwrap()),
}
.into()
})
.widget_instance()
}
fn create_arrow_head_length_widget(head_length: f64) -> WidgetInstance {
NumberInput::new(Some(head_length))
.unit(" px")
.label("Head L")
.min(0.1)
.max(1000.)
.on_update(|number_input: &NumberInput| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ArrowHeadLength(number_input.value.unwrap()),
}
.into()
})
.widget_instance()
}
fn create_spiral_type_widget(spiral_type: SpiralType) -> WidgetInstance {
let entries = vec![vec![
MenuListEntry::new("Archimedean").label("Archimedean").on_commit(move |_| {
@ -304,6 +358,15 @@ impl LayoutHolder for ShapeTool {
widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
}
if self.options.shape_type == ShapeType::Arrow {
widgets.push(create_arrow_shaft_width_widget(self.options.arrow_shaft_width));
widgets.push(Separator::new(SeparatorStyle::Related).widget_instance());
widgets.push(create_arrow_head_width_widget(self.options.arrow_head_width));
widgets.push(Separator::new(SeparatorStyle::Related).widget_instance());
widgets.push(create_arrow_head_length_widget(self.options.arrow_head_length));
widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
}
if self.options.shape_type != ShapeType::Line {
widgets.append(&mut self.options.fill.create_widgets(
"Fill",
@ -414,6 +477,15 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Shap
ShapeOptionsUpdate::GridType(grid_type) => {
self.options.grid_type = grid_type;
}
ShapeOptionsUpdate::ArrowShaftWidth(shaft_width) => {
self.options.arrow_shaft_width = shaft_width;
}
ShapeOptionsUpdate::ArrowHeadWidth(head_width) => {
self.options.arrow_head_width = head_width;
}
ShapeOptionsUpdate::ArrowHeadLength(head_length) => {
self.options.arrow_head_length = head_length;
}
}
update_dynamic_hints(&self.fsm_state, responses, &self.tool_data);
@ -854,7 +926,13 @@ 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::Arrow => Arrow::create_node(
document,
tool_data.data.drag_start,
tool_options.arrow_shaft_width,
tool_options.arrow_head_width,
tool_options.arrow_head_length,
),
ShapeType::Line => Line::create_node(document, tool_data.data.drag_start),
};

View File

@ -330,14 +330,16 @@ impl<PointId: Identifier> Subpath<PointId> {
let head_base_distance = (length - head_length).max(0.);
let head_base = start + direction * head_base_distance;
// Arrow path starts at the tail, traces around the shape, and returns to the tail
let anchors = [
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, // Tail center (origin)
start + perpendicular * half_shaft, // Tail top
head_base + perpendicular * half_shaft, // Head base top (shaft)
head_base + perpendicular * half_head, // Head base top (wide)
end, // Tip
head_base - perpendicular * half_head, // Head base bottom (wide)
head_base - perpendicular * half_shaft, // Head base bottom (shaft)
start - perpendicular * half_shaft, // Tail bottom
];
Self::from_anchors(anchors, true)