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:
parent
479688d86b
commit
4fea2b0fe7
|
|
@ -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![]),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -312,6 +312,37 @@ impl<PointId: Identifier> Subpath<PointId> {
|
|||
Self::from_anchors([p1, p2], false)
|
||||
}
|
||||
|
||||
/// 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_distance = (length - head_length).max(0.);
|
||||
let head_base = start + direction * head_base_distance;
|
||||
|
||||
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 + perpendicular * 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;
|
||||
|
|
|
|||
|
|
@ -188,16 +188,26 @@ fn star<T: AsU64>(
|
|||
|
||||
/// Generates a line with endpoints at the two chosen coordinates.
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn line(
|
||||
fn arrow(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
/// Coordinate of the line's initial endpoint.
|
||||
#[default(0., 0.)]
|
||||
start: PixelSize,
|
||||
/// Coordinate of the line's terminal endpoint.
|
||||
#[default(100., 100.)]
|
||||
end: PixelSize,
|
||||
#[default(0., 0.)] start: PixelSize,
|
||||
#[default(100., 0.)] end: PixelSize,
|
||||
#[unit(" px")]
|
||||
#[default(10)]
|
||||
shaft_width: f64,
|
||||
#[unit(" px")]
|
||||
#[default(30)]
|
||||
head_width: f64,
|
||||
#[unit(" px")]
|
||||
#[default(20)]
|
||||
head_length: f64,
|
||||
) -> Table<Vector> {
|
||||
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"))]
|
||||
fn line(_: impl Ctx, _primary: (), #[default(0., 0.)] start: PixelSize, #[default(100., 100.)] end: PixelSize) -> Table<Vector> {
|
||||
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_line(start, end)))
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue