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 c0d1081501..30f42b8730 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -368,6 +368,10 @@ pub fn get_spiral_id(layer: LayerNodeIdentifier, network_interface: &NodeNetwork NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Spiral") } +pub fn get_teardrop_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { + NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Teardrop") +} + pub fn get_text_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Text") } diff --git a/editor/src/messages/tool/common_functionality/shapes/mod.rs b/editor/src/messages/tool/common_functionality/shapes/mod.rs index 5031a6224e..3526e3eee7 100644 --- a/editor/src/messages/tool/common_functionality/shapes/mod.rs +++ b/editor/src/messages/tool/common_functionality/shapes/mod.rs @@ -8,8 +8,10 @@ pub mod rectangle_shape; pub mod shape_utility; pub mod spiral_shape; pub mod star_shape; +pub mod teardrop_shape; pub use super::shapes::ellipse_shape::Ellipse; pub use super::shapes::line_shape::{Line, LineEnd}; pub use super::shapes::rectangle_shape::Rectangle; +pub use super::shapes::teardrop_shape::Teardrop; pub use crate::messages::tool::tool_messages::shape_tool::ShapeToolData; 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 a94847c514..15004007d1 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -28,6 +28,7 @@ pub enum ShapeType { Polygon = 0, Star, Circle, + Teardrop, Arc, Spiral, Grid, @@ -42,6 +43,7 @@ impl ShapeType { Self::Polygon => "Polygon", Self::Star => "Star", Self::Circle => "Circle", + Self::Teardrop => "Teardrop", Self::Arc => "Arc", Self::Grid => "Grid", Self::Spiral => "Spiral", 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..f86bf83a05 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/shapes/teardrop_shape.rs @@ -0,0 +1,54 @@ +use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; +use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_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::common_functionality::shapes::shape_utility::ShapeToolModifierKey; +use crate::messages::tool::tool_messages::shape_tool::ShapeToolData; +use crate::messages::tool::tool_messages::tool_prelude::*; +use glam::DAffine2; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; + +#[derive(Default)] +pub struct Teardrop; + +impl Teardrop { + pub fn create_node() -> NodeTemplate { + let node_type = resolve_document_node_type("Teardrop").expect("Teardrop 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; + }; + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 1), + input: NodeInput::value(TaggedValue::F64(((start.x - end.x) / 2.).abs()), false), + }); + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 2), + input: NodeInput::value(TaggedValue::F64(((start.y - end.y) / 2.).abs()), false), + }); + responses.add(GraphOperationMessage::TransformSet { + layer, + transform: DAffine2::from_translation(start.midpoint(end)), + transform_in: TransformIn::Viewport, + skip_rerender: false, + }); + } + } +} diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index ead0e1e1ce..61005708f8 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -16,6 +16,7 @@ use crate::messages::tool::common_functionality::shapes::polygon_shape::Polygon; use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, 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::transformation_cage::{BoundingBoxManager, EdgeBool}; @@ -150,6 +151,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("Arc").label("Arc").on_commit(move |_| { ShapeToolMessage::UpdateOptions { options: ShapeOptionsUpdate::ShapeType(ShapeType::Arc), @@ -804,7 +811,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::Teardrop + | ShapeType::Arc + | ShapeType::Spiral + | ShapeType::Grid + | ShapeType::Rectangle + | ShapeType::Ellipse => { tool_data.data.start(document, input, viewport); } ShapeType::Line => { @@ -823,6 +838,7 @@ impl Fsm for ShapeToolFsmState { ShapeType::Polygon => Polygon::create_node(tool_options.vertices), ShapeType::Star => Star::create_node(tool_options.vertices), ShapeType::Circle => Circle::create_node(), + ShapeType::Teardrop => Teardrop::create_node(), ShapeType::Arc => Arc::create_node(tool_options.arc_type), ShapeType::Spiral => Spiral::create_node(tool_options.spiral_type, tool_options.turns), ShapeType::Grid => Grid::create_node(tool_options.grid_type), @@ -837,7 +853,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::Teardrop + | ShapeType::Arc + | ShapeType::Spiral + | ShapeType::Grid + | ShapeType::Rectangle + | ShapeType::Ellipse => { defered_responses.add(GraphOperationMessage::TransformSet { layer, transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), @@ -873,6 +897,7 @@ impl Fsm for ShapeToolFsmState { ShapeType::Polygon => Polygon::update_shape(document, input, viewport, layer, tool_data, modifier, responses), 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::Teardrop => Teardrop::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::Grid => Grid::update_shape(document, input, layer, tool_options.grid_type, tool_data, modifier, responses), @@ -1120,6 +1145,11 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque vec![HintGroup(vec![ + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Teardrop"), + HintInfo::keys([Key::Shift], "Constrain Circular").prepend_plus(), + HintInfo::keys([Key::Alt], "From Center").prepend_plus(), + ])], ShapeType::Arc => vec![HintGroup(vec![ HintInfo::mouse(MouseMotion::LmbDrag, "Draw Arc"), HintInfo::keys([Key::Shift], "Constrain Arc").prepend_plus(), @@ -1138,7 +1168,7 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]), ShapeType::Rectangle => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")]), - ShapeType::Ellipse => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Circular"), HintInfo::keys([Key::Alt], "From Center")]), + ShapeType::Ellipse | ShapeType::Teardrop => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Circular"), HintInfo::keys([Key::Alt], "From Center")]), ShapeType::Grid => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]), ShapeType::Line => HintGroup(vec![ HintInfo::keys([Key::Shift], "15° Increments"), diff --git a/node-graph/libraries/vector-types/src/subpath/core.rs b/node-graph/libraries/vector-types/src/subpath/core.rs index c5600d61a1..e065c4f8b8 100644 --- a/node-graph/libraries/vector-types/src/subpath/core.rs +++ b/node-graph/libraries/vector-types/src/subpath/core.rs @@ -227,6 +227,29 @@ impl Subpath { Self::new(manipulator_groups, true) } + /// Constructs a teardrop with `corner1` and `corner2` as the two corners of the bounding box. + pub fn new_teardrop(corner1: DVec2, corner2: DVec2) -> Self { + let size = (corner1 - corner2).abs(); + let center = (corner1 + corner2) / 2.; + let top = DVec2::new(center.x, corner1.y); + let bottom = DVec2::new(center.x, corner2.y); + let left = DVec2::new(corner1.x, center.y / 2.); + let right = DVec2::new(corner2.x, center.y / 2.); + + // Based on https://pomax.github.io/bezierinfo/#circles_cubic + const HANDLE_OFFSET_FACTOR: f64 = 0.551784777779014; + let bottom_handle_offset = size * HANDLE_OFFSET_FACTOR * 0.5; + let sides_handle_offset = size * HANDLE_OFFSET_FACTOR * 0.375; //*0.5*0.75 + + let manipulator_groups = vec![ + ManipulatorGroup::new(top, None, None), + ManipulatorGroup::new(right, Some(right - sides_handle_offset * DVec2::Y), Some(right + bottom_handle_offset * DVec2::Y)), + ManipulatorGroup::new(bottom, Some(bottom + bottom_handle_offset * DVec2::X), Some(bottom - bottom_handle_offset * DVec2::X)), + ManipulatorGroup::new(left, Some(left + bottom_handle_offset * DVec2::Y), Some(left - sides_handle_offset * DVec2::Y)), + ]; + Self::new(manipulator_groups, true) + } + /// Constructs an arc by a `radius`, `angle_start` and `angle_size`. Angles must be in radians. Slice option makes it look like pie or pacman. pub fn new_arc(radius: f64, start_angle: f64, sweep_angle: f64, arc_type: ArcType) -> Self { // Prevents glitches from numerical imprecision that have been observed during animation playback after about a minute diff --git a/node-graph/nodes/vector/src/generator_nodes.rs b/node-graph/nodes/vector/src/generator_nodes.rs index df587528ef..aa17ee0ebc 100644 --- a/node-graph/nodes/vector/src/generator_nodes.rs +++ b/node-graph/nodes/vector/src/generator_nodes.rs @@ -51,6 +51,33 @@ fn circle( Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius)))) } +/// Generates a teardrop shape with a chosen radius +#[node_macro::node(category("Vector: Shape"))] +fn teardrop( + _: impl Ctx, + _primary: (), + #[unit(" px")] + #[default(50)] + radius_x: f64, + #[unit(" px")] + #[default(25)] + radius_y: f64, +) -> Table { + let corner1 = DVec2::new(-radius_x, -radius_y * 2.); + let corner2 = DVec2::new(radius_x, radius_y * 0.75); + + let mut teardrop = Vector::from_subpath(subpath::Subpath::new_teardrop(corner1, corner2)); + + let len = teardrop.segment_domain.ids().len(); + for i in 0..len { + teardrop + .colinear_manipulators + .push([HandleId::end(teardrop.segment_domain.ids()[i]), HandleId::primary(teardrop.segment_domain.ids()[(i + 1) % len])]); + } + + Table::new_from_element(teardrop) +} + /// Generates an arc shape forming a portion of a circle which may be open, closed, or a pie slice. #[node_macro::node(category("Vector: Shape"))] fn arc(