-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add new Teardrop shape #4466
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add new Teardrop shape #4466
Changes from all commits
06da3db
9b9463b
64e9446
c949ee0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| use super::shape_utility::ShapeToolModifierKey; | ||
| use super::*; | ||
| use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_proto_node_type; | ||
| use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; | ||
| use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate}; | ||
| use crate::messages::tool::common_functionality::graph_modification_utils; | ||
| use crate::messages::tool::tool_messages::tool_prelude::*; | ||
| use graph_craft::document::NodeInput; | ||
| use graph_craft::document::value::TaggedValue; | ||
| use std::collections::VecDeque; | ||
|
|
||
| #[derive(Default)] | ||
| pub struct Teardrop; | ||
|
|
||
| impl Teardrop { | ||
| pub fn create_node() -> NodeTemplate { | ||
| let node_type = resolve_proto_node_type(graphene_std::vector::generator_nodes::teardrop::IDENTIFIER).expect("Teardrop node can't be found"); | ||
| node_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(0.5), false)), Some(NodeInput::value(TaggedValue::F64(0.5), false))]) | ||
| } | ||
|
|
||
| pub fn update_shape( | ||
| document: &DocumentMessageHandler, | ||
| ipp: &InputPreprocessorMessageHandler, | ||
| viewport: &ViewportMessageHandler, | ||
| layer: LayerNodeIdentifier, | ||
| shape_tool_data: &mut ShapeToolData, | ||
| modifier: ShapeToolModifierKey, | ||
| responses: &mut VecDeque<Message>, | ||
| ) { | ||
| let [center, lock_ratio, _] = modifier; | ||
|
|
||
| if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, viewport, center, lock_ratio) { | ||
| let Some(node_id) = graph_modification_utils::get_teardrop_id(layer, &document.network_interface) else { | ||
| return; | ||
| }; | ||
|
|
||
| let radius = ((start - end) / 2. / viewport_zoom(document)).abs(); | ||
|
|
||
| responses.add(NodeGraphMessage::SetInput { | ||
| input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::teardrop::WidthInput), | ||
| input: NodeInput::value(TaggedValue::F64(radius.x), false), | ||
| }); | ||
| responses.add(NodeGraphMessage::SetInput { | ||
| input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::teardrop::HeightInput), | ||
| input: NodeInput::value(TaggedValue::F64(radius.y), false), | ||
| }); | ||
| responses.add(window_aligned_transform_set(document, layer, start.midpoint(end), DVec2::ONE)); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ use crate::messages::tool::common_functionality::shapes::polygon_shape::Polygon; | |
| use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, clicked_on_shape_endpoints, transform_cage_overlays}; | ||
| use crate::messages::tool::common_functionality::shapes::spiral_shape::Spiral; | ||
| use crate::messages::tool::common_functionality::shapes::star_shape::Star; | ||
| use crate::messages::tool::common_functionality::shapes::teardrop_shape::Teardrop; | ||
| use crate::messages::tool::common_functionality::shapes::{Ellipse, Line, Rectangle}; | ||
| use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapTypeConfiguration}; | ||
| use crate::messages::tool::common_functionality::stroke_options::{StrokeOptionsUpdate, apply_stroke_option, create_stroke_options_popover_widget}; | ||
|
|
@@ -200,6 +201,12 @@ fn create_shape_option_widget(shape_type: ShapeType) -> WidgetInstance { | |
| } | ||
| .into() | ||
| }), | ||
| MenuListEntry::new("Teardrop").label("Teardrop").on_commit(move |_| { | ||
| ShapeToolMessage::UpdateOptions { | ||
| options: ShapeOptionsUpdate::ShapeType(ShapeType::Teardrop), | ||
| } | ||
| .into() | ||
| }), | ||
| MenuListEntry::new("Grid").label("Grid").on_commit(move |_| { | ||
| ShapeToolMessage::UpdateOptions { | ||
| options: ShapeOptionsUpdate::ShapeType(ShapeType::Grid), | ||
|
|
@@ -323,6 +330,7 @@ fn sync_shape_options_from_selection(options: &mut ShapeToolOptions, tool_data: | |
| (circle::IDENTIFIER, ShapeType::Circle), | ||
| (arc::IDENTIFIER, ShapeType::Arc), | ||
| (spiral::IDENTIFIER, ShapeType::Spiral), | ||
| (teardrop::IDENTIFIER, ShapeType::Teardrop), | ||
| (grid::IDENTIFIER, ShapeType::Grid), | ||
| (arrow::IDENTIFIER, ShapeType::Arrow), | ||
| ] | ||
|
|
@@ -340,7 +348,7 @@ fn sync_shape_options_from_selection(options: &mut ShapeToolOptions, tool_data: | |
| } | ||
|
|
||
| // Only the shapes whose control bar exposes per-shape parameters need a sync below. | ||
| // The rest (Ellipse, Rectangle, Line) just keep `shape_type` in step and rely on the shared Stroke/Fill controls. | ||
| // The rest (Teardrop, Ellipse, Rectangle, Line) just keep `shape_type` in step and rely on the shared Stroke/Fill controls. | ||
| match shape_type { | ||
| ShapeType::Polygon | ShapeType::Star => { | ||
| // Both `regular_polygon` and `star` are generic over `T: AsU64`, but the control bar widget always writes `u32`, | ||
|
|
@@ -407,7 +415,7 @@ fn sync_shape_options_from_selection(options: &mut ShapeToolOptions, tool_data: | |
| changed = true; | ||
| } | ||
| } | ||
| ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Line | ShapeType::Circle => {} | ||
| ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Line | ShapeType::Circle | ShapeType::Teardrop => {} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: The Shape tool does not expose the teardrop's Prompt for AI agents |
||
| } | ||
|
|
||
| changed | ||
|
|
@@ -1088,7 +1096,15 @@ 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::Teardrop | ||
| | ShapeType::Grid | ||
| | ShapeType::Rectangle | ||
| | ShapeType::Ellipse => { | ||
| tool_data.data.start(document, input, viewport); | ||
| } | ||
| ShapeType::Arrow | ShapeType::Line => { | ||
|
|
@@ -1109,6 +1125,7 @@ impl Fsm for ShapeToolFsmState { | |
| ShapeType::Circle => Circle::create_node(), | ||
| ShapeType::Arc => Arc::create_node(tool_options.arc_type), | ||
| ShapeType::Spiral => Spiral::create_node(tool_options.spiral_type, tool_options.turns), | ||
| ShapeType::Teardrop => Teardrop::create_node(), | ||
| ShapeType::Grid => Grid::create_node(tool_options.grid_type), | ||
| ShapeType::Arrow => Arrow::create_node(tool_options.arrow_shaft_width, tool_options.arrow_head_width, tool_options.arrow_head_length), | ||
| ShapeType::Line => Line::create_node(), | ||
|
|
@@ -1122,7 +1139,15 @@ 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::Polygon | ||
| | ShapeType::Star | ||
| | ShapeType::Circle | ||
| | ShapeType::Arc | ||
| | ShapeType::Spiral | ||
| | ShapeType::Teardrop | ||
| | ShapeType::Grid | ||
| | ShapeType::Rectangle | ||
| | ShapeType::Ellipse => { | ||
| defered_responses.add(GraphOperationMessage::TransformSet { | ||
| layer, | ||
| transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), | ||
|
|
@@ -1184,6 +1209,7 @@ impl Fsm for ShapeToolFsmState { | |
| 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::Spiral => Spiral::update_shape(document, input, viewport, layer, tool_data, responses), | ||
| ShapeType::Teardrop => Teardrop::update_shape(document, input, viewport, layer, tool_data, modifier, responses), | ||
| ShapeType::Grid => Grid::update_shape(document, input, layer, tool_options.grid_type, tool_data, modifier, responses), | ||
| ShapeType::Arrow => Arrow::update_shape(document, input, viewport, layer, tool_data, modifier, responses), | ||
| ShapeType::Line => Line::update_shape(document, input, viewport, layer, tool_data, modifier, responses), | ||
|
|
@@ -1427,6 +1453,11 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque<Mess | |
| HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Spiral")]), | ||
| HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Turns")]), | ||
| ], | ||
| ShapeType::Teardrop => vec![HintGroup(vec![ | ||
| HintInfo::mouse(MouseMotion::LmbDrag, "Draw Teardrop"), | ||
| HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(), | ||
| HintInfo::keys([Key::Alt], "From Center").prepend_plus(), | ||
| ])], | ||
| ShapeType::Grid => vec![HintGroup(vec![ | ||
| HintInfo::mouse(MouseMotion::LmbDrag, "Draw Grid"), | ||
| HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(), | ||
|
|
@@ -1460,7 +1491,9 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque<Mess | |
| ShapeToolFsmState::Drawing(shape) => { | ||
| let mut common_hint_group = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]; | ||
| let tool_hint_group = match shape { | ||
| ShapeType::Polygon | ShapeType::Star | ShapeType::Arc => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]), | ||
| ShapeType::Polygon | ShapeType::Star | ShapeType::Arc | ShapeType::Teardrop => { | ||
| HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]) | ||
| } | ||
| ShapeType::Circle => HintGroup(vec![HintInfo::keys([Key::Alt], "From Center")]), | ||
| ShapeType::Spiral => HintGroup(vec![]), | ||
| ShapeType::Grid => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]), | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -260,6 +260,41 @@ pub fn spiral_bezpath(a: f64, outer_radius: f64, turns: f64, start_angle: f64, d | |||||
| bezpath_from_anchors(&anchors, false) | ||||||
| } | ||||||
|
|
||||||
| /// Constructs a teardrop with `corner1` and `corner2` as the two corners of the bounding box. | ||||||
| pub fn teardrop_bezpath(corner1: DVec2, corner2: DVec2, velocity: f64) -> BezPath { | ||||||
| let size = (corner1 - corner2).abs(); | ||||||
|
|
||||||
| // the bottom half of the teardrop is a circle, upon which these calculations are heavily based | ||||||
| let circle_center = DVec2::new((corner1.x + corner2.x) / 2., (corner1.y + (2. * velocity - 1.) * corner2.y) / (2. * velocity)); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: When Prompt for AI agents
Suggested change
|
||||||
|
|
||||||
| let top = DVec2::new(circle_center.x, corner1.y); | ||||||
| let bottom = DVec2::new(circle_center.x, corner2.y); | ||||||
| let left = DVec2::new(corner1.x, circle_center.y); | ||||||
| let right = DVec2::new(corner2.x, circle_center.y); | ||||||
|
|
||||||
| // because we modify the dimensions vertically, the handle_offset remains the same as | ||||||
| // for a circle *horizontally*, but the vertical handle_offset must be adjusted | ||||||
| let horizontal_handle_offset = size * HANDLE_OFFSET_FACTOR * 0.5; | ||||||
| let vertical_handle_offset = horizontal_handle_offset / velocity; | ||||||
|
|
||||||
| // I've found that the teardrop looks better when its sides go up a little steeper than they go down | ||||||
| let roundness_multiplier = 1.6; | ||||||
| let roundness = vertical_handle_offset * roundness_multiplier; | ||||||
|
|
||||||
| // both handles for the top point of the teardrop, to make it pointier | ||||||
| let point_handle_multiplier = 0.28; | ||||||
| let point_handles = Some(top + size * point_handle_multiplier * DVec2::Y); | ||||||
|
|
||||||
| let anchors = [ | ||||||
| Anchor::new(top, point_handles, point_handles), | ||||||
| Anchor::new(right, Some(right - roundness * DVec2::Y), Some(right + vertical_handle_offset * DVec2::Y)), | ||||||
| Anchor::new(bottom, Some(bottom + horizontal_handle_offset * DVec2::X), Some(bottom - horizontal_handle_offset * DVec2::X)), | ||||||
| Anchor::new(left, Some(left + vertical_handle_offset * DVec2::Y), Some(left - roundness * DVec2::Y)), | ||||||
| ]; | ||||||
|
|
||||||
| bezpath_from_anchors(&anchors, true) | ||||||
| } | ||||||
|
|
||||||
| pub fn calculate_growth_factor(a: f64, turns: f64, outer_radius: f64, spiral_type: SpiralType) -> f64 { | ||||||
| match spiral_type { | ||||||
| SpiralType::Archimedean => { | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -77,6 +77,39 @@ fn spiral( | |||||||
| ))) | ||||||||
| } | ||||||||
|
|
||||||||
| /// Generates a teardrop shape using the given dimensions | ||||||||
| #[node_macro::node(category("Vector: Shape"))] | ||||||||
| fn teardrop( | ||||||||
| _: impl Ctx, | ||||||||
| _primary: (), | ||||||||
| #[unit(" px")] | ||||||||
| #[default(30)] | ||||||||
| width: Item<f64>, | ||||||||
| #[unit(" px")] | ||||||||
| #[default(51)] | ||||||||
| height: Item<f64>, | ||||||||
| #[default(1.7)] | ||||||||
| #[range] | ||||||||
| #[soft(1.4..3.8)] | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: When a node input supplies Prompt for AI agents
Suggested change
|
||||||||
| velocity: Item<f64>, | ||||||||
| ) -> Item<Vector> { | ||||||||
| let radius = DVec2::new(*width.element(), *height.element()); | ||||||||
| let corner1 = -radius; | ||||||||
| let corner2 = radius; | ||||||||
| let velocity = *velocity.element(); | ||||||||
|
|
||||||||
| let mut teardrop = Vector::from_bezpath(shapes::teardrop_bezpath(corner1, corner2, velocity)); | ||||||||
|
|
||||||||
| let len = teardrop.segment_domain.ids().len(); | ||||||||
| for i in 0..len - 1 { | ||||||||
| teardrop | ||||||||
| .colinear_manipulators | ||||||||
| .push([HandleId::end(teardrop.segment_domain.ids()[i]), HandleId::primary(teardrop.segment_domain.ids()[(i + 1) % len])]); | ||||||||
| } | ||||||||
|
|
||||||||
| Item::new_from_element(teardrop) | ||||||||
| } | ||||||||
|
|
||||||||
| /// Generates an ellipse shape (an oval or stretched circle) with the chosen radii. | ||||||||
| #[node_macro::node(category("Vector: Shape"))] | ||||||||
| fn ellipse( | ||||||||
|
|
||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: When the Teardrop node is not registered,
create_nodepanics while starting a draw and brings down the editor. Handle the missing definition locally or propagate the failure instead of callingexpect.Prompt for AI agents