From 06da3dbca66c6efcfbe72bdbf675ac46c726900f Mon Sep 17 00:00:00 2001 From: Dallin Yauney <70169075+DallinYauney@users.noreply.github.com> Date: Fri, 21 Aug 2026 04:40:32 -0600 Subject: [PATCH 1/4] initial teardrop implementation --- .../node_graph/node_graph_message_handler.rs | 14 ++++-- .../graph_modification_utils.rs | 4 ++ .../tool/common_functionality/shapes/mod.rs | 1 + .../shapes/shape_utility.rs | 8 ++- .../shapes/teardrop_shape.rs | 50 +++++++++++++++++++ .../messages/tool/tool_messages/shape_tool.rs | 40 +++++++++++++-- .../src/vector/algorithms/shapes.rs | 43 ++++++++++++++++ .../nodes/vector/src/generator_nodes.rs | 28 +++++++++++ 8 files changed, 179 insertions(+), 9 deletions(-) create mode 100644 editor/src/messages/tool/common_functionality/shapes/teardrop_shape.rs diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 8748b1257f..0cc4b6ee67 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -1764,9 +1764,17 @@ impl<'a> MessageHandler> for NodeG let is_fill_node = reference.as_ref().is_some_and(|r| *r == DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER)); let is_fill_input = is_fill_node && input_index == graphene_std::vector::fill::PaintInput::INDEX; let is_shape_generator_node = reference.as_ref().is_some_and(|r| { - [regular_polygon::IDENTIFIER, star::IDENTIFIER, arc::IDENTIFIER, spiral::IDENTIFIER, grid::IDENTIFIER, arrow::IDENTIFIER] - .into_iter() - .any(|id| *r == DefinitionIdentifier::ProtoNode(id)) + [ + regular_polygon::IDENTIFIER, + star::IDENTIFIER, + arc::IDENTIFIER, + spiral::IDENTIFIER, + grid::IDENTIFIER, + arrow::IDENTIFIER, + teardrop::IDENTIFIER, + ] + .into_iter() + .any(|id| *r == DefinitionIdentifier::ProtoNode(id)) }); let input = NodeInput::value(*value, false); 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 67f5a0e6cb..796e53c504 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -583,6 +583,10 @@ pub fn get_spiral_id(layer: LayerNodeIdentifier, network_interface: &NodeNetwork NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::spiral::IDENTIFIER)) } +pub fn get_teardrop_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { + NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::teardrop::IDENTIFIER)) +} + pub fn get_text_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER)) } diff --git a/editor/src/messages/tool/common_functionality/shapes/mod.rs b/editor/src/messages/tool/common_functionality/shapes/mod.rs index 4d74b15ba5..637628047c 100644 --- a/editor/src/messages/tool/common_functionality/shapes/mod.rs +++ b/editor/src/messages/tool/common_functionality/shapes/mod.rs @@ -9,6 +9,7 @@ pub mod rectangle_shape; pub mod shape_utility; pub mod spiral_shape; pub mod star_shape; +pub mod teardrop_shape; pub use super::resize::{viewport_zoom, window_aligned_transform_set}; pub use super::shapes::arrow_shape::Arrow; 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 57a6591caf..92a1402d4f 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -32,6 +32,7 @@ pub enum ShapeType { Circle, Arc, Spiral, + Teardrop, Grid, Arrow, Line, // KEEP THIS AT THE END @@ -47,6 +48,7 @@ impl ShapeType { ShapeType::Circle, ShapeType::Arc, ShapeType::Spiral, + ShapeType::Teardrop, ShapeType::Grid, ShapeType::Arrow, ShapeType::Line, // KEEP THIS AT THE END @@ -57,7 +59,10 @@ impl ShapeType { /// True if this shape mode's fill checkbox is ticked by default when nothing is selected. /// Spiral/Grid/Line are open paths and default to fill-off, the closed shapes default to fill-on. pub fn defaults_to_fill(&self) -> bool { - matches!(self, Self::Polygon | Self::Star | Self::Circle | Self::Arc | Self::Rectangle | Self::Ellipse | Self::Arrow) + matches!( + self, + Self::Polygon | Self::Star | Self::Circle | Self::Arc | Self::Rectangle | Self::Ellipse | Self::Arrow | Self::Teardrop + ) } pub fn name(&self) -> String { @@ -67,6 +72,7 @@ impl ShapeType { Self::Circle => "Circle", Self::Arc => "Arc", Self::Spiral => "Spiral", + Self::Teardrop => "Teardrop", Self::Grid => "Grid", Self::Arrow => "Arrow", Self::Line => "Line", // KEEP THIS AT THE END diff --git a/editor/src/messages/tool/common_functionality/shapes/teardrop_shape.rs b/editor/src/messages/tool/common_functionality/shapes/teardrop_shape.rs new file mode 100644 index 0000000000..9d1f01e30b --- /dev/null +++ b/editor/src/messages/tool/common_functionality/shapes/teardrop_shape.rs @@ -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, + ) { + 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)); + } + } +} diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index eda939a304..5b36a4d871 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -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,13 @@ 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), + // options: StrokeOptionsUpdate::Align(graphene_std::vector::style::StrokeAlign::Inside), + } + .into() + }), MenuListEntry::new("Grid").label("Grid").on_commit(move |_| { ShapeToolMessage::UpdateOptions { options: ShapeOptionsUpdate::ShapeType(ShapeType::Grid), @@ -323,6 +331,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 +349,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 +416,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 => {} } changed @@ -1088,7 +1097,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 +1126,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 +1140,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 +1210,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 +1454,7 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque vec![HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Teardrop")])], ShapeType::Grid => vec![HintGroup(vec![ HintInfo::mouse(MouseMotion::LmbDrag, "Draw Grid"), HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(), @@ -1460,7 +1488,9 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque { 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")]), diff --git a/node-graph/libraries/vector-types/src/vector/algorithms/shapes.rs b/node-graph/libraries/vector-types/src/vector/algorithms/shapes.rs index 33c6265cee..8a285b3e9e 100644 --- a/node-graph/libraries/vector-types/src/vector/algorithms/shapes.rs +++ b/node-graph/libraries/vector-types/src/vector/algorithms/shapes.rs @@ -260,6 +260,49 @@ 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) -> BezPath { + let size = (corner1 - corner2).abs(); + + // the ratio of height to width at which the bottom half is a perfect circle. the optimal ratio. + // it looks good to have this value anywhere from 1.7 to 2.4. I hope to one day have a gizmo for this value. + // note also that the node's default values are currently calculated assuming a value of 1.7. + let height_width_ratio = 1.7; + + // 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. * height_width_ratio - 1.) * corner2.y) / (2. * height_width_ratio)); + + 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 / height_width_ratio; + + // 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; + + // let roundness_multiplier = 0.6; + // let roundness = vertical_handle_offset * roundness_multiplier * height_width_ratio; + + // 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 => { diff --git a/node-graph/nodes/vector/src/generator_nodes.rs b/node-graph/nodes/vector/src/generator_nodes.rs index a77b5c98eb..691585ec22 100644 --- a/node-graph/nodes/vector/src/generator_nodes.rs +++ b/node-graph/nodes/vector/src/generator_nodes.rs @@ -77,6 +77,34 @@ 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, + #[unit(" px")] + #[default(51)] + height: Item, +) -> Item { + let radius = DVec2::new(*width.element(), *height.element()); + let corner1 = -radius; + let corner2 = radius; + + let mut teardrop = Vector::from_bezpath(shapes::teardrop_bezpath(corner1, corner2)); + + 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( From 9b9463b57ebbffcda8309368b7afd3d4d10e1082 Mon Sep 17 00:00:00 2001 From: Dallin Yauney <70169075+DallinYauney@users.noreply.github.com> Date: Fri, 21 Aug 2026 05:44:37 -0600 Subject: [PATCH 2/4] add "velocity" slider to replace internal variable `width_height_ratio` --- .../vector-types/src/vector/algorithms/shapes.rs | 13 ++++--------- node-graph/nodes/vector/src/generator_nodes.rs | 7 ++++++- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/node-graph/libraries/vector-types/src/vector/algorithms/shapes.rs b/node-graph/libraries/vector-types/src/vector/algorithms/shapes.rs index 8a285b3e9e..5abebbeffe 100644 --- a/node-graph/libraries/vector-types/src/vector/algorithms/shapes.rs +++ b/node-graph/libraries/vector-types/src/vector/algorithms/shapes.rs @@ -261,16 +261,11 @@ pub fn spiral_bezpath(a: f64, outer_radius: f64, turns: f64, start_angle: f64, d } /// Constructs a teardrop with `corner1` and `corner2` as the two corners of the bounding box. -pub fn teardrop_bezpath(corner1: DVec2, corner2: DVec2) -> BezPath { +pub fn teardrop_bezpath(corner1: DVec2, corner2: DVec2, velocity: f64) -> BezPath { let size = (corner1 - corner2).abs(); - // the ratio of height to width at which the bottom half is a perfect circle. the optimal ratio. - // it looks good to have this value anywhere from 1.7 to 2.4. I hope to one day have a gizmo for this value. - // note also that the node's default values are currently calculated assuming a value of 1.7. - let height_width_ratio = 1.7; - // 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. * height_width_ratio - 1.) * corner2.y) / (2. * height_width_ratio)); + let circle_center = DVec2::new((corner1.x + corner2.x) / 2., (corner1.y + (2. * velocity - 1.) * corner2.y) / (2. * velocity)); let top = DVec2::new(circle_center.x, corner1.y); let bottom = DVec2::new(circle_center.x, corner2.y); @@ -280,14 +275,14 @@ pub fn teardrop_bezpath(corner1: DVec2, corner2: DVec2) -> BezPath { // 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 / height_width_ratio; + 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; // let roundness_multiplier = 0.6; - // let roundness = vertical_handle_offset * roundness_multiplier * height_width_ratio; + // let roundness = vertical_handle_offset * roundness_multiplier * pointiness; // both handles for the top point of the teardrop, to make it pointier let point_handle_multiplier = 0.28; diff --git a/node-graph/nodes/vector/src/generator_nodes.rs b/node-graph/nodes/vector/src/generator_nodes.rs index 691585ec22..ca5fbb08ae 100644 --- a/node-graph/nodes/vector/src/generator_nodes.rs +++ b/node-graph/nodes/vector/src/generator_nodes.rs @@ -88,12 +88,17 @@ fn teardrop( #[unit(" px")] #[default(51)] height: Item, + #[default(1.7)] + #[range] + #[soft(1.4..3.8)] + velocity: Item, ) -> Item { 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)); + 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 { From 64e9446c00cf70b159f1f6f70a136cc287ecfd9d Mon Sep 17 00:00:00 2001 From: Dallin Yauney <70169075+DallinYauney@users.noreply.github.com> Date: Fri, 21 Aug 2026 07:31:11 -0600 Subject: [PATCH 3/4] fix some hints not being shown during Ready state --- editor/src/messages/tool/tool_messages/shape_tool.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 5b36a4d871..607bd90602 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -1454,7 +1454,11 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque vec![HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Teardrop")])], + 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(), From c949ee0636f7b67967290a756c9bc542a71573cb Mon Sep 17 00:00:00 2001 From: Dallin Yauney <70169075+DallinYauney@users.noreply.github.com> Date: Fri, 21 Aug 2026 08:12:29 -0600 Subject: [PATCH 4/4] tidy up for PR --- editor/src/messages/tool/tool_messages/shape_tool.rs | 1 - .../libraries/vector-types/src/vector/algorithms/shapes.rs | 3 --- 2 files changed, 4 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 607bd90602..34d06204a6 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -204,7 +204,6 @@ fn create_shape_option_widget(shape_type: ShapeType) -> WidgetInstance { MenuListEntry::new("Teardrop").label("Teardrop").on_commit(move |_| { ShapeToolMessage::UpdateOptions { options: ShapeOptionsUpdate::ShapeType(ShapeType::Teardrop), - // options: StrokeOptionsUpdate::Align(graphene_std::vector::style::StrokeAlign::Inside), } .into() }), diff --git a/node-graph/libraries/vector-types/src/vector/algorithms/shapes.rs b/node-graph/libraries/vector-types/src/vector/algorithms/shapes.rs index 5abebbeffe..6b280a06ef 100644 --- a/node-graph/libraries/vector-types/src/vector/algorithms/shapes.rs +++ b/node-graph/libraries/vector-types/src/vector/algorithms/shapes.rs @@ -281,9 +281,6 @@ pub fn teardrop_bezpath(corner1: DVec2, corner2: DVec2, velocity: f64) -> BezPat let roundness_multiplier = 1.6; let roundness = vertical_handle_offset * roundness_multiplier; - // let roundness_multiplier = 0.6; - // let roundness = vertical_handle_offset * roundness_multiplier * pointiness; - // 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);