From 04ecdd1c99721f06a6a423a1b7a03a2133f37375 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Fri, 30 May 2025 04:27:48 +0530 Subject: [PATCH 01/16] no trait ,not to fix line --- .../messages/input_mapper/input_mappings.rs | 42 +- editor/src/messages/prelude.rs | 4 +- .../tool/common_functionality/resize.rs | 2 +- editor/src/messages/tool/mod.rs | 1 + .../src/messages/tool/shapes/convex_shape.rs | 68 + .../src/messages/tool/shapes/ellipse_shape.rs | 54 + editor/src/messages/tool/shapes/line_shape.rs | 130 ++ editor/src/messages/tool/shapes/mod.rs | 12 + .../messages/tool/shapes/rectangle_shape.rs | 54 + .../src/messages/tool/shapes/shape_utility.rs | 98 ++ editor/src/messages/tool/shapes/star_shape.rs | 77 + editor/src/messages/tool/tool_message.rs | 13 +- .../src/messages/tool/tool_message_handler.rs | 47 +- .../tool/tool_messages/ellipse_tool.rs | 882 ++++++------ .../messages/tool/tool_messages/line_tool.rs | 1248 ++++++++--------- editor/src/messages/tool/tool_messages/mod.rs | 1 + .../tool/tool_messages/rectangle_tool.rs | 646 ++++----- .../messages/tool/tool_messages/shape_tool.rs | 414 ++++++ editor/src/messages/tool/utility_types.rs | 99 +- 19 files changed, 2438 insertions(+), 1454 deletions(-) create mode 100644 editor/src/messages/tool/shapes/convex_shape.rs create mode 100644 editor/src/messages/tool/shapes/ellipse_shape.rs create mode 100644 editor/src/messages/tool/shapes/line_shape.rs create mode 100644 editor/src/messages/tool/shapes/mod.rs create mode 100644 editor/src/messages/tool/shapes/rectangle_shape.rs create mode 100644 editor/src/messages/tool/shapes/shape_utility.rs create mode 100644 editor/src/messages/tool/shapes/star_shape.rs create mode 100644 editor/src/messages/tool/tool_messages/shape_tool.rs diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index bd3c6285e3..bbf66f9b87 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -172,11 +172,17 @@ pub fn input_mappings() -> Mapping { entry!(KeyDown(Escape); action_dispatch=GradientToolMessage::Abort), // // RectangleToolMessage - entry!(KeyDown(MouseLeft); action_dispatch=RectangleToolMessage::DragStart), - entry!(KeyUp(MouseLeft); action_dispatch=RectangleToolMessage::DragStop), - entry!(KeyDown(MouseRight); action_dispatch=RectangleToolMessage::Abort), - entry!(KeyDown(Escape); action_dispatch=RectangleToolMessage::Abort), - entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=RectangleToolMessage::PointerMove { center: Alt, lock_ratio: Shift }), + // entry!(KeyDown(MouseLeft); action_dispatch=RectangleToolMessage::DragStart), + // entry!(KeyUp(MouseLeft); action_dispatch=RectangleToolMessage::DragStop), + // entry!(KeyDown(MouseRight); action_dispatch=RectangleToolMessage::Abort), + // entry!(KeyDown(Escape); action_dispatch=RectangleToolMessage::Abort), + // entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=RectangleToolMessage::PointerMove { center: Alt, lock_ratio: Shift }), + // ShapeToolMessage + entry!(KeyDown(MouseLeft); action_dispatch=ShapeToolMessage::DragStart), + entry!(KeyUp(MouseLeft); action_dispatch=ShapeToolMessage::DragStop), + entry!(KeyDown(MouseRight); action_dispatch=ShapeToolMessage::Abort), + entry!(KeyDown(Escape); action_dispatch=ShapeToolMessage::Abort), + entry!(PointerMove; refresh_keys=[Alt, Shift, Control], action_dispatch=ShapeToolMessage::PointerMove ([Alt, Shift, Control, Shift])), // // ImaginateToolMessage // entry!(KeyDown(MouseLeft); action_dispatch=ImaginateToolMessage::DragStart), @@ -186,11 +192,11 @@ pub fn input_mappings() -> Mapping { // entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=ImaginateToolMessage::Resize { center: Alt, lock_ratio: Shift }), // // EllipseToolMessage - entry!(KeyDown(MouseLeft); action_dispatch=EllipseToolMessage::DragStart), - entry!(KeyUp(MouseLeft); action_dispatch=EllipseToolMessage::DragStop), - entry!(KeyDown(MouseRight); action_dispatch=EllipseToolMessage::Abort), - entry!(KeyDown(Escape); action_dispatch=EllipseToolMessage::Abort), - entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=EllipseToolMessage::PointerMove { center: Alt, lock_ratio: Shift }), + // entry!(KeyDown(MouseLeft); action_dispatch=EllipseToolMessage::DragStart), + // entry!(KeyUp(MouseLeft); action_dispatch=EllipseToolMessage::DragStop), + // entry!(KeyDown(MouseRight); action_dispatch=EllipseToolMessage::Abort), + // entry!(KeyDown(Escape); action_dispatch=EllipseToolMessage::Abort), + // entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=EllipseToolMessage::PointerMove { center: Alt, lock_ratio: Shift }), // // PolygonToolMessage entry!(KeyDown(MouseLeft); action_dispatch=PolygonToolMessage::DragStart), @@ -200,11 +206,11 @@ pub fn input_mappings() -> Mapping { entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=PolygonToolMessage::PointerMove { center: Alt, lock_ratio: Shift }), // // LineToolMessage - entry!(KeyDown(MouseLeft); action_dispatch=LineToolMessage::DragStart), - entry!(KeyUp(MouseLeft); action_dispatch=LineToolMessage::DragStop), - entry!(KeyDown(MouseRight); action_dispatch=LineToolMessage::Abort), - entry!(KeyDown(Escape); action_dispatch=LineToolMessage::Abort), - entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=LineToolMessage::PointerMove { center: Alt, lock_angle: Control, snap_angle: Shift }), + // entry!(KeyDown(MouseLeft); action_dispatch=LineToolMessage::DragStart), + // entry!(KeyUp(MouseLeft); action_dispatch=LineToolMessage::DragStop), + // entry!(KeyDown(MouseRight); action_dispatch=LineToolMessage::Abort), + // entry!(KeyDown(Escape); action_dispatch=LineToolMessage::Abort), + // entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=LineToolMessage::PointerMove { center: Alt, lock_angle: Control, snap_angle: Shift }), // // PathToolMessage entry!(KeyDown(Delete); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath), @@ -308,9 +314,9 @@ pub fn input_mappings() -> Mapping { entry!(KeyDown(KeyA); action_dispatch=ToolMessage::ActivateToolPath), entry!(KeyDown(KeyP); action_dispatch=ToolMessage::ActivateToolPen), entry!(KeyDown(KeyN); action_dispatch=ToolMessage::ActivateToolFreehand), - entry!(KeyDown(KeyL); action_dispatch=ToolMessage::ActivateToolLine), - entry!(KeyDown(KeyM); action_dispatch=ToolMessage::ActivateToolRectangle), - entry!(KeyDown(KeyE); action_dispatch=ToolMessage::ActivateToolEllipse), + entry!(KeyDown(KeyL); action_dispatch=ToolMessage::ActivateShapeLine), + entry!(KeyDown(KeyM); action_dispatch=ToolMessage::ActivateShapeRectangle), + entry!(KeyDown(KeyE); action_dispatch=ToolMessage::ActivateShapeEllipse), entry!(KeyDown(KeyY); action_dispatch=ToolMessage::ActivateToolPolygon), entry!(KeyDown(KeyB); action_dispatch=ToolMessage::ActivateToolBrush), entry!(KeyDown(KeyX); modifiers=[Accel, Shift], action_dispatch=ToolMessage::ResetColors), diff --git a/editor/src/messages/prelude.rs b/editor/src/messages/prelude.rs index 8c03ac51fe..8511625290 100644 --- a/editor/src/messages/prelude.rs +++ b/editor/src/messages/prelude.rs @@ -34,19 +34,17 @@ pub use crate::messages::broadcast::broadcast_event::{BroadcastEvent, BroadcastE pub use crate::messages::message::{Message, MessageDiscriminant}; pub use crate::messages::tool::tool_messages::artboard_tool::{ArtboardToolMessage, ArtboardToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::brush_tool::{BrushToolMessage, BrushToolMessageDiscriminant}; -pub use crate::messages::tool::tool_messages::ellipse_tool::{EllipseToolMessage, EllipseToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::eyedropper_tool::{EyedropperToolMessage, EyedropperToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::fill_tool::{FillToolMessage, FillToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::freehand_tool::{FreehandToolMessage, FreehandToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::gradient_tool::{GradientToolMessage, GradientToolMessageDiscriminant}; // pub use crate::messages::tool::tool_messages::imaginate_tool::{ImaginateToolMessage, ImaginateToolMessageDiscriminant}; -pub use crate::messages::tool::tool_messages::line_tool::{LineToolMessage, LineToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::navigate_tool::{NavigateToolMessage, NavigateToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::path_tool::{PathToolMessage, PathToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::pen_tool::{PenToolMessage, PenToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::polygon_tool::{PolygonToolMessage, PolygonToolMessageDiscriminant}; -pub use crate::messages::tool::tool_messages::rectangle_tool::{RectangleToolMessage, RectangleToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::select_tool::{SelectToolMessage, SelectToolMessageDiscriminant}; +pub use crate::messages::tool::tool_messages::shape_tool::{ShapeToolMessage, ShapeToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::spline_tool::{SplineToolMessage, SplineToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::text_tool::{TextToolMessage, TextToolMessageDiscriminant}; diff --git a/editor/src/messages/tool/common_functionality/resize.rs b/editor/src/messages/tool/common_functionality/resize.rs index f81b13fed9..fc8f1ee6da 100644 --- a/editor/src/messages/tool/common_functionality/resize.rs +++ b/editor/src/messages/tool/common_functionality/resize.rs @@ -8,7 +8,7 @@ use glam::{DAffine2, DVec2, Vec2Swizzles}; #[derive(Clone, Debug, Default)] pub struct Resize { /// Stored as a document position so the start doesn't move if the canvas is panned. - drag_start: DVec2, + pub drag_start: DVec2, pub layer: Option, pub snap_manager: SnapManager, } diff --git a/editor/src/messages/tool/mod.rs b/editor/src/messages/tool/mod.rs index ca03f01e8b..f2c28f27dc 100644 --- a/editor/src/messages/tool/mod.rs +++ b/editor/src/messages/tool/mod.rs @@ -2,6 +2,7 @@ mod tool_message; mod tool_message_handler; pub mod common_functionality; +pub mod shapes; pub mod tool_messages; pub mod transform_layer; pub mod utility_types; diff --git a/editor/src/messages/tool/shapes/convex_shape.rs b/editor/src/messages/tool/shapes/convex_shape.rs new file mode 100644 index 0000000000..ea83972a92 --- /dev/null +++ b/editor/src/messages/tool/shapes/convex_shape.rs @@ -0,0 +1,68 @@ +use super::shape_utility::ShapeToolModifierKey; +use super::shape_utility::update_radius_sign; +use super::*; +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::tool_messages::tool_prelude::*; +use glam::DAffine2; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; +use std::collections::VecDeque; + +#[derive(Default)] +pub struct Convex; + +impl Convex { + pub fn create_node(vertices: u32) -> NodeTemplate { + let node_type = resolve_document_node_type("Regular Polygon").expect("Regular Polygon does not exist"); + node_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::U32(vertices), false)), Some(NodeInput::value(TaggedValue::F64(0.5), false))]) + } + + pub fn update_shape( + document: &DocumentMessageHandler, + ipp: &InputPreprocessorMessageHandler, + layer: LayerNodeIdentifier, + shape_tool_data: &mut ShapeToolData, + modifier: ShapeToolModifierKey, + responses: &mut VecDeque, + ) -> bool { + let (center, lock_ratio) = (modifier[0], modifier[1]); + if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) { + // TODO: We need to determine how to allow the polygon node to make irregular shapes + update_radius_sign(end, start, layer, document, responses); + + let dimensions = (start - end).abs(); + let mut scale = DVec2::ONE; + let radius: f64; + + // We keep the smaller dimension's scale at 1 and scale the other dimension accordingly + if dimensions.x > dimensions.y { + scale.x = dimensions.x / dimensions.y; + radius = dimensions.y / 2.; + } else { + scale.y = dimensions.y / dimensions.x; + radius = dimensions.x / 2.; + } + + let Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface) else { + return false; + }; + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 2), + input: NodeInput::value(TaggedValue::F64(radius), false), + }); + + responses.add(GraphOperationMessage::TransformSet { + layer, + transform: DAffine2::from_scale_angle_translation(scale, 0., (start + end) / 2.), + transform_in: TransformIn::Viewport, + skip_rerender: false, + }); + } + false + } +} diff --git a/editor/src/messages/tool/shapes/ellipse_shape.rs b/editor/src/messages/tool/shapes/ellipse_shape.rs new file mode 100644 index 0000000000..d662466833 --- /dev/null +++ b/editor/src/messages/tool/shapes/ellipse_shape.rs @@ -0,0 +1,54 @@ +use super::shape_utility::ShapeToolModifierKey; +use super::*; +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::tool_messages::tool_prelude::*; +use glam::DAffine2; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; +use std::collections::VecDeque; + +#[derive(Default)] +pub struct Ellipse; + +impl Ellipse { + pub fn create_node() -> NodeTemplate { + let node_type = resolve_document_node_type("Ellipse").expect("Ellipse node does not exist"); + 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, + layer: LayerNodeIdentifier, + shape_tool_data: &mut ShapeToolData, + modifier: ShapeToolModifierKey, + responses: &mut VecDeque, + ) -> bool { + let (center, lock_ratio) = (modifier[0], modifier[1]); + if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) { + let Some(node_id) = graph_modification_utils::get_ellipse_id(layer, &document.network_interface) else { + return true; + }; + + 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, + }); + } + false + } +} diff --git a/editor/src/messages/tool/shapes/line_shape.rs b/editor/src/messages/tool/shapes/line_shape.rs new file mode 100644 index 0000000000..02db424567 --- /dev/null +++ b/editor/src/messages/tool/shapes/line_shape.rs @@ -0,0 +1,130 @@ +use super::shape_utility::{LineInitData, ShapeToolModifierKey}; +use super::*; +use crate::consts::LINE_ROTATE_SNAP_ANGLE; +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::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapTypeConfiguration}; +use crate::messages::tool::tool_messages::shape_tool::ShapeToolData; +use crate::messages::tool::tool_messages::tool_prelude::*; +use glam::DVec2; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; +use std::collections::VecDeque; + +#[derive(Clone, Debug, Default)] +pub enum LineEnd { + #[default] + Start, + End, +} + +#[derive(Default)] +pub struct Line; + +impl Line { + pub fn create_node(document: &DocumentMessageHandler, init_data: LineInitData) -> NodeTemplate { + let drag_start = init_data.drag_start; + let node_type = resolve_document_node_type("Line").expect("Line node does not exist"); + node_type.node_template_input_override([ + None, + Some(NodeInput::value(TaggedValue::DVec2(document.metadata().document_to_viewport.transform_point2(drag_start)), false)), + Some(NodeInput::value(TaggedValue::DVec2(document.metadata().document_to_viewport.transform_point2(drag_start)), false)), + ]) + } + + pub fn update_shape( + document: &DocumentMessageHandler, + ipp: &InputPreprocessorMessageHandler, + layer: LayerNodeIdentifier, + shape_tool_data: &mut ShapeToolData, + modifier: ShapeToolModifierKey, + responses: &mut VecDeque, + ) -> bool { + let (center, snap_angle, lock_angle) = (modifier[0], modifier[3], modifier[2]); + shape_tool_data.drag_current = ipp.mouse.position; + let keyboard = &ipp.keyboard; + let ignore = vec![layer]; + let snap_data = SnapData::ignore(document, ipp, &ignore); + let document_points = generate_line(shape_tool_data, snap_data, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center)); + + let Some(node_id) = graph_modification_utils::get_line_id(layer, &document.network_interface) else { + return true; + }; + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 1), + input: NodeInput::value(TaggedValue::DVec2(document_points[0]), false), + }); + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 2), + input: NodeInput::value(TaggedValue::DVec2(document_points[1]), false), + }); + responses.add(NodeGraphMessage::RunDocumentGraph); + false + } +} + +fn generate_line(tool_data: &mut ShapeToolData, snap_data: SnapData, lock_angle: bool, snap_angle: bool, center: bool) -> [DVec2; 2] { + let document_to_viewport = snap_data.document.metadata().document_to_viewport; + let mut document_points = [tool_data.data.drag_start, document_to_viewport.inverse().transform_point2(tool_data.drag_current)]; + + let mut angle = -(document_points[1] - document_points[0]).angle_to(DVec2::X); + let mut line_length = (document_points[1] - document_points[0]).length(); + + if lock_angle { + angle = tool_data.angle; + } else if snap_angle { + let snap_resolution = LINE_ROTATE_SNAP_ANGLE.to_radians(); + angle = (angle / snap_resolution).round() * snap_resolution; + } + + tool_data.angle = angle; + + if lock_angle { + let angle_vec = DVec2::new(angle.cos(), angle.sin()); + line_length = (document_points[1] - document_points[0]).dot(angle_vec); + } + + document_points[1] = document_points[0] + line_length * DVec2::new(angle.cos(), angle.sin()); + + let constrained = snap_angle || lock_angle; + let snap = &mut tool_data.data.snap_manager; + + let near_point = SnapCandidatePoint::handle_neighbors(document_points[1], [tool_data.data.drag_start]); + let far_point = SnapCandidatePoint::handle_neighbors(2. * document_points[0] - document_points[1], [tool_data.data.drag_start]); + let config = SnapTypeConfiguration::default(); + + if constrained { + let constraint = SnapConstraint::Line { + origin: document_points[0], + direction: document_points[1] - document_points[0], + }; + if center { + let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config); + let snapped_far = snap.constrained_snap(&snap_data, &far_point, constraint, config); + let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far }; + document_points[1] = document_points[0] * 2. - best.snapped_point_document; + document_points[0] = best.snapped_point_document; + snap.update_indicator(best); + } else { + let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config); + document_points[1] = snapped.snapped_point_document; + snap.update_indicator(snapped); + } + } else if center { + let snapped = snap.free_snap(&snap_data, &near_point, config); + let snapped_far = snap.free_snap(&snap_data, &far_point, config); + let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far }; + document_points[1] = document_points[0] * 2. - best.snapped_point_document; + document_points[0] = best.snapped_point_document; + snap.update_indicator(best); + } else { + let snapped = snap.free_snap(&snap_data, &near_point, config); + document_points[1] = snapped.snapped_point_document; + snap.update_indicator(snapped); + } + + document_points +} diff --git a/editor/src/messages/tool/shapes/mod.rs b/editor/src/messages/tool/shapes/mod.rs new file mode 100644 index 0000000000..304d9c9dd5 --- /dev/null +++ b/editor/src/messages/tool/shapes/mod.rs @@ -0,0 +1,12 @@ +pub mod convex_shape; +pub mod ellipse_shape; +pub mod line_shape; +pub mod rectangle_shape; +pub mod shape_utility; +pub mod star_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::tool_messages::shape_tool::ShapeToolData; +use glam::DVec2; diff --git a/editor/src/messages/tool/shapes/rectangle_shape.rs b/editor/src/messages/tool/shapes/rectangle_shape.rs new file mode 100644 index 0000000000..04e0791a03 --- /dev/null +++ b/editor/src/messages/tool/shapes/rectangle_shape.rs @@ -0,0 +1,54 @@ +use super::shape_utility::ShapeToolModifierKey; +use super::*; +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::tool_messages::tool_prelude::*; +use glam::DAffine2; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; +use std::collections::VecDeque; + +#[derive(Default)] +pub struct Rectangle; + +impl Rectangle { + pub fn create_node() -> NodeTemplate { + let node_type = resolve_document_node_type("Rectangle").expect("Rectangle node does not exist"); + node_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(1.), false)), Some(NodeInput::value(TaggedValue::F64(1.), false))]) + } + + pub fn update_shape( + document: &DocumentMessageHandler, + ipp: &InputPreprocessorMessageHandler, + layer: LayerNodeIdentifier, + shape_tool_data: &mut ShapeToolData, + modifier: ShapeToolModifierKey, + responses: &mut VecDeque, + ) -> bool { + let (center, lock_ratio) = (modifier[0], modifier[1]); + if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) { + let Some(node_id) = graph_modification_utils::get_rectangle_id(layer, &document.network_interface) else { + return true; + }; + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 1), + input: NodeInput::value(TaggedValue::F64((start.x - end.x).abs()), false), + }); + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 2), + input: NodeInput::value(TaggedValue::F64((start.y - end.y).abs()), false), + }); + responses.add(GraphOperationMessage::TransformSet { + layer, + transform: DAffine2::from_translation(start.midpoint(end)), + transform_in: TransformIn::Viewport, + skip_rerender: false, + }); + } + false + } +} diff --git a/editor/src/messages/tool/shapes/shape_utility.rs b/editor/src/messages/tool/shapes/shape_utility.rs new file mode 100644 index 0000000000..e290de6a93 --- /dev/null +++ b/editor/src/messages/tool/shapes/shape_utility.rs @@ -0,0 +1,98 @@ +use crate::messages::message::Message; +use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; +use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; +use crate::messages::prelude::{DocumentMessageHandler, NodeGraphMessage, Responses}; +use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; +use crate::messages::tool::tool_messages::tool_prelude::Key; +use crate::messages::tool::utility_types::*; +use glam::DVec2; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; +use std::collections::VecDeque; + +#[derive(Debug, Clone, Copy, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)] +pub enum ShapeType { + #[default] + Convex = 0, + Star = 1, + Rectangle = 2, + Ellipse = 3, + Line = 4, +} + +impl ShapeType { + pub fn name(&self) -> String { + match self { + Self::Convex => "Convex", + Self::Star => "Star", + Self::Rectangle => "Rectangle", + Self::Ellipse => "Ellipse", + Self::Line => "Line", + } + .into() + } + + pub fn tooltip(&self) -> String { + match self { + Self::Line => "Line tool", + Self::Rectangle => "Rectangle tool", + Self::Ellipse => "Ellipse tool", + _ => "", + } + .into() + } + + pub fn icon_name(&self) -> String { + match self { + Self::Line => "VectorLineTool", + Self::Rectangle => "VectorRectangleTool", + Self::Ellipse => "VectorEllipseTool", + _ => "", + } + .into() + } + + pub fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { + match self { + Self::Line => ToolType::Line, + Self::Rectangle => ToolType::Rectangle, + Self::Ellipse => ToolType::Ellipse, + _ => ToolType::Shape, + } + } +} + +pub struct LineInitData { + pub drag_start: DVec2, +} + +// Center, Lock Ratio, Lock Angle, Snap Angle +pub type ShapeToolModifierKey = [Key; 4]; + +pub fn update_radius_sign(end: DVec2, start: DVec2, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, responses: &mut VecDeque) { + let sign_num = if end[1] > start[1] { 1. } else { -1. }; + let new_layer = NodeGraphLayer::new(layer, &document.network_interface); + + if new_layer.find_input("Regular Polygon", 1).unwrap_or(&TaggedValue::U32(0)).to_u32() % 2 == 1 { + let Some(polygon_node_id) = new_layer.upstream_node_id_from_name("Regular Polygon") else { return }; + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(polygon_node_id, 2), + input: NodeInput::value(TaggedValue::F64(sign_num * 0.5), false), + }); + return; + } + + if new_layer.find_input("Star", 1).unwrap_or(&TaggedValue::U32(0)).to_u32() % 2 == 1 { + let Some(star_node_id) = new_layer.upstream_node_id_from_name("Star") else { return }; + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(star_node_id, 2), + input: NodeInput::value(TaggedValue::F64(sign_num * 0.5), false), + }); + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(star_node_id, 3), + input: NodeInput::value(TaggedValue::F64(sign_num * 0.25), false), + }); + } +} diff --git a/editor/src/messages/tool/shapes/star_shape.rs b/editor/src/messages/tool/shapes/star_shape.rs new file mode 100644 index 0000000000..fc9d013ab2 --- /dev/null +++ b/editor/src/messages/tool/shapes/star_shape.rs @@ -0,0 +1,77 @@ +use super::shape_utility::{ShapeToolModifierKey, update_radius_sign}; +use super::*; +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::tool_messages::tool_prelude::*; +use glam::DAffine2; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; +use std::collections::VecDeque; + +#[derive(Default)] +pub struct Star; + +impl Star { + pub fn create_node(vertices: u32) -> NodeTemplate { + let node_type = resolve_document_node_type("Star").expect(" Star node does not exist"); + node_type.node_template_input_override([ + None, + Some(NodeInput::value(TaggedValue::U32(vertices), false)), + Some(NodeInput::value(TaggedValue::F64(0.5), false)), + Some(NodeInput::value(TaggedValue::F64(0.25), false)), + ]) + } + + pub fn update_shape( + document: &DocumentMessageHandler, + ipp: &InputPreprocessorMessageHandler, + layer: LayerNodeIdentifier, + shape_tool_data: &mut ShapeToolData, + modifier: ShapeToolModifierKey, + responses: &mut VecDeque, + ) -> bool { + let (center, lock_ratio) = (modifier[0], modifier[1]); + if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) { + // TODO: We need to determine how to allow the polygon node to make irregular shapes + update_radius_sign(end, start, layer, document, responses); + + let dimensions = (start - end).abs(); + let mut scale = DVec2::ONE; + let radius: f64; + + // We keep the smaller dimension's scale at 1 and scale the other dimension accordingly + if dimensions.x > dimensions.y { + scale.x = dimensions.x / dimensions.y; + radius = dimensions.y / 2.; + } else { + scale.y = dimensions.y / dimensions.x; + radius = dimensions.x / 2.; + } + + let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface) else { + return false; + }; + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 2), + input: NodeInput::value(TaggedValue::F64(radius), false), + }); + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 3), + input: NodeInput::value(TaggedValue::F64(radius / 2.), false), + }); + + responses.add(GraphOperationMessage::TransformSet { + layer, + transform: DAffine2::from_scale_angle_translation(scale, 0., (start + end) / 2.), + transform_in: TransformIn::Viewport, + skip_rerender: false, + }); + } + false + } +} diff --git a/editor/src/messages/tool/tool_message.rs b/editor/src/messages/tool/tool_message.rs index ede4e4008d..16c81106ff 100644 --- a/editor/src/messages/tool/tool_message.rs +++ b/editor/src/messages/tool/tool_message.rs @@ -32,11 +32,7 @@ pub enum ToolMessage { #[child] Spline(SplineToolMessage), #[child] - Line(LineToolMessage), - #[child] - Rectangle(RectangleToolMessage), - #[child] - Ellipse(EllipseToolMessage), + Shape(ShapeToolMessage), #[child] Polygon(PolygonToolMessage), #[child] @@ -70,9 +66,10 @@ pub enum ToolMessage { ActivateToolPen, ActivateToolFreehand, ActivateToolSpline, - ActivateToolLine, - ActivateToolRectangle, - ActivateToolEllipse, + ActivateToolShape, + ActivateShapeRectangle, + ActivateShapeEllipse, + ActivateShapeLine, ActivateToolPolygon, ActivateToolBrush, diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index b70affe51b..4b50655149 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -1,4 +1,5 @@ use super::common_functionality::shape_editor::ShapeState; +use super::shapes::shape_utility::ShapeType::{Ellipse, Line, Rectangle}; use super::utility_types::{ToolActionHandlerData, ToolFsmState, tool_message_to_tool_type}; use crate::application::generate_uuid; use crate::messages::layout::utility_types::widget_prelude::*; @@ -58,21 +59,52 @@ impl MessageHandler> for ToolMessageHandler { ToolMessage::ActivateToolPen => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Pen }), ToolMessage::ActivateToolFreehand => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Freehand }), ToolMessage::ActivateToolSpline => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Spline }), - ToolMessage::ActivateToolLine => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Line }), - ToolMessage::ActivateToolRectangle => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Rectangle }), - ToolMessage::ActivateToolEllipse => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Ellipse }), + ToolMessage::ActivateToolShape => { + if self.tool_state.tool_data.active_shape_type.is_some() { + self.tool_state.tool_data.active_shape_type = None; + self.tool_state.tool_data.active_tool_type = ToolType::Shape; + } + responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape }); + responses.add(ShapeToolMessage::HideShapeTypeWidget(false)) + } ToolMessage::ActivateToolPolygon => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Polygon }), ToolMessage::ActivateToolBrush => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Brush }), + ToolMessage::ActivateShapeRectangle | ToolMessage::ActivateShapeEllipse | ToolMessage::ActivateShapeLine => { + responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape }); + let shape = match message { + ToolMessage::ActivateShapeLine => Line, + ToolMessage::ActivateShapeEllipse => Ellipse, + ToolMessage::ActivateShapeRectangle => Rectangle, + _ => unreachable!(), + }; + self.tool_state.tool_data.active_shape_type = Some(shape.tool_type()); + responses.add(ToolMessage::RefreshToolOptions); + self.tool_state.tool_data.send_layout(responses, LayoutTarget::ToolShelf); + responses.add(ShapeToolMessage::HideShapeTypeWidget(true)); + responses.add(ShapeToolMessage::SetShape(shape)); + } // ToolMessage::ActivateToolImaginate => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Imaginate }), ToolMessage::ActivateTool { tool_type } => { let tool_data = &mut self.tool_state.tool_data; let old_tool = tool_data.active_tool_type; + // let shape = tool_type; + let tool_type = tool_type.get_tool(); + let old_tool = old_tool.get_tool(); + + // tool_data.active_shape_type = if tool_type != ToolType::Shape { None } else { old_shape }; + + responses.add(ToolMessage::RefreshToolOptions); + tool_data.send_layout(responses, LayoutTarget::ToolShelf); // Do nothing if switching to the same tool if self.tool_is_active && tool_type == old_tool { return; } + + if tool_type != ToolType::Shape { + tool_data.active_shape_type = None; + } self.tool_is_active = true; // Send the old and new tools a transition to their FSM Abort states @@ -307,14 +339,17 @@ impl MessageHandler> for ToolMessageHandler { ActivateToolPen, ActivateToolFreehand, ActivateToolSpline, - ActivateToolLine, - ActivateToolRectangle, - ActivateToolEllipse, + ActivateToolShape, + ActivateToolPolygon, ActivateToolBrush, // ActivateToolImaginate, + ActivateShapeRectangle, + ActivateShapeEllipse, + ActivateShapeLine, + SelectRandomPrimaryColor, ResetColors, SwapColors, diff --git a/editor/src/messages/tool/tool_messages/ellipse_tool.rs b/editor/src/messages/tool/tool_messages/ellipse_tool.rs index e86a3763d8..27be03f4ee 100644 --- a/editor/src/messages/tool/tool_messages/ellipse_tool.rs +++ b/editor/src/messages/tool/tool_messages/ellipse_tool.rs @@ -1,441 +1,441 @@ -use super::tool_prelude::*; -use crate::consts::DEFAULT_STROKE_WIDTH; -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::overlays::utility_types::OverlayContext; -use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; -use crate::messages::tool::common_functionality::auto_panning::AutoPanning; -use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; -use crate::messages::tool::common_functionality::graph_modification_utils; -use crate::messages::tool::common_functionality::resize::Resize; -use crate::messages::tool::common_functionality::snapping::SnapData; -use graph_craft::document::value::TaggedValue; -use graph_craft::document::{NodeId, NodeInput}; -use graphene_core::Color; - -#[derive(Default)] -pub struct EllipseTool { - fsm_state: EllipseToolFsmState, - data: EllipseToolData, - options: EllipseToolOptions, -} - -pub struct EllipseToolOptions { - line_weight: f64, - fill: ToolColorOptions, - stroke: ToolColorOptions, -} - -impl Default for EllipseToolOptions { - fn default() -> Self { - Self { - line_weight: DEFAULT_STROKE_WIDTH, - fill: ToolColorOptions::new_secondary(), - stroke: ToolColorOptions::new_primary(), - } - } -} - -#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] -pub enum EllipseOptionsUpdate { - FillColor(Option), - FillColorType(ToolColorType), - LineWeight(f64), - StrokeColor(Option), - StrokeColorType(ToolColorType), - WorkingColors(Option, Option), -} - -#[impl_message(Message, ToolMessage, Ellipse)] -#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] -pub enum EllipseToolMessage { - // Standard messages - Overlays(OverlayContext), - Abort, - WorkingColorChanged, - - // Tool-specific messages - DragStart, - DragStop, - PointerMove { center: Key, lock_ratio: Key }, - PointerOutsideViewport { center: Key, lock_ratio: Key }, - UpdateOptions(EllipseOptionsUpdate), -} - -impl ToolMetadata for EllipseTool { - fn icon_name(&self) -> String { - "VectorEllipseTool".into() - } - fn tooltip(&self) -> String { - "Ellipse Tool".into() - } - fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { - ToolType::Ellipse - } -} - -fn create_weight_widget(line_weight: f64) -> WidgetHolder { - NumberInput::new(Some(line_weight)) - .unit(" px") - .label("Weight") - .min(0.) - .max((1_u64 << f64::MANTISSA_DIGITS) as f64) - .on_update(|number_input: &NumberInput| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) - .widget_holder() -} - -impl LayoutHolder for EllipseTool { - fn layout(&self) -> Layout { - let mut widgets = self.options.fill.create_widgets( - "Fill", - true, - |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColor(None)).into(), - |color_type: ToolColorType| WidgetCallback::new(move |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColorType(color_type.clone())).into()), - |color: &ColorInput| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), - ); - - widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); - - widgets.append(&mut self.options.stroke.create_widgets( - "Stroke", - true, - |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColor(None)).into(), - |color_type: ToolColorType| WidgetCallback::new(move |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColorType(color_type.clone())).into()), - |color: &ColorInput| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), - )); - widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); - widgets.push(create_weight_widget(self.options.line_weight)); - - Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) - } -} - -impl<'a> MessageHandler> for EllipseTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { - let ToolMessage::Ellipse(EllipseToolMessage::UpdateOptions(action)) = message else { - self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true); - return; - }; - match action { - EllipseOptionsUpdate::FillColor(color) => { - self.options.fill.custom_color = color; - self.options.fill.color_type = ToolColorType::Custom; - } - EllipseOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, - EllipseOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, - EllipseOptionsUpdate::StrokeColor(color) => { - self.options.stroke.custom_color = color; - self.options.stroke.color_type = ToolColorType::Custom; - } - EllipseOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, - EllipseOptionsUpdate::WorkingColors(primary, secondary) => { - self.options.stroke.primary_working_color = primary; - self.options.stroke.secondary_working_color = secondary; - self.options.fill.primary_working_color = primary; - self.options.fill.secondary_working_color = secondary; - } - } - - self.send_layout(responses, LayoutTarget::ToolOptions); - } - - fn actions(&self) -> ActionList { - match self.fsm_state { - EllipseToolFsmState::Ready => actions!(EllipseToolMessageDiscriminant; - DragStart, - PointerMove, - ), - EllipseToolFsmState::Drawing => actions!(EllipseToolMessageDiscriminant; - DragStop, - Abort, - PointerMove, - ), - } - } -} - -impl ToolTransition for EllipseTool { - fn event_to_message_map(&self) -> EventToMessageMap { - EventToMessageMap { - overlay_provider: Some(|overlay_context| EllipseToolMessage::Overlays(overlay_context).into()), - tool_abort: Some(EllipseToolMessage::Abort.into()), - working_color_changed: Some(EllipseToolMessage::WorkingColorChanged.into()), - ..Default::default() - } - } -} - -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -enum EllipseToolFsmState { - #[default] - Ready, - Drawing, -} - -#[derive(Clone, Debug, Default)] -struct EllipseToolData { - data: Resize, - auto_panning: AutoPanning, -} - -impl Fsm for EllipseToolFsmState { - type ToolData = EllipseToolData; - type ToolOptions = EllipseToolOptions; - - fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { - let ToolActionHandlerData { - document, global_tool_data, input, .. - } = tool_action_data; - - let shape_data = &mut tool_data.data; - - let ToolMessage::Ellipse(event) = event else { return self }; - match (self, event) { - (_, EllipseToolMessage::Overlays(mut overlay_context)) => { - shape_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); - self - } - (EllipseToolFsmState::Ready, EllipseToolMessage::DragStart) => { - shape_data.start(document, input); - responses.add(DocumentMessage::StartTransaction); - - // Create a new ellipse vector shape - let node_type = resolve_document_node_type("Ellipse").expect("Ellipse node does not exist"); - let node = node_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(0.5), false)), Some(NodeInput::value(TaggedValue::F64(0.5), false))]); - let nodes = vec![(NodeId(0), node)]; - - let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input), responses); - responses.add(Message::StartBuffer); - responses.add(GraphOperationMessage::TransformSet { - layer, - transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), - transform_in: TransformIn::Viewport, - skip_rerender: false, - }); - tool_options.fill.apply_fill(layer, responses); - tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); - shape_data.layer = Some(layer); - - EllipseToolFsmState::Drawing - } - (EllipseToolFsmState::Drawing, EllipseToolMessage::PointerMove { center, lock_ratio }) => { - if let Some([start, end]) = shape_data.calculate_points(document, input, center, lock_ratio) { - if let Some(layer) = shape_data.layer { - let Some(node_id) = graph_modification_utils::get_ellipse_id(layer, &document.network_interface) else { - return self; - }; - - 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 + end) / 2.), - transform_in: TransformIn::Viewport, - skip_rerender: false, - }); - } - } - - // Auto-panning - let messages = [ - EllipseToolMessage::PointerOutsideViewport { center, lock_ratio }.into(), - EllipseToolMessage::PointerMove { center, lock_ratio }.into(), - ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); - - self - } - (_, EllipseToolMessage::PointerMove { .. }) => { - shape_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); - responses.add(OverlaysMessage::Draw); - self - } - (EllipseToolFsmState::Drawing, EllipseToolMessage::PointerOutsideViewport { .. }) => { - // Auto-panning - let _ = tool_data.auto_panning.shift_viewport(input, responses); - - EllipseToolFsmState::Drawing - } - (state, EllipseToolMessage::PointerOutsideViewport { center, lock_ratio }) => { - // Auto-panning - let messages = [ - EllipseToolMessage::PointerOutsideViewport { center, lock_ratio }.into(), - EllipseToolMessage::PointerMove { center, lock_ratio }.into(), - ]; - tool_data.auto_panning.stop(&messages, responses); - - state - } - (EllipseToolFsmState::Drawing, EllipseToolMessage::DragStop) => { - input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); - shape_data.cleanup(responses); - - EllipseToolFsmState::Ready - } - (EllipseToolFsmState::Drawing, EllipseToolMessage::Abort) => { - responses.add(DocumentMessage::AbortTransaction); - shape_data.cleanup(responses); - - EllipseToolFsmState::Ready - } - (_, EllipseToolMessage::WorkingColorChanged) => { - responses.add(EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::WorkingColors( - Some(global_tool_data.primary_color), - Some(global_tool_data.secondary_color), - ))); - self - } - _ => self, - } - } - - fn update_hints(&self, responses: &mut VecDeque) { - let hint_data = match self { - EllipseToolFsmState::Ready => HintData(vec![HintGroup(vec![ - HintInfo::mouse(MouseMotion::LmbDrag, "Draw Ellipse"), - HintInfo::keys([Key::Shift], "Constrain Circular").prepend_plus(), - HintInfo::keys([Key::Alt], "From Center").prepend_plus(), - ])]), - EllipseToolFsmState::Drawing => HintData(vec![ - HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), - HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Circular"), HintInfo::keys([Key::Alt], "From Center")]), - ]), - }; - - responses.add(FrontendMessage::UpdateInputHints { hint_data }); - } - - fn update_cursor(&self, responses: &mut VecDeque) { - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }); - } -} - -#[cfg(test)] -mod test_ellipse { - pub use crate::test_utils::test_prelude::*; - use glam::DAffine2; - use graphene_core::vector::generator_nodes::ellipse; - - #[derive(Debug, PartialEq)] - struct ResolvedEllipse { - radius_x: f64, - radius_y: f64, - transform: DAffine2, - } - - async fn get_ellipse(editor: &mut EditorTestUtils) -> Vec { - let instrumented = editor.eval_graph().await; - - let document = editor.active_document(); - let layers = document.metadata().all_layers(); - layers - .filter_map(|layer| { - let node_graph_layer = NodeGraphLayer::new(layer, &document.network_interface); - let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::protonode_identifier())?; - Some(ResolvedEllipse { - radius_x: instrumented.grab_protonode_input::(&vec![ellipse_node], &editor.runtime).unwrap(), - radius_y: instrumented.grab_protonode_input::(&vec![ellipse_node], &editor.runtime).unwrap(), - transform: document.metadata().transform_to_document(layer), - }) - }) - .collect() - } - - #[tokio::test] - async fn ellipse_draw_simple() { - let mut editor = EditorTestUtils::create(); - editor.new_document().await; - editor.drag_tool(ToolType::Ellipse, 10., 10., 19., 0., ModifierKeys::empty()).await; - - assert_eq!(editor.active_document().metadata().all_layers().count(), 1); - - let ellipse = get_ellipse(&mut editor).await; - assert_eq!(ellipse.len(), 1); - assert_eq!( - ellipse[0], - ResolvedEllipse { - radius_x: 4.5, - radius_y: 5., - transform: DAffine2::from_translation(DVec2::new(14.5, 5.)) // Uses center - } - ); - } - - #[tokio::test] - async fn ellipse_draw_circle() { - let mut editor = EditorTestUtils::create(); - editor.new_document().await; - editor.drag_tool(ToolType::Ellipse, 10., 10., -10., 11., ModifierKeys::SHIFT).await; - - let ellipse = get_ellipse(&mut editor).await; - assert_eq!(ellipse.len(), 1); - assert_eq!( - ellipse[0], - ResolvedEllipse { - radius_x: 10., - radius_y: 10., - transform: DAffine2::from_translation(DVec2::new(0., 20.)) // Uses center - } - ); - } - - #[tokio::test] - async fn ellipse_draw_square_rotated() { - let mut editor = EditorTestUtils::create(); - editor.new_document().await; - editor - .handle_message(NavigationMessage::CanvasTiltSet { - // 45 degree rotation of content clockwise - angle_radians: f64::consts::FRAC_PI_4, - }) - .await; - editor.drag_tool(ToolType::Ellipse, 0., 0., 1., 10., ModifierKeys::SHIFT).await; // Viewport coordinates - - let ellipse = get_ellipse(&mut editor).await; - assert_eq!(ellipse.len(), 1); - println!("{ellipse:?}"); - assert_eq!(ellipse[0].radius_x, 5.); - assert_eq!(ellipse[0].radius_y, 5.); - - assert!( - ellipse[0] - .transform - .abs_diff_eq(DAffine2::from_angle_translation(-f64::consts::FRAC_PI_4, DVec2::X * f64::consts::FRAC_1_SQRT_2 * 10.), 0.001) - ); - } - - #[tokio::test] - async fn ellipse_draw_center_square_rotated() { - let mut editor = EditorTestUtils::create(); - editor.new_document().await; - editor - .handle_message(NavigationMessage::CanvasTiltSet { - // 45 degree rotation of content clockwise - angle_radians: f64::consts::FRAC_PI_4, - }) - .await; - editor.drag_tool(ToolType::Ellipse, 0., 0., 1., 10., ModifierKeys::SHIFT | ModifierKeys::ALT).await; // Viewport coordinates - - let ellipse = get_ellipse(&mut editor).await; - assert_eq!(ellipse.len(), 1); - assert_eq!(ellipse[0].radius_x, 10.); - assert_eq!(ellipse[0].radius_y, 10.); - assert!(ellipse[0].transform.abs_diff_eq(DAffine2::from_angle(-f64::consts::FRAC_PI_4), 0.001)); - } - - #[tokio::test] - async fn ellipse_cancel() { - let mut editor = EditorTestUtils::create(); - editor.new_document().await; - editor.drag_tool_cancel_rmb(ToolType::Ellipse).await; - - let ellipse = get_ellipse(&mut editor).await; - assert_eq!(ellipse.len(), 0); - } -} +// use super::tool_prelude::*; +// use crate::consts::DEFAULT_STROKE_WIDTH; +// 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::overlays::utility_types::OverlayContext; +// use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; +// use crate::messages::tool::common_functionality::auto_panning::AutoPanning; +// use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; +// use crate::messages::tool::common_functionality::graph_modification_utils; +// use crate::messages::tool::common_functionality::resize::Resize; +// use crate::messages::tool::common_functionality::snapping::SnapData; +// use graph_craft::document::value::TaggedValue; +// use graph_craft::document::{NodeId, NodeInput}; +// use graphene_core::Color; + +// #[derive(Default)] +// pub struct EllipseTool { +// fsm_state: EllipseToolFsmState, +// data: EllipseToolData, +// options: EllipseToolOptions, +// } + +// pub struct EllipseToolOptions { +// line_weight: f64, +// fill: ToolColorOptions, +// stroke: ToolColorOptions, +// } + +// impl Default for EllipseToolOptions { +// fn default() -> Self { +// Self { +// line_weight: DEFAULT_STROKE_WIDTH, +// fill: ToolColorOptions::new_secondary(), +// stroke: ToolColorOptions::new_primary(), +// } +// } +// } + +// #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +// pub enum EllipseOptionsUpdate { +// FillColor(Option), +// FillColorType(ToolColorType), +// LineWeight(f64), +// StrokeColor(Option), +// StrokeColorType(ToolColorType), +// WorkingColors(Option, Option), +// } + +// #[impl_message(Message, ToolMessage, Ellipse)] +// #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +// pub enum EllipseToolMessage { +// // Standard messages +// Overlays(OverlayContext), +// Abort, +// WorkingColorChanged, + +// // Tool-specific messages +// DragStart, +// DragStop, +// PointerMove { center: Key, lock_ratio: Key }, +// PointerOutsideViewport { center: Key, lock_ratio: Key }, +// UpdateOptions(EllipseOptionsUpdate), +// } + +// impl ToolMetadata for EllipseTool { +// fn icon_name(&self) -> String { +// "VectorEllipseTool".into() +// } +// fn tooltip(&self) -> String { +// "Ellipse Tool".into() +// } +// fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { +// ToolType::Ellipse +// } +// } + +// fn create_weight_widget(line_weight: f64) -> WidgetHolder { +// NumberInput::new(Some(line_weight)) +// .unit(" px") +// .label("Weight") +// .min(0.) +// .max((1_u64 << f64::MANTISSA_DIGITS) as f64) +// .on_update(|number_input: &NumberInput| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) +// .widget_holder() +// } + +// impl LayoutHolder for EllipseTool { +// fn layout(&self) -> Layout { +// let mut widgets = self.options.fill.create_widgets( +// "Fill", +// true, +// |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColor(None)).into(), +// |color_type: ToolColorType| WidgetCallback::new(move |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColorType(color_type.clone())).into()), +// |color: &ColorInput| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), +// ); + +// widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); + +// widgets.append(&mut self.options.stroke.create_widgets( +// "Stroke", +// true, +// |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColor(None)).into(), +// |color_type: ToolColorType| WidgetCallback::new(move |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColorType(color_type.clone())).into()), +// |color: &ColorInput| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), +// )); +// widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); +// widgets.push(create_weight_widget(self.options.line_weight)); + +// Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) +// } +// } + +// impl<'a> MessageHandler> for EllipseTool { +// fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { +// let ToolMessage::Ellipse(EllipseToolMessage::UpdateOptions(action)) = message else { +// self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true); +// return; +// }; +// match action { +// EllipseOptionsUpdate::FillColor(color) => { +// self.options.fill.custom_color = color; +// self.options.fill.color_type = ToolColorType::Custom; +// } +// EllipseOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, +// EllipseOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, +// EllipseOptionsUpdate::StrokeColor(color) => { +// self.options.stroke.custom_color = color; +// self.options.stroke.color_type = ToolColorType::Custom; +// } +// EllipseOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, +// EllipseOptionsUpdate::WorkingColors(primary, secondary) => { +// self.options.stroke.primary_working_color = primary; +// self.options.stroke.secondary_working_color = secondary; +// self.options.fill.primary_working_color = primary; +// self.options.fill.secondary_working_color = secondary; +// } +// } + +// self.send_layout(responses, LayoutTarget::ToolOptions); +// } + +// fn actions(&self) -> ActionList { +// match self.fsm_state { +// EllipseToolFsmState::Ready => actions!(EllipseToolMessageDiscriminant; +// DragStart, +// PointerMove, +// ), +// EllipseToolFsmState::Drawing => actions!(EllipseToolMessageDiscriminant; +// DragStop, +// Abort, +// PointerMove, +// ), +// } +// } +// } + +// impl ToolTransition for EllipseTool { +// fn event_to_message_map(&self) -> EventToMessageMap { +// EventToMessageMap { +// overlay_provider: Some(|overlay_context| EllipseToolMessage::Overlays(overlay_context).into()), +// tool_abort: Some(EllipseToolMessage::Abort.into()), +// working_color_changed: Some(EllipseToolMessage::WorkingColorChanged.into()), +// ..Default::default() +// } +// } +// } + +// #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +// enum EllipseToolFsmState { +// #[default] +// Ready, +// Drawing, +// } + +// #[derive(Clone, Debug, Default)] +// struct EllipseToolData { +// data: Resize, +// auto_panning: AutoPanning, +// } + +// impl Fsm for EllipseToolFsmState { +// type ToolData = EllipseToolData; +// type ToolOptions = EllipseToolOptions; + +// fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { +// let ToolActionHandlerData { +// document, global_tool_data, input, .. +// } = tool_action_data; + +// let shape_data = &mut tool_data.data; + +// let ToolMessage::Ellipse(event) = event else { return self }; +// match (self, event) { +// (_, EllipseToolMessage::Overlays(mut overlay_context)) => { +// shape_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); +// self +// } +// (EllipseToolFsmState::Ready, EllipseToolMessage::DragStart) => { +// shape_data.start(document, input); +// responses.add(DocumentMessage::StartTransaction); + +// // Create a new ellipse vector shape +// let node_type = resolve_document_node_type("Ellipse").expect("Ellipse node does not exist"); +// let node = node_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(0.5), false)), Some(NodeInput::value(TaggedValue::F64(0.5), false))]); +// let nodes = vec![(NodeId(0), node)]; + +// let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input), responses); +// responses.add(Message::StartBuffer); +// responses.add(GraphOperationMessage::TransformSet { +// layer, +// transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), +// transform_in: TransformIn::Viewport, +// skip_rerender: false, +// }); +// tool_options.fill.apply_fill(layer, responses); +// tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); +// shape_data.layer = Some(layer); + +// EllipseToolFsmState::Drawing +// } +// (EllipseToolFsmState::Drawing, EllipseToolMessage::PointerMove { center, lock_ratio }) => { +// if let Some([start, end]) = shape_data.calculate_points(document, input, center, lock_ratio) { +// if let Some(layer) = shape_data.layer { +// let Some(node_id) = graph_modification_utils::get_ellipse_id(layer, &document.network_interface) else { +// return self; +// }; + +// 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 + end) / 2.), +// transform_in: TransformIn::Viewport, +// skip_rerender: false, +// }); +// } +// } + +// // Auto-panning +// let messages = [ +// EllipseToolMessage::PointerOutsideViewport { center, lock_ratio }.into(), +// EllipseToolMessage::PointerMove { center, lock_ratio }.into(), +// ]; +// tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + +// self +// } +// (_, EllipseToolMessage::PointerMove { .. }) => { +// shape_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); +// responses.add(OverlaysMessage::Draw); +// self +// } +// (EllipseToolFsmState::Drawing, EllipseToolMessage::PointerOutsideViewport { .. }) => { +// // Auto-panning +// let _ = tool_data.auto_panning.shift_viewport(input, responses); + +// EllipseToolFsmState::Drawing +// } +// (state, EllipseToolMessage::PointerOutsideViewport { center, lock_ratio }) => { +// // Auto-panning +// let messages = [ +// EllipseToolMessage::PointerOutsideViewport { center, lock_ratio }.into(), +// EllipseToolMessage::PointerMove { center, lock_ratio }.into(), +// ]; +// tool_data.auto_panning.stop(&messages, responses); + +// state +// } +// (EllipseToolFsmState::Drawing, EllipseToolMessage::DragStop) => { +// input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); +// shape_data.cleanup(responses); + +// EllipseToolFsmState::Ready +// } +// (EllipseToolFsmState::Drawing, EllipseToolMessage::Abort) => { +// responses.add(DocumentMessage::AbortTransaction); +// shape_data.cleanup(responses); + +// EllipseToolFsmState::Ready +// } +// (_, EllipseToolMessage::WorkingColorChanged) => { +// responses.add(EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::WorkingColors( +// Some(global_tool_data.primary_color), +// Some(global_tool_data.secondary_color), +// ))); +// self +// } +// _ => self, +// } +// } + +// fn update_hints(&self, responses: &mut VecDeque) { +// let hint_data = match self { +// EllipseToolFsmState::Ready => HintData(vec![HintGroup(vec![ +// HintInfo::mouse(MouseMotion::LmbDrag, "Draw Ellipse"), +// HintInfo::keys([Key::Shift], "Constrain Circular").prepend_plus(), +// HintInfo::keys([Key::Alt], "From Center").prepend_plus(), +// ])]), +// EllipseToolFsmState::Drawing => HintData(vec![ +// HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), +// HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Circular"), HintInfo::keys([Key::Alt], "From Center")]), +// ]), +// }; + +// responses.add(FrontendMessage::UpdateInputHints { hint_data }); +// } + +// fn update_cursor(&self, responses: &mut VecDeque) { +// responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }); +// } +// } + +// #[cfg(test)] +// mod test_ellipse { +// pub use crate::test_utils::test_prelude::*; +// use glam::DAffine2; +// use graphene_core::vector::generator_nodes::ellipse; + +// #[derive(Debug, PartialEq)] +// struct ResolvedEllipse { +// radius_x: f64, +// radius_y: f64, +// transform: DAffine2, +// } + +// async fn get_ellipse(editor: &mut EditorTestUtils) -> Vec { +// let instrumented = editor.eval_graph().await; + +// let document = editor.active_document(); +// let layers = document.metadata().all_layers(); +// layers +// .filter_map(|layer| { +// let node_graph_layer = NodeGraphLayer::new(layer, &document.network_interface); +// let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::protonode_identifier())?; +// Some(ResolvedEllipse { +// radius_x: instrumented.grab_protonode_input::(&vec![ellipse_node], &editor.runtime).unwrap(), +// radius_y: instrumented.grab_protonode_input::(&vec![ellipse_node], &editor.runtime).unwrap(), +// transform: document.metadata().transform_to_document(layer), +// }) +// }) +// .collect() +// } + +// #[tokio::test] +// async fn ellipse_draw_simple() { +// let mut editor = EditorTestUtils::create(); +// editor.new_document().await; +// editor.drag_tool(ToolType::Ellipse, 10., 10., 19., 0., ModifierKeys::empty()).await; + +// assert_eq!(editor.active_document().metadata().all_layers().count(), 1); + +// let ellipse = get_ellipse(&mut editor).await; +// assert_eq!(ellipse.len(), 1); +// assert_eq!( +// ellipse[0], +// ResolvedEllipse { +// radius_x: 4.5, +// radius_y: 5., +// transform: DAffine2::from_translation(DVec2::new(14.5, 5.)) // Uses center +// } +// ); +// } + +// #[tokio::test] +// async fn ellipse_draw_circle() { +// let mut editor = EditorTestUtils::create(); +// editor.new_document().await; +// editor.drag_tool(ToolType::Ellipse, 10., 10., -10., 11., ModifierKeys::SHIFT).await; + +// let ellipse = get_ellipse(&mut editor).await; +// assert_eq!(ellipse.len(), 1); +// assert_eq!( +// ellipse[0], +// ResolvedEllipse { +// radius_x: 10., +// radius_y: 10., +// transform: DAffine2::from_translation(DVec2::new(0., 20.)) // Uses center +// } +// ); +// } + +// #[tokio::test] +// async fn ellipse_draw_square_rotated() { +// let mut editor = EditorTestUtils::create(); +// editor.new_document().await; +// editor +// .handle_message(NavigationMessage::CanvasTiltSet { +// // 45 degree rotation of content clockwise +// angle_radians: f64::consts::FRAC_PI_4, +// }) +// .await; +// editor.drag_tool(ToolType::Ellipse, 0., 0., 1., 10., ModifierKeys::SHIFT).await; // Viewport coordinates + +// let ellipse = get_ellipse(&mut editor).await; +// assert_eq!(ellipse.len(), 1); +// println!("{ellipse:?}"); +// assert_eq!(ellipse[0].radius_x, 5.); +// assert_eq!(ellipse[0].radius_y, 5.); + +// assert!( +// ellipse[0] +// .transform +// .abs_diff_eq(DAffine2::from_angle_translation(-f64::consts::FRAC_PI_4, DVec2::X * f64::consts::FRAC_1_SQRT_2 * 10.), 0.001) +// ); +// } + +// #[tokio::test] +// async fn ellipse_draw_center_square_rotated() { +// let mut editor = EditorTestUtils::create(); +// editor.new_document().await; +// editor +// .handle_message(NavigationMessage::CanvasTiltSet { +// // 45 degree rotation of content clockwise +// angle_radians: f64::consts::FRAC_PI_4, +// }) +// .await; +// editor.drag_tool(ToolType::Ellipse, 0., 0., 1., 10., ModifierKeys::SHIFT | ModifierKeys::ALT).await; // Viewport coordinates + +// let ellipse = get_ellipse(&mut editor).await; +// assert_eq!(ellipse.len(), 1); +// assert_eq!(ellipse[0].radius_x, 10.); +// assert_eq!(ellipse[0].radius_y, 10.); +// assert!(ellipse[0].transform.abs_diff_eq(DAffine2::from_angle(-f64::consts::FRAC_PI_4), 0.001)); +// } + +// #[tokio::test] +// async fn ellipse_cancel() { +// let mut editor = EditorTestUtils::create(); +// editor.new_document().await; +// editor.drag_tool_cancel_rmb(ToolType::Ellipse).await; + +// let ellipse = get_ellipse(&mut editor).await; +// assert_eq!(ellipse.len(), 0); +// } +// } diff --git a/editor/src/messages/tool/tool_messages/line_tool.rs b/editor/src/messages/tool/tool_messages/line_tool.rs index 0bd281a620..130ca866be 100644 --- a/editor/src/messages/tool/tool_messages/line_tool.rs +++ b/editor/src/messages/tool/tool_messages/line_tool.rs @@ -1,624 +1,624 @@ -use super::tool_prelude::*; -use crate::consts::{BOUNDS_SELECT_THRESHOLD, DEFAULT_STROKE_WIDTH, LINE_ROTATE_SNAP_ANGLE}; -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; -use crate::messages::tool::common_functionality::auto_panning::AutoPanning; -use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; -use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer}; -use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration}; -use graph_craft::document::value::TaggedValue; -use graph_craft::document::{NodeId, NodeInput}; -use graphene_core::Color; - -#[derive(Default)] -pub struct LineTool { - fsm_state: LineToolFsmState, - tool_data: LineToolData, - options: LineOptions, -} - -pub struct LineOptions { - line_weight: f64, - stroke: ToolColorOptions, -} - -impl Default for LineOptions { - fn default() -> Self { - Self { - line_weight: DEFAULT_STROKE_WIDTH, - stroke: ToolColorOptions::new_primary(), - } - } -} - -#[impl_message(Message, ToolMessage, Line)] -#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] -pub enum LineToolMessage { - // Standard messages - Overlays(OverlayContext), - Abort, - WorkingColorChanged, - - // Tool-specific messages - DragStart, - DragStop, - PointerMove { center: Key, lock_angle: Key, snap_angle: Key }, - PointerOutsideViewport { center: Key, lock_angle: Key, snap_angle: Key }, - UpdateOptions(LineOptionsUpdate), -} - -#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] -pub enum LineOptionsUpdate { - LineWeight(f64), - StrokeColor(Option), - StrokeColorType(ToolColorType), - WorkingColors(Option, Option), -} - -impl ToolMetadata for LineTool { - fn icon_name(&self) -> String { - "VectorLineTool".into() - } - fn tooltip(&self) -> String { - "Line Tool".into() - } - fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { - ToolType::Line - } -} - -fn create_weight_widget(line_weight: f64) -> WidgetHolder { - NumberInput::new(Some(line_weight)) - .unit(" px") - .label("Weight") - .min(0.) - .max((1_u64 << f64::MANTISSA_DIGITS) as f64) - .on_update(|number_input: &NumberInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) - .widget_holder() -} - -impl LayoutHolder for LineTool { - fn layout(&self) -> Layout { - let mut widgets = self.options.stroke.create_widgets( - "Stroke", - true, - |_| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(None)).into(), - |color_type: ToolColorType| WidgetCallback::new(move |_| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColorType(color_type.clone())).into()), - |color: &ColorInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), - ); - widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); - widgets.push(create_weight_widget(self.options.line_weight)); - - Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) - } -} - -impl<'a> MessageHandler> for LineTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { - let ToolMessage::Line(LineToolMessage::UpdateOptions(action)) = message else { - self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); - return; - }; - match action { - LineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, - LineOptionsUpdate::StrokeColor(color) => { - self.options.stroke.custom_color = color; - self.options.stroke.color_type = ToolColorType::Custom; - } - LineOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, - LineOptionsUpdate::WorkingColors(primary, secondary) => { - self.options.stroke.primary_working_color = primary; - self.options.stroke.secondary_working_color = secondary; - } - } - - self.send_layout(responses, LayoutTarget::ToolOptions); - } - - fn actions(&self) -> ActionList { - match self.fsm_state { - LineToolFsmState::Ready => actions!(LineToolMessageDiscriminant; DragStart, PointerMove), - LineToolFsmState::Drawing => actions!(LineToolMessageDiscriminant; DragStop, PointerMove, Abort), - } - } -} - -impl ToolTransition for LineTool { - fn event_to_message_map(&self) -> EventToMessageMap { - EventToMessageMap { - overlay_provider: Some(|overlay_context| LineToolMessage::Overlays(overlay_context).into()), - tool_abort: Some(LineToolMessage::Abort.into()), - working_color_changed: Some(LineToolMessage::WorkingColorChanged.into()), - ..Default::default() - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] -enum LineToolFsmState { - #[default] - Ready, - Drawing, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum LineEnd { - Start, - End, -} - -#[derive(Clone, Debug, Default)] -struct LineToolData { - drag_begin: DVec2, - drag_start_shifted: DVec2, - drag_current_shifted: DVec2, - drag_start: DVec2, - drag_current: DVec2, - angle: f64, - weight: f64, - selected_layers_with_position: HashMap, - editing_layer: Option, - snap_manager: SnapManager, - auto_panning: AutoPanning, - dragging_endpoint: Option, -} - -impl Fsm for LineToolFsmState { - type ToolData = LineToolData; - type ToolOptions = LineOptions; - - fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { - let ToolActionHandlerData { - document, global_tool_data, input, .. - } = tool_action_data; - - let ToolMessage::Line(event) = event else { return self }; - match (self, event) { - (_, LineToolMessage::Overlays(mut overlay_context)) => { - tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); - - tool_data.selected_layers_with_position = document - .network_interface - .selected_nodes() - .selected_visible_and_unlocked_layers(&document.network_interface) - .filter_map(|layer| { - let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Line")?; - - let (Some(&TaggedValue::DVec2(start)), Some(&TaggedValue::DVec2(end))) = (node_inputs[1].as_value(), node_inputs[2].as_value()) else { - return None; - }; - - let [viewport_start, viewport_end] = [start, end].map(|point| document.metadata().transform_to_viewport(layer).transform_point2(point)); - if !start.abs_diff_eq(end, f64::EPSILON * 1000.) { - overlay_context.line(viewport_start, viewport_end, None, None); - overlay_context.square(viewport_start, Some(6.), None, None); - overlay_context.square(viewport_end, Some(6.), None, None); - } - - Some((layer, [start, end])) - }) - .collect::>(); - - self - } - (LineToolFsmState::Ready, LineToolMessage::DragStart) => { - let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); - let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); - tool_data.drag_start = snapped.snapped_point_document; - tool_data.drag_begin = document.metadata().document_to_viewport.transform_point2(tool_data.drag_start); - - responses.add(DocumentMessage::StartTransaction); - - for (layer, [document_start, document_end]) in tool_data.selected_layers_with_position.iter() { - let transform = document.metadata().transform_to_viewport(*layer); - let viewport_x = transform.transform_vector2(DVec2::X).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD; - let viewport_y = transform.transform_vector2(DVec2::Y).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD; - let threshold_x = transform.inverse().transform_vector2(viewport_x).length(); - let threshold_y = transform.inverse().transform_vector2(viewport_y).length(); - - let drag_start = input.mouse.position; - let [start, end] = [document_start, document_end].map(|point| transform.transform_point2(*point)); - - let start_click = (drag_start.y - start.y).abs() < threshold_y && (drag_start.x - start.x).abs() < threshold_x; - let end_click = (drag_start.y - end.y).abs() < threshold_y && (drag_start.x - end.x).abs() < threshold_x; - - if start_click || end_click { - tool_data.dragging_endpoint = Some(if end_click { LineEnd::End } else { LineEnd::Start }); - tool_data.drag_start = if end_click { *document_start } else { *document_end }; - tool_data.editing_layer = Some(*layer); - return LineToolFsmState::Drawing; - } - } - - let node_type = resolve_document_node_type("Line").expect("Line node does not exist"); - let node = node_type.node_template_input_override([ - None, - Some(NodeInput::value( - TaggedValue::DVec2(document.metadata().document_to_viewport.transform_point2(tool_data.drag_start)), - false, - )), - Some(NodeInput::value( - TaggedValue::DVec2(document.metadata().document_to_viewport.transform_point2(tool_data.drag_start)), - false, - )), - ]); - let nodes = vec![(NodeId(0), node)]; - - let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input), responses); - responses.add(Message::StartBuffer); - - tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); - - tool_data.editing_layer = Some(layer); - tool_data.angle = 0.; - tool_data.weight = tool_options.line_weight; - - LineToolFsmState::Drawing - } - (LineToolFsmState::Drawing, LineToolMessage::PointerMove { center, snap_angle, lock_angle }) => { - let Some(layer) = tool_data.editing_layer else { return LineToolFsmState::Ready }; - - tool_data.drag_current_shifted = document.metadata().transform_to_viewport(layer).inverse().transform_point2(input.mouse.position); - tool_data.drag_current = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); - tool_data.drag_start_shifted = document.metadata().transform_to_viewport(layer).inverse().transform_point2(tool_data.drag_begin); - - let keyboard = &input.keyboard; - let ignore = vec![layer]; - let snap_data = SnapData::ignore(document, input, &ignore); - let mut document_points = generate_line(tool_data, snap_data, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center)); - - if tool_data.dragging_endpoint == Some(LineEnd::Start) { - document_points.swap(0, 1); - } - - let Some(node_id) = graph_modification_utils::get_line_id(layer, &document.network_interface) else { - return LineToolFsmState::Ready; - }; - - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 1), - input: NodeInput::value(TaggedValue::DVec2(document_points[0]), false), - }); - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 2), - input: NodeInput::value(TaggedValue::DVec2(document_points[1]), false), - }); - responses.add(NodeGraphMessage::RunDocumentGraph); - - // Auto-panning - let messages = [ - LineToolMessage::PointerOutsideViewport { center, snap_angle, lock_angle }.into(), - LineToolMessage::PointerMove { center, snap_angle, lock_angle }.into(), - ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); - - LineToolFsmState::Drawing - } - (_, LineToolMessage::PointerMove { .. }) => { - tool_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); - responses.add(OverlaysMessage::Draw); - self - } - (LineToolFsmState::Drawing, LineToolMessage::PointerOutsideViewport { .. }) => { - // Auto-panning - let _ = tool_data.auto_panning.shift_viewport(input, responses); - - LineToolFsmState::Drawing - } - (state, LineToolMessage::PointerOutsideViewport { center, lock_angle, snap_angle }) => { - // Auto-panning - let messages = [ - LineToolMessage::PointerOutsideViewport { center, lock_angle, snap_angle }.into(), - LineToolMessage::PointerMove { center, lock_angle, snap_angle }.into(), - ]; - tool_data.auto_panning.stop(&messages, responses); - - state - } - (LineToolFsmState::Drawing, LineToolMessage::DragStop) => { - tool_data.snap_manager.cleanup(responses); - tool_data.editing_layer.take(); - input.mouse.finish_transaction(tool_data.drag_start, responses); - LineToolFsmState::Ready - } - (LineToolFsmState::Drawing, LineToolMessage::Abort) => { - tool_data.snap_manager.cleanup(responses); - tool_data.editing_layer.take(); - responses.add(DocumentMessage::AbortTransaction); - LineToolFsmState::Ready - } - (_, LineToolMessage::WorkingColorChanged) => { - responses.add(LineToolMessage::UpdateOptions(LineOptionsUpdate::WorkingColors( - Some(global_tool_data.primary_color), - Some(global_tool_data.secondary_color), - ))); - self - } - _ => self, - } - } - - fn update_hints(&self, responses: &mut VecDeque) { - let hint_data = match self { - LineToolFsmState::Ready => HintData(vec![HintGroup(vec![ - HintInfo::mouse(MouseMotion::LmbDrag, "Draw Line"), - HintInfo::keys([Key::Shift], "15° Increments").prepend_plus(), - HintInfo::keys([Key::Alt], "From Center").prepend_plus(), - HintInfo::keys([Key::Control], "Lock Angle").prepend_plus(), - ])]), - LineToolFsmState::Drawing => HintData(vec![ - HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), - HintGroup(vec![ - HintInfo::keys([Key::Shift], "15° Increments"), - HintInfo::keys([Key::Alt], "From Center"), - HintInfo::keys([Key::Control], "Lock Angle"), - ]), - ]), - }; - - responses.add(FrontendMessage::UpdateInputHints { hint_data }); - } - - fn update_cursor(&self, responses: &mut VecDeque) { - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }); - } -} - -fn generate_line(tool_data: &mut LineToolData, snap_data: SnapData, lock_angle: bool, snap_angle: bool, center: bool) -> [DVec2; 2] { - let shift = tool_data.drag_current_shifted - tool_data.drag_current; - let mut document_points = [tool_data.drag_start, tool_data.drag_current]; - - let mut angle = -(document_points[1] - document_points[0]).angle_to(DVec2::X); - let mut line_length = (document_points[1] - document_points[0]).length(); - - if lock_angle { - angle = tool_data.angle; - } else if snap_angle { - let snap_resolution = LINE_ROTATE_SNAP_ANGLE.to_radians(); - angle = (angle / snap_resolution).round() * snap_resolution; - } - - tool_data.angle = angle; - - let angle_vec = DVec2::from_angle(angle); - if lock_angle { - line_length = (document_points[1] - document_points[0]).dot(angle_vec); - } - - document_points[1] = document_points[0] + line_length * angle_vec; - - let constrained = snap_angle || lock_angle; - let snap = &mut tool_data.snap_manager; - - let near_point = SnapCandidatePoint::handle_neighbors(document_points[1], [tool_data.drag_start]); - let far_point = SnapCandidatePoint::handle_neighbors(2. * document_points[0] - document_points[1], [tool_data.drag_start]); - let mid_point = SnapCandidatePoint::handle_neighbors((tool_data.drag_start + document_points[1]) / 2., [tool_data.drag_start]); - let config = SnapTypeConfiguration::default(); - - if constrained { - let constraint = SnapConstraint::Line { - origin: document_points[0], - direction: document_points[1] - document_points[0], - }; - if center { - let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config); - let snapped_far = snap.constrained_snap(&snap_data, &far_point, constraint, config); - let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far }; - document_points[1] = document_points[0] * 2. - best.snapped_point_document; - document_points[0] = best.snapped_point_document; - snap.update_indicator(best); - } else { - let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config); - let snapped_mid = snap.constrained_snap(&snap_data, &mid_point, constraint, config); - let best = if snap_data.document.snapping_state.path.line_midpoint && snapped_mid.other_snap_better(&snapped_mid) { - document_points[1] += (snapped_mid.snapped_point_document - mid_point.document_point) * 2.; - snapped_mid - } else { - document_points[1] = snapped.snapped_point_document; - snapped.clone() - }; - snap.update_indicator(best); - } - } else if center { - let snapped = snap.free_snap(&snap_data, &near_point, config); - let snapped_far = snap.free_snap(&snap_data, &far_point, config); - let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far }; - document_points[1] = document_points[0] * 2. - best.snapped_point_document; - document_points[0] = best.snapped_point_document; - snap.update_indicator(best); - } else { - let snapped = snap.free_snap(&snap_data, &near_point, config); - let snapped_mid = snap.free_snap(&snap_data, &mid_point, config); - let best = if snap_data.document.snapping_state.path.line_midpoint && snapped_mid.other_snap_better(&snapped_mid) { - document_points[1] += (snapped_mid.snapped_point_document - mid_point.document_point) * 2.; - snapped_mid - } else { - document_points[1] = snapped.snapped_point_document; - snapped.clone() - }; - snap.update_indicator(best); - } - - // Snapping happens in other space, while document graph renders in another. - document_points.map(|vector| vector + shift) -} - -#[cfg(test)] -mod test_line_tool { - use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; - use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; - use crate::test_utils::test_prelude::*; - use glam::DAffine2; - use graph_craft::document::value::TaggedValue; - - async fn get_line_node_inputs(editor: &mut EditorTestUtils) -> Option<(DVec2, DVec2)> { - let document = editor.active_document(); - let network_interface = &document.network_interface; - let node_id = network_interface - .selected_nodes() - .selected_visible_and_unlocked_layers(network_interface) - .filter_map(|layer| { - let node_inputs = NodeGraphLayer::new(layer, &network_interface).find_node_inputs("Line")?; - let (Some(&TaggedValue::DVec2(start)), Some(&TaggedValue::DVec2(end))) = (node_inputs[1].as_value(), node_inputs[2].as_value()) else { - return None; - }; - Some((start, end)) - }) - .next(); - node_id - } - - #[tokio::test] - async fn test_line_tool_basicdraw() { - let mut editor = EditorTestUtils::create(); - editor.new_document().await; - editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::empty()).await; - if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { - match (start_input, end_input) { - (start_input, end_input) => { - assert!((start_input - DVec2::ZERO).length() < 1., "Start point should be near (0,0)"); - assert!((end_input - DVec2::new(100., 100.)).length() < 1., "End point should be near (100,100)"); - } - } - } - } - - #[tokio::test] - async fn test_line_tool_with_transformed_viewport() { - let mut editor = EditorTestUtils::create(); - editor.new_document().await; - editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2. }).await; - editor.handle_message(NavigationMessage::CanvasPan { delta: DVec2::new(100., 50.) }).await; - editor - .handle_message(NavigationMessage::CanvasTiltSet { - angle_radians: (30. as f64).to_radians(), - }) - .await; - editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::empty()).await; - if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { - let document = editor.active_document(); - let document_to_viewport = document.metadata().document_to_viewport; - let viewport_to_document = document_to_viewport.inverse(); - - let expected_start = viewport_to_document.transform_point2(DVec2::ZERO); - let expected_end = viewport_to_document.transform_point2(DVec2::new(100., 100.)); - - assert!( - (start_input - expected_start).length() < 1., - "Start point should match expected document coordinates. Got {:?}, expected {:?}", - start_input, - expected_start - ); - assert!( - (end_input - expected_end).length() < 1., - "End point should match expected document coordinates. Got {:?}, expected {:?}", - end_input, - expected_end - ); - } else { - panic!("Line was not created successfully with transformed viewport"); - } - } - - #[tokio::test] - async fn test_line_tool_ctrl_anglelock() { - let mut editor = EditorTestUtils::create(); - editor.new_document().await; - editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::CONTROL).await; - if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { - match (start_input, end_input) { - (start_input, end_input) => { - let line_vec = end_input - start_input; - let original_angle = line_vec.angle_to(DVec2::X); - editor.drag_tool(ToolType::Line, 0., 0., 200., 50., ModifierKeys::CONTROL).await; - if let Some((updated_start, updated_end)) = get_line_node_inputs(&mut editor).await { - match (updated_start, updated_end) { - (updated_start, updated_end) => { - let updated_line_vec = updated_end - updated_start; - let updated_angle = updated_line_vec.angle_to(DVec2::X); - assert!((original_angle - updated_angle).abs() < 0.1, "Line angle should be locked when Ctrl is kept pressed"); - assert!((updated_start - updated_end).length() > 1., "Line should be able to change length when Ctrl is kept pressed"); - } - } - } - } - } - } - } - - #[tokio::test] - async fn test_line_tool_alt() { - let mut editor = EditorTestUtils::create(); - editor.new_document().await; - editor.drag_tool(ToolType::Line, 100., 100., 200., 100., ModifierKeys::ALT).await; - if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { - match (start_input, end_input) { - (start_input, end_input) => { - let expected_start = DVec2::new(0., 100.); - let expected_end = DVec2::new(200., 100.); - assert!((start_input - expected_start).length() < 1., "start point should be near (0,100)"); - assert!((end_input - expected_end).length() < 1., "end point should be near (200,100)"); - } - } - } - } - - #[tokio::test] - async fn test_line_tool_alt_shift_drag() { - let mut editor = EditorTestUtils::create(); - editor.new_document().await; - editor.drag_tool(ToolType::Line, 100., 100., 150., 120., ModifierKeys::ALT | ModifierKeys::SHIFT).await; - if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { - match (start_input, end_input) { - (start_input, end_input) => { - let line_vec = end_input - start_input; - let angle_radians = line_vec.angle_to(DVec2::X); - let angle_degrees = angle_radians.to_degrees(); - let nearest_angle = (angle_degrees / 15.).round() * 15.; - - assert!((angle_degrees - nearest_angle).abs() < 1., "Angle should snap to the nearest 15 degrees"); - } - } - } - } - - #[tokio::test] - async fn test_line_tool_with_transformed_artboard() { - let mut editor = EditorTestUtils::create(); - editor.new_document().await; - editor.drag_tool(ToolType::Artboard, 0., 0., 200., 200., ModifierKeys::empty()).await; - - let artboard_id = editor.get_selected_layer().await.expect("Should have selected the artboard"); - - editor - .handle_message(GraphOperationMessage::TransformChange { - layer: artboard_id, - transform: DAffine2::from_angle(45.0_f64.to_radians()), - transform_in: TransformIn::Local, - skip_rerender: false, - }) - .await; - - editor.drag_tool(ToolType::Line, 50., 50., 150., 150., ModifierKeys::empty()).await; - - let (start_input, end_input) = get_line_node_inputs(&mut editor).await.expect("Line was not created successfully within transformed artboard"); - // The line should still be diagonal with equal change in x and y - let line_vector = end_input - start_input; - // Verifying the line is approximately 100*sqrt(2) units in length (diagonal of 100x100 square) - let line_length = line_vector.length(); - assert!( - (line_length - 141.42).abs() < 1.0, // 100 * sqrt(2) ~= 141.42 - "Line length should be approximately 141.42 units. Got: {line_length}" - ); - assert!((line_vector.x - 100.0).abs() < 1.0, "X-component of line vector should be approximately 100. Got: {}", line_vector.x); - assert!( - (line_vector.y.abs() - 100.0).abs() < 1.0, - "Absolute Y-component of line vector should be approximately 100. Got: {}", - line_vector.y.abs() - ); - let angle_degrees = line_vector.angle_to(DVec2::X).to_degrees(); - assert!((angle_degrees - (-45.0)).abs() < 1.0, "Line angle should be close to -45 degrees. Got: {angle_degrees}"); - } -} +// use super::tool_prelude::*; +// use crate::consts::{BOUNDS_SELECT_THRESHOLD, DEFAULT_STROKE_WIDTH, LINE_ROTATE_SNAP_ANGLE}; +// 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; +// use crate::messages::tool::common_functionality::auto_panning::AutoPanning; +// use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; +// use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer}; +// use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration}; +// use graph_craft::document::value::TaggedValue; +// use graph_craft::document::{NodeId, NodeInput}; +// use graphene_core::Color; + +// #[derive(Default)] +// pub struct LineTool { +// fsm_state: LineToolFsmState, +// tool_data: LineToolData, +// options: LineOptions, +// } + +// pub struct LineOptions { +// line_weight: f64, +// stroke: ToolColorOptions, +// } + +// impl Default for LineOptions { +// fn default() -> Self { +// Self { +// line_weight: DEFAULT_STROKE_WIDTH, +// stroke: ToolColorOptions::new_primary(), +// } +// } +// } + +// #[impl_message(Message, ToolMessage, Line)] +// #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +// pub enum LineToolMessage { +// // Standard messages +// Overlays(OverlayContext), +// Abort, +// WorkingColorChanged, + +// // Tool-specific messages +// DragStart, +// DragStop, +// PointerMove { center: Key, lock_angle: Key, snap_angle: Key }, +// PointerOutsideViewport { center: Key, lock_angle: Key, snap_angle: Key }, +// UpdateOptions(LineOptionsUpdate), +// } + +// #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +// pub enum LineOptionsUpdate { +// LineWeight(f64), +// StrokeColor(Option), +// StrokeColorType(ToolColorType), +// WorkingColors(Option, Option), +// } + +// impl ToolMetadata for LineTool { +// fn icon_name(&self) -> String { +// "VectorLineTool".into() +// } +// fn tooltip(&self) -> String { +// "Line Tool".into() +// } +// fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { +// ToolType::Line +// } +// } + +// fn create_weight_widget(line_weight: f64) -> WidgetHolder { +// NumberInput::new(Some(line_weight)) +// .unit(" px") +// .label("Weight") +// .min(0.) +// .max((1_u64 << f64::MANTISSA_DIGITS) as f64) +// .on_update(|number_input: &NumberInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) +// .widget_holder() +// } + +// impl LayoutHolder for LineTool { +// fn layout(&self) -> Layout { +// let mut widgets = self.options.stroke.create_widgets( +// "Stroke", +// true, +// |_| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(None)).into(), +// |color_type: ToolColorType| WidgetCallback::new(move |_| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColorType(color_type.clone())).into()), +// |color: &ColorInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), +// ); +// widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); +// widgets.push(create_weight_widget(self.options.line_weight)); + +// Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) +// } +// } + +// impl<'a> MessageHandler> for LineTool { +// fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { +// let ToolMessage::Line(LineToolMessage::UpdateOptions(action)) = message else { +// self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); +// return; +// }; +// match action { +// LineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, +// LineOptionsUpdate::StrokeColor(color) => { +// self.options.stroke.custom_color = color; +// self.options.stroke.color_type = ToolColorType::Custom; +// } +// LineOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, +// LineOptionsUpdate::WorkingColors(primary, secondary) => { +// self.options.stroke.primary_working_color = primary; +// self.options.stroke.secondary_working_color = secondary; +// } +// } + +// self.send_layout(responses, LayoutTarget::ToolOptions); +// } + +// fn actions(&self) -> ActionList { +// match self.fsm_state { +// LineToolFsmState::Ready => actions!(LineToolMessageDiscriminant; DragStart, PointerMove), +// LineToolFsmState::Drawing => actions!(LineToolMessageDiscriminant; DragStop, PointerMove, Abort), +// } +// } +// } + +// impl ToolTransition for LineTool { +// fn event_to_message_map(&self) -> EventToMessageMap { +// EventToMessageMap { +// overlay_provider: Some(|overlay_context| LineToolMessage::Overlays(overlay_context).into()), +// tool_abort: Some(LineToolMessage::Abort.into()), +// working_color_changed: Some(LineToolMessage::WorkingColorChanged.into()), +// ..Default::default() +// } +// } +// } + +// #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +// enum LineToolFsmState { +// #[default] +// Ready, +// Drawing, +// } + +// #[derive(Clone, Copy, Debug, PartialEq, Eq)] +// enum LineEnd { +// Start, +// End, +// } + +// #[derive(Clone, Debug, Default)] +// struct LineToolData { +// drag_begin: DVec2, +// drag_start_shifted: DVec2, +// drag_current_shifted: DVec2, +// drag_start: DVec2, +// drag_current: DVec2, +// angle: f64, +// weight: f64, +// selected_layers_with_position: HashMap, +// editing_layer: Option, +// snap_manager: SnapManager, +// auto_panning: AutoPanning, +// dragging_endpoint: Option, +// } + +// impl Fsm for LineToolFsmState { +// type ToolData = LineToolData; +// type ToolOptions = LineOptions; + +// fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { +// let ToolActionHandlerData { +// document, global_tool_data, input, .. +// } = tool_action_data; + +// let ToolMessage::Line(event) = event else { return self }; +// match (self, event) { +// (_, LineToolMessage::Overlays(mut overlay_context)) => { +// tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); + +// tool_data.selected_layers_with_position = document +// .network_interface +// .selected_nodes() +// .selected_visible_and_unlocked_layers(&document.network_interface) +// .filter_map(|layer| { +// let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Line")?; + +// let (Some(&TaggedValue::DVec2(start)), Some(&TaggedValue::DVec2(end))) = (node_inputs[1].as_value(), node_inputs[2].as_value()) else { +// return None; +// }; + +// let [viewport_start, viewport_end] = [start, end].map(|point| document.metadata().transform_to_viewport(layer).transform_point2(point)); +// if !start.abs_diff_eq(end, f64::EPSILON * 1000.) { +// overlay_context.line(viewport_start, viewport_end, None, None); +// overlay_context.square(viewport_start, Some(6.), None, None); +// overlay_context.square(viewport_end, Some(6.), None, None); +// } + +// Some((layer, [start, end])) +// }) +// .collect::>(); + +// self +// } +// (LineToolFsmState::Ready, LineToolMessage::DragStart) => { +// let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); +// let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); +// tool_data.drag_start = snapped.snapped_point_document; +// tool_data.drag_begin = document.metadata().document_to_viewport.transform_point2(tool_data.drag_start); + +// responses.add(DocumentMessage::StartTransaction); + +// for (layer, [document_start, document_end]) in tool_data.selected_layers_with_position.iter() { +// let transform = document.metadata().transform_to_viewport(*layer); +// let viewport_x = transform.transform_vector2(DVec2::X).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD; +// let viewport_y = transform.transform_vector2(DVec2::Y).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD; +// let threshold_x = transform.inverse().transform_vector2(viewport_x).length(); +// let threshold_y = transform.inverse().transform_vector2(viewport_y).length(); + +// let drag_start = input.mouse.position; +// let [start, end] = [document_start, document_end].map(|point| transform.transform_point2(*point)); + +// let start_click = (drag_start.y - start.y).abs() < threshold_y && (drag_start.x - start.x).abs() < threshold_x; +// let end_click = (drag_start.y - end.y).abs() < threshold_y && (drag_start.x - end.x).abs() < threshold_x; + +// if start_click || end_click { +// tool_data.dragging_endpoint = Some(if end_click { LineEnd::End } else { LineEnd::Start }); +// tool_data.drag_start = if end_click { *document_start } else { *document_end }; +// tool_data.editing_layer = Some(*layer); +// return LineToolFsmState::Drawing; +// } +// } + +// let node_type = resolve_document_node_type("Line").expect("Line node does not exist"); +// let node = node_type.node_template_input_override([ +// None, +// Some(NodeInput::value( +// TaggedValue::DVec2(document.metadata().document_to_viewport.transform_point2(tool_data.drag_start)), +// false, +// )), +// Some(NodeInput::value( +// TaggedValue::DVec2(document.metadata().document_to_viewport.transform_point2(tool_data.drag_start)), +// false, +// )), +// ]); +// let nodes = vec![(NodeId(0), node)]; + +// let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input), responses); +// responses.add(Message::StartBuffer); + +// tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); + +// tool_data.editing_layer = Some(layer); +// tool_data.angle = 0.; +// tool_data.weight = tool_options.line_weight; + +// LineToolFsmState::Drawing +// } +// (LineToolFsmState::Drawing, LineToolMessage::PointerMove { center, snap_angle, lock_angle }) => { +// let Some(layer) = tool_data.editing_layer else { return LineToolFsmState::Ready }; + +// tool_data.drag_current_shifted = document.metadata().transform_to_viewport(layer).inverse().transform_point2(input.mouse.position); +// tool_data.drag_current = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); +// tool_data.drag_start_shifted = document.metadata().transform_to_viewport(layer).inverse().transform_point2(tool_data.drag_begin); + +// let keyboard = &input.keyboard; +// let ignore = vec![layer]; +// let snap_data = SnapData::ignore(document, input, &ignore); +// let mut document_points = generate_line(tool_data, snap_data, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center)); + +// if tool_data.dragging_endpoint == Some(LineEnd::Start) { +// document_points.swap(0, 1); +// } + +// let Some(node_id) = graph_modification_utils::get_line_id(layer, &document.network_interface) else { +// return LineToolFsmState::Ready; +// }; + +// responses.add(NodeGraphMessage::SetInput { +// input_connector: InputConnector::node(node_id, 1), +// input: NodeInput::value(TaggedValue::DVec2(document_points[0]), false), +// }); +// responses.add(NodeGraphMessage::SetInput { +// input_connector: InputConnector::node(node_id, 2), +// input: NodeInput::value(TaggedValue::DVec2(document_points[1]), false), +// }); +// responses.add(NodeGraphMessage::RunDocumentGraph); + +// // Auto-panning +// let messages = [ +// LineToolMessage::PointerOutsideViewport { center, snap_angle, lock_angle }.into(), +// LineToolMessage::PointerMove { center, snap_angle, lock_angle }.into(), +// ]; +// tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + +// LineToolFsmState::Drawing +// } +// (_, LineToolMessage::PointerMove { .. }) => { +// tool_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); +// responses.add(OverlaysMessage::Draw); +// self +// } +// (LineToolFsmState::Drawing, LineToolMessage::PointerOutsideViewport { .. }) => { +// // Auto-panning +// let _ = tool_data.auto_panning.shift_viewport(input, responses); + +// LineToolFsmState::Drawing +// } +// (state, LineToolMessage::PointerOutsideViewport { center, lock_angle, snap_angle }) => { +// // Auto-panning +// let messages = [ +// LineToolMessage::PointerOutsideViewport { center, lock_angle, snap_angle }.into(), +// LineToolMessage::PointerMove { center, lock_angle, snap_angle }.into(), +// ]; +// tool_data.auto_panning.stop(&messages, responses); + +// state +// } +// (LineToolFsmState::Drawing, LineToolMessage::DragStop) => { +// tool_data.snap_manager.cleanup(responses); +// tool_data.editing_layer.take(); +// input.mouse.finish_transaction(tool_data.drag_start, responses); +// LineToolFsmState::Ready +// } +// (LineToolFsmState::Drawing, LineToolMessage::Abort) => { +// tool_data.snap_manager.cleanup(responses); +// tool_data.editing_layer.take(); +// responses.add(DocumentMessage::AbortTransaction); +// LineToolFsmState::Ready +// } +// (_, LineToolMessage::WorkingColorChanged) => { +// responses.add(LineToolMessage::UpdateOptions(LineOptionsUpdate::WorkingColors( +// Some(global_tool_data.primary_color), +// Some(global_tool_data.secondary_color), +// ))); +// self +// } +// _ => self, +// } +// } + +// fn update_hints(&self, responses: &mut VecDeque) { +// let hint_data = match self { +// LineToolFsmState::Ready => HintData(vec![HintGroup(vec![ +// HintInfo::mouse(MouseMotion::LmbDrag, "Draw Line"), +// HintInfo::keys([Key::Shift], "15° Increments").prepend_plus(), +// HintInfo::keys([Key::Alt], "From Center").prepend_plus(), +// HintInfo::keys([Key::Control], "Lock Angle").prepend_plus(), +// ])]), +// LineToolFsmState::Drawing => HintData(vec![ +// HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), +// HintGroup(vec![ +// HintInfo::keys([Key::Shift], "15° Increments"), +// HintInfo::keys([Key::Alt], "From Center"), +// HintInfo::keys([Key::Control], "Lock Angle"), +// ]), +// ]), +// }; + +// responses.add(FrontendMessage::UpdateInputHints { hint_data }); +// } + +// fn update_cursor(&self, responses: &mut VecDeque) { +// responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }); +// } +// } + +// fn generate_line(tool_data: &mut LineToolData, snap_data: SnapData, lock_angle: bool, snap_angle: bool, center: bool) -> [DVec2; 2] { +// let shift = tool_data.drag_current_shifted - tool_data.drag_current; +// let mut document_points = [tool_data.drag_start, tool_data.drag_current]; + +// let mut angle = -(document_points[1] - document_points[0]).angle_to(DVec2::X); +// let mut line_length = (document_points[1] - document_points[0]).length(); + +// if lock_angle { +// angle = tool_data.angle; +// } else if snap_angle { +// let snap_resolution = LINE_ROTATE_SNAP_ANGLE.to_radians(); +// angle = (angle / snap_resolution).round() * snap_resolution; +// } + +// tool_data.angle = angle; + +// let angle_vec = DVec2::from_angle(angle); +// if lock_angle { +// line_length = (document_points[1] - document_points[0]).dot(angle_vec); +// } + +// document_points[1] = document_points[0] + line_length * angle_vec; + +// let constrained = snap_angle || lock_angle; +// let snap = &mut tool_data.snap_manager; + +// let near_point = SnapCandidatePoint::handle_neighbors(document_points[1], [tool_data.drag_start]); +// let far_point = SnapCandidatePoint::handle_neighbors(2. * document_points[0] - document_points[1], [tool_data.drag_start]); +// let mid_point = SnapCandidatePoint::handle_neighbors((tool_data.drag_start + document_points[1]) / 2., [tool_data.drag_start]); +// let config = SnapTypeConfiguration::default(); + +// if constrained { +// let constraint = SnapConstraint::Line { +// origin: document_points[0], +// direction: document_points[1] - document_points[0], +// }; +// if center { +// let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config); +// let snapped_far = snap.constrained_snap(&snap_data, &far_point, constraint, config); +// let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far }; +// document_points[1] = document_points[0] * 2. - best.snapped_point_document; +// document_points[0] = best.snapped_point_document; +// snap.update_indicator(best); +// } else { +// let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config); +// let snapped_mid = snap.constrained_snap(&snap_data, &mid_point, constraint, config); +// let best = if snap_data.document.snapping_state.path.line_midpoint && snapped_mid.other_snap_better(&snapped_mid) { +// document_points[1] += (snapped_mid.snapped_point_document - mid_point.document_point) * 2.; +// snapped_mid +// } else { +// document_points[1] = snapped.snapped_point_document; +// snapped.clone() +// }; +// snap.update_indicator(best); +// } +// } else if center { +// let snapped = snap.free_snap(&snap_data, &near_point, config); +// let snapped_far = snap.free_snap(&snap_data, &far_point, config); +// let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far }; +// document_points[1] = document_points[0] * 2. - best.snapped_point_document; +// document_points[0] = best.snapped_point_document; +// snap.update_indicator(best); +// } else { +// let snapped = snap.free_snap(&snap_data, &near_point, config); +// let snapped_mid = snap.free_snap(&snap_data, &mid_point, config); +// let best = if snap_data.document.snapping_state.path.line_midpoint && snapped_mid.other_snap_better(&snapped_mid) { +// document_points[1] += (snapped_mid.snapped_point_document - mid_point.document_point) * 2.; +// snapped_mid +// } else { +// document_points[1] = snapped.snapped_point_document; +// snapped.clone() +// }; +// snap.update_indicator(best); +// } + +// // Snapping happens in other space, while document graph renders in another. +// document_points.map(|vector| vector + shift) +// } + +// #[cfg(test)] +// mod test_line_tool { +// use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; +// use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; +// use crate::test_utils::test_prelude::*; +// use glam::DAffine2; +// use graph_craft::document::value::TaggedValue; + +// async fn get_line_node_inputs(editor: &mut EditorTestUtils) -> Option<(DVec2, DVec2)> { +// let document = editor.active_document(); +// let network_interface = &document.network_interface; +// let node_id = network_interface +// .selected_nodes() +// .selected_visible_and_unlocked_layers(network_interface) +// .filter_map(|layer| { +// let node_inputs = NodeGraphLayer::new(layer, &network_interface).find_node_inputs("Line")?; +// let (Some(&TaggedValue::DVec2(start)), Some(&TaggedValue::DVec2(end))) = (node_inputs[1].as_value(), node_inputs[2].as_value()) else { +// return None; +// }; +// Some((start, end)) +// }) +// .next(); +// node_id +// } + +// #[tokio::test] +// async fn test_line_tool_basicdraw() { +// let mut editor = EditorTestUtils::create(); +// editor.new_document().await; +// editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::empty()).await; +// if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { +// match (start_input, end_input) { +// (start_input, end_input) => { +// assert!((start_input - DVec2::ZERO).length() < 1., "Start point should be near (0,0)"); +// assert!((end_input - DVec2::new(100., 100.)).length() < 1., "End point should be near (100,100)"); +// } +// } +// } +// } + +// #[tokio::test] +// async fn test_line_tool_with_transformed_viewport() { +// let mut editor = EditorTestUtils::create(); +// editor.new_document().await; +// editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2. }).await; +// editor.handle_message(NavigationMessage::CanvasPan { delta: DVec2::new(100., 50.) }).await; +// editor +// .handle_message(NavigationMessage::CanvasTiltSet { +// angle_radians: (30. as f64).to_radians(), +// }) +// .await; +// editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::empty()).await; +// if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { +// let document = editor.active_document(); +// let document_to_viewport = document.metadata().document_to_viewport; +// let viewport_to_document = document_to_viewport.inverse(); + +// let expected_start = viewport_to_document.transform_point2(DVec2::ZERO); +// let expected_end = viewport_to_document.transform_point2(DVec2::new(100., 100.)); + +// assert!( +// (start_input - expected_start).length() < 1., +// "Start point should match expected document coordinates. Got {:?}, expected {:?}", +// start_input, +// expected_start +// ); +// assert!( +// (end_input - expected_end).length() < 1., +// "End point should match expected document coordinates. Got {:?}, expected {:?}", +// end_input, +// expected_end +// ); +// } else { +// panic!("Line was not created successfully with transformed viewport"); +// } +// } + +// #[tokio::test] +// async fn test_line_tool_ctrl_anglelock() { +// let mut editor = EditorTestUtils::create(); +// editor.new_document().await; +// editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::CONTROL).await; +// if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { +// match (start_input, end_input) { +// (start_input, end_input) => { +// let line_vec = end_input - start_input; +// let original_angle = line_vec.angle_to(DVec2::X); +// editor.drag_tool(ToolType::Line, 0., 0., 200., 50., ModifierKeys::CONTROL).await; +// if let Some((updated_start, updated_end)) = get_line_node_inputs(&mut editor).await { +// match (updated_start, updated_end) { +// (updated_start, updated_end) => { +// let updated_line_vec = updated_end - updated_start; +// let updated_angle = updated_line_vec.angle_to(DVec2::X); +// assert!((original_angle - updated_angle).abs() < 0.1, "Line angle should be locked when Ctrl is kept pressed"); +// assert!((updated_start - updated_end).length() > 1., "Line should be able to change length when Ctrl is kept pressed"); +// } +// } +// } +// } +// } +// } +// } + +// #[tokio::test] +// async fn test_line_tool_alt() { +// let mut editor = EditorTestUtils::create(); +// editor.new_document().await; +// editor.drag_tool(ToolType::Line, 100., 100., 200., 100., ModifierKeys::ALT).await; +// if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { +// match (start_input, end_input) { +// (start_input, end_input) => { +// let expected_start = DVec2::new(0., 100.); +// let expected_end = DVec2::new(200., 100.); +// assert!((start_input - expected_start).length() < 1., "start point should be near (0,100)"); +// assert!((end_input - expected_end).length() < 1., "end point should be near (200,100)"); +// } +// } +// } +// } + +// #[tokio::test] +// async fn test_line_tool_alt_shift_drag() { +// let mut editor = EditorTestUtils::create(); +// editor.new_document().await; +// editor.drag_tool(ToolType::Line, 100., 100., 150., 120., ModifierKeys::ALT | ModifierKeys::SHIFT).await; +// if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { +// match (start_input, end_input) { +// (start_input, end_input) => { +// let line_vec = end_input - start_input; +// let angle_radians = line_vec.angle_to(DVec2::X); +// let angle_degrees = angle_radians.to_degrees(); +// let nearest_angle = (angle_degrees / 15.).round() * 15.; + +// assert!((angle_degrees - nearest_angle).abs() < 1., "Angle should snap to the nearest 15 degrees"); +// } +// } +// } +// } + +// #[tokio::test] +// async fn test_line_tool_with_transformed_artboard() { +// let mut editor = EditorTestUtils::create(); +// editor.new_document().await; +// editor.drag_tool(ToolType::Artboard, 0., 0., 200., 200., ModifierKeys::empty()).await; + +// let artboard_id = editor.get_selected_layer().await.expect("Should have selected the artboard"); + +// editor +// .handle_message(GraphOperationMessage::TransformChange { +// layer: artboard_id, +// transform: DAffine2::from_angle(45.0_f64.to_radians()), +// transform_in: TransformIn::Local, +// skip_rerender: false, +// }) +// .await; + +// editor.drag_tool(ToolType::Line, 50., 50., 150., 150., ModifierKeys::empty()).await; + +// let (start_input, end_input) = get_line_node_inputs(&mut editor).await.expect("Line was not created successfully within transformed artboard"); +// // The line should still be diagonal with equal change in x and y +// let line_vector = end_input - start_input; +// // Verifying the line is approximately 100*sqrt(2) units in length (diagonal of 100x100 square) +// let line_length = line_vector.length(); +// assert!( +// (line_length - 141.42).abs() < 1.0, // 100 * sqrt(2) ~= 141.42 +// "Line length should be approximately 141.42 units. Got: {line_length}" +// ); +// assert!((line_vector.x - 100.0).abs() < 1.0, "X-component of line vector should be approximately 100. Got: {}", line_vector.x); +// assert!( +// (line_vector.y.abs() - 100.0).abs() < 1.0, +// "Absolute Y-component of line vector should be approximately 100. Got: {}", +// line_vector.y.abs() +// ); +// let angle_degrees = line_vector.angle_to(DVec2::X).to_degrees(); +// assert!((angle_degrees - (-45.0)).abs() < 1.0, "Line angle should be close to -45 degrees. Got: {angle_degrees}"); +// } +// } diff --git a/editor/src/messages/tool/tool_messages/mod.rs b/editor/src/messages/tool/tool_messages/mod.rs index 65ac871ab2..b387024820 100644 --- a/editor/src/messages/tool/tool_messages/mod.rs +++ b/editor/src/messages/tool/tool_messages/mod.rs @@ -13,6 +13,7 @@ pub mod pen_tool; pub mod polygon_tool; pub mod rectangle_tool; pub mod select_tool; +pub mod shape_tool; pub mod spline_tool; pub mod text_tool; diff --git a/editor/src/messages/tool/tool_messages/rectangle_tool.rs b/editor/src/messages/tool/tool_messages/rectangle_tool.rs index ee5e73e1bf..98e8399219 100644 --- a/editor/src/messages/tool/tool_messages/rectangle_tool.rs +++ b/editor/src/messages/tool/tool_messages/rectangle_tool.rs @@ -1,323 +1,323 @@ -use super::tool_prelude::*; -use crate::consts::DEFAULT_STROKE_WIDTH; -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::overlays::utility_types::OverlayContext; -use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; -use crate::messages::tool::common_functionality::auto_panning::AutoPanning; -use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; -use crate::messages::tool::common_functionality::graph_modification_utils; -use crate::messages::tool::common_functionality::resize::Resize; -use crate::messages::tool::common_functionality::snapping::SnapData; -use graph_craft::document::value::TaggedValue; -use graph_craft::document::{NodeId, NodeInput}; -use graphene_core::Color; - -#[derive(Default)] -pub struct RectangleTool { - fsm_state: RectangleToolFsmState, - tool_data: RectangleToolData, - options: RectangleToolOptions, -} - -pub struct RectangleToolOptions { - line_weight: f64, - fill: ToolColorOptions, - stroke: ToolColorOptions, -} - -impl Default for RectangleToolOptions { - fn default() -> Self { - Self { - line_weight: DEFAULT_STROKE_WIDTH, - fill: ToolColorOptions::new_secondary(), - stroke: ToolColorOptions::new_primary(), - } - } -} - -#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] -pub enum RectangleOptionsUpdate { - FillColor(Option), - FillColorType(ToolColorType), - LineWeight(f64), - StrokeColor(Option), - StrokeColorType(ToolColorType), - WorkingColors(Option, Option), -} - -#[impl_message(Message, ToolMessage, Rectangle)] -#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] -pub enum RectangleToolMessage { - // Standard messages - Overlays(OverlayContext), - Abort, - WorkingColorChanged, - - // Tool-specific messages - DragStart, - DragStop, - PointerMove { center: Key, lock_ratio: Key }, - PointerOutsideViewport { center: Key, lock_ratio: Key }, - UpdateOptions(RectangleOptionsUpdate), -} - -fn create_weight_widget(line_weight: f64) -> WidgetHolder { - NumberInput::new(Some(line_weight)) - .unit(" px") - .label("Weight") - .min(0.) - .max((1_u64 << f64::MANTISSA_DIGITS) as f64) - .on_update(|number_input: &NumberInput| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) - .widget_holder() -} - -impl LayoutHolder for RectangleTool { - fn layout(&self) -> Layout { - let mut widgets = self.options.fill.create_widgets( - "Fill", - true, - |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColor(None)).into(), - |color_type: ToolColorType| WidgetCallback::new(move |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColorType(color_type.clone())).into()), - |color: &ColorInput| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), - ); - - widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); - - widgets.append(&mut self.options.stroke.create_widgets( - "Stroke", - true, - |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColor(None)).into(), - |color_type: ToolColorType| WidgetCallback::new(move |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColorType(color_type.clone())).into()), - |color: &ColorInput| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), - )); - widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); - widgets.push(create_weight_widget(self.options.line_weight)); - - Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) - } -} - -impl<'a> MessageHandler> for RectangleTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { - let ToolMessage::Rectangle(RectangleToolMessage::UpdateOptions(action)) = message else { - self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); - return; - }; - match action { - RectangleOptionsUpdate::FillColor(color) => { - self.options.fill.custom_color = color; - self.options.fill.color_type = ToolColorType::Custom; - } - RectangleOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, - RectangleOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, - RectangleOptionsUpdate::StrokeColor(color) => { - self.options.stroke.custom_color = color; - self.options.stroke.color_type = ToolColorType::Custom; - } - RectangleOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, - RectangleOptionsUpdate::WorkingColors(primary, secondary) => { - self.options.stroke.primary_working_color = primary; - self.options.stroke.secondary_working_color = secondary; - self.options.fill.primary_working_color = primary; - self.options.fill.secondary_working_color = secondary; - } - } - - self.send_layout(responses, LayoutTarget::ToolOptions); - } - - fn actions(&self) -> ActionList { - match self.fsm_state { - RectangleToolFsmState::Ready => actions!(RectangleToolMessageDiscriminant; - DragStart, - PointerMove, - ), - RectangleToolFsmState::Drawing => actions!(RectangleToolMessageDiscriminant; - DragStop, - Abort, - PointerMove, - ), - } - } -} - -impl ToolMetadata for RectangleTool { - fn icon_name(&self) -> String { - "VectorRectangleTool".into() - } - fn tooltip(&self) -> String { - "Rectangle Tool".into() - } - fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { - ToolType::Rectangle - } -} - -impl ToolTransition for RectangleTool { - fn event_to_message_map(&self) -> EventToMessageMap { - EventToMessageMap { - overlay_provider: Some(|overlay_context| RectangleToolMessage::Overlays(overlay_context).into()), - tool_abort: Some(RectangleToolMessage::Abort.into()), - working_color_changed: Some(RectangleToolMessage::WorkingColorChanged.into()), - ..Default::default() - } - } -} - -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -enum RectangleToolFsmState { - #[default] - Ready, - Drawing, -} - -#[derive(Clone, Debug, Default)] -struct RectangleToolData { - data: Resize, - auto_panning: AutoPanning, -} - -impl Fsm for RectangleToolFsmState { - type ToolData = RectangleToolData; - type ToolOptions = RectangleToolOptions; - - fn transition( - self, - event: ToolMessage, - tool_data: &mut Self::ToolData, - ToolActionHandlerData { - document, global_tool_data, input, .. - }: &mut ToolActionHandlerData, - tool_options: &Self::ToolOptions, - responses: &mut VecDeque, - ) -> Self { - let shape_data = &mut tool_data.data; - - let ToolMessage::Rectangle(event) = event else { return self }; - match (self, event) { - (_, RectangleToolMessage::Overlays(mut overlay_context)) => { - shape_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); - self - } - (RectangleToolFsmState::Ready, RectangleToolMessage::DragStart) => { - shape_data.start(document, input); - - responses.add(DocumentMessage::StartTransaction); - - let node_type = resolve_document_node_type("Rectangle").expect("Rectangle node does not exist"); - let node = node_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(1.), false)), Some(NodeInput::value(TaggedValue::F64(1.), false))]); - let nodes = vec![(NodeId(0), node)]; - - let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input), responses); - responses.add(Message::StartBuffer); - responses.add(GraphOperationMessage::TransformSet { - layer, - transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), - transform_in: TransformIn::Viewport, - skip_rerender: false, - }); - tool_options.fill.apply_fill(layer, responses); - tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); - shape_data.layer = Some(layer); - - RectangleToolFsmState::Drawing - } - (RectangleToolFsmState::Drawing, RectangleToolMessage::PointerMove { center, lock_ratio }) => { - if let Some([start, end]) = shape_data.calculate_points(document, input, center, lock_ratio) { - if let Some(layer) = shape_data.layer { - let Some(node_id) = graph_modification_utils::get_rectangle_id(layer, &document.network_interface) else { - return self; - }; - - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 1), - input: NodeInput::value(TaggedValue::F64((start.x - end.x).abs()), false), - }); - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 2), - input: NodeInput::value(TaggedValue::F64((start.y - end.y).abs()), false), - }); - responses.add(GraphOperationMessage::TransformSet { - layer, - transform: DAffine2::from_translation((start + end) / 2.), - transform_in: TransformIn::Viewport, - skip_rerender: false, - }); - } - } - - // Auto-panning - let messages = [ - RectangleToolMessage::PointerOutsideViewport { center, lock_ratio }.into(), - RectangleToolMessage::PointerMove { center, lock_ratio }.into(), - ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); - - self - } - (_, RectangleToolMessage::PointerMove { .. }) => { - shape_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); - responses.add(OverlaysMessage::Draw); - self - } - (RectangleToolFsmState::Drawing, RectangleToolMessage::PointerOutsideViewport { .. }) => { - // Auto-panning - let _ = tool_data.auto_panning.shift_viewport(input, responses); - - RectangleToolFsmState::Drawing - } - (state, RectangleToolMessage::PointerOutsideViewport { center, lock_ratio }) => { - // Auto-panning - let messages = [ - RectangleToolMessage::PointerOutsideViewport { center, lock_ratio }.into(), - RectangleToolMessage::PointerMove { center, lock_ratio }.into(), - ]; - tool_data.auto_panning.stop(&messages, responses); - - state - } - (RectangleToolFsmState::Drawing, RectangleToolMessage::DragStop) => { - input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); - shape_data.cleanup(responses); - - RectangleToolFsmState::Ready - } - (RectangleToolFsmState::Drawing, RectangleToolMessage::Abort) => { - responses.add(DocumentMessage::AbortTransaction); - - shape_data.cleanup(responses); - - RectangleToolFsmState::Ready - } - (_, RectangleToolMessage::WorkingColorChanged) => { - responses.add(RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::WorkingColors( - Some(global_tool_data.primary_color), - Some(global_tool_data.secondary_color), - ))); - self - } - _ => self, - } - } - - fn update_hints(&self, responses: &mut VecDeque) { - let hint_data = match self { - RectangleToolFsmState::Ready => HintData(vec![HintGroup(vec![ - HintInfo::mouse(MouseMotion::LmbDrag, "Draw Rectangle"), - HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(), - HintInfo::keys([Key::Alt], "From Center").prepend_plus(), - ])]), - RectangleToolFsmState::Drawing => HintData(vec![ - HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), - HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")]), - ]), - }; - - responses.add(FrontendMessage::UpdateInputHints { hint_data }); - } - - fn update_cursor(&self, responses: &mut VecDeque) { - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }); - } -} +// use super::tool_prelude::*; +// use crate::consts::DEFAULT_STROKE_WIDTH; +// 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::overlays::utility_types::OverlayContext; +// use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; +// use crate::messages::tool::common_functionality::auto_panning::AutoPanning; +// use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; +// use crate::messages::tool::common_functionality::graph_modification_utils; +// use crate::messages::tool::common_functionality::resize::Resize; +// use crate::messages::tool::common_functionality::snapping::SnapData; +// use graph_craft::document::value::TaggedValue; +// use graph_craft::document::{NodeId, NodeInput}; +// use graphene_core::Color; + +// #[derive(Default)] +// pub struct RectangleTool { +// fsm_state: RectangleToolFsmState, +// tool_data: RectangleToolData, +// options: RectangleToolOptions, +// } + +// pub struct RectangleToolOptions { +// line_weight: f64, +// fill: ToolColorOptions, +// stroke: ToolColorOptions, +// } + +// impl Default for RectangleToolOptions { +// fn default() -> Self { +// Self { +// line_weight: DEFAULT_STROKE_WIDTH, +// fill: ToolColorOptions::new_secondary(), +// stroke: ToolColorOptions::new_primary(), +// } +// } +// } + +// #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +// pub enum RectangleOptionsUpdate { +// FillColor(Option), +// FillColorType(ToolColorType), +// LineWeight(f64), +// StrokeColor(Option), +// StrokeColorType(ToolColorType), +// WorkingColors(Option, Option), +// } + +// #[impl_message(Message, ToolMessage, Rectangle)] +// #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +// pub enum RectangleToolMessage { +// // Standard messages +// Overlays(OverlayContext), +// Abort, +// WorkingColorChanged, + +// // Tool-specific messages +// DragStart, +// DragStop, +// PointerMove { center: Key, lock_ratio: Key }, +// PointerOutsideViewport { center: Key, lock_ratio: Key }, +// UpdateOptions(RectangleOptionsUpdate), +// } + +// fn create_weight_widget(line_weight: f64) -> WidgetHolder { +// NumberInput::new(Some(line_weight)) +// .unit(" px") +// .label("Weight") +// .min(0.) +// .max((1_u64 << f64::MANTISSA_DIGITS) as f64) +// .on_update(|number_input: &NumberInput| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) +// .widget_holder() +// } + +// impl LayoutHolder for RectangleTool { +// fn layout(&self) -> Layout { +// let mut widgets = self.options.fill.create_widgets( +// "Fill", +// true, +// |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColor(None)).into(), +// |color_type: ToolColorType| WidgetCallback::new(move |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColorType(color_type.clone())).into()), +// |color: &ColorInput| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), +// ); + +// widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); + +// widgets.append(&mut self.options.stroke.create_widgets( +// "Stroke", +// true, +// |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColor(None)).into(), +// |color_type: ToolColorType| WidgetCallback::new(move |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColorType(color_type.clone())).into()), +// |color: &ColorInput| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), +// )); +// widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); +// widgets.push(create_weight_widget(self.options.line_weight)); + +// Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) +// } +// } + +// impl<'a> MessageHandler> for RectangleTool { +// fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { +// let ToolMessage::Rectangle(RectangleToolMessage::UpdateOptions(action)) = message else { +// self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); +// return; +// }; +// match action { +// RectangleOptionsUpdate::FillColor(color) => { +// self.options.fill.custom_color = color; +// self.options.fill.color_type = ToolColorType::Custom; +// } +// RectangleOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, +// RectangleOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, +// RectangleOptionsUpdate::StrokeColor(color) => { +// self.options.stroke.custom_color = color; +// self.options.stroke.color_type = ToolColorType::Custom; +// } +// RectangleOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, +// RectangleOptionsUpdate::WorkingColors(primary, secondary) => { +// self.options.stroke.primary_working_color = primary; +// self.options.stroke.secondary_working_color = secondary; +// self.options.fill.primary_working_color = primary; +// self.options.fill.secondary_working_color = secondary; +// } +// } + +// self.send_layout(responses, LayoutTarget::ToolOptions); +// } + +// fn actions(&self) -> ActionList { +// match self.fsm_state { +// RectangleToolFsmState::Ready => actions!(RectangleToolMessageDiscriminant; +// DragStart, +// PointerMove, +// ), +// RectangleToolFsmState::Drawing => actions!(RectangleToolMessageDiscriminant; +// DragStop, +// Abort, +// PointerMove, +// ), +// } +// } +// } + +// impl ToolMetadata for RectangleTool { +// fn icon_name(&self) -> String { +// "VectorRectangleTool".into() +// } +// fn tooltip(&self) -> String { +// "Rectangle Tool".into() +// } +// fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { +// ToolType::Rectangle +// } +// } + +// impl ToolTransition for RectangleTool { +// fn event_to_message_map(&self) -> EventToMessageMap { +// EventToMessageMap { +// overlay_provider: Some(|overlay_context| RectangleToolMessage::Overlays(overlay_context).into()), +// tool_abort: Some(RectangleToolMessage::Abort.into()), +// working_color_changed: Some(RectangleToolMessage::WorkingColorChanged.into()), +// ..Default::default() +// } +// } +// } + +// #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +// enum RectangleToolFsmState { +// #[default] +// Ready, +// Drawing, +// } + +// #[derive(Clone, Debug, Default)] +// struct RectangleToolData { +// data: Resize, +// auto_panning: AutoPanning, +// } + +// impl Fsm for RectangleToolFsmState { +// type ToolData = RectangleToolData; +// type ToolOptions = RectangleToolOptions; + +// fn transition( +// self, +// event: ToolMessage, +// tool_data: &mut Self::ToolData, +// ToolActionHandlerData { +// document, global_tool_data, input, .. +// }: &mut ToolActionHandlerData, +// tool_options: &Self::ToolOptions, +// responses: &mut VecDeque, +// ) -> Self { +// let shape_data = &mut tool_data.data; + +// let ToolMessage::Rectangle(event) = event else { return self }; +// match (self, event) { +// (_, RectangleToolMessage::Overlays(mut overlay_context)) => { +// shape_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); +// self +// } +// (RectangleToolFsmState::Ready, RectangleToolMessage::DragStart) => { +// shape_data.start(document, input); + +// responses.add(DocumentMessage::StartTransaction); + +// let node_type = resolve_document_node_type("Rectangle").expect("Rectangle node does not exist"); +// let node = node_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(1.), false)), Some(NodeInput::value(TaggedValue::F64(1.), false))]); +// let nodes = vec![(NodeId(0), node)]; + +// let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input), responses); +// responses.add(Message::StartBuffer); +// responses.add(GraphOperationMessage::TransformSet { +// layer, +// transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), +// transform_in: TransformIn::Viewport, +// skip_rerender: false, +// }); +// tool_options.fill.apply_fill(layer, responses); +// tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); +// shape_data.layer = Some(layer); + +// RectangleToolFsmState::Drawing +// } +// (RectangleToolFsmState::Drawing, RectangleToolMessage::PointerMove { center, lock_ratio }) => { +// if let Some([start, end]) = shape_data.calculate_points(document, input, center, lock_ratio) { +// if let Some(layer) = shape_data.layer { +// let Some(node_id) = graph_modification_utils::get_rectangle_id(layer, &document.network_interface) else { +// return self; +// }; + +// responses.add(NodeGraphMessage::SetInput { +// input_connector: InputConnector::node(node_id, 1), +// input: NodeInput::value(TaggedValue::F64((start.x - end.x).abs()), false), +// }); +// responses.add(NodeGraphMessage::SetInput { +// input_connector: InputConnector::node(node_id, 2), +// input: NodeInput::value(TaggedValue::F64((start.y - end.y).abs()), false), +// }); +// responses.add(GraphOperationMessage::TransformSet { +// layer, +// transform: DAffine2::from_translation((start + end) / 2.), +// transform_in: TransformIn::Viewport, +// skip_rerender: false, +// }); +// } +// } + +// // Auto-panning +// let messages = [ +// RectangleToolMessage::PointerOutsideViewport { center, lock_ratio }.into(), +// RectangleToolMessage::PointerMove { center, lock_ratio }.into(), +// ]; +// tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + +// self +// } +// (_, RectangleToolMessage::PointerMove { .. }) => { +// shape_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); +// responses.add(OverlaysMessage::Draw); +// self +// } +// (RectangleToolFsmState::Drawing, RectangleToolMessage::PointerOutsideViewport { .. }) => { +// // Auto-panning +// let _ = tool_data.auto_panning.shift_viewport(input, responses); + +// RectangleToolFsmState::Drawing +// } +// (state, RectangleToolMessage::PointerOutsideViewport { center, lock_ratio }) => { +// // Auto-panning +// let messages = [ +// RectangleToolMessage::PointerOutsideViewport { center, lock_ratio }.into(), +// RectangleToolMessage::PointerMove { center, lock_ratio }.into(), +// ]; +// tool_data.auto_panning.stop(&messages, responses); + +// state +// } +// (RectangleToolFsmState::Drawing, RectangleToolMessage::DragStop) => { +// input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); +// shape_data.cleanup(responses); + +// RectangleToolFsmState::Ready +// } +// (RectangleToolFsmState::Drawing, RectangleToolMessage::Abort) => { +// responses.add(DocumentMessage::AbortTransaction); + +// shape_data.cleanup(responses); + +// RectangleToolFsmState::Ready +// } +// (_, RectangleToolMessage::WorkingColorChanged) => { +// responses.add(RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::WorkingColors( +// Some(global_tool_data.primary_color), +// Some(global_tool_data.secondary_color), +// ))); +// self +// } +// _ => self, +// } +// } + +// fn update_hints(&self, responses: &mut VecDeque) { +// let hint_data = match self { +// RectangleToolFsmState::Ready => HintData(vec![HintGroup(vec![ +// HintInfo::mouse(MouseMotion::LmbDrag, "Draw Rectangle"), +// HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(), +// HintInfo::keys([Key::Alt], "From Center").prepend_plus(), +// ])]), +// RectangleToolFsmState::Drawing => HintData(vec![ +// HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), +// HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")]), +// ]), +// }; + +// responses.add(FrontendMessage::UpdateInputHints { hint_data }); +// } + +// fn update_cursor(&self, responses: &mut VecDeque) { +// responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }); +// } +// } diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs new file mode 100644 index 0000000000..9ed79d5221 --- /dev/null +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -0,0 +1,414 @@ +use std::vec; + +use super::tool_prelude::*; +use crate::consts::DEFAULT_STROKE_WIDTH; +use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; +use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; +use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; +use crate::messages::tool::common_functionality::auto_panning::AutoPanning; +use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; +use crate::messages::tool::common_functionality::graph_modification_utils; +use crate::messages::tool::common_functionality::resize::Resize; +use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapTypeConfiguration}; +use crate::messages::tool::shapes::convex_shape::Convex; +use crate::messages::tool::shapes::shape_utility::{LineInitData, ShapeToolModifierKey, ShapeType}; +use crate::messages::tool::shapes::star_shape::Star; +use crate::messages::tool::shapes::{Ellipse, Line, Rectangle}; +use graph_craft::document::NodeId; +use graphene_core::Color; + +#[derive(Default)] +pub struct ShapeTool { + fsm_state: ShapeToolFsmState, + tool_data: ShapeToolData, + options: ShapeToolOptions, +} + +pub struct ShapeToolOptions { + line_weight: f64, + fill: ToolColorOptions, + stroke: ToolColorOptions, + vertices: u32, + shape_type: ShapeType, +} + +impl Default for ShapeToolOptions { + fn default() -> Self { + Self { + line_weight: DEFAULT_STROKE_WIDTH, + fill: ToolColorOptions::new_secondary(), + stroke: ToolColorOptions::new_primary(), + shape_type: ShapeType::Convex, + vertices: 5, + } + } +} + +#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +pub enum ShapeOptionsUpdate { + FillColor(Option), + FillColorType(ToolColorType), + LineWeight(f64), + StrokeColor(Option), + StrokeColorType(ToolColorType), + WorkingColors(Option, Option), + Vertices(u32), + ShapeType(ShapeType), +} + +#[impl_message(Message, ToolMessage, Shape)] +#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +pub enum ShapeToolMessage { + // Standard messages + Overlays(OverlayContext), + Abort, + WorkingColorChanged, + + // Tool-specific messages + DragStart, + DragStop, + HideShapeTypeWidget(bool), + PointerMove(ShapeToolModifierKey), + PointerOutsideViewport(ShapeToolModifierKey), + UpdateOptions(ShapeOptionsUpdate), + SetShape(ShapeType), +} + +fn create_sides_widget(vertices: u32) -> WidgetHolder { + NumberInput::new(Some(vertices as f64)) + .label("Sides") + .int() + .min(3.) + .max(1000.) + .mode(NumberInputMode::Increment) + .on_update(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(number_input.value.unwrap() as u32)).into()) + .widget_holder() +} + +fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder { + let entries = vec![vec![ + MenuListEntry::new("convex") + .label("Convex") + .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Convex)).into()), + MenuListEntry::new("star") + .label("Star") + .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Star)).into()), + MenuListEntry::new("rectangle") + .label("Rectangle") + .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Rectangle)).into()), + MenuListEntry::new("ellipse") + .label("Ellipse") + .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Ellipse)).into()), + MenuListEntry::new("line") + .label("Line") + .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Line)).into()), + ]]; + DropdownInput::new(entries).selected_index(Some(shape_type as u32)).widget_holder() +} + +fn create_weight_widget(line_weight: f64) -> WidgetHolder { + NumberInput::new(Some(line_weight)) + .unit(" px") + .label("Weight") + .min(0.) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) + .on_update(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) + .widget_holder() +} + +impl LayoutHolder for ShapeTool { + fn layout(&self) -> Layout { + let mut widgets = vec![]; + + if !self.tool_data.hide_shape_option_widget { + widgets.push(create_shape_option_widget(self.options.shape_type)); + widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); + + if self.options.shape_type == ShapeType::Convex || self.options.shape_type == ShapeType::Star { + widgets.push(create_sides_widget(self.options.vertices)); + widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); + } + } + + if self.options.shape_type != ShapeType::Line { + widgets.append(&mut self.options.fill.create_widgets( + "Fill", + true, + |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColor(None)).into(), + |color_type: ToolColorType| WidgetCallback::new(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColorType(color_type.clone())).into()), + |color: &ColorInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColor(color.value.as_solid())).into(), + )); + + widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); + } + + widgets.append(&mut self.options.stroke.create_widgets( + "Stroke", + true, + |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColor(None)).into(), + |color_type: ToolColorType| WidgetCallback::new(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColorType(color_type.clone())).into()), + |color: &ColorInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColor(color.value.as_solid())).into(), + )); + widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); + widgets.push(create_weight_widget(self.options.line_weight)); + + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) + } +} + +impl<'a> MessageHandler> for ShapeTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { + let ToolMessage::Shape(ShapeToolMessage::UpdateOptions(action)) = message else { + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); + return; + }; + match action { + ShapeOptionsUpdate::FillColor(color) => { + self.options.fill.custom_color = color; + self.options.fill.color_type = ToolColorType::Custom; + } + ShapeOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, + ShapeOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, + ShapeOptionsUpdate::StrokeColor(color) => { + self.options.stroke.custom_color = color; + self.options.stroke.color_type = ToolColorType::Custom; + } + ShapeOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, + ShapeOptionsUpdate::WorkingColors(primary, secondary) => { + self.options.stroke.primary_working_color = primary; + self.options.stroke.secondary_working_color = secondary; + self.options.fill.primary_working_color = primary; + self.options.fill.secondary_working_color = secondary; + } + ShapeOptionsUpdate::ShapeType(shape) => { + self.options.shape_type = shape; + } + ShapeOptionsUpdate::Vertices(vertices) => { + self.options.vertices = vertices; + } + } + + self.send_layout(responses, LayoutTarget::ToolOptions); + } + + fn actions(&self) -> ActionList { + match self.fsm_state { + ShapeToolFsmState::Ready => actions!(ShapeToolMessageDiscriminant; + DragStart, + PointerMove, + SetShape, + Abort, + HideShapeTypeWidget + ), + ShapeToolFsmState::Drawing => actions!(ShapeToolMessageDiscriminant; + DragStop, + Abort, + PointerMove, + SetShape, + HideShapeTypeWidget + ), + } + } +} + +impl ToolMetadata for ShapeTool { + fn icon_name(&self) -> String { + "VectorPolygonTool".into() + } + fn tooltip(&self) -> String { + "Shape Tool".into() + } + fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { + ToolType::Shape + } +} + +impl ToolTransition for ShapeTool { + fn event_to_message_map(&self) -> EventToMessageMap { + EventToMessageMap { + overlay_provider: Some(|overlay_context| ShapeToolMessage::Overlays(overlay_context).into()), + tool_abort: Some(ShapeToolMessage::Abort.into()), + working_color_changed: Some(ShapeToolMessage::WorkingColorChanged.into()), + ..Default::default() + } + } +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +enum ShapeToolFsmState { + #[default] + Ready, + Drawing, +} + +#[derive(Clone, Debug, Default)] +pub struct ShapeToolData { + pub data: Resize, + pub drag_current: DVec2, + pub angle: f64, + pub weight: f64, + pub selected_layers_with_position: HashMap, + auto_panning: AutoPanning, + pub hide_shape_option_widget: bool, + current_shape: ShapeType, +} + +impl Fsm for ShapeToolFsmState { + type ToolData = ShapeToolData; + type ToolOptions = ShapeToolOptions; + + fn transition( + self, + event: ToolMessage, + tool_data: &mut Self::ToolData, + ToolActionHandlerData { + document, global_tool_data, input, .. + }: &mut ToolActionHandlerData, + tool_options: &Self::ToolOptions, + responses: &mut VecDeque, + ) -> Self { + let shape_data = &mut tool_data.data; + + let ToolMessage::Shape(event) = event else { return self }; + match (self, event) { + (_, ShapeToolMessage::Overlays(mut overlay_context)) => { + shape_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); + self + } + (ShapeToolFsmState::Ready, ShapeToolMessage::DragStart) => { + match tool_options.shape_type { + ShapeType::Convex | ShapeType::Star | ShapeType::Ellipse | ShapeType::Rectangle => shape_data.start(document, input), + ShapeType::Line => { + let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); + let snapped = shape_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); + shape_data.drag_start = snapped.snapped_point_document; + } + } + + responses.add(DocumentMessage::StartTransaction); + + let node = match tool_options.shape_type { + ShapeType::Convex => Convex::create_node(tool_options.vertices), + ShapeType::Star => Star::create_node(tool_options.vertices), + ShapeType::Rectangle => Rectangle::create_node(), + ShapeType::Ellipse => Ellipse::create_node(), + ShapeType::Line => Line::create_node(&document, LineInitData { drag_start: shape_data.drag_start }), + }; + let nodes = vec![(NodeId(0), node)]; + let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input), responses); + + responses.add(Message::StartBuffer); + + tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); + match tool_options.shape_type { + ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Convex | ShapeType::Star => { + responses.add(GraphOperationMessage::TransformSet { + layer, + transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), + transform_in: TransformIn::Viewport, + skip_rerender: false, + }); + + tool_options.fill.apply_fill(layer, responses); + } + ShapeType::Line => { + tool_data.angle = 0.; + tool_data.weight = tool_options.line_weight; + } + } + + shape_data.layer = Some(layer); + + ShapeToolFsmState::Drawing + } + (ShapeToolFsmState::Drawing, ShapeToolMessage::PointerMove(modifier)) => { + let Some(layer) = shape_data.layer else { return ShapeToolFsmState::Ready }; + if match tool_options.shape_type { + ShapeType::Rectangle => Rectangle::update_shape(&document, &input, layer, tool_data, modifier, responses), + ShapeType::Ellipse => Ellipse::update_shape(&document, &input, layer, tool_data, modifier, responses), + ShapeType::Line => Line::update_shape(&document, &input, layer, tool_data, modifier, responses), + ShapeType::Convex => Convex::update_shape(&document, &input, layer, tool_data, modifier, responses), + ShapeType::Star => Star::update_shape(&document, &input, layer, tool_data, modifier, responses), + } { + return if tool_options.shape_type == ShapeType::Line { ShapeToolFsmState::Ready } else { self }; + } + + // Auto-panning + let messages = [ShapeToolMessage::PointerOutsideViewport(modifier).into(), ShapeToolMessage::PointerMove(modifier).into()]; + tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + + self + } + (_, ShapeToolMessage::PointerMove { .. }) => { + shape_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); + responses.add(OverlaysMessage::Draw); + self + } + (ShapeToolFsmState::Drawing, ShapeToolMessage::PointerOutsideViewport { .. }) => { + // Auto-panning + let _ = tool_data.auto_panning.shift_viewport(input, responses); + + ShapeToolFsmState::Drawing + } + (state, ShapeToolMessage::PointerOutsideViewport(modifier)) => { + // Auto-panning + let messages = [ShapeToolMessage::PointerOutsideViewport(modifier).into(), ShapeToolMessage::PointerMove(modifier).into()]; + tool_data.auto_panning.stop(&messages, responses); + + state + } + (ShapeToolFsmState::Drawing, ShapeToolMessage::DragStop) => { + input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); + shape_data.cleanup(responses); + + ShapeToolFsmState::Ready + } + (ShapeToolFsmState::Drawing, ShapeToolMessage::Abort) => { + responses.add(DocumentMessage::AbortTransaction); + shape_data.cleanup(responses); + + ShapeToolFsmState::Ready + } + (_, ShapeToolMessage::WorkingColorChanged) => { + responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::WorkingColors( + Some(global_tool_data.primary_color), + Some(global_tool_data.secondary_color), + ))); + self + } + (_, ShapeToolMessage::SetShape(shape)) => { + responses.add(DocumentMessage::AbortTransaction); + shape_data.cleanup(responses); + responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(shape))); + + ShapeToolFsmState::Ready + } + (_, ShapeToolMessage::HideShapeTypeWidget(hide)) => { + tool_data.hide_shape_option_widget = hide; + responses.add(ToolMessage::RefreshToolOptions); + self + } + _ => self, + } + } + + fn update_hints(&self, responses: &mut VecDeque) { + let hint_data = match self { + ShapeToolFsmState::Ready => HintData(vec![HintGroup(vec![ + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Shape"), + HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(), + HintInfo::keys([Key::Alt], "From Center").prepend_plus(), + ])]), + ShapeToolFsmState::Drawing => HintData(vec![ + HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), + HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")]), + ]), + }; + + responses.add(FrontendMessage::UpdateInputHints { hint_data }); + } + + fn update_cursor(&self, responses: &mut VecDeque) { + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }); + } +} diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index 88a1a8423d..70f789d768 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -11,6 +11,7 @@ use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::overlays::utility_types::OverlayProvider; use crate::messages::preferences::PreferencesMessageHandler; use crate::messages::prelude::*; +use crate::messages::tool::shapes::shape_utility::ShapeType; use crate::node_graph_executor::NodeGraphExecutor; use graphene_core::raster::color::Color; use graphene_core::text::FontCache; @@ -162,7 +163,7 @@ pub trait ToolTransition { on: event, send: Box::new(mapping.into()), }); - }; + } }; let event_to_tool_map = self.event_to_message_map(); @@ -182,7 +183,7 @@ pub trait ToolTransition { on: event, message: Box::new(mapping.into()), }); - }; + } }; let event_to_tool_map = self.event_to_message_map(); @@ -204,6 +205,7 @@ pub trait ToolMetadata { pub struct ToolData { pub active_tool_type: ToolType, + pub active_shape_type: Option, pub tools: HashMap>, } @@ -225,32 +227,51 @@ impl ToolData { impl LayoutHolder for ToolData { fn layout(&self) -> Layout { + let active_tool = self.active_shape_type.unwrap_or(self.active_tool_type); + let tool_groups_layout = list_tools_in_groups() .iter() - .map(|tool_group| tool_group.iter().map(|tool_availability| { - match tool_availability { - ToolAvailability::Available(tool) => ToolEntry::new(tool.tool_type(), tool.icon_name()) - .tooltip(tool.tooltip()) - .tooltip_shortcut(action_keys!(tool_type_to_activate_tool_message(tool.tool_type()))), - ToolAvailability::ComingSoon(tool) => tool.clone(), - } - }) - .collect::>()) + .map(|tool_group| + tool_group + .iter() + .map(|tool_availability| { + match tool_availability { + ToolAvailability::Available(tool) => + ToolEntry::new(tool.tool_type(), tool.icon_name()) + .tooltip(tool.tooltip()) + .tooltip_shortcut(action_keys!(tool_type_to_activate_tool_message(tool.tool_type()))), + ToolAvailability::AvailableAsShape(shape) => + ToolEntry::new(shape.tool_type(), shape.icon_name()) + .tooltip(shape.tooltip()) + .tooltip_shortcut(action_keys!(tool_type_to_activate_tool_message(shape.tool_type()))), + ToolAvailability::ComingSoon(tool) => tool.clone(), + } + }) + .collect::>() + ) .flat_map(|group| { let separator = std::iter::once(Separator::new(SeparatorType::Section).direction(SeparatorDirection::Vertical).widget_holder()); let buttons = group.into_iter().map(|ToolEntry { tooltip, tooltip_shortcut, tool_type, icon_name }| { IconButton::new(icon_name, 32) .disabled(false) - .active(self.active_tool_type == tool_type) + .active(match tool_type { + ToolType::Line | ToolType::Ellipse | ToolType::Rectangle => {self.active_shape_type.is_some() && active_tool == tool_type}, + _ => active_tool == tool_type, + }) .tooltip(tooltip.clone()) .tooltip_shortcut(tooltip_shortcut) .on_update(move |_| { - if !tooltip.contains("Coming Soon") { - ToolMessage::ActivateTool { tool_type }.into() - } else { - DialogMessage::RequestComingSoonDialog { issue: None }.into() + match tool_type { + ToolType::Line => ToolMessage::ActivateShapeLine.into(), + ToolType::Ellipse => ToolMessage::ActivateShapeEllipse.into(), + ToolType::Rectangle => ToolMessage::ActivateShapeRectangle.into(), + ToolType::Shape => ToolMessage::ActivateToolShape .into(), + _ => { + if !tooltip.contains("Coming Soon") { (ToolMessage::ActivateTool { tool_type }).into() } else { (DialogMessage::RequestComingSoonDialog { issue: None }).into() } + } } - }).widget_holder() + }) + .widget_holder() }); separator.chain(buttons) @@ -287,11 +308,13 @@ impl Default for ToolFsmState { Self { tool_data: ToolData { active_tool_type: ToolType::Select, + active_shape_type: None, tools: list_tools_in_groups() .into_iter() .flatten() .filter_map(|tool| match tool { ToolAvailability::Available(tool) => Some((tool.tool_type(), tool)), + ToolAvailability::AvailableAsShape(_) => None, ToolAvailability::ComingSoon(_) => None, }) .collect(), @@ -327,12 +350,15 @@ pub enum ToolType { Pen, Freehand, Spline, - Line, - Rectangle, - Ellipse, + Shape, Polygon, Text, + // Shape group + Rectangle, + Ellipse, + Line, + // Raster tool group Brush, Heal, @@ -344,8 +370,21 @@ pub enum ToolType { Frame, } +impl ToolType { + pub fn get_shape(&self) -> Option { + match self { + Self::Rectangle | Self::Line | Self::Ellipse => Some(*self), + _ => None, + } + } + + pub fn get_tool(self) -> Self { + if self.get_shape().is_some() { ToolType::Shape } else { self } + } +} enum ToolAvailability { Available(Box), + AvailableAsShape(ShapeType), ComingSoon(ToolEntry), } @@ -367,10 +406,11 @@ fn list_tools_in_groups() -> Vec> { ToolAvailability::Available(Box::::default()), ToolAvailability::Available(Box::::default()), ToolAvailability::Available(Box::::default()), - ToolAvailability::Available(Box::::default()), - ToolAvailability::Available(Box::::default()), - ToolAvailability::Available(Box::::default()), - ToolAvailability::Available(Box::::default()), + ToolAvailability::Available(Box::::default()), + ToolAvailability::AvailableAsShape(ShapeType::Rectangle), + ToolAvailability::AvailableAsShape(ShapeType::Ellipse), + ToolAvailability::AvailableAsShape(ShapeType::Line), + // ToolAvailability::Available(Box::::default()), ToolAvailability::Available(Box::::default()), ], vec![ @@ -403,9 +443,7 @@ pub fn tool_message_to_tool_type(tool_message: &ToolMessage) -> ToolType { ToolMessage::Pen(_) => ToolType::Pen, ToolMessage::Freehand(_) => ToolType::Freehand, ToolMessage::Spline(_) => ToolType::Spline, - ToolMessage::Line(_) => ToolType::Line, - ToolMessage::Rectangle(_) => ToolType::Rectangle, - ToolMessage::Ellipse(_) => ToolType::Ellipse, + ToolMessage::Shape(_) => ToolType::Shape, ToolMessage::Polygon(_) => ToolType::Polygon, ToolMessage::Text(_) => ToolType::Text, @@ -436,11 +474,12 @@ pub fn tool_type_to_activate_tool_message(tool_type: ToolType) -> ToolMessageDis ToolType::Pen => ToolMessageDiscriminant::ActivateToolPen, ToolType::Freehand => ToolMessageDiscriminant::ActivateToolFreehand, ToolType::Spline => ToolMessageDiscriminant::ActivateToolSpline, - ToolType::Line => ToolMessageDiscriminant::ActivateToolLine, - ToolType::Rectangle => ToolMessageDiscriminant::ActivateToolRectangle, - ToolType::Ellipse => ToolMessageDiscriminant::ActivateToolEllipse, + ToolType::Shape => ToolMessageDiscriminant::ActivateToolShape, ToolType::Polygon => ToolMessageDiscriminant::ActivateToolPolygon, ToolType::Text => ToolMessageDiscriminant::ActivateToolText, + ToolType::Rectangle => ToolMessageDiscriminant::ActivateShapeRectangle, + ToolType::Ellipse => ToolMessageDiscriminant::ActivateShapeEllipse, + ToolType::Line => ToolMessageDiscriminant::ActivateShapeLine, // Raster tool group ToolType::Brush => ToolMessageDiscriminant::ActivateToolBrush, From 0c02880b5d38001274d92e9c3ffdb465bfd454a6 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Sat, 31 May 2025 00:07:34 +0530 Subject: [PATCH 02/16] add hints --- .../src/messages/tool/shapes/shape_utility.rs | 2 +- .../messages/tool/tool_messages/shape_tool.rs | 125 ++++++++++++------ 2 files changed, 88 insertions(+), 39 deletions(-) diff --git a/editor/src/messages/tool/shapes/shape_utility.rs b/editor/src/messages/tool/shapes/shape_utility.rs index e290de6a93..9cf4751d61 100644 --- a/editor/src/messages/tool/shapes/shape_utility.rs +++ b/editor/src/messages/tool/shapes/shape_utility.rs @@ -10,7 +10,7 @@ use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; use std::collections::VecDeque; -#[derive(Debug, Clone, Copy, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)] pub enum ShapeType { #[default] Convex = 0, diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 9ed79d5221..21b1c03343 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -78,8 +78,8 @@ fn create_sides_widget(vertices: u32) -> WidgetHolder { NumberInput::new(Some(vertices as f64)) .label("Sides") .int() - .min(3.) - .max(1000.) + .min(3.0) + .max(1000.0) .mode(NumberInputMode::Increment) .on_update(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(number_input.value.unwrap() as u32)).into()) .widget_holder() @@ -110,7 +110,7 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder { NumberInput::new(Some(line_weight)) .unit(" px") .label("Weight") - .min(0.) + .min(0.0) .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .on_update(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) .widget_holder() @@ -167,13 +167,19 @@ impl<'a> MessageHandler> for ShapeTo self.options.fill.custom_color = color; self.options.fill.color_type = ToolColorType::Custom; } - ShapeOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, - ShapeOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, + ShapeOptionsUpdate::FillColorType(color_type) => { + self.options.fill.color_type = color_type; + } + ShapeOptionsUpdate::LineWeight(line_weight) => { + self.options.line_weight = line_weight; + } ShapeOptionsUpdate::StrokeColor(color) => { self.options.stroke.custom_color = color; self.options.stroke.color_type = ToolColorType::Custom; } - ShapeOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, + ShapeOptionsUpdate::StrokeColorType(color_type) => { + self.options.stroke.color_type = color_type; + } ShapeOptionsUpdate::WorkingColors(primary, secondary) => { self.options.stroke.primary_working_color = primary; self.options.stroke.secondary_working_color = secondary; @@ -188,19 +194,20 @@ impl<'a> MessageHandler> for ShapeTo } } + self.fsm_state.update_hints(responses); self.send_layout(responses, LayoutTarget::ToolOptions); } fn actions(&self) -> ActionList { match self.fsm_state { - ShapeToolFsmState::Ready => actions!(ShapeToolMessageDiscriminant; + ShapeToolFsmState::Ready(_) => actions!(ShapeToolMessageDiscriminant; DragStart, PointerMove, SetShape, Abort, HideShapeTypeWidget ), - ShapeToolFsmState::Drawing => actions!(ShapeToolMessageDiscriminant; + ShapeToolFsmState::Drawing(_) => actions!(ShapeToolMessageDiscriminant; DragStop, Abort, PointerMove, @@ -234,11 +241,16 @@ impl ToolTransition for ShapeTool { } } -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] enum ShapeToolFsmState { - #[default] - Ready, - Drawing, + Ready(ShapeType), + Drawing(ShapeType), +} + +impl Default for ShapeToolFsmState { + fn default() -> Self { + ShapeToolFsmState::Ready(ShapeType::default()) + } } #[derive(Clone, Debug, Default)] @@ -269,13 +281,15 @@ impl Fsm for ShapeToolFsmState { ) -> Self { let shape_data = &mut tool_data.data; - let ToolMessage::Shape(event) = event else { return self }; + let ToolMessage::Shape(event) = event else { + return self; + }; match (self, event) { (_, ShapeToolMessage::Overlays(mut overlay_context)) => { shape_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); self } - (ShapeToolFsmState::Ready, ShapeToolMessage::DragStart) => { + (ShapeToolFsmState::Ready(_), ShapeToolMessage::DragStart) => { match tool_options.shape_type { ShapeType::Convex | ShapeType::Star | ShapeType::Ellipse | ShapeType::Rectangle => shape_data.start(document, input), ShapeType::Line => { @@ -304,7 +318,7 @@ impl Fsm for ShapeToolFsmState { ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Convex | ShapeType::Star => { responses.add(GraphOperationMessage::TransformSet { layer, - transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), + transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0.0, input.mouse.position), transform_in: TransformIn::Viewport, skip_rerender: false, }); @@ -312,25 +326,27 @@ impl Fsm for ShapeToolFsmState { tool_options.fill.apply_fill(layer, responses); } ShapeType::Line => { - tool_data.angle = 0.; + tool_data.angle = 0.0; tool_data.weight = tool_options.line_weight; } } shape_data.layer = Some(layer); - ShapeToolFsmState::Drawing + ShapeToolFsmState::Drawing(tool_options.shape_type) } - (ShapeToolFsmState::Drawing, ShapeToolMessage::PointerMove(modifier)) => { - let Some(layer) = shape_data.layer else { return ShapeToolFsmState::Ready }; - if match tool_options.shape_type { + (ShapeToolFsmState::Drawing(shape), ShapeToolMessage::PointerMove(modifier)) => { + let Some(layer) = shape_data.layer else { + return ShapeToolFsmState::Ready(shape); + }; + if (match tool_options.shape_type { ShapeType::Rectangle => Rectangle::update_shape(&document, &input, layer, tool_data, modifier, responses), ShapeType::Ellipse => Ellipse::update_shape(&document, &input, layer, tool_data, modifier, responses), ShapeType::Line => Line::update_shape(&document, &input, layer, tool_data, modifier, responses), ShapeType::Convex => Convex::update_shape(&document, &input, layer, tool_data, modifier, responses), ShapeType::Star => Star::update_shape(&document, &input, layer, tool_data, modifier, responses), - } { - return if tool_options.shape_type == ShapeType::Line { ShapeToolFsmState::Ready } else { self }; + }) { + return if tool_options.shape_type == ShapeType::Line { ShapeToolFsmState::Ready(shape) } else { self }; } // Auto-panning @@ -344,11 +360,11 @@ impl Fsm for ShapeToolFsmState { responses.add(OverlaysMessage::Draw); self } - (ShapeToolFsmState::Drawing, ShapeToolMessage::PointerOutsideViewport { .. }) => { + (ShapeToolFsmState::Drawing(shape), ShapeToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning let _ = tool_data.auto_panning.shift_viewport(input, responses); - ShapeToolFsmState::Drawing + ShapeToolFsmState::Drawing(shape) } (state, ShapeToolMessage::PointerOutsideViewport(modifier)) => { // Auto-panning @@ -357,17 +373,17 @@ impl Fsm for ShapeToolFsmState { state } - (ShapeToolFsmState::Drawing, ShapeToolMessage::DragStop) => { + (ShapeToolFsmState::Drawing(shape), ShapeToolMessage::DragStop) => { input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); shape_data.cleanup(responses); - ShapeToolFsmState::Ready + ShapeToolFsmState::Ready(shape) } - (ShapeToolFsmState::Drawing, ShapeToolMessage::Abort) => { + (ShapeToolFsmState::Drawing(shape), ShapeToolMessage::Abort) => { responses.add(DocumentMessage::AbortTransaction); shape_data.cleanup(responses); - ShapeToolFsmState::Ready + ShapeToolFsmState::Ready(shape) } (_, ShapeToolMessage::WorkingColorChanged) => { responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::WorkingColors( @@ -381,7 +397,7 @@ impl Fsm for ShapeToolFsmState { shape_data.cleanup(responses); responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(shape))); - ShapeToolFsmState::Ready + ShapeToolFsmState::Ready(shape) } (_, ShapeToolMessage::HideShapeTypeWidget(hide)) => { tool_data.hide_shape_option_widget = hide; @@ -394,15 +410,48 @@ impl Fsm for ShapeToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { - ShapeToolFsmState::Ready => HintData(vec![HintGroup(vec![ - HintInfo::mouse(MouseMotion::LmbDrag, "Draw Shape"), - HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(), - HintInfo::keys([Key::Alt], "From Center").prepend_plus(), - ])]), - ShapeToolFsmState::Drawing => HintData(vec![ - HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), - HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")]), - ]), + ShapeToolFsmState::Ready(shape) => { + let hint_infos = match shape { + ShapeType::Convex | ShapeType::Star => vec![ + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Polygon"), + HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(), + HintInfo::keys([Key::Alt], "From Center").prepend_plus(), // HintInfo::keys([Key::Alt], "From Center").prepend_plus(), + ], + ShapeType::Ellipse => vec![ + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Ellipse"), + HintInfo::keys([Key::Shift], "Constrain Circular").prepend_plus(), + HintInfo::keys([Key::Alt], "From Center").prepend_plus(), // HintInfo::keys([Key::Alt], "From Center").prepend_plus(), + ], + ShapeType::Line => vec![ + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Line"), + HintInfo::keys([Key::Shift], "15 Regular").prepend_plus(), + HintInfo::keys([Key::Shift], "15° Increments").prepend_plus(), + HintInfo::keys([Key::Alt], "From Center").prepend_plus(), + HintInfo::keys([Key::Control], "Lock Angle").prepend_plus(), + ], + ShapeType::Rectangle => vec![ + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Rectangle"), + HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(), + HintInfo::keys([Key::Alt], "From Center").prepend_plus(), + ], + }; + HintData(vec![HintGroup(hint_infos)]) + } + 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::Convex | ShapeType::Star => 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::Line => HintGroup(vec![ + HintInfo::keys([Key::Shift], "15° Increments"), + HintInfo::keys([Key::Alt], "From Center"), + HintInfo::keys([Key::Control], "Lock Angle"), + ]), + }; + common_hint_group.push(tool_hint_group); + HintData(common_hint_group) + } }; responses.add(FrontendMessage::UpdateInputHints { hint_data }); From eb94b104eae3b7c51007b6978a9f568255e676f5 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Sun, 1 Jun 2025 04:23:09 +0530 Subject: [PATCH 03/16] line modification even when other shapes are selected --- editor/src/messages/tool/shapes/line_shape.rs | 91 ++++++++++-- .../src/messages/tool/shapes/shape_utility.rs | 2 +- .../messages/tool/tool_messages/shape_tool.rs | 129 +++++++++++++++--- 3 files changed, 186 insertions(+), 36 deletions(-) diff --git a/editor/src/messages/tool/shapes/line_shape.rs b/editor/src/messages/tool/shapes/line_shape.rs index 02db424567..614873cb49 100644 --- a/editor/src/messages/tool/shapes/line_shape.rs +++ b/editor/src/messages/tool/shapes/line_shape.rs @@ -1,10 +1,11 @@ -use super::shape_utility::{LineInitData, ShapeToolModifierKey}; -use super::*; -use crate::consts::LINE_ROTATE_SNAP_ANGLE; +use super::shape_utility::ShapeToolModifierKey; +use crate::consts::{BOUNDS_SELECT_THRESHOLD, LINE_ROTATE_SNAP_ANGLE}; 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::tool::common_functionality::graph_modification_utils; +pub use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapTypeConfiguration}; use crate::messages::tool::tool_messages::shape_tool::ShapeToolData; use crate::messages::tool::tool_messages::tool_prelude::*; @@ -13,19 +14,32 @@ use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; use std::collections::VecDeque; -#[derive(Clone, Debug, Default)] +#[derive(Clone, PartialEq, Debug, Default)] pub enum LineEnd { #[default] Start, End, } +#[derive(Clone, Debug, Default)] +pub struct LineToolData { + pub drag_begin: DVec2, + pub drag_start_shifted: DVec2, + pub drag_current_shifted: DVec2, + pub drag_start: DVec2, + pub drag_current: DVec2, + pub angle: f64, + pub weight: f64, + pub selected_layers_with_position: HashMap, + pub editing_layer: Option, + pub dragging_endpoint: Option, +} + #[derive(Default)] pub struct Line; impl Line { - pub fn create_node(document: &DocumentMessageHandler, init_data: LineInitData) -> NodeTemplate { - let drag_start = init_data.drag_start; + pub fn create_node(document: &DocumentMessageHandler, drag_start: DVec2) -> NodeTemplate { let node_type = resolve_document_node_type("Line").expect("Line node does not exist"); node_type.node_template_input_override([ None, @@ -43,11 +57,15 @@ impl Line { responses: &mut VecDeque, ) -> bool { let (center, snap_angle, lock_angle) = (modifier[0], modifier[3], modifier[2]); - shape_tool_data.drag_current = ipp.mouse.position; + shape_tool_data.line_data.drag_current = ipp.mouse.position; let keyboard = &ipp.keyboard; let ignore = vec![layer]; let snap_data = SnapData::ignore(document, ipp, &ignore); - let document_points = generate_line(shape_tool_data, snap_data, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center)); + let mut document_points = generate_line(shape_tool_data, snap_data, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center)); + + if shape_tool_data.line_data.dragging_endpoint == Some(LineEnd::Start) { + document_points.swap(0, 1); + } let Some(node_id) = graph_modification_utils::get_line_id(layer, &document.network_interface) else { return true; @@ -64,23 +82,70 @@ impl Line { responses.add(NodeGraphMessage::RunDocumentGraph); false } + + pub fn overlays(document: &DocumentMessageHandler, shape_tool_data: &mut ShapeToolData, overlay_context: &mut OverlayContext) { + shape_tool_data.line_data.selected_layers_with_position = document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter_map(|layer| { + let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Line")?; + + let (Some(&TaggedValue::DVec2(start)), Some(&TaggedValue::DVec2(end))) = (node_inputs[1].as_value(), node_inputs[2].as_value()) else { + return None; + }; + + let [viewport_start, viewport_end] = [start, end].map(|point| document.metadata().transform_to_viewport(layer).transform_point2(point)); + if !start.abs_diff_eq(end, f64::EPSILON * 1000.) { + overlay_context.line(viewport_start, viewport_end, None, None); + overlay_context.square(viewport_start, Some(6.), None, None); + overlay_context.square(viewport_end, Some(6.), None, None); + } + + Some((layer, [start, end])) + }) + .collect::>(); + } + pub fn dragging_endpoints(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, line_data: &mut LineToolData) -> bool { + for (layer, [document_start, document_end]) in line_data.selected_layers_with_position.iter() { + let transform = document.metadata().transform_to_viewport(*layer); + let viewport_x = transform.transform_vector2(DVec2::X).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD; + let viewport_y = transform.transform_vector2(DVec2::Y).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD; + let threshold_x = transform.inverse().transform_vector2(viewport_x).length(); + let threshold_y = transform.inverse().transform_vector2(viewport_y).length(); + + let drag_start = input.mouse.position; + let [start, end] = [document_start, document_end].map(|point| transform.transform_point2(*point)); + + let start_click = (drag_start.y - start.y).abs() < threshold_y && (drag_start.x - start.x).abs() < threshold_x; + let end_click = (drag_start.y - end.y).abs() < threshold_y && (drag_start.x - end.x).abs() < threshold_x; + + if start_click || end_click { + line_data.dragging_endpoint = Some(if end_click { LineEnd::End } else { LineEnd::Start }); + line_data.drag_start = if end_click { *document_start } else { *document_end }; + line_data.editing_layer = Some(*layer); + return true; + } + } + false + } } fn generate_line(tool_data: &mut ShapeToolData, snap_data: SnapData, lock_angle: bool, snap_angle: bool, center: bool) -> [DVec2; 2] { let document_to_viewport = snap_data.document.metadata().document_to_viewport; - let mut document_points = [tool_data.data.drag_start, document_to_viewport.inverse().transform_point2(tool_data.drag_current)]; + let mut document_points = [tool_data.line_data.drag_start, document_to_viewport.inverse().transform_point2(tool_data.line_data.drag_current)]; let mut angle = -(document_points[1] - document_points[0]).angle_to(DVec2::X); let mut line_length = (document_points[1] - document_points[0]).length(); if lock_angle { - angle = tool_data.angle; + angle = tool_data.line_data.angle; } else if snap_angle { let snap_resolution = LINE_ROTATE_SNAP_ANGLE.to_radians(); angle = (angle / snap_resolution).round() * snap_resolution; } - tool_data.angle = angle; + tool_data.line_data.angle = angle; if lock_angle { let angle_vec = DVec2::new(angle.cos(), angle.sin()); @@ -92,8 +157,8 @@ fn generate_line(tool_data: &mut ShapeToolData, snap_data: SnapData, lock_angle: let constrained = snap_angle || lock_angle; let snap = &mut tool_data.data.snap_manager; - let near_point = SnapCandidatePoint::handle_neighbors(document_points[1], [tool_data.data.drag_start]); - let far_point = SnapCandidatePoint::handle_neighbors(2. * document_points[0] - document_points[1], [tool_data.data.drag_start]); + let near_point = SnapCandidatePoint::handle_neighbors(document_points[1], [tool_data.line_data.drag_start]); + let far_point = SnapCandidatePoint::handle_neighbors(2. * document_points[0] - document_points[1], [tool_data.line_data.drag_start]); let config = SnapTypeConfiguration::default(); if constrained { diff --git a/editor/src/messages/tool/shapes/shape_utility.rs b/editor/src/messages/tool/shapes/shape_utility.rs index 9cf4751d61..8ee16bc8ac 100644 --- a/editor/src/messages/tool/shapes/shape_utility.rs +++ b/editor/src/messages/tool/shapes/shape_utility.rs @@ -8,7 +8,7 @@ use crate::messages::tool::utility_types::*; use glam::DVec2; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; -use std::collections::VecDeque; +use std::collections::{HashMap, VecDeque}; #[derive(Debug, Clone, Copy, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)] pub enum ShapeType { diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 21b1c03343..a51546e491 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -1,20 +1,24 @@ use std::vec; +use super::path_tool::DraggingState; use super::tool_prelude::*; -use crate::consts::DEFAULT_STROKE_WIDTH; +use crate::consts::{BOUNDS_SELECT_THRESHOLD, DEFAULT_STROKE_WIDTH, SNAP_POINT_TOLERANCE}; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; 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::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; -use crate::messages::tool::common_functionality::graph_modification_utils; +use crate::messages::tool::common_functionality::graph_modification_utils::{self, *}; use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapTypeConfiguration}; +use crate::messages::tool::common_functionality::utility_functions::closest_point; use crate::messages::tool::shapes::convex_shape::Convex; -use crate::messages::tool::shapes::shape_utility::{LineInitData, ShapeToolModifierKey, ShapeType}; +use crate::messages::tool::shapes::line_shape::LineToolData; +use crate::messages::tool::shapes::shape_utility::{ShapeToolModifierKey, ShapeType}; use crate::messages::tool::shapes::star_shape::Star; -use crate::messages::tool::shapes::{Ellipse, Line, Rectangle}; +use crate::messages::tool::shapes::{Ellipse, Line, LineEnd, Rectangle}; use graph_craft::document::NodeId; +use graph_craft::document::value::TaggedValue; use graphene_core::Color; #[derive(Default)] @@ -214,6 +218,13 @@ impl<'a> MessageHandler> for ShapeTo SetShape, HideShapeTypeWidget ), + ShapeToolFsmState::LineDraggingEndpoints => actions!(ShapeToolMessageDiscriminant; + DragStop, + Abort, + PointerMove, + SetShape, + HideShapeTypeWidget + ), } } } @@ -245,6 +256,7 @@ impl ToolTransition for ShapeTool { enum ShapeToolFsmState { Ready(ShapeType), Drawing(ShapeType), + LineDraggingEndpoints, } impl Default for ShapeToolFsmState { @@ -256,13 +268,9 @@ impl Default for ShapeToolFsmState { #[derive(Clone, Debug, Default)] pub struct ShapeToolData { pub data: Resize, - pub drag_current: DVec2, - pub angle: f64, - pub weight: f64, - pub selected_layers_with_position: HashMap, auto_panning: AutoPanning, pub hide_shape_option_widget: bool, - current_shape: ShapeType, + pub line_data: LineToolData, } impl Fsm for ShapeToolFsmState { @@ -274,12 +282,17 @@ impl Fsm for ShapeToolFsmState { event: ToolMessage, tool_data: &mut Self::ToolData, ToolActionHandlerData { - document, global_tool_data, input, .. + document, + global_tool_data, + input, + preferences, + .. }: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { let shape_data = &mut tool_data.data; + let line_data = &mut tool_data.line_data; let ToolMessage::Shape(event) = event else { return self; @@ -287,27 +300,52 @@ impl Fsm for ShapeToolFsmState { match (self, event) { (_, ShapeToolMessage::Overlays(mut overlay_context)) => { shape_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); + Line::overlays(document, tool_data, &mut overlay_context); + self } (ShapeToolFsmState::Ready(_), ShapeToolMessage::DragStart) => { + let intersectins = document.click(input); + + log::info!("{:?}", intersectins); + + if let Some((layer, _, _)) = closest_point( + document, + input.mouse.position, + SNAP_POINT_TOLERANCE, + document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface), + |_| false, + preferences, + ) { + if check_clicked_on_endpoints(layer, document, input, line_data) { + return ShapeToolFsmState::LineDraggingEndpoints; + } + } + match tool_options.shape_type { ShapeType::Convex | ShapeType::Star | ShapeType::Ellipse | ShapeType::Rectangle => shape_data.start(document, input), ShapeType::Line => { let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); let snapped = shape_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); - shape_data.drag_start = snapped.snapped_point_document; + line_data.drag_start = snapped.snapped_point_document; + line_data.drag_begin = document.metadata().document_to_viewport.transform_point2(line_data.drag_start); } } responses.add(DocumentMessage::StartTransaction); + if tool_options.shape_type == ShapeType::Line && Line::dragging_endpoints(document, input, line_data) { + return ShapeToolFsmState::Drawing(tool_options.shape_type); + } + let node = match tool_options.shape_type { ShapeType::Convex => Convex::create_node(tool_options.vertices), ShapeType::Star => Star::create_node(tool_options.vertices), ShapeType::Rectangle => Rectangle::create_node(), ShapeType::Ellipse => Ellipse::create_node(), - ShapeType::Line => Line::create_node(&document, LineInitData { drag_start: shape_data.drag_start }), + ShapeType::Line => Line::create_node(&document, line_data.drag_start), }; + let nodes = vec![(NodeId(0), node)]; let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input), responses); @@ -326,8 +364,9 @@ impl Fsm for ShapeToolFsmState { tool_options.fill.apply_fill(layer, responses); } ShapeType::Line => { - tool_data.angle = 0.0; - tool_data.weight = tool_options.line_weight; + line_data.angle = 0.0; + line_data.weight = tool_options.line_weight; + line_data.editing_layer = Some(layer); } } @@ -339,13 +378,13 @@ impl Fsm for ShapeToolFsmState { let Some(layer) = shape_data.layer else { return ShapeToolFsmState::Ready(shape); }; - if (match tool_options.shape_type { + if match tool_options.shape_type { ShapeType::Rectangle => Rectangle::update_shape(&document, &input, layer, tool_data, modifier, responses), ShapeType::Ellipse => Ellipse::update_shape(&document, &input, layer, tool_data, modifier, responses), ShapeType::Line => Line::update_shape(&document, &input, layer, tool_data, modifier, responses), ShapeType::Convex => Convex::update_shape(&document, &input, layer, tool_data, modifier, responses), ShapeType::Star => Star::update_shape(&document, &input, layer, tool_data, modifier, responses), - }) { + } { return if tool_options.shape_type == ShapeType::Line { ShapeToolFsmState::Ready(shape) } else { self }; } @@ -355,16 +394,29 @@ impl Fsm for ShapeToolFsmState { self } + (ShapeToolFsmState::LineDraggingEndpoints, ShapeToolMessage::PointerMove(modifier)) => { + log::info!("reaching here"); + let Some(layer) = line_data.editing_layer else { + return ShapeToolFsmState::Ready(tool_options.shape_type); + }; + + Line::update_shape(&document, &input, layer, tool_data, modifier, responses); + // Auto-panning + let messages = [ShapeToolMessage::PointerOutsideViewport(modifier).into(), ShapeToolMessage::PointerMove(modifier).into()]; + tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + + self + } (_, ShapeToolMessage::PointerMove { .. }) => { shape_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); responses.add(OverlaysMessage::Draw); self } - (ShapeToolFsmState::Drawing(shape), ShapeToolMessage::PointerOutsideViewport { .. }) => { + (ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::LineDraggingEndpoints, ShapeToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning let _ = tool_data.auto_panning.shift_viewport(input, responses); - ShapeToolFsmState::Drawing(shape) + ShapeToolFsmState::Drawing(tool_options.shape_type) } (state, ShapeToolMessage::PointerOutsideViewport(modifier)) => { // Auto-panning @@ -373,11 +425,11 @@ impl Fsm for ShapeToolFsmState { state } - (ShapeToolFsmState::Drawing(shape), ShapeToolMessage::DragStop) => { + (ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::LineDraggingEndpoints, ShapeToolMessage::DragStop) => { input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); shape_data.cleanup(responses); - ShapeToolFsmState::Ready(shape) + ShapeToolFsmState::Ready(tool_options.shape_type) } (ShapeToolFsmState::Drawing(shape), ShapeToolMessage::Abort) => { responses.add(DocumentMessage::AbortTransaction); @@ -424,7 +476,6 @@ impl Fsm for ShapeToolFsmState { ], ShapeType::Line => vec![ HintInfo::mouse(MouseMotion::LmbDrag, "Draw Line"), - HintInfo::keys([Key::Shift], "15 Regular").prepend_plus(), HintInfo::keys([Key::Shift], "15° Increments").prepend_plus(), HintInfo::keys([Key::Alt], "From Center").prepend_plus(), HintInfo::keys([Key::Control], "Lock Angle").prepend_plus(), @@ -452,6 +503,10 @@ impl Fsm for ShapeToolFsmState { common_hint_group.push(tool_hint_group); HintData(common_hint_group) } + ShapeToolFsmState::LineDraggingEndpoints => { + let mut common_hint_group = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]; + HintData(common_hint_group) + } }; responses.add(FrontendMessage::UpdateInputHints { hint_data }); @@ -461,3 +516,33 @@ impl Fsm for ShapeToolFsmState { responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }); } } + +fn check_clicked_on_endpoints(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, line_data: &mut LineToolData) -> bool { + let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Line") else { + return false; + }; + + let (Some(&TaggedValue::DVec2(document_start)), Some(&TaggedValue::DVec2(document_end))) = (node_inputs[1].as_value(), node_inputs[2].as_value()) else { + return false; + }; + + let transform = document.metadata().transform_to_viewport(layer); + let viewport_x = transform.transform_vector2(DVec2::X).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD; + let viewport_y = transform.transform_vector2(DVec2::Y).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD; + let threshold_x = transform.inverse().transform_vector2(viewport_x).length(); + let threshold_y = transform.inverse().transform_vector2(viewport_y).length(); + + let drag_start = input.mouse.position; + let [start, end] = [document_start, document_end].map(|point| transform.transform_point2(point)); + + let start_click = (drag_start.y - start.y).abs() < threshold_y && (drag_start.x - start.x).abs() < threshold_x; + let end_click = (drag_start.y - end.y).abs() < threshold_y && (drag_start.x - end.x).abs() < threshold_x; + + if start_click || end_click { + line_data.dragging_endpoint = Some(if end_click { LineEnd::End } else { LineEnd::Start }); + line_data.drag_start = if end_click { document_start } else { document_end }; + line_data.editing_layer = Some(layer); + return true; + } + false +} From d64803a5c445f5642d56f7267c65659e46a06ce0 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Sun, 1 Jun 2025 21:00:57 +0530 Subject: [PATCH 04/16] added transform and anchor overlays --- editor/src/messages/tool/shapes/line_shape.rs | 33 +++++- .../src/messages/tool/shapes/shape_utility.rs | 65 ++++++++++- .../messages/tool/tool_messages/shape_tool.rs | 109 ++++++------------ 3 files changed, 127 insertions(+), 80 deletions(-) diff --git a/editor/src/messages/tool/shapes/line_shape.rs b/editor/src/messages/tool/shapes/line_shape.rs index 614873cb49..03e7496071 100644 --- a/editor/src/messages/tool/shapes/line_shape.rs +++ b/editor/src/messages/tool/shapes/line_shape.rs @@ -23,9 +23,6 @@ pub enum LineEnd { #[derive(Clone, Debug, Default)] pub struct LineToolData { - pub drag_begin: DVec2, - pub drag_start_shifted: DVec2, - pub drag_current_shifted: DVec2, pub drag_start: DVec2, pub drag_current: DVec2, pub angle: f64, @@ -193,3 +190,33 @@ fn generate_line(tool_data: &mut ShapeToolData, snap_data: SnapData, lock_angle: document_points } + +pub fn clicked_on_line_endpoints(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, line_data: &mut LineToolData) -> bool { + let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Line") else { + return false; + }; + + let (Some(&TaggedValue::DVec2(document_start)), Some(&TaggedValue::DVec2(document_end))) = (node_inputs[1].as_value(), node_inputs[2].as_value()) else { + return false; + }; + + let transform = document.metadata().transform_to_viewport(layer); + let viewport_x = transform.transform_vector2(DVec2::X).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD; + let viewport_y = transform.transform_vector2(DVec2::Y).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD; + let threshold_x = transform.inverse().transform_vector2(viewport_x).length(); + let threshold_y = transform.inverse().transform_vector2(viewport_y).length(); + + let drag_start = input.mouse.position; + let [start, end] = [document_start, document_end].map(|point| transform.transform_point2(point)); + + let start_click = (drag_start.y - start.y).abs() < threshold_y && (drag_start.x - start.x).abs() < threshold_x; + let end_click = (drag_start.y - end.y).abs() < threshold_y && (drag_start.x - end.x).abs() < threshold_x; + + if start_click || end_click { + line_data.dragging_endpoint = Some(if end_click { LineEnd::End } else { LineEnd::Start }); + line_data.drag_start = if end_click { document_start } else { document_end }; + line_data.editing_layer = Some(layer); + return true; + } + false +} diff --git a/editor/src/messages/tool/shapes/shape_utility.rs b/editor/src/messages/tool/shapes/shape_utility.rs index 8ee16bc8ac..c7388671c0 100644 --- a/editor/src/messages/tool/shapes/shape_utility.rs +++ b/editor/src/messages/tool/shapes/shape_utility.rs @@ -1,14 +1,18 @@ use crate::messages::message::Message; +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; use crate::messages::prelude::{DocumentMessageHandler, NodeGraphMessage, Responses}; -use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; +use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer}; +use crate::messages::tool::common_functionality::transformation_cage::BoundingBoxManager; use crate::messages::tool::tool_messages::tool_prelude::Key; use crate::messages::tool::utility_types::*; -use glam::DVec2; +use glam::{DMat2, DVec2}; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; -use std::collections::{HashMap, VecDeque}; +use std::collections::VecDeque; + +use super::ShapeToolData; #[derive(Debug, Clone, Copy, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)] pub enum ShapeType { @@ -96,3 +100,58 @@ pub fn update_radius_sign(end: DVec2, start: DVec2, layer: LayerNodeIdentifier, }); } } + +pub fn transform_cage_overlays(document: &DocumentMessageHandler, tool_data: &mut ShapeToolData, overlay_context: &mut OverlayContext) { + let mut transform = document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter(|layer| graph_modification_utils::get_line_id(*layer, &document.network_interface).is_none()) + .find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[])) + .map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface)) + .unwrap_or_default(); + + // Check if the matrix is not invertible + let mut transform_tampered = false; + if transform.matrix2.determinant() == 0. { + transform.matrix2 += DMat2::IDENTITY * 1e-4; // TODO: Is this the cleanest way to handle this? + transform_tampered = true; + } + + let bounds = document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter(|layer| graph_modification_utils::get_line_id(*layer, &document.network_interface).is_none()) + .filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[])) + .filter_map(|layer| { + document + .metadata() + .bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer)) + }) + .reduce(graphene_core::renderer::Quad::combine_bounds); + + if let Some(bounds) = bounds { + let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default()); + + bounding_box_manager.bounds = bounds; + bounding_box_manager.transform = transform; + bounding_box_manager.transform_tampered = transform_tampered; + bounding_box_manager.render_overlays(overlay_context, true); + } else { + tool_data.bounding_box_manager.take(); + } +} + +pub fn anchor_overlays(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { + for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) { + let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue }; + let transform = document.metadata().transform_to_viewport(layer); + + overlay_context.outline_vector(&vector_data, transform); + + for (_, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) { + overlay_context.manipulator_anchor(transform.transform_point2(position), false, None); + } + } +} diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index a51546e491..ccdd01cd46 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -1,25 +1,22 @@ -use std::vec; - -use super::path_tool::DraggingState; use super::tool_prelude::*; -use crate::consts::{BOUNDS_SELECT_THRESHOLD, DEFAULT_STROKE_WIDTH, SNAP_POINT_TOLERANCE}; +use crate::consts::{DEFAULT_STROKE_WIDTH, SNAP_POINT_TOLERANCE}; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; -use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; -use crate::messages::tool::common_functionality::graph_modification_utils::{self, *}; +use crate::messages::tool::common_functionality::graph_modification_utils::{self}; use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapTypeConfiguration}; +use crate::messages::tool::common_functionality::transformation_cage::BoundingBoxManager; use crate::messages::tool::common_functionality::utility_functions::closest_point; use crate::messages::tool::shapes::convex_shape::Convex; -use crate::messages::tool::shapes::line_shape::LineToolData; -use crate::messages::tool::shapes::shape_utility::{ShapeToolModifierKey, ShapeType}; +use crate::messages::tool::shapes::line_shape::{LineToolData, clicked_on_line_endpoints}; +use crate::messages::tool::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays}; use crate::messages::tool::shapes::star_shape::Star; -use crate::messages::tool::shapes::{Ellipse, Line, LineEnd, Rectangle}; +use crate::messages::tool::shapes::{Ellipse, Line, Rectangle}; use graph_craft::document::NodeId; -use graph_craft::document::value::TaggedValue; use graphene_core::Color; +use std::vec; #[derive(Default)] pub struct ShapeTool { @@ -218,7 +215,7 @@ impl<'a> MessageHandler> for ShapeTo SetShape, HideShapeTypeWidget ), - ShapeToolFsmState::LineDraggingEndpoints => actions!(ShapeToolMessageDiscriminant; + ShapeToolFsmState::DraggingLineEndpoints => actions!(ShapeToolMessageDiscriminant; DragStop, Abort, PointerMove, @@ -256,7 +253,7 @@ impl ToolTransition for ShapeTool { enum ShapeToolFsmState { Ready(ShapeType), Drawing(ShapeType), - LineDraggingEndpoints, + DraggingLineEndpoints, } impl Default for ShapeToolFsmState { @@ -271,6 +268,7 @@ pub struct ShapeToolData { auto_panning: AutoPanning, pub hide_shape_option_widget: bool, pub line_data: LineToolData, + pub bounding_box_manager: Option, } impl Fsm for ShapeToolFsmState { @@ -302,13 +300,16 @@ impl Fsm for ShapeToolFsmState { shape_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); Line::overlays(document, tool_data, &mut overlay_context); + if input.keyboard.key(Key::Control) { + anchor_overlays(document, &mut overlay_context); + } else { + transform_cage_overlays(document, tool_data, &mut overlay_context); + } + self } (ShapeToolFsmState::Ready(_), ShapeToolMessage::DragStart) => { - let intersectins = document.click(input); - - log::info!("{:?}", intersectins); - + // If clicked on endpoints of a selected line, drag its endpoints if let Some((layer, _, _)) = closest_point( document, input.mouse.position, @@ -317,8 +318,8 @@ impl Fsm for ShapeToolFsmState { |_| false, preferences, ) { - if check_clicked_on_endpoints(layer, document, input, line_data) { - return ShapeToolFsmState::LineDraggingEndpoints; + if clicked_on_line_endpoints(layer, document, input, line_data) && !input.keyboard.key(Key::Control) { + return ShapeToolFsmState::DraggingLineEndpoints; } } @@ -328,16 +329,11 @@ impl Fsm for ShapeToolFsmState { let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); let snapped = shape_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); line_data.drag_start = snapped.snapped_point_document; - line_data.drag_begin = document.metadata().document_to_viewport.transform_point2(line_data.drag_start); } } responses.add(DocumentMessage::StartTransaction); - if tool_options.shape_type == ShapeType::Line && Line::dragging_endpoints(document, input, line_data) { - return ShapeToolFsmState::Drawing(tool_options.shape_type); - } - let node = match tool_options.shape_type { ShapeType::Convex => Convex::create_node(tool_options.vertices), ShapeType::Star => Star::create_node(tool_options.vertices), @@ -394,8 +390,7 @@ impl Fsm for ShapeToolFsmState { self } - (ShapeToolFsmState::LineDraggingEndpoints, ShapeToolMessage::PointerMove(modifier)) => { - log::info!("reaching here"); + (ShapeToolFsmState::DraggingLineEndpoints, ShapeToolMessage::PointerMove(modifier)) => { let Some(layer) = line_data.editing_layer else { return ShapeToolFsmState::Ready(tool_options.shape_type); }; @@ -412,30 +407,22 @@ impl Fsm for ShapeToolFsmState { responses.add(OverlaysMessage::Draw); self } - (ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::LineDraggingEndpoints, ShapeToolMessage::PointerOutsideViewport { .. }) => { + (_, ShapeToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning let _ = tool_data.auto_panning.shift_viewport(input, responses); - - ShapeToolFsmState::Drawing(tool_options.shape_type) - } - (state, ShapeToolMessage::PointerOutsideViewport(modifier)) => { - // Auto-panning - let messages = [ShapeToolMessage::PointerOutsideViewport(modifier).into(), ShapeToolMessage::PointerMove(modifier).into()]; - tool_data.auto_panning.stop(&messages, responses); - - state + self } - (ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::LineDraggingEndpoints, ShapeToolMessage::DragStop) => { + (ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::DraggingLineEndpoints, ShapeToolMessage::DragStop) => { input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); shape_data.cleanup(responses); ShapeToolFsmState::Ready(tool_options.shape_type) } - (ShapeToolFsmState::Drawing(shape), ShapeToolMessage::Abort) => { + (ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::DraggingLineEndpoints, ShapeToolMessage::Abort) => { responses.add(DocumentMessage::AbortTransaction); shape_data.cleanup(responses); - ShapeToolFsmState::Ready(shape) + ShapeToolFsmState::Ready(tool_options.shape_type) } (_, ShapeToolMessage::WorkingColorChanged) => { responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::WorkingColors( @@ -467,12 +454,12 @@ impl Fsm for ShapeToolFsmState { ShapeType::Convex | ShapeType::Star => vec![ HintInfo::mouse(MouseMotion::LmbDrag, "Draw Polygon"), HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(), - HintInfo::keys([Key::Alt], "From Center").prepend_plus(), // HintInfo::keys([Key::Alt], "From Center").prepend_plus(), + HintInfo::keys([Key::Alt], "From Center").prepend_plus(), ], ShapeType::Ellipse => vec![ HintInfo::mouse(MouseMotion::LmbDrag, "Draw Ellipse"), HintInfo::keys([Key::Shift], "Constrain Circular").prepend_plus(), - HintInfo::keys([Key::Alt], "From Center").prepend_plus(), // HintInfo::keys([Key::Alt], "From Center").prepend_plus(), + HintInfo::keys([Key::Alt], "From Center").prepend_plus(), ], ShapeType::Line => vec![ HintInfo::mouse(MouseMotion::LmbDrag, "Draw Line"), @@ -503,10 +490,14 @@ impl Fsm for ShapeToolFsmState { common_hint_group.push(tool_hint_group); HintData(common_hint_group) } - ShapeToolFsmState::LineDraggingEndpoints => { - let mut common_hint_group = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]; - HintData(common_hint_group) - } + ShapeToolFsmState::DraggingLineEndpoints => HintData(vec![ + HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), + HintGroup(vec![ + HintInfo::keys([Key::Shift], "15° Increments"), + HintInfo::keys([Key::Alt], "From Center"), + HintInfo::keys([Key::Control], "Lock Angle"), + ]), + ]), }; responses.add(FrontendMessage::UpdateInputHints { hint_data }); @@ -516,33 +507,3 @@ impl Fsm for ShapeToolFsmState { responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }); } } - -fn check_clicked_on_endpoints(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, line_data: &mut LineToolData) -> bool { - let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Line") else { - return false; - }; - - let (Some(&TaggedValue::DVec2(document_start)), Some(&TaggedValue::DVec2(document_end))) = (node_inputs[1].as_value(), node_inputs[2].as_value()) else { - return false; - }; - - let transform = document.metadata().transform_to_viewport(layer); - let viewport_x = transform.transform_vector2(DVec2::X).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD; - let viewport_y = transform.transform_vector2(DVec2::Y).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD; - let threshold_x = transform.inverse().transform_vector2(viewport_x).length(); - let threshold_y = transform.inverse().transform_vector2(viewport_y).length(); - - let drag_start = input.mouse.position; - let [start, end] = [document_start, document_end].map(|point| transform.transform_point2(point)); - - let start_click = (drag_start.y - start.y).abs() < threshold_y && (drag_start.x - start.x).abs() < threshold_x; - let end_click = (drag_start.y - end.y).abs() < threshold_y && (drag_start.x - end.x).abs() < threshold_x; - - if start_click || end_click { - line_data.dragging_endpoint = Some(if end_click { LineEnd::End } else { LineEnd::Start }); - line_data.drag_start = if end_click { document_start } else { document_end }; - line_data.editing_layer = Some(layer); - return true; - } - false -} From 3af2017a61bd91f2fd9be2adc02390e5b0b20f1a Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Sun, 1 Jun 2025 21:14:38 +0530 Subject: [PATCH 05/16] removed old code --- .../messages/input_mapper/input_mappings.rs | 27 - editor/src/messages/prelude.rs | 1 - editor/src/messages/tool/tool_message.rs | 2 - .../tool/tool_messages/ellipse_tool.rs | 441 ------------- .../messages/tool/tool_messages/line_tool.rs | 624 ------------------ editor/src/messages/tool/tool_messages/mod.rs | 4 - .../tool/tool_messages/polygon_tool.rs | 436 ------------ .../tool/tool_messages/rectangle_tool.rs | 323 --------- editor/src/messages/tool/utility_types.rs | 3 - 9 files changed, 1861 deletions(-) delete mode 100644 editor/src/messages/tool/tool_messages/ellipse_tool.rs delete mode 100644 editor/src/messages/tool/tool_messages/line_tool.rs delete mode 100644 editor/src/messages/tool/tool_messages/polygon_tool.rs delete mode 100644 editor/src/messages/tool/tool_messages/rectangle_tool.rs diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index bbf66f9b87..4b1b3e8c59 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -171,12 +171,6 @@ pub fn input_mappings() -> Mapping { entry!(KeyDown(MouseRight); action_dispatch=GradientToolMessage::Abort), entry!(KeyDown(Escape); action_dispatch=GradientToolMessage::Abort), // - // RectangleToolMessage - // entry!(KeyDown(MouseLeft); action_dispatch=RectangleToolMessage::DragStart), - // entry!(KeyUp(MouseLeft); action_dispatch=RectangleToolMessage::DragStop), - // entry!(KeyDown(MouseRight); action_dispatch=RectangleToolMessage::Abort), - // entry!(KeyDown(Escape); action_dispatch=RectangleToolMessage::Abort), - // entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=RectangleToolMessage::PointerMove { center: Alt, lock_ratio: Shift }), // ShapeToolMessage entry!(KeyDown(MouseLeft); action_dispatch=ShapeToolMessage::DragStart), entry!(KeyUp(MouseLeft); action_dispatch=ShapeToolMessage::DragStop), @@ -191,27 +185,6 @@ pub fn input_mappings() -> Mapping { // entry!(KeyDown(Escape); action_dispatch=ImaginateToolMessage::Abort), // entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=ImaginateToolMessage::Resize { center: Alt, lock_ratio: Shift }), // - // EllipseToolMessage - // entry!(KeyDown(MouseLeft); action_dispatch=EllipseToolMessage::DragStart), - // entry!(KeyUp(MouseLeft); action_dispatch=EllipseToolMessage::DragStop), - // entry!(KeyDown(MouseRight); action_dispatch=EllipseToolMessage::Abort), - // entry!(KeyDown(Escape); action_dispatch=EllipseToolMessage::Abort), - // entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=EllipseToolMessage::PointerMove { center: Alt, lock_ratio: Shift }), - // - // PolygonToolMessage - entry!(KeyDown(MouseLeft); action_dispatch=PolygonToolMessage::DragStart), - entry!(KeyUp(MouseLeft); action_dispatch=PolygonToolMessage::DragStop), - entry!(KeyDown(MouseRight); action_dispatch=PolygonToolMessage::Abort), - entry!(KeyDown(Escape); action_dispatch=PolygonToolMessage::Abort), - entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=PolygonToolMessage::PointerMove { center: Alt, lock_ratio: Shift }), - // - // LineToolMessage - // entry!(KeyDown(MouseLeft); action_dispatch=LineToolMessage::DragStart), - // entry!(KeyUp(MouseLeft); action_dispatch=LineToolMessage::DragStop), - // entry!(KeyDown(MouseRight); action_dispatch=LineToolMessage::Abort), - // entry!(KeyDown(Escape); action_dispatch=LineToolMessage::Abort), - // entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=LineToolMessage::PointerMove { center: Alt, lock_angle: Control, snap_angle: Shift }), - // // PathToolMessage entry!(KeyDown(Delete); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath), entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath), diff --git a/editor/src/messages/prelude.rs b/editor/src/messages/prelude.rs index 8511625290..eb6900a53f 100644 --- a/editor/src/messages/prelude.rs +++ b/editor/src/messages/prelude.rs @@ -42,7 +42,6 @@ pub use crate::messages::tool::tool_messages::gradient_tool::{GradientToolMessag pub use crate::messages::tool::tool_messages::navigate_tool::{NavigateToolMessage, NavigateToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::path_tool::{PathToolMessage, PathToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::pen_tool::{PenToolMessage, PenToolMessageDiscriminant}; -pub use crate::messages::tool::tool_messages::polygon_tool::{PolygonToolMessage, PolygonToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::select_tool::{SelectToolMessage, SelectToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::shape_tool::{ShapeToolMessage, ShapeToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::spline_tool::{SplineToolMessage, SplineToolMessageDiscriminant}; diff --git a/editor/src/messages/tool/tool_message.rs b/editor/src/messages/tool/tool_message.rs index 16c81106ff..2ae9ae8357 100644 --- a/editor/src/messages/tool/tool_message.rs +++ b/editor/src/messages/tool/tool_message.rs @@ -34,8 +34,6 @@ pub enum ToolMessage { #[child] Shape(ShapeToolMessage), #[child] - Polygon(PolygonToolMessage), - #[child] Text(TextToolMessage), #[child] diff --git a/editor/src/messages/tool/tool_messages/ellipse_tool.rs b/editor/src/messages/tool/tool_messages/ellipse_tool.rs deleted file mode 100644 index 27be03f4ee..0000000000 --- a/editor/src/messages/tool/tool_messages/ellipse_tool.rs +++ /dev/null @@ -1,441 +0,0 @@ -// use super::tool_prelude::*; -// use crate::consts::DEFAULT_STROKE_WIDTH; -// 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::overlays::utility_types::OverlayContext; -// use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; -// use crate::messages::tool::common_functionality::auto_panning::AutoPanning; -// use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; -// use crate::messages::tool::common_functionality::graph_modification_utils; -// use crate::messages::tool::common_functionality::resize::Resize; -// use crate::messages::tool::common_functionality::snapping::SnapData; -// use graph_craft::document::value::TaggedValue; -// use graph_craft::document::{NodeId, NodeInput}; -// use graphene_core::Color; - -// #[derive(Default)] -// pub struct EllipseTool { -// fsm_state: EllipseToolFsmState, -// data: EllipseToolData, -// options: EllipseToolOptions, -// } - -// pub struct EllipseToolOptions { -// line_weight: f64, -// fill: ToolColorOptions, -// stroke: ToolColorOptions, -// } - -// impl Default for EllipseToolOptions { -// fn default() -> Self { -// Self { -// line_weight: DEFAULT_STROKE_WIDTH, -// fill: ToolColorOptions::new_secondary(), -// stroke: ToolColorOptions::new_primary(), -// } -// } -// } - -// #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] -// pub enum EllipseOptionsUpdate { -// FillColor(Option), -// FillColorType(ToolColorType), -// LineWeight(f64), -// StrokeColor(Option), -// StrokeColorType(ToolColorType), -// WorkingColors(Option, Option), -// } - -// #[impl_message(Message, ToolMessage, Ellipse)] -// #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] -// pub enum EllipseToolMessage { -// // Standard messages -// Overlays(OverlayContext), -// Abort, -// WorkingColorChanged, - -// // Tool-specific messages -// DragStart, -// DragStop, -// PointerMove { center: Key, lock_ratio: Key }, -// PointerOutsideViewport { center: Key, lock_ratio: Key }, -// UpdateOptions(EllipseOptionsUpdate), -// } - -// impl ToolMetadata for EllipseTool { -// fn icon_name(&self) -> String { -// "VectorEllipseTool".into() -// } -// fn tooltip(&self) -> String { -// "Ellipse Tool".into() -// } -// fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { -// ToolType::Ellipse -// } -// } - -// fn create_weight_widget(line_weight: f64) -> WidgetHolder { -// NumberInput::new(Some(line_weight)) -// .unit(" px") -// .label("Weight") -// .min(0.) -// .max((1_u64 << f64::MANTISSA_DIGITS) as f64) -// .on_update(|number_input: &NumberInput| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) -// .widget_holder() -// } - -// impl LayoutHolder for EllipseTool { -// fn layout(&self) -> Layout { -// let mut widgets = self.options.fill.create_widgets( -// "Fill", -// true, -// |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColor(None)).into(), -// |color_type: ToolColorType| WidgetCallback::new(move |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColorType(color_type.clone())).into()), -// |color: &ColorInput| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), -// ); - -// widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); - -// widgets.append(&mut self.options.stroke.create_widgets( -// "Stroke", -// true, -// |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColor(None)).into(), -// |color_type: ToolColorType| WidgetCallback::new(move |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColorType(color_type.clone())).into()), -// |color: &ColorInput| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), -// )); -// widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); -// widgets.push(create_weight_widget(self.options.line_weight)); - -// Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) -// } -// } - -// impl<'a> MessageHandler> for EllipseTool { -// fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { -// let ToolMessage::Ellipse(EllipseToolMessage::UpdateOptions(action)) = message else { -// self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true); -// return; -// }; -// match action { -// EllipseOptionsUpdate::FillColor(color) => { -// self.options.fill.custom_color = color; -// self.options.fill.color_type = ToolColorType::Custom; -// } -// EllipseOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, -// EllipseOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, -// EllipseOptionsUpdate::StrokeColor(color) => { -// self.options.stroke.custom_color = color; -// self.options.stroke.color_type = ToolColorType::Custom; -// } -// EllipseOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, -// EllipseOptionsUpdate::WorkingColors(primary, secondary) => { -// self.options.stroke.primary_working_color = primary; -// self.options.stroke.secondary_working_color = secondary; -// self.options.fill.primary_working_color = primary; -// self.options.fill.secondary_working_color = secondary; -// } -// } - -// self.send_layout(responses, LayoutTarget::ToolOptions); -// } - -// fn actions(&self) -> ActionList { -// match self.fsm_state { -// EllipseToolFsmState::Ready => actions!(EllipseToolMessageDiscriminant; -// DragStart, -// PointerMove, -// ), -// EllipseToolFsmState::Drawing => actions!(EllipseToolMessageDiscriminant; -// DragStop, -// Abort, -// PointerMove, -// ), -// } -// } -// } - -// impl ToolTransition for EllipseTool { -// fn event_to_message_map(&self) -> EventToMessageMap { -// EventToMessageMap { -// overlay_provider: Some(|overlay_context| EllipseToolMessage::Overlays(overlay_context).into()), -// tool_abort: Some(EllipseToolMessage::Abort.into()), -// working_color_changed: Some(EllipseToolMessage::WorkingColorChanged.into()), -// ..Default::default() -// } -// } -// } - -// #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -// enum EllipseToolFsmState { -// #[default] -// Ready, -// Drawing, -// } - -// #[derive(Clone, Debug, Default)] -// struct EllipseToolData { -// data: Resize, -// auto_panning: AutoPanning, -// } - -// impl Fsm for EllipseToolFsmState { -// type ToolData = EllipseToolData; -// type ToolOptions = EllipseToolOptions; - -// fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { -// let ToolActionHandlerData { -// document, global_tool_data, input, .. -// } = tool_action_data; - -// let shape_data = &mut tool_data.data; - -// let ToolMessage::Ellipse(event) = event else { return self }; -// match (self, event) { -// (_, EllipseToolMessage::Overlays(mut overlay_context)) => { -// shape_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); -// self -// } -// (EllipseToolFsmState::Ready, EllipseToolMessage::DragStart) => { -// shape_data.start(document, input); -// responses.add(DocumentMessage::StartTransaction); - -// // Create a new ellipse vector shape -// let node_type = resolve_document_node_type("Ellipse").expect("Ellipse node does not exist"); -// let node = node_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(0.5), false)), Some(NodeInput::value(TaggedValue::F64(0.5), false))]); -// let nodes = vec![(NodeId(0), node)]; - -// let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input), responses); -// responses.add(Message::StartBuffer); -// responses.add(GraphOperationMessage::TransformSet { -// layer, -// transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), -// transform_in: TransformIn::Viewport, -// skip_rerender: false, -// }); -// tool_options.fill.apply_fill(layer, responses); -// tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); -// shape_data.layer = Some(layer); - -// EllipseToolFsmState::Drawing -// } -// (EllipseToolFsmState::Drawing, EllipseToolMessage::PointerMove { center, lock_ratio }) => { -// if let Some([start, end]) = shape_data.calculate_points(document, input, center, lock_ratio) { -// if let Some(layer) = shape_data.layer { -// let Some(node_id) = graph_modification_utils::get_ellipse_id(layer, &document.network_interface) else { -// return self; -// }; - -// 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 + end) / 2.), -// transform_in: TransformIn::Viewport, -// skip_rerender: false, -// }); -// } -// } - -// // Auto-panning -// let messages = [ -// EllipseToolMessage::PointerOutsideViewport { center, lock_ratio }.into(), -// EllipseToolMessage::PointerMove { center, lock_ratio }.into(), -// ]; -// tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); - -// self -// } -// (_, EllipseToolMessage::PointerMove { .. }) => { -// shape_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); -// responses.add(OverlaysMessage::Draw); -// self -// } -// (EllipseToolFsmState::Drawing, EllipseToolMessage::PointerOutsideViewport { .. }) => { -// // Auto-panning -// let _ = tool_data.auto_panning.shift_viewport(input, responses); - -// EllipseToolFsmState::Drawing -// } -// (state, EllipseToolMessage::PointerOutsideViewport { center, lock_ratio }) => { -// // Auto-panning -// let messages = [ -// EllipseToolMessage::PointerOutsideViewport { center, lock_ratio }.into(), -// EllipseToolMessage::PointerMove { center, lock_ratio }.into(), -// ]; -// tool_data.auto_panning.stop(&messages, responses); - -// state -// } -// (EllipseToolFsmState::Drawing, EllipseToolMessage::DragStop) => { -// input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); -// shape_data.cleanup(responses); - -// EllipseToolFsmState::Ready -// } -// (EllipseToolFsmState::Drawing, EllipseToolMessage::Abort) => { -// responses.add(DocumentMessage::AbortTransaction); -// shape_data.cleanup(responses); - -// EllipseToolFsmState::Ready -// } -// (_, EllipseToolMessage::WorkingColorChanged) => { -// responses.add(EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::WorkingColors( -// Some(global_tool_data.primary_color), -// Some(global_tool_data.secondary_color), -// ))); -// self -// } -// _ => self, -// } -// } - -// fn update_hints(&self, responses: &mut VecDeque) { -// let hint_data = match self { -// EllipseToolFsmState::Ready => HintData(vec![HintGroup(vec![ -// HintInfo::mouse(MouseMotion::LmbDrag, "Draw Ellipse"), -// HintInfo::keys([Key::Shift], "Constrain Circular").prepend_plus(), -// HintInfo::keys([Key::Alt], "From Center").prepend_plus(), -// ])]), -// EllipseToolFsmState::Drawing => HintData(vec![ -// HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), -// HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Circular"), HintInfo::keys([Key::Alt], "From Center")]), -// ]), -// }; - -// responses.add(FrontendMessage::UpdateInputHints { hint_data }); -// } - -// fn update_cursor(&self, responses: &mut VecDeque) { -// responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }); -// } -// } - -// #[cfg(test)] -// mod test_ellipse { -// pub use crate::test_utils::test_prelude::*; -// use glam::DAffine2; -// use graphene_core::vector::generator_nodes::ellipse; - -// #[derive(Debug, PartialEq)] -// struct ResolvedEllipse { -// radius_x: f64, -// radius_y: f64, -// transform: DAffine2, -// } - -// async fn get_ellipse(editor: &mut EditorTestUtils) -> Vec { -// let instrumented = editor.eval_graph().await; - -// let document = editor.active_document(); -// let layers = document.metadata().all_layers(); -// layers -// .filter_map(|layer| { -// let node_graph_layer = NodeGraphLayer::new(layer, &document.network_interface); -// let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::protonode_identifier())?; -// Some(ResolvedEllipse { -// radius_x: instrumented.grab_protonode_input::(&vec![ellipse_node], &editor.runtime).unwrap(), -// radius_y: instrumented.grab_protonode_input::(&vec![ellipse_node], &editor.runtime).unwrap(), -// transform: document.metadata().transform_to_document(layer), -// }) -// }) -// .collect() -// } - -// #[tokio::test] -// async fn ellipse_draw_simple() { -// let mut editor = EditorTestUtils::create(); -// editor.new_document().await; -// editor.drag_tool(ToolType::Ellipse, 10., 10., 19., 0., ModifierKeys::empty()).await; - -// assert_eq!(editor.active_document().metadata().all_layers().count(), 1); - -// let ellipse = get_ellipse(&mut editor).await; -// assert_eq!(ellipse.len(), 1); -// assert_eq!( -// ellipse[0], -// ResolvedEllipse { -// radius_x: 4.5, -// radius_y: 5., -// transform: DAffine2::from_translation(DVec2::new(14.5, 5.)) // Uses center -// } -// ); -// } - -// #[tokio::test] -// async fn ellipse_draw_circle() { -// let mut editor = EditorTestUtils::create(); -// editor.new_document().await; -// editor.drag_tool(ToolType::Ellipse, 10., 10., -10., 11., ModifierKeys::SHIFT).await; - -// let ellipse = get_ellipse(&mut editor).await; -// assert_eq!(ellipse.len(), 1); -// assert_eq!( -// ellipse[0], -// ResolvedEllipse { -// radius_x: 10., -// radius_y: 10., -// transform: DAffine2::from_translation(DVec2::new(0., 20.)) // Uses center -// } -// ); -// } - -// #[tokio::test] -// async fn ellipse_draw_square_rotated() { -// let mut editor = EditorTestUtils::create(); -// editor.new_document().await; -// editor -// .handle_message(NavigationMessage::CanvasTiltSet { -// // 45 degree rotation of content clockwise -// angle_radians: f64::consts::FRAC_PI_4, -// }) -// .await; -// editor.drag_tool(ToolType::Ellipse, 0., 0., 1., 10., ModifierKeys::SHIFT).await; // Viewport coordinates - -// let ellipse = get_ellipse(&mut editor).await; -// assert_eq!(ellipse.len(), 1); -// println!("{ellipse:?}"); -// assert_eq!(ellipse[0].radius_x, 5.); -// assert_eq!(ellipse[0].radius_y, 5.); - -// assert!( -// ellipse[0] -// .transform -// .abs_diff_eq(DAffine2::from_angle_translation(-f64::consts::FRAC_PI_4, DVec2::X * f64::consts::FRAC_1_SQRT_2 * 10.), 0.001) -// ); -// } - -// #[tokio::test] -// async fn ellipse_draw_center_square_rotated() { -// let mut editor = EditorTestUtils::create(); -// editor.new_document().await; -// editor -// .handle_message(NavigationMessage::CanvasTiltSet { -// // 45 degree rotation of content clockwise -// angle_radians: f64::consts::FRAC_PI_4, -// }) -// .await; -// editor.drag_tool(ToolType::Ellipse, 0., 0., 1., 10., ModifierKeys::SHIFT | ModifierKeys::ALT).await; // Viewport coordinates - -// let ellipse = get_ellipse(&mut editor).await; -// assert_eq!(ellipse.len(), 1); -// assert_eq!(ellipse[0].radius_x, 10.); -// assert_eq!(ellipse[0].radius_y, 10.); -// assert!(ellipse[0].transform.abs_diff_eq(DAffine2::from_angle(-f64::consts::FRAC_PI_4), 0.001)); -// } - -// #[tokio::test] -// async fn ellipse_cancel() { -// let mut editor = EditorTestUtils::create(); -// editor.new_document().await; -// editor.drag_tool_cancel_rmb(ToolType::Ellipse).await; - -// let ellipse = get_ellipse(&mut editor).await; -// assert_eq!(ellipse.len(), 0); -// } -// } diff --git a/editor/src/messages/tool/tool_messages/line_tool.rs b/editor/src/messages/tool/tool_messages/line_tool.rs deleted file mode 100644 index 130ca866be..0000000000 --- a/editor/src/messages/tool/tool_messages/line_tool.rs +++ /dev/null @@ -1,624 +0,0 @@ -// use super::tool_prelude::*; -// use crate::consts::{BOUNDS_SELECT_THRESHOLD, DEFAULT_STROKE_WIDTH, LINE_ROTATE_SNAP_ANGLE}; -// 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; -// use crate::messages::tool::common_functionality::auto_panning::AutoPanning; -// use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; -// use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer}; -// use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration}; -// use graph_craft::document::value::TaggedValue; -// use graph_craft::document::{NodeId, NodeInput}; -// use graphene_core::Color; - -// #[derive(Default)] -// pub struct LineTool { -// fsm_state: LineToolFsmState, -// tool_data: LineToolData, -// options: LineOptions, -// } - -// pub struct LineOptions { -// line_weight: f64, -// stroke: ToolColorOptions, -// } - -// impl Default for LineOptions { -// fn default() -> Self { -// Self { -// line_weight: DEFAULT_STROKE_WIDTH, -// stroke: ToolColorOptions::new_primary(), -// } -// } -// } - -// #[impl_message(Message, ToolMessage, Line)] -// #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] -// pub enum LineToolMessage { -// // Standard messages -// Overlays(OverlayContext), -// Abort, -// WorkingColorChanged, - -// // Tool-specific messages -// DragStart, -// DragStop, -// PointerMove { center: Key, lock_angle: Key, snap_angle: Key }, -// PointerOutsideViewport { center: Key, lock_angle: Key, snap_angle: Key }, -// UpdateOptions(LineOptionsUpdate), -// } - -// #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] -// pub enum LineOptionsUpdate { -// LineWeight(f64), -// StrokeColor(Option), -// StrokeColorType(ToolColorType), -// WorkingColors(Option, Option), -// } - -// impl ToolMetadata for LineTool { -// fn icon_name(&self) -> String { -// "VectorLineTool".into() -// } -// fn tooltip(&self) -> String { -// "Line Tool".into() -// } -// fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { -// ToolType::Line -// } -// } - -// fn create_weight_widget(line_weight: f64) -> WidgetHolder { -// NumberInput::new(Some(line_weight)) -// .unit(" px") -// .label("Weight") -// .min(0.) -// .max((1_u64 << f64::MANTISSA_DIGITS) as f64) -// .on_update(|number_input: &NumberInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) -// .widget_holder() -// } - -// impl LayoutHolder for LineTool { -// fn layout(&self) -> Layout { -// let mut widgets = self.options.stroke.create_widgets( -// "Stroke", -// true, -// |_| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(None)).into(), -// |color_type: ToolColorType| WidgetCallback::new(move |_| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColorType(color_type.clone())).into()), -// |color: &ColorInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), -// ); -// widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); -// widgets.push(create_weight_widget(self.options.line_weight)); - -// Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) -// } -// } - -// impl<'a> MessageHandler> for LineTool { -// fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { -// let ToolMessage::Line(LineToolMessage::UpdateOptions(action)) = message else { -// self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); -// return; -// }; -// match action { -// LineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, -// LineOptionsUpdate::StrokeColor(color) => { -// self.options.stroke.custom_color = color; -// self.options.stroke.color_type = ToolColorType::Custom; -// } -// LineOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, -// LineOptionsUpdate::WorkingColors(primary, secondary) => { -// self.options.stroke.primary_working_color = primary; -// self.options.stroke.secondary_working_color = secondary; -// } -// } - -// self.send_layout(responses, LayoutTarget::ToolOptions); -// } - -// fn actions(&self) -> ActionList { -// match self.fsm_state { -// LineToolFsmState::Ready => actions!(LineToolMessageDiscriminant; DragStart, PointerMove), -// LineToolFsmState::Drawing => actions!(LineToolMessageDiscriminant; DragStop, PointerMove, Abort), -// } -// } -// } - -// impl ToolTransition for LineTool { -// fn event_to_message_map(&self) -> EventToMessageMap { -// EventToMessageMap { -// overlay_provider: Some(|overlay_context| LineToolMessage::Overlays(overlay_context).into()), -// tool_abort: Some(LineToolMessage::Abort.into()), -// working_color_changed: Some(LineToolMessage::WorkingColorChanged.into()), -// ..Default::default() -// } -// } -// } - -// #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] -// enum LineToolFsmState { -// #[default] -// Ready, -// Drawing, -// } - -// #[derive(Clone, Copy, Debug, PartialEq, Eq)] -// enum LineEnd { -// Start, -// End, -// } - -// #[derive(Clone, Debug, Default)] -// struct LineToolData { -// drag_begin: DVec2, -// drag_start_shifted: DVec2, -// drag_current_shifted: DVec2, -// drag_start: DVec2, -// drag_current: DVec2, -// angle: f64, -// weight: f64, -// selected_layers_with_position: HashMap, -// editing_layer: Option, -// snap_manager: SnapManager, -// auto_panning: AutoPanning, -// dragging_endpoint: Option, -// } - -// impl Fsm for LineToolFsmState { -// type ToolData = LineToolData; -// type ToolOptions = LineOptions; - -// fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { -// let ToolActionHandlerData { -// document, global_tool_data, input, .. -// } = tool_action_data; - -// let ToolMessage::Line(event) = event else { return self }; -// match (self, event) { -// (_, LineToolMessage::Overlays(mut overlay_context)) => { -// tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); - -// tool_data.selected_layers_with_position = document -// .network_interface -// .selected_nodes() -// .selected_visible_and_unlocked_layers(&document.network_interface) -// .filter_map(|layer| { -// let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Line")?; - -// let (Some(&TaggedValue::DVec2(start)), Some(&TaggedValue::DVec2(end))) = (node_inputs[1].as_value(), node_inputs[2].as_value()) else { -// return None; -// }; - -// let [viewport_start, viewport_end] = [start, end].map(|point| document.metadata().transform_to_viewport(layer).transform_point2(point)); -// if !start.abs_diff_eq(end, f64::EPSILON * 1000.) { -// overlay_context.line(viewport_start, viewport_end, None, None); -// overlay_context.square(viewport_start, Some(6.), None, None); -// overlay_context.square(viewport_end, Some(6.), None, None); -// } - -// Some((layer, [start, end])) -// }) -// .collect::>(); - -// self -// } -// (LineToolFsmState::Ready, LineToolMessage::DragStart) => { -// let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); -// let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); -// tool_data.drag_start = snapped.snapped_point_document; -// tool_data.drag_begin = document.metadata().document_to_viewport.transform_point2(tool_data.drag_start); - -// responses.add(DocumentMessage::StartTransaction); - -// for (layer, [document_start, document_end]) in tool_data.selected_layers_with_position.iter() { -// let transform = document.metadata().transform_to_viewport(*layer); -// let viewport_x = transform.transform_vector2(DVec2::X).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD; -// let viewport_y = transform.transform_vector2(DVec2::Y).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD; -// let threshold_x = transform.inverse().transform_vector2(viewport_x).length(); -// let threshold_y = transform.inverse().transform_vector2(viewport_y).length(); - -// let drag_start = input.mouse.position; -// let [start, end] = [document_start, document_end].map(|point| transform.transform_point2(*point)); - -// let start_click = (drag_start.y - start.y).abs() < threshold_y && (drag_start.x - start.x).abs() < threshold_x; -// let end_click = (drag_start.y - end.y).abs() < threshold_y && (drag_start.x - end.x).abs() < threshold_x; - -// if start_click || end_click { -// tool_data.dragging_endpoint = Some(if end_click { LineEnd::End } else { LineEnd::Start }); -// tool_data.drag_start = if end_click { *document_start } else { *document_end }; -// tool_data.editing_layer = Some(*layer); -// return LineToolFsmState::Drawing; -// } -// } - -// let node_type = resolve_document_node_type("Line").expect("Line node does not exist"); -// let node = node_type.node_template_input_override([ -// None, -// Some(NodeInput::value( -// TaggedValue::DVec2(document.metadata().document_to_viewport.transform_point2(tool_data.drag_start)), -// false, -// )), -// Some(NodeInput::value( -// TaggedValue::DVec2(document.metadata().document_to_viewport.transform_point2(tool_data.drag_start)), -// false, -// )), -// ]); -// let nodes = vec![(NodeId(0), node)]; - -// let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input), responses); -// responses.add(Message::StartBuffer); - -// tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); - -// tool_data.editing_layer = Some(layer); -// tool_data.angle = 0.; -// tool_data.weight = tool_options.line_weight; - -// LineToolFsmState::Drawing -// } -// (LineToolFsmState::Drawing, LineToolMessage::PointerMove { center, snap_angle, lock_angle }) => { -// let Some(layer) = tool_data.editing_layer else { return LineToolFsmState::Ready }; - -// tool_data.drag_current_shifted = document.metadata().transform_to_viewport(layer).inverse().transform_point2(input.mouse.position); -// tool_data.drag_current = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); -// tool_data.drag_start_shifted = document.metadata().transform_to_viewport(layer).inverse().transform_point2(tool_data.drag_begin); - -// let keyboard = &input.keyboard; -// let ignore = vec![layer]; -// let snap_data = SnapData::ignore(document, input, &ignore); -// let mut document_points = generate_line(tool_data, snap_data, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center)); - -// if tool_data.dragging_endpoint == Some(LineEnd::Start) { -// document_points.swap(0, 1); -// } - -// let Some(node_id) = graph_modification_utils::get_line_id(layer, &document.network_interface) else { -// return LineToolFsmState::Ready; -// }; - -// responses.add(NodeGraphMessage::SetInput { -// input_connector: InputConnector::node(node_id, 1), -// input: NodeInput::value(TaggedValue::DVec2(document_points[0]), false), -// }); -// responses.add(NodeGraphMessage::SetInput { -// input_connector: InputConnector::node(node_id, 2), -// input: NodeInput::value(TaggedValue::DVec2(document_points[1]), false), -// }); -// responses.add(NodeGraphMessage::RunDocumentGraph); - -// // Auto-panning -// let messages = [ -// LineToolMessage::PointerOutsideViewport { center, snap_angle, lock_angle }.into(), -// LineToolMessage::PointerMove { center, snap_angle, lock_angle }.into(), -// ]; -// tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); - -// LineToolFsmState::Drawing -// } -// (_, LineToolMessage::PointerMove { .. }) => { -// tool_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); -// responses.add(OverlaysMessage::Draw); -// self -// } -// (LineToolFsmState::Drawing, LineToolMessage::PointerOutsideViewport { .. }) => { -// // Auto-panning -// let _ = tool_data.auto_panning.shift_viewport(input, responses); - -// LineToolFsmState::Drawing -// } -// (state, LineToolMessage::PointerOutsideViewport { center, lock_angle, snap_angle }) => { -// // Auto-panning -// let messages = [ -// LineToolMessage::PointerOutsideViewport { center, lock_angle, snap_angle }.into(), -// LineToolMessage::PointerMove { center, lock_angle, snap_angle }.into(), -// ]; -// tool_data.auto_panning.stop(&messages, responses); - -// state -// } -// (LineToolFsmState::Drawing, LineToolMessage::DragStop) => { -// tool_data.snap_manager.cleanup(responses); -// tool_data.editing_layer.take(); -// input.mouse.finish_transaction(tool_data.drag_start, responses); -// LineToolFsmState::Ready -// } -// (LineToolFsmState::Drawing, LineToolMessage::Abort) => { -// tool_data.snap_manager.cleanup(responses); -// tool_data.editing_layer.take(); -// responses.add(DocumentMessage::AbortTransaction); -// LineToolFsmState::Ready -// } -// (_, LineToolMessage::WorkingColorChanged) => { -// responses.add(LineToolMessage::UpdateOptions(LineOptionsUpdate::WorkingColors( -// Some(global_tool_data.primary_color), -// Some(global_tool_data.secondary_color), -// ))); -// self -// } -// _ => self, -// } -// } - -// fn update_hints(&self, responses: &mut VecDeque) { -// let hint_data = match self { -// LineToolFsmState::Ready => HintData(vec![HintGroup(vec![ -// HintInfo::mouse(MouseMotion::LmbDrag, "Draw Line"), -// HintInfo::keys([Key::Shift], "15° Increments").prepend_plus(), -// HintInfo::keys([Key::Alt], "From Center").prepend_plus(), -// HintInfo::keys([Key::Control], "Lock Angle").prepend_plus(), -// ])]), -// LineToolFsmState::Drawing => HintData(vec![ -// HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), -// HintGroup(vec![ -// HintInfo::keys([Key::Shift], "15° Increments"), -// HintInfo::keys([Key::Alt], "From Center"), -// HintInfo::keys([Key::Control], "Lock Angle"), -// ]), -// ]), -// }; - -// responses.add(FrontendMessage::UpdateInputHints { hint_data }); -// } - -// fn update_cursor(&self, responses: &mut VecDeque) { -// responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }); -// } -// } - -// fn generate_line(tool_data: &mut LineToolData, snap_data: SnapData, lock_angle: bool, snap_angle: bool, center: bool) -> [DVec2; 2] { -// let shift = tool_data.drag_current_shifted - tool_data.drag_current; -// let mut document_points = [tool_data.drag_start, tool_data.drag_current]; - -// let mut angle = -(document_points[1] - document_points[0]).angle_to(DVec2::X); -// let mut line_length = (document_points[1] - document_points[0]).length(); - -// if lock_angle { -// angle = tool_data.angle; -// } else if snap_angle { -// let snap_resolution = LINE_ROTATE_SNAP_ANGLE.to_radians(); -// angle = (angle / snap_resolution).round() * snap_resolution; -// } - -// tool_data.angle = angle; - -// let angle_vec = DVec2::from_angle(angle); -// if lock_angle { -// line_length = (document_points[1] - document_points[0]).dot(angle_vec); -// } - -// document_points[1] = document_points[0] + line_length * angle_vec; - -// let constrained = snap_angle || lock_angle; -// let snap = &mut tool_data.snap_manager; - -// let near_point = SnapCandidatePoint::handle_neighbors(document_points[1], [tool_data.drag_start]); -// let far_point = SnapCandidatePoint::handle_neighbors(2. * document_points[0] - document_points[1], [tool_data.drag_start]); -// let mid_point = SnapCandidatePoint::handle_neighbors((tool_data.drag_start + document_points[1]) / 2., [tool_data.drag_start]); -// let config = SnapTypeConfiguration::default(); - -// if constrained { -// let constraint = SnapConstraint::Line { -// origin: document_points[0], -// direction: document_points[1] - document_points[0], -// }; -// if center { -// let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config); -// let snapped_far = snap.constrained_snap(&snap_data, &far_point, constraint, config); -// let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far }; -// document_points[1] = document_points[0] * 2. - best.snapped_point_document; -// document_points[0] = best.snapped_point_document; -// snap.update_indicator(best); -// } else { -// let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config); -// let snapped_mid = snap.constrained_snap(&snap_data, &mid_point, constraint, config); -// let best = if snap_data.document.snapping_state.path.line_midpoint && snapped_mid.other_snap_better(&snapped_mid) { -// document_points[1] += (snapped_mid.snapped_point_document - mid_point.document_point) * 2.; -// snapped_mid -// } else { -// document_points[1] = snapped.snapped_point_document; -// snapped.clone() -// }; -// snap.update_indicator(best); -// } -// } else if center { -// let snapped = snap.free_snap(&snap_data, &near_point, config); -// let snapped_far = snap.free_snap(&snap_data, &far_point, config); -// let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far }; -// document_points[1] = document_points[0] * 2. - best.snapped_point_document; -// document_points[0] = best.snapped_point_document; -// snap.update_indicator(best); -// } else { -// let snapped = snap.free_snap(&snap_data, &near_point, config); -// let snapped_mid = snap.free_snap(&snap_data, &mid_point, config); -// let best = if snap_data.document.snapping_state.path.line_midpoint && snapped_mid.other_snap_better(&snapped_mid) { -// document_points[1] += (snapped_mid.snapped_point_document - mid_point.document_point) * 2.; -// snapped_mid -// } else { -// document_points[1] = snapped.snapped_point_document; -// snapped.clone() -// }; -// snap.update_indicator(best); -// } - -// // Snapping happens in other space, while document graph renders in another. -// document_points.map(|vector| vector + shift) -// } - -// #[cfg(test)] -// mod test_line_tool { -// use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; -// use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; -// use crate::test_utils::test_prelude::*; -// use glam::DAffine2; -// use graph_craft::document::value::TaggedValue; - -// async fn get_line_node_inputs(editor: &mut EditorTestUtils) -> Option<(DVec2, DVec2)> { -// let document = editor.active_document(); -// let network_interface = &document.network_interface; -// let node_id = network_interface -// .selected_nodes() -// .selected_visible_and_unlocked_layers(network_interface) -// .filter_map(|layer| { -// let node_inputs = NodeGraphLayer::new(layer, &network_interface).find_node_inputs("Line")?; -// let (Some(&TaggedValue::DVec2(start)), Some(&TaggedValue::DVec2(end))) = (node_inputs[1].as_value(), node_inputs[2].as_value()) else { -// return None; -// }; -// Some((start, end)) -// }) -// .next(); -// node_id -// } - -// #[tokio::test] -// async fn test_line_tool_basicdraw() { -// let mut editor = EditorTestUtils::create(); -// editor.new_document().await; -// editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::empty()).await; -// if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { -// match (start_input, end_input) { -// (start_input, end_input) => { -// assert!((start_input - DVec2::ZERO).length() < 1., "Start point should be near (0,0)"); -// assert!((end_input - DVec2::new(100., 100.)).length() < 1., "End point should be near (100,100)"); -// } -// } -// } -// } - -// #[tokio::test] -// async fn test_line_tool_with_transformed_viewport() { -// let mut editor = EditorTestUtils::create(); -// editor.new_document().await; -// editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2. }).await; -// editor.handle_message(NavigationMessage::CanvasPan { delta: DVec2::new(100., 50.) }).await; -// editor -// .handle_message(NavigationMessage::CanvasTiltSet { -// angle_radians: (30. as f64).to_radians(), -// }) -// .await; -// editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::empty()).await; -// if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { -// let document = editor.active_document(); -// let document_to_viewport = document.metadata().document_to_viewport; -// let viewport_to_document = document_to_viewport.inverse(); - -// let expected_start = viewport_to_document.transform_point2(DVec2::ZERO); -// let expected_end = viewport_to_document.transform_point2(DVec2::new(100., 100.)); - -// assert!( -// (start_input - expected_start).length() < 1., -// "Start point should match expected document coordinates. Got {:?}, expected {:?}", -// start_input, -// expected_start -// ); -// assert!( -// (end_input - expected_end).length() < 1., -// "End point should match expected document coordinates. Got {:?}, expected {:?}", -// end_input, -// expected_end -// ); -// } else { -// panic!("Line was not created successfully with transformed viewport"); -// } -// } - -// #[tokio::test] -// async fn test_line_tool_ctrl_anglelock() { -// let mut editor = EditorTestUtils::create(); -// editor.new_document().await; -// editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::CONTROL).await; -// if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { -// match (start_input, end_input) { -// (start_input, end_input) => { -// let line_vec = end_input - start_input; -// let original_angle = line_vec.angle_to(DVec2::X); -// editor.drag_tool(ToolType::Line, 0., 0., 200., 50., ModifierKeys::CONTROL).await; -// if let Some((updated_start, updated_end)) = get_line_node_inputs(&mut editor).await { -// match (updated_start, updated_end) { -// (updated_start, updated_end) => { -// let updated_line_vec = updated_end - updated_start; -// let updated_angle = updated_line_vec.angle_to(DVec2::X); -// assert!((original_angle - updated_angle).abs() < 0.1, "Line angle should be locked when Ctrl is kept pressed"); -// assert!((updated_start - updated_end).length() > 1., "Line should be able to change length when Ctrl is kept pressed"); -// } -// } -// } -// } -// } -// } -// } - -// #[tokio::test] -// async fn test_line_tool_alt() { -// let mut editor = EditorTestUtils::create(); -// editor.new_document().await; -// editor.drag_tool(ToolType::Line, 100., 100., 200., 100., ModifierKeys::ALT).await; -// if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { -// match (start_input, end_input) { -// (start_input, end_input) => { -// let expected_start = DVec2::new(0., 100.); -// let expected_end = DVec2::new(200., 100.); -// assert!((start_input - expected_start).length() < 1., "start point should be near (0,100)"); -// assert!((end_input - expected_end).length() < 1., "end point should be near (200,100)"); -// } -// } -// } -// } - -// #[tokio::test] -// async fn test_line_tool_alt_shift_drag() { -// let mut editor = EditorTestUtils::create(); -// editor.new_document().await; -// editor.drag_tool(ToolType::Line, 100., 100., 150., 120., ModifierKeys::ALT | ModifierKeys::SHIFT).await; -// if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { -// match (start_input, end_input) { -// (start_input, end_input) => { -// let line_vec = end_input - start_input; -// let angle_radians = line_vec.angle_to(DVec2::X); -// let angle_degrees = angle_radians.to_degrees(); -// let nearest_angle = (angle_degrees / 15.).round() * 15.; - -// assert!((angle_degrees - nearest_angle).abs() < 1., "Angle should snap to the nearest 15 degrees"); -// } -// } -// } -// } - -// #[tokio::test] -// async fn test_line_tool_with_transformed_artboard() { -// let mut editor = EditorTestUtils::create(); -// editor.new_document().await; -// editor.drag_tool(ToolType::Artboard, 0., 0., 200., 200., ModifierKeys::empty()).await; - -// let artboard_id = editor.get_selected_layer().await.expect("Should have selected the artboard"); - -// editor -// .handle_message(GraphOperationMessage::TransformChange { -// layer: artboard_id, -// transform: DAffine2::from_angle(45.0_f64.to_radians()), -// transform_in: TransformIn::Local, -// skip_rerender: false, -// }) -// .await; - -// editor.drag_tool(ToolType::Line, 50., 50., 150., 150., ModifierKeys::empty()).await; - -// let (start_input, end_input) = get_line_node_inputs(&mut editor).await.expect("Line was not created successfully within transformed artboard"); -// // The line should still be diagonal with equal change in x and y -// let line_vector = end_input - start_input; -// // Verifying the line is approximately 100*sqrt(2) units in length (diagonal of 100x100 square) -// let line_length = line_vector.length(); -// assert!( -// (line_length - 141.42).abs() < 1.0, // 100 * sqrt(2) ~= 141.42 -// "Line length should be approximately 141.42 units. Got: {line_length}" -// ); -// assert!((line_vector.x - 100.0).abs() < 1.0, "X-component of line vector should be approximately 100. Got: {}", line_vector.x); -// assert!( -// (line_vector.y.abs() - 100.0).abs() < 1.0, -// "Absolute Y-component of line vector should be approximately 100. Got: {}", -// line_vector.y.abs() -// ); -// let angle_degrees = line_vector.angle_to(DVec2::X).to_degrees(); -// assert!((angle_degrees - (-45.0)).abs() < 1.0, "Line angle should be close to -45 degrees. Got: {angle_degrees}"); -// } -// } diff --git a/editor/src/messages/tool/tool_messages/mod.rs b/editor/src/messages/tool/tool_messages/mod.rs index b387024820..b11a20ec09 100644 --- a/editor/src/messages/tool/tool_messages/mod.rs +++ b/editor/src/messages/tool/tool_messages/mod.rs @@ -1,17 +1,13 @@ pub mod artboard_tool; pub mod brush_tool; -pub mod ellipse_tool; pub mod eyedropper_tool; pub mod fill_tool; pub mod freehand_tool; pub mod gradient_tool; // pub mod imaginate_tool; -pub mod line_tool; pub mod navigate_tool; pub mod path_tool; pub mod pen_tool; -pub mod polygon_tool; -pub mod rectangle_tool; pub mod select_tool; pub mod shape_tool; pub mod spline_tool; diff --git a/editor/src/messages/tool/tool_messages/polygon_tool.rs b/editor/src/messages/tool/tool_messages/polygon_tool.rs deleted file mode 100644 index 51cce795f8..0000000000 --- a/editor/src/messages/tool/tool_messages/polygon_tool.rs +++ /dev/null @@ -1,436 +0,0 @@ -use super::tool_prelude::*; -use crate::consts::DEFAULT_STROKE_WIDTH; -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::overlays::utility_types::OverlayContext; -use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; -use crate::messages::tool::common_functionality::auto_panning::AutoPanning; -use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; -use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer}; -use crate::messages::tool::common_functionality::resize::Resize; -use crate::messages::tool::common_functionality::snapping::SnapData; -use graph_craft::document::value::TaggedValue; -use graph_craft::document::{NodeId, NodeInput}; -use graphene_core::Color; - -#[derive(Default)] -pub struct PolygonTool { - fsm_state: PolygonToolFsmState, - tool_data: PolygonToolData, - options: PolygonOptions, -} - -pub struct PolygonOptions { - line_weight: f64, - fill: ToolColorOptions, - stroke: ToolColorOptions, - vertices: u32, - polygon_type: PolygonType, -} - -impl Default for PolygonOptions { - fn default() -> Self { - Self { - vertices: 5, - line_weight: DEFAULT_STROKE_WIDTH, - fill: ToolColorOptions::new_secondary(), - stroke: ToolColorOptions::new_primary(), - polygon_type: PolygonType::Convex, - } - } -} - -#[impl_message(Message, ToolMessage, Polygon)] -#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] -pub enum PolygonToolMessage { - // Standard messages - Overlays(OverlayContext), - Abort, - WorkingColorChanged, - - // Tool-specific messages - DragStart, - DragStop, - PointerMove { center: Key, lock_ratio: Key }, - PointerOutsideViewport { center: Key, lock_ratio: Key }, - UpdateOptions(PolygonOptionsUpdate), -} - -#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] -pub enum PolygonType { - Convex = 0, - Star = 1, -} - -#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] -pub enum PolygonOptionsUpdate { - FillColor(Option), - FillColorType(ToolColorType), - LineWeight(f64), - PolygonType(PolygonType), - StrokeColor(Option), - StrokeColorType(ToolColorType), - Vertices(u32), - WorkingColors(Option, Option), -} - -impl ToolMetadata for PolygonTool { - fn icon_name(&self) -> String { - "VectorPolygonTool".into() - } - fn tooltip(&self) -> String { - "Polygon Tool".into() - } - fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { - ToolType::Polygon - } -} - -fn create_sides_widget(vertices: u32) -> WidgetHolder { - NumberInput::new(Some(vertices as f64)) - .label("Sides") - .int() - .min(3.) - .max(1000.) - .mode(NumberInputMode::Increment) - .on_update(|number_input: &NumberInput| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::Vertices(number_input.value.unwrap() as u32)).into()) - .widget_holder() -} - -fn create_star_option_widget(polygon_type: PolygonType) -> WidgetHolder { - let entries = vec![ - RadioEntryData::new("convex") - .label("Convex") - .on_update(move |_| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::PolygonType(PolygonType::Convex)).into()), - RadioEntryData::new("star") - .label("Star") - .on_update(move |_| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::PolygonType(PolygonType::Star)).into()), - ]; - RadioInput::new(entries).selected_index(Some(polygon_type as u32)).widget_holder() -} - -fn create_weight_widget(line_weight: f64) -> WidgetHolder { - NumberInput::new(Some(line_weight)) - .unit(" px") - .label("Weight") - .min(0.) - .max((1_u64 << f64::MANTISSA_DIGITS) as f64) - .on_update(|number_input: &NumberInput| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) - .widget_holder() -} - -impl LayoutHolder for PolygonTool { - fn layout(&self) -> Layout { - let mut widgets = vec![ - create_star_option_widget(self.options.polygon_type), - Separator::new(SeparatorType::Related).widget_holder(), - create_sides_widget(self.options.vertices), - ]; - - widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); - - widgets.append(&mut self.options.fill.create_widgets( - "Fill", - true, - |_| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::FillColor(None)).into(), - |color_type: ToolColorType| WidgetCallback::new(move |_| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::FillColorType(color_type.clone())).into()), - |color: &ColorInput| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), - )); - - widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); - - widgets.append(&mut self.options.stroke.create_widgets( - "Stroke", - true, - |_| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::StrokeColor(None)).into(), - |color_type: ToolColorType| WidgetCallback::new(move |_| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::StrokeColorType(color_type.clone())).into()), - |color: &ColorInput| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), - )); - widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); - widgets.push(create_weight_widget(self.options.line_weight)); - - Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) - } -} -impl<'a> MessageHandler> for PolygonTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { - let ToolMessage::Polygon(PolygonToolMessage::UpdateOptions(action)) = message else { - self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); - return; - }; - match action { - PolygonOptionsUpdate::Vertices(vertices) => self.options.vertices = vertices, - PolygonOptionsUpdate::PolygonType(polygon_type) => self.options.polygon_type = polygon_type, - PolygonOptionsUpdate::FillColor(color) => { - self.options.fill.custom_color = color; - self.options.fill.color_type = ToolColorType::Custom; - } - PolygonOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, - PolygonOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, - PolygonOptionsUpdate::StrokeColor(color) => { - self.options.stroke.custom_color = color; - self.options.stroke.color_type = ToolColorType::Custom; - } - PolygonOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, - PolygonOptionsUpdate::WorkingColors(primary, secondary) => { - self.options.stroke.primary_working_color = primary; - self.options.stroke.secondary_working_color = secondary; - self.options.fill.primary_working_color = primary; - self.options.fill.secondary_working_color = secondary; - } - } - - self.send_layout(responses, LayoutTarget::ToolOptions); - } - - fn actions(&self) -> ActionList { - match self.fsm_state { - PolygonToolFsmState::Ready => actions!(PolygonToolMessageDiscriminant; - DragStart, - PointerMove, - ), - PolygonToolFsmState::Drawing => actions!(PolygonToolMessageDiscriminant; - DragStop, - Abort, - PointerMove, - ), - } - } -} - -impl ToolTransition for PolygonTool { - fn event_to_message_map(&self) -> EventToMessageMap { - EventToMessageMap { - overlay_provider: Some(|overlay_context| PolygonToolMessage::Overlays(overlay_context).into()), - tool_abort: Some(PolygonToolMessage::Abort.into()), - working_color_changed: Some(PolygonToolMessage::WorkingColorChanged.into()), - ..Default::default() - } - } -} - -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -enum PolygonToolFsmState { - #[default] - Ready, - Drawing, -} - -#[derive(Clone, Debug, Default)] -struct PolygonToolData { - data: Resize, - auto_panning: AutoPanning, -} - -impl Fsm for PolygonToolFsmState { - type ToolData = PolygonToolData; - type ToolOptions = PolygonOptions; - - fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { - let ToolActionHandlerData { - document, global_tool_data, input, .. - } = tool_action_data; - - let polygon_data = &mut tool_data.data; - - let ToolMessage::Polygon(event) = event else { return self }; - match (self, event) { - (_, PolygonToolMessage::Overlays(mut overlay_context)) => { - polygon_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); - self - } - (PolygonToolFsmState::Ready, PolygonToolMessage::DragStart) => { - polygon_data.start(document, input); - responses.add(DocumentMessage::StartTransaction); - - let node = match tool_options.polygon_type { - PolygonType::Convex => resolve_document_node_type("Regular Polygon") - .expect("Regular Polygon node does not exist") - .node_template_input_override([ - None, - Some(NodeInput::value(TaggedValue::U32(tool_options.vertices), false)), - Some(NodeInput::value(TaggedValue::F64(0.5), false)), - ]), - PolygonType::Star => resolve_document_node_type("Star").expect("Star node does not exist").node_template_input_override([ - None, - Some(NodeInput::value(TaggedValue::U32(tool_options.vertices), false)), - Some(NodeInput::value(TaggedValue::F64(0.5), false)), - Some(NodeInput::value(TaggedValue::F64(0.25), false)), - ]), - }; - - let nodes = vec![(NodeId(0), node)]; - - let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input), responses); - responses.add(Message::StartBuffer); - responses.add(GraphOperationMessage::TransformSet { - layer, - transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), - transform_in: TransformIn::Viewport, - skip_rerender: false, - }); - tool_options.fill.apply_fill(layer, responses); - tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); - polygon_data.layer = Some(layer); - - PolygonToolFsmState::Drawing - } - (PolygonToolFsmState::Drawing, PolygonToolMessage::PointerMove { center, lock_ratio }) => { - if let Some([start, end]) = tool_data.data.calculate_points(document, input, center, lock_ratio) { - if let Some(layer) = tool_data.data.layer { - // TODO: We need to determine how to allow the polygon node to make irregular shapes - update_radius_sign(end, start, layer, document, responses); - - let dimensions = (start - end).abs(); - let mut scale = DVec2::ONE; - let radius: f64; - - // We keep the smaller dimension's scale at 1 and scale the other dimension accordingly - if dimensions.x > dimensions.y { - scale.x = dimensions.x / dimensions.y; - radius = dimensions.y / 2.; - } else { - scale.y = dimensions.y / dimensions.x; - radius = dimensions.x / 2.; - } - - match tool_options.polygon_type { - PolygonType::Convex => { - let Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface) else { - return self; - }; - - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 2), - input: NodeInput::value(TaggedValue::F64(radius), false), - }); - } - PolygonType::Star => { - let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface) else { - return self; - }; - - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 2), - input: NodeInput::value(TaggedValue::F64(radius), false), - }); - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 3), - input: NodeInput::value(TaggedValue::F64(radius / 2.), false), - }); - } - } - - responses.add(GraphOperationMessage::TransformSet { - layer, - transform: DAffine2::from_scale_angle_translation(scale, 0., (start + end) / 2.), - transform_in: TransformIn::Viewport, - skip_rerender: false, - }); - } - } - - // Auto-panning - let messages = [ - PolygonToolMessage::PointerOutsideViewport { center, lock_ratio }.into(), - PolygonToolMessage::PointerMove { center, lock_ratio }.into(), - ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); - - self - } - (_, PolygonToolMessage::PointerMove { .. }) => { - polygon_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); - responses.add(OverlaysMessage::Draw); - self - } - (PolygonToolFsmState::Drawing, PolygonToolMessage::PointerOutsideViewport { .. }) => { - // Auto-panning - let _ = tool_data.auto_panning.shift_viewport(input, responses); - - PolygonToolFsmState::Drawing - } - (state, PolygonToolMessage::PointerOutsideViewport { center, lock_ratio }) => { - // Auto-panning - let messages = [ - PolygonToolMessage::PointerOutsideViewport { center, lock_ratio }.into(), - PolygonToolMessage::PointerMove { center, lock_ratio }.into(), - ]; - tool_data.auto_panning.stop(&messages, responses); - - state - } - (PolygonToolFsmState::Drawing, PolygonToolMessage::DragStop) => { - input.mouse.finish_transaction(polygon_data.viewport_drag_start(document), responses); - polygon_data.cleanup(responses); - - PolygonToolFsmState::Ready - } - (PolygonToolFsmState::Drawing, PolygonToolMessage::Abort) => { - responses.add(DocumentMessage::AbortTransaction); - - polygon_data.cleanup(responses); - - PolygonToolFsmState::Ready - } - (_, PolygonToolMessage::WorkingColorChanged) => { - responses.add(PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::WorkingColors( - Some(global_tool_data.primary_color), - Some(global_tool_data.secondary_color), - ))); - self - } - _ => self, - } - } - - fn update_hints(&self, responses: &mut VecDeque) { - let hint_data = match self { - PolygonToolFsmState::Ready => HintData(vec![HintGroup(vec![ - HintInfo::mouse(MouseMotion::LmbDrag, "Draw Polygon"), - HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(), - HintInfo::keys([Key::Alt], "From Center").prepend_plus(), - ])]), - PolygonToolFsmState::Drawing => HintData(vec![ - HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), - HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]), - ]), - }; - - responses.add(FrontendMessage::UpdateInputHints { hint_data }); - } - - fn update_cursor(&self, responses: &mut VecDeque) { - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }); - } -} - -/// In the case where the polygon/star is upside down and the number of sides is odd, we negate the radius instead of using a negative scale. -fn update_radius_sign(end: DVec2, start: DVec2, layer: LayerNodeIdentifier, document: &mut DocumentMessageHandler, responses: &mut VecDeque) { - let sign_num = if end[1] > start[1] { 1. } else { -1. }; - let new_layer = NodeGraphLayer::new(layer, &document.network_interface); - - if new_layer.find_input("Regular Polygon", 1).unwrap_or(&TaggedValue::U32(0)).to_u32() % 2 == 1 { - let Some(polygon_node_id) = new_layer.upstream_node_id_from_name("Regular Polygon") else { return }; - - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(polygon_node_id, 2), - input: NodeInput::value(TaggedValue::F64(sign_num * 0.5), false), - }); - return; - } - - if new_layer.find_input("Star", 1).unwrap_or(&TaggedValue::U32(0)).to_u32() % 2 == 1 { - let Some(star_node_id) = new_layer.upstream_node_id_from_name("Star") else { return }; - - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(star_node_id, 2), - input: NodeInput::value(TaggedValue::F64(sign_num * 0.5), false), - }); - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(star_node_id, 3), - input: NodeInput::value(TaggedValue::F64(sign_num * 0.25), false), - }); - } -} diff --git a/editor/src/messages/tool/tool_messages/rectangle_tool.rs b/editor/src/messages/tool/tool_messages/rectangle_tool.rs deleted file mode 100644 index 98e8399219..0000000000 --- a/editor/src/messages/tool/tool_messages/rectangle_tool.rs +++ /dev/null @@ -1,323 +0,0 @@ -// use super::tool_prelude::*; -// use crate::consts::DEFAULT_STROKE_WIDTH; -// 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::overlays::utility_types::OverlayContext; -// use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; -// use crate::messages::tool::common_functionality::auto_panning::AutoPanning; -// use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; -// use crate::messages::tool::common_functionality::graph_modification_utils; -// use crate::messages::tool::common_functionality::resize::Resize; -// use crate::messages::tool::common_functionality::snapping::SnapData; -// use graph_craft::document::value::TaggedValue; -// use graph_craft::document::{NodeId, NodeInput}; -// use graphene_core::Color; - -// #[derive(Default)] -// pub struct RectangleTool { -// fsm_state: RectangleToolFsmState, -// tool_data: RectangleToolData, -// options: RectangleToolOptions, -// } - -// pub struct RectangleToolOptions { -// line_weight: f64, -// fill: ToolColorOptions, -// stroke: ToolColorOptions, -// } - -// impl Default for RectangleToolOptions { -// fn default() -> Self { -// Self { -// line_weight: DEFAULT_STROKE_WIDTH, -// fill: ToolColorOptions::new_secondary(), -// stroke: ToolColorOptions::new_primary(), -// } -// } -// } - -// #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] -// pub enum RectangleOptionsUpdate { -// FillColor(Option), -// FillColorType(ToolColorType), -// LineWeight(f64), -// StrokeColor(Option), -// StrokeColorType(ToolColorType), -// WorkingColors(Option, Option), -// } - -// #[impl_message(Message, ToolMessage, Rectangle)] -// #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] -// pub enum RectangleToolMessage { -// // Standard messages -// Overlays(OverlayContext), -// Abort, -// WorkingColorChanged, - -// // Tool-specific messages -// DragStart, -// DragStop, -// PointerMove { center: Key, lock_ratio: Key }, -// PointerOutsideViewport { center: Key, lock_ratio: Key }, -// UpdateOptions(RectangleOptionsUpdate), -// } - -// fn create_weight_widget(line_weight: f64) -> WidgetHolder { -// NumberInput::new(Some(line_weight)) -// .unit(" px") -// .label("Weight") -// .min(0.) -// .max((1_u64 << f64::MANTISSA_DIGITS) as f64) -// .on_update(|number_input: &NumberInput| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) -// .widget_holder() -// } - -// impl LayoutHolder for RectangleTool { -// fn layout(&self) -> Layout { -// let mut widgets = self.options.fill.create_widgets( -// "Fill", -// true, -// |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColor(None)).into(), -// |color_type: ToolColorType| WidgetCallback::new(move |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColorType(color_type.clone())).into()), -// |color: &ColorInput| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), -// ); - -// widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); - -// widgets.append(&mut self.options.stroke.create_widgets( -// "Stroke", -// true, -// |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColor(None)).into(), -// |color_type: ToolColorType| WidgetCallback::new(move |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColorType(color_type.clone())).into()), -// |color: &ColorInput| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), -// )); -// widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); -// widgets.push(create_weight_widget(self.options.line_weight)); - -// Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) -// } -// } - -// impl<'a> MessageHandler> for RectangleTool { -// fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { -// let ToolMessage::Rectangle(RectangleToolMessage::UpdateOptions(action)) = message else { -// self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); -// return; -// }; -// match action { -// RectangleOptionsUpdate::FillColor(color) => { -// self.options.fill.custom_color = color; -// self.options.fill.color_type = ToolColorType::Custom; -// } -// RectangleOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, -// RectangleOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, -// RectangleOptionsUpdate::StrokeColor(color) => { -// self.options.stroke.custom_color = color; -// self.options.stroke.color_type = ToolColorType::Custom; -// } -// RectangleOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, -// RectangleOptionsUpdate::WorkingColors(primary, secondary) => { -// self.options.stroke.primary_working_color = primary; -// self.options.stroke.secondary_working_color = secondary; -// self.options.fill.primary_working_color = primary; -// self.options.fill.secondary_working_color = secondary; -// } -// } - -// self.send_layout(responses, LayoutTarget::ToolOptions); -// } - -// fn actions(&self) -> ActionList { -// match self.fsm_state { -// RectangleToolFsmState::Ready => actions!(RectangleToolMessageDiscriminant; -// DragStart, -// PointerMove, -// ), -// RectangleToolFsmState::Drawing => actions!(RectangleToolMessageDiscriminant; -// DragStop, -// Abort, -// PointerMove, -// ), -// } -// } -// } - -// impl ToolMetadata for RectangleTool { -// fn icon_name(&self) -> String { -// "VectorRectangleTool".into() -// } -// fn tooltip(&self) -> String { -// "Rectangle Tool".into() -// } -// fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { -// ToolType::Rectangle -// } -// } - -// impl ToolTransition for RectangleTool { -// fn event_to_message_map(&self) -> EventToMessageMap { -// EventToMessageMap { -// overlay_provider: Some(|overlay_context| RectangleToolMessage::Overlays(overlay_context).into()), -// tool_abort: Some(RectangleToolMessage::Abort.into()), -// working_color_changed: Some(RectangleToolMessage::WorkingColorChanged.into()), -// ..Default::default() -// } -// } -// } - -// #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -// enum RectangleToolFsmState { -// #[default] -// Ready, -// Drawing, -// } - -// #[derive(Clone, Debug, Default)] -// struct RectangleToolData { -// data: Resize, -// auto_panning: AutoPanning, -// } - -// impl Fsm for RectangleToolFsmState { -// type ToolData = RectangleToolData; -// type ToolOptions = RectangleToolOptions; - -// fn transition( -// self, -// event: ToolMessage, -// tool_data: &mut Self::ToolData, -// ToolActionHandlerData { -// document, global_tool_data, input, .. -// }: &mut ToolActionHandlerData, -// tool_options: &Self::ToolOptions, -// responses: &mut VecDeque, -// ) -> Self { -// let shape_data = &mut tool_data.data; - -// let ToolMessage::Rectangle(event) = event else { return self }; -// match (self, event) { -// (_, RectangleToolMessage::Overlays(mut overlay_context)) => { -// shape_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); -// self -// } -// (RectangleToolFsmState::Ready, RectangleToolMessage::DragStart) => { -// shape_data.start(document, input); - -// responses.add(DocumentMessage::StartTransaction); - -// let node_type = resolve_document_node_type("Rectangle").expect("Rectangle node does not exist"); -// let node = node_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(1.), false)), Some(NodeInput::value(TaggedValue::F64(1.), false))]); -// let nodes = vec![(NodeId(0), node)]; - -// let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input), responses); -// responses.add(Message::StartBuffer); -// responses.add(GraphOperationMessage::TransformSet { -// layer, -// transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), -// transform_in: TransformIn::Viewport, -// skip_rerender: false, -// }); -// tool_options.fill.apply_fill(layer, responses); -// tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); -// shape_data.layer = Some(layer); - -// RectangleToolFsmState::Drawing -// } -// (RectangleToolFsmState::Drawing, RectangleToolMessage::PointerMove { center, lock_ratio }) => { -// if let Some([start, end]) = shape_data.calculate_points(document, input, center, lock_ratio) { -// if let Some(layer) = shape_data.layer { -// let Some(node_id) = graph_modification_utils::get_rectangle_id(layer, &document.network_interface) else { -// return self; -// }; - -// responses.add(NodeGraphMessage::SetInput { -// input_connector: InputConnector::node(node_id, 1), -// input: NodeInput::value(TaggedValue::F64((start.x - end.x).abs()), false), -// }); -// responses.add(NodeGraphMessage::SetInput { -// input_connector: InputConnector::node(node_id, 2), -// input: NodeInput::value(TaggedValue::F64((start.y - end.y).abs()), false), -// }); -// responses.add(GraphOperationMessage::TransformSet { -// layer, -// transform: DAffine2::from_translation((start + end) / 2.), -// transform_in: TransformIn::Viewport, -// skip_rerender: false, -// }); -// } -// } - -// // Auto-panning -// let messages = [ -// RectangleToolMessage::PointerOutsideViewport { center, lock_ratio }.into(), -// RectangleToolMessage::PointerMove { center, lock_ratio }.into(), -// ]; -// tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); - -// self -// } -// (_, RectangleToolMessage::PointerMove { .. }) => { -// shape_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); -// responses.add(OverlaysMessage::Draw); -// self -// } -// (RectangleToolFsmState::Drawing, RectangleToolMessage::PointerOutsideViewport { .. }) => { -// // Auto-panning -// let _ = tool_data.auto_panning.shift_viewport(input, responses); - -// RectangleToolFsmState::Drawing -// } -// (state, RectangleToolMessage::PointerOutsideViewport { center, lock_ratio }) => { -// // Auto-panning -// let messages = [ -// RectangleToolMessage::PointerOutsideViewport { center, lock_ratio }.into(), -// RectangleToolMessage::PointerMove { center, lock_ratio }.into(), -// ]; -// tool_data.auto_panning.stop(&messages, responses); - -// state -// } -// (RectangleToolFsmState::Drawing, RectangleToolMessage::DragStop) => { -// input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); -// shape_data.cleanup(responses); - -// RectangleToolFsmState::Ready -// } -// (RectangleToolFsmState::Drawing, RectangleToolMessage::Abort) => { -// responses.add(DocumentMessage::AbortTransaction); - -// shape_data.cleanup(responses); - -// RectangleToolFsmState::Ready -// } -// (_, RectangleToolMessage::WorkingColorChanged) => { -// responses.add(RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::WorkingColors( -// Some(global_tool_data.primary_color), -// Some(global_tool_data.secondary_color), -// ))); -// self -// } -// _ => self, -// } -// } - -// fn update_hints(&self, responses: &mut VecDeque) { -// let hint_data = match self { -// RectangleToolFsmState::Ready => HintData(vec![HintGroup(vec![ -// HintInfo::mouse(MouseMotion::LmbDrag, "Draw Rectangle"), -// HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(), -// HintInfo::keys([Key::Alt], "From Center").prepend_plus(), -// ])]), -// RectangleToolFsmState::Drawing => HintData(vec![ -// HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), -// HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")]), -// ]), -// }; - -// responses.add(FrontendMessage::UpdateInputHints { hint_data }); -// } - -// fn update_cursor(&self, responses: &mut VecDeque) { -// responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }); -// } -// } diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index 70f789d768..1733643d35 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -410,7 +410,6 @@ fn list_tools_in_groups() -> Vec> { ToolAvailability::AvailableAsShape(ShapeType::Rectangle), ToolAvailability::AvailableAsShape(ShapeType::Ellipse), ToolAvailability::AvailableAsShape(ShapeType::Line), - // ToolAvailability::Available(Box::::default()), ToolAvailability::Available(Box::::default()), ], vec![ @@ -444,7 +443,6 @@ pub fn tool_message_to_tool_type(tool_message: &ToolMessage) -> ToolType { ToolMessage::Freehand(_) => ToolType::Freehand, ToolMessage::Spline(_) => ToolType::Spline, ToolMessage::Shape(_) => ToolType::Shape, - ToolMessage::Polygon(_) => ToolType::Polygon, ToolMessage::Text(_) => ToolType::Text, // Raster tool group @@ -475,7 +473,6 @@ pub fn tool_type_to_activate_tool_message(tool_type: ToolType) -> ToolMessageDis ToolType::Freehand => ToolMessageDiscriminant::ActivateToolFreehand, ToolType::Spline => ToolMessageDiscriminant::ActivateToolSpline, ToolType::Shape => ToolMessageDiscriminant::ActivateToolShape, - ToolType::Polygon => ToolMessageDiscriminant::ActivateToolPolygon, ToolType::Text => ToolMessageDiscriminant::ActivateToolText, ToolType::Rectangle => ToolMessageDiscriminant::ActivateShapeRectangle, ToolType::Ellipse => ToolMessageDiscriminant::ActivateShapeEllipse, From a51713a3db98e0826a29448c5ae16cc53d668238 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Wed, 4 Jun 2025 00:45:31 +0530 Subject: [PATCH 06/16] fixed transform added hints need to fix modifier keys use --- .../common_functionality/utility_functions.rs | 225 ++++++++++++++- .../src/messages/tool/shapes/shape_utility.rs | 8 +- .../tool/tool_messages/select_tool.rs | 252 ++++------------- .../messages/tool/tool_messages/shape_tool.rs | 263 +++++++++++++++--- editor/src/messages/tool/utility_types.rs | 4 +- editor/src/test_utils.rs | 5 +- 6 files changed, 505 insertions(+), 252 deletions(-) diff --git a/editor/src/messages/tool/common_functionality/utility_functions.rs b/editor/src/messages/tool/common_functionality/utility_functions.rs index 61b300a183..033ebc9429 100644 --- a/editor/src/messages/tool/common_functionality/utility_functions.rs +++ b/editor/src/messages/tool/common_functionality/utility_functions.rs @@ -1,12 +1,19 @@ +use crate::consts::ROTATE_INCREMENT; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; +use crate::messages::portfolio::document::utility_types::transformation::Selected; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::graph_modification_utils::get_text; +use crate::messages::tool::common_functionality::transformation_cage::SelectedEdges; use crate::messages::tool::tool_messages::path_tool::PathOverlayMode; -use glam::DVec2; +use crate::messages::tool::utility_types::ToolType; +use glam::{DAffine2, DVec2}; use graphene_core::renderer::Quad; use graphene_core::text::{FontCache, load_face}; use graphene_std::vector::{ManipulatorPointId, PointId, SegmentId, VectorData}; +use super::snapping::{SnapCandidatePoint, SnapData, SnapManager}; +use super::transformation_cage::{BoundingBoxManager, SizeSnapData}; + /// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable. pub fn should_extend( document: &DocumentMessageHandler, @@ -137,3 +144,219 @@ pub fn is_visible_point( } } } + +pub fn resize_bounds( + document: &DocumentMessageHandler, + responses: &mut VecDeque, + bounds: &mut BoundingBoxManager, + dragging_layers: &mut Vec, + snap_manager: &mut SnapManager, + snap_candidates: &mut Vec, + input: &InputPreprocessorMessageHandler, + center: bool, + constrain: bool, + tool: ToolType, +) { + if let Some(movement) = &mut bounds.selected_edges { + let center = center.then_some(bounds.center_of_transformation); + let snap = Some(SizeSnapData { + manager: snap_manager, + points: snap_candidates, + snap_data: SnapData::ignore(document, input, &dragging_layers), + }); + let (position, size) = movement.new_size(input.mouse.position, bounds.original_bound_transform, center, constrain, snap); + let (delta, mut pivot) = movement.bounds_to_scale_transform(position, size); + + let pivot_transform = DAffine2::from_translation(pivot); + let transformation = pivot_transform * delta * pivot_transform.inverse(); + + dragging_layers.retain(|layer| { + if *layer != LayerNodeIdentifier::ROOT_PARENT { + document.network_interface.document_network().nodes.contains_key(&layer.to_node()) + } else { + log::error!("ROOT_PARENT should not be part of layers_dragging"); + false + } + }); + let selected = &dragging_layers; + let mut selected = Selected::new(&mut bounds.original_transforms, &mut pivot, selected, responses, &document.network_interface, None, &tool, None); + + selected.apply_transformation(bounds.original_bound_transform * transformation * bounds.original_bound_transform.inverse(), None); + } +} + +pub fn rotate_bounds( + document: &DocumentMessageHandler, + responses: &mut VecDeque, + bounds: &mut BoundingBoxManager, + dragging_layers: &mut Vec, + drag_start: DVec2, + mouse_position: DVec2, + snap_angle: bool, + tool: ToolType, +) { + let angle = { + let start_offset = drag_start - bounds.center_of_transformation; + let end_offset = mouse_position - bounds.center_of_transformation; + start_offset.angle_to(end_offset) + }; + + let snapped_angle = if snap_angle { + let snap_resolution = ROTATE_INCREMENT.to_radians(); + (angle / snap_resolution).round() * snap_resolution + } else { + angle + }; + + let delta = DAffine2::from_angle(snapped_angle); + + dragging_layers.retain(|layer| { + if *layer != LayerNodeIdentifier::ROOT_PARENT { + document.network_interface.document_network().nodes.contains_key(&layer.to_node()) + } else { + log::error!("ROOT_PARENT should not be part of replacement_selected_layers"); + false + } + }); + let mut selected = Selected::new( + &mut bounds.original_transforms, + &mut bounds.center_of_transformation, + &dragging_layers, + responses, + &document.network_interface, + None, + &tool, + None, + ); + + selected.update_transforms(delta, None, None); +} + +pub fn skew_bounds( + document: &DocumentMessageHandler, + responses: &mut VecDeque, + bounds: &mut BoundingBoxManager, + free_movement: bool, + layers: &mut Vec, + mouse_position: DVec2, + tool: ToolType, +) { + if let Some(movement) = &mut bounds.selected_edges { + let transformation = movement.skew_transform(mouse_position, bounds.original_bound_transform, free_movement); + + layers.retain(|layer| { + if *layer != LayerNodeIdentifier::ROOT_PARENT { + document.network_interface.document_network().nodes.contains_key(&layer.to_node()) + } else { + log::error!("ROOT_PARENT should not be part of layers_dragging"); + false + } + }); + let selected = &layers; + let mut pivot = DVec2::ZERO; + let mut selected = Selected::new(&mut bounds.original_transforms, &mut pivot, selected, responses, &document.network_interface, None, &tool, None); + + selected.apply_transformation(bounds.original_bound_transform * transformation * bounds.original_bound_transform.inverse(), None); + } +} + +pub fn transforming_tranform_cage( + document: &DocumentMessageHandler, + mut bounding_box_manager: &mut Option, + input: &InputPreprocessorMessageHandler, + responses: &mut VecDeque, + layers_dragging: &mut Vec, +) -> (bool, bool, bool) { + let dragging_bounds = bounding_box_manager.as_mut().and_then(|bounding_box| { + let edges = bounding_box.check_selected_edges(input.mouse.position); + + bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| { + let selected_edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds); + bounding_box.opposite_pivot = selected_edges.calculate_pivot(); + selected_edges + }); + + edges + }); + + let rotating_bounds = bounding_box_manager.as_ref().map(|bounding_box| bounding_box.check_rotate(input.mouse.position)).unwrap_or_default(); + + let selected: Vec<_> = document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface).collect(); + + let is_flat_layer = bounding_box_manager.as_ref().map(|bounding_box_manager| bounding_box_manager.transform_tampered).unwrap_or(true); + + if dragging_bounds.is_some() && !is_flat_layer { + responses.add(DocumentMessage::StartTransaction); + + *layers_dragging = selected; + + if let Some(bounds) = &mut bounding_box_manager { + bounds.original_bound_transform = bounds.transform; + + layers_dragging.retain(|layer| { + if *layer != LayerNodeIdentifier::ROOT_PARENT { + document.network_interface.document_network().nodes.contains_key(&layer.to_node()) + } else { + log::error!("ROOT_PARENT should not be part of layers_dragging"); + false + } + }); + + let mut selected = Selected::new( + &mut bounds.original_transforms, + &mut bounds.center_of_transformation, + &layers_dragging, + responses, + &document.network_interface, + None, + &ToolType::Select, + None, + ); + bounds.center_of_transformation = selected.mean_average_of_pivots(); + + // Check if we're hovering over a skew triangle + let edges = bounds.check_selected_edges(input.mouse.position); + if let Some(edges) = edges { + let closest_edge = bounds.get_closest_edge(edges, input.mouse.position); + if bounds.check_skew_handle(input.mouse.position, closest_edge) { + return (false, false, true); + } + } + } + return (true, false, false); + } + + if rotating_bounds { + responses.add(DocumentMessage::StartTransaction); + + if let Some(bounds) = &mut bounding_box_manager { + layers_dragging.retain(|layer| { + if *layer != LayerNodeIdentifier::ROOT_PARENT { + document.network_interface.document_network().nodes.contains_key(&layer.to_node()) + } else { + log::error!("ROOT_PARENT should not be part of layers_dragging"); + false + } + }); + + let mut selected = Selected::new( + &mut bounds.original_transforms, + &mut bounds.center_of_transformation, + &selected, + responses, + &document.network_interface, + None, + &ToolType::Select, + None, + ); + + bounds.center_of_transformation = selected.mean_average_of_pivots(); + } + + *layers_dragging = selected; + + return (false, true, false); + } + + return (false, false, false); +} diff --git a/editor/src/messages/tool/shapes/shape_utility.rs b/editor/src/messages/tool/shapes/shape_utility.rs index c7388671c0..8c0366770e 100644 --- a/editor/src/messages/tool/shapes/shape_utility.rs +++ b/editor/src/messages/tool/shapes/shape_utility.rs @@ -3,7 +3,7 @@ use crate::messages::portfolio::document::overlays::utility_types::OverlayContex use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; use crate::messages::prelude::{DocumentMessageHandler, NodeGraphMessage, Responses}; -use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer}; +use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; use crate::messages::tool::common_functionality::transformation_cage::BoundingBoxManager; use crate::messages::tool::tool_messages::tool_prelude::Key; use crate::messages::tool::utility_types::*; @@ -66,10 +66,6 @@ impl ShapeType { } } -pub struct LineInitData { - pub drag_start: DVec2, -} - // Center, Lock Ratio, Lock Angle, Snap Angle pub type ShapeToolModifierKey = [Key; 4]; @@ -106,7 +102,6 @@ pub fn transform_cage_overlays(document: &DocumentMessageHandler, tool_data: &mu .network_interface .selected_nodes() .selected_visible_and_unlocked_layers(&document.network_interface) - .filter(|layer| graph_modification_utils::get_line_id(*layer, &document.network_interface).is_none()) .find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[])) .map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface)) .unwrap_or_default(); @@ -122,7 +117,6 @@ pub fn transform_cage_overlays(document: &DocumentMessageHandler, tool_data: &mu .network_interface .selected_nodes() .selected_visible_and_unlocked_layers(&document.network_interface) - .filter(|layer| graph_modification_utils::get_line_id(*layer, &document.network_interface).is_none()) .filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[])) .filter_map(|layer| { document diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 7c3af34d54..236d30fbfb 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -2,7 +2,7 @@ use super::tool_prelude::*; use crate::consts::{ - COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COMPASS_ROSE_HOVER_RING_DIAMETER, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, RESIZE_HANDLE_SIZE, ROTATE_INCREMENT, + COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COMPASS_ROSE_HOVER_RING_DIAMETER, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, RESIZE_HANDLE_SIZE, SELECTION_DRAG_ANGLE, SELECTION_TOLERANCE, }; use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; @@ -12,7 +12,6 @@ use crate::messages::portfolio::document::utility_types::document_metadata::{Doc use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GroupFolderType}; use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface, NodeTemplate}; use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes; -use crate::messages::portfolio::document::utility_types::transformation::Selected; use crate::messages::preferences::SelectionMode; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose}; @@ -22,7 +21,7 @@ use crate::messages::tool::common_functionality::pivot::Pivot; use crate::messages::tool::common_functionality::shape_editor::SelectionShapeType; use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager}; use crate::messages::tool::common_functionality::transformation_cage::*; -use crate::messages::tool::common_functionality::utility_functions::text_bounding_box; +use crate::messages::tool::common_functionality::utility_functions::{resize_bounds, rotate_bounds, skew_bounds, text_bounding_box, transforming_tranform_cage}; use bezier_rs::Subpath; use glam::DMat2; use graph_craft::document::NodeId; @@ -858,35 +857,25 @@ impl Fsm for SelectToolFsmState { remove_from_selection, select_deepest, lasso_select, - skew, + .. }, ) => { tool_data.drag_start = input.mouse.position; tool_data.drag_current = input.mouse.position; tool_data.selection_mode = None; - let dragging_bounds = tool_data.bounding_box_manager.as_mut().and_then(|bounding_box| { - let edges = bounding_box.check_selected_edges(input.mouse.position); - - bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| { - let selected_edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds); - bounding_box.opposite_pivot = selected_edges.calculate_pivot(); - selected_edges - }); - - edges - }); - - let rotating_bounds = tool_data - .bounding_box_manager - .as_ref() - .map(|bounding_box| bounding_box.check_rotate(input.mouse.position)) - .unwrap_or_default(); - let mut selected: Vec<_> = document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface).collect(); let intersection_list = document.click_list(input).collect::>(); let intersection = document.find_deepest(&intersection_list); + let (resize, rotate, skew) = transforming_tranform_cage( + document, + &mut tool_data.bounding_box_manager, + input, + responses, + &mut tool_data.layers_dragging, + ); + // If the user is dragging the bounding box bounds, go into ResizingBounds mode. // If the user is dragging the rotate trigger, go into RotatingBounds mode. // If the user clicks on a layer that is in their current selection, go into the dragging mode. @@ -904,11 +893,6 @@ impl Fsm for SelectToolFsmState { let show_compass = bounds.is_some_and(|quad| quad.all_sides_at_least_width(COMPASS_ROSE_HOVER_RING_DIAMETER) && quad.contains(mouse_position)); let can_grab_compass_rose = compass_rose_state.can_grab() && (show_compass || bounds.is_none()); - let is_flat_layer = tool_data - .bounding_box_manager - .as_ref() - .map(|bounding_box_manager| bounding_box_manager.transform_tampered) - .unwrap_or(true); let state = // Dragging the pivot @@ -921,47 +905,13 @@ impl Fsm for SelectToolFsmState { SelectToolFsmState::DraggingPivot } // Dragging one (or two, forming a corner) of the transform cage bounding box edges - else if dragging_bounds.is_some() && !is_flat_layer { - responses.add(DocumentMessage::StartTransaction); - - tool_data.layers_dragging = selected; - - if let Some(bounds) = &mut tool_data.bounding_box_manager { - bounds.original_bound_transform = bounds.transform; - - tool_data.layers_dragging.retain(|layer| { - if *layer != LayerNodeIdentifier::ROOT_PARENT { - document.network_interface.document_network().nodes.contains_key(&layer.to_node()) - } else { - log::error!("ROOT_PARENT should not be part of layers_dragging"); - false - } - }); - - let mut selected = Selected::new( - &mut bounds.original_transforms, - &mut bounds.center_of_transformation, - &tool_data.layers_dragging, - responses, - &document.network_interface, - None, - &ToolType::Select, - None - ); - bounds.center_of_transformation = selected.mean_average_of_pivots(); - - // Check if we're hovering over a skew triangle - let edges = bounds.check_selected_edges(input.mouse.position); - if let Some(edges) = edges { - let closest_edge = bounds.get_closest_edge(edges, input.mouse.position); - if bounds.check_skew_handle(input.mouse.position, closest_edge) { - tool_data.get_snap_candidates(document, input); - return SelectToolFsmState::SkewingBounds { skew }; - } - } - } + else if resize { tool_data.get_snap_candidates(document, input); SelectToolFsmState::ResizingBounds + }else if skew{ + tool_data.get_snap_candidates(document, input); + SelectToolFsmState::SkewingBounds { skew: Key::Control } + } // Dragging the selected layers around to transform them else if can_grab_compass_rose || intersection.is_some_and(|intersection| selected.iter().any(|selected_layer| intersection.starts_with(*selected_layer, document.metadata()))) { @@ -983,34 +933,7 @@ impl Fsm for SelectToolFsmState { SelectToolFsmState::Dragging { axis, using_compass, has_dragged: false, deepest: input.keyboard.key(select_deepest), remove: input.keyboard.key(extend_selection) } } // Dragging near the transform cage bounding box to rotate it - else if rotating_bounds { - responses.add(DocumentMessage::StartTransaction); - - if let Some(bounds) = &mut tool_data.bounding_box_manager { - tool_data.layers_dragging.retain(|layer| { - if *layer != LayerNodeIdentifier::ROOT_PARENT { - document.network_interface.document_network().nodes.contains_key(&layer.to_node()) - } else { - log::error!("ROOT_PARENT should not be part of layers_dragging"); - false - } - }); - let mut selected = Selected::new( - &mut bounds.original_transforms, - &mut bounds.center_of_transformation, - &selected, - responses, - &document.network_interface, - None, - &ToolType::Select, - None - ); - - bounds.center_of_transformation = selected.mean_average_of_pivots(); - } - - tool_data.layers_dragging = selected; - + else if rotate { SelectToolFsmState::RotatingBounds } // Dragging a selection box @@ -1117,123 +1040,52 @@ impl Fsm for SelectToolFsmState { } (SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove(modifier_keys)) => { if let Some(bounds) = &mut tool_data.bounding_box_manager { - if let Some(movement) = &mut bounds.selected_edges { - let (center, constrain) = (input.keyboard.key(modifier_keys.center), input.keyboard.key(modifier_keys.axis_align)); - - let center = center.then_some(bounds.center_of_transformation); - let snap = Some(SizeSnapData { - manager: &mut tool_data.snap_manager, - points: &mut tool_data.snap_candidates, - snap_data: SnapData::ignore(document, input, &tool_data.layers_dragging), - }); - let (position, size) = movement.new_size(input.mouse.position, bounds.original_bound_transform, center, constrain, snap); - let (delta, mut pivot) = movement.bounds_to_scale_transform(position, size); - - let pivot_transform = DAffine2::from_translation(pivot); - let transformation = pivot_transform * delta * pivot_transform.inverse(); - - tool_data.layers_dragging.retain(|layer| { - if *layer != LayerNodeIdentifier::ROOT_PARENT { - document.network_interface.document_network().nodes.contains_key(&layer.to_node()) - } else { - log::error!("ROOT_PARENT should not be part of layers_dragging"); - false - } - }); - let selected = &tool_data.layers_dragging; - let mut selected = Selected::new( - &mut bounds.original_transforms, - &mut pivot, - selected, - responses, - &document.network_interface, - None, - &ToolType::Select, - None, - ); - - selected.apply_transformation(bounds.original_bound_transform * transformation * bounds.original_bound_transform.inverse(), None); - - // Auto-panning - let messages = [ - SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(), - SelectToolMessage::PointerMove(modifier_keys).into(), - ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); - } + resize_bounds( + document, + responses, + bounds, + &mut tool_data.layers_dragging, + &mut tool_data.snap_manager, + &mut tool_data.snap_candidates, + input, + input.keyboard.key(modifier_keys.center), + input.keyboard.key(modifier_keys.axis_align), + ToolType::Select, + ); + let messages = [ + SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(), + SelectToolMessage::PointerMove(modifier_keys).into(), + ]; + tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); } SelectToolFsmState::ResizingBounds } (SelectToolFsmState::SkewingBounds { skew }, SelectToolMessage::PointerMove(_)) => { if let Some(bounds) = &mut tool_data.bounding_box_manager { - if let Some(movement) = &mut bounds.selected_edges { - let free_movement = input.keyboard.key(skew); - let transformation = movement.skew_transform(input.mouse.position, bounds.original_bound_transform, free_movement); - - tool_data.layers_dragging.retain(|layer| { - if *layer != LayerNodeIdentifier::ROOT_PARENT { - document.network_interface.document_network().nodes.contains_key(&layer.to_node()) - } else { - log::error!("ROOT_PARENT should not be part of layers_dragging"); - false - } - }); - let selected = &tool_data.layers_dragging; - let mut pivot = DVec2::ZERO; - let mut selected = Selected::new( - &mut bounds.original_transforms, - &mut pivot, - selected, - responses, - &document.network_interface, - None, - &ToolType::Select, - None, - ); - - selected.apply_transformation(bounds.original_bound_transform * transformation * bounds.original_bound_transform.inverse(), None); - } + skew_bounds( + document, + responses, + bounds, + input.keyboard.key(skew), + &mut tool_data.layers_dragging, + input.mouse.position, + ToolType::Select, + ); } SelectToolFsmState::SkewingBounds { skew } } - (SelectToolFsmState::RotatingBounds, SelectToolMessage::PointerMove(modifier_keys)) => { + (SelectToolFsmState::RotatingBounds, SelectToolMessage::PointerMove(_)) => { if let Some(bounds) = &mut tool_data.bounding_box_manager { - let angle = { - let start_offset = tool_data.drag_start - bounds.center_of_transformation; - let end_offset = input.mouse.position - bounds.center_of_transformation; - - start_offset.angle_to(end_offset) - }; - - let snapped_angle = if input.keyboard.key(modifier_keys.snap_angle) { - let snap_resolution = ROTATE_INCREMENT.to_radians(); - (angle / snap_resolution).round() * snap_resolution - } else { - angle - }; - - let delta = DAffine2::from_angle(snapped_angle); - - tool_data.layers_dragging.retain(|layer| { - if *layer != LayerNodeIdentifier::ROOT_PARENT { - document.network_interface.document_network().nodes.contains_key(&layer.to_node()) - } else { - log::error!("ROOT_PARENT should not be part of replacement_selected_layers"); - false - } - }); - let mut selected = Selected::new( - &mut bounds.original_transforms, - &mut bounds.center_of_transformation, - &tool_data.layers_dragging, + rotate_bounds( + document, responses, - &document.network_interface, - None, - &ToolType::Select, - None, + bounds, + &mut tool_data.layers_dragging, + tool_data.drag_start, + input.mouse.position, + input.keyboard.key(Key::Shift), + ToolType::Select, ); - - selected.update_transforms(delta, None, None); } SelectToolFsmState::RotatingBounds diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index ccdd01cd46..802e5ddc08 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -2,13 +2,14 @@ use super::tool_prelude::*; use crate::consts::{DEFAULT_STROKE_WIDTH, SNAP_POINT_TOLERANCE}; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; +use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::graph_modification_utils::{self}; use crate::messages::tool::common_functionality::resize::Resize; -use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapTypeConfiguration}; -use crate::messages::tool::common_functionality::transformation_cage::BoundingBoxManager; -use crate::messages::tool::common_functionality::utility_functions::closest_point; +use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapTypeConfiguration}; +use crate::messages::tool::common_functionality::transformation_cage::{BoundingBoxManager, EdgeBool}; +use crate::messages::tool::common_functionality::utility_functions::{closest_point, resize_bounds, rotate_bounds, skew_bounds, transforming_tranform_cage}; use crate::messages::tool::shapes::convex_shape::Convex; use crate::messages::tool::shapes::line_shape::{LineToolData, clicked_on_line_endpoints}; use crate::messages::tool::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays}; @@ -16,6 +17,7 @@ use crate::messages::tool::shapes::star_shape::Star; use crate::messages::tool::shapes::{Ellipse, Line, Rectangle}; use graph_craft::document::NodeId; use graphene_core::Color; +use graphene_std::renderer::Quad; use std::vec; #[derive(Default)] @@ -94,15 +96,6 @@ fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder { MenuListEntry::new("star") .label("Star") .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Star)).into()), - MenuListEntry::new("rectangle") - .label("Rectangle") - .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Rectangle)).into()), - MenuListEntry::new("ellipse") - .label("Ellipse") - .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Ellipse)).into()), - MenuListEntry::new("line") - .label("Line") - .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Line)).into()), ]]; DropdownInput::new(entries).selected_index(Some(shape_type as u32)).widget_holder() } @@ -189,6 +182,7 @@ impl<'a> MessageHandler> for ShapeTo } ShapeOptionsUpdate::ShapeType(shape) => { self.options.shape_type = shape; + self.tool_data.current_shape = shape; } ShapeOptionsUpdate::Vertices(vertices) => { self.options.vertices = vertices; @@ -208,20 +202,19 @@ impl<'a> MessageHandler> for ShapeTo Abort, HideShapeTypeWidget ), - ShapeToolFsmState::Drawing(_) => actions!(ShapeToolMessageDiscriminant; - DragStop, - Abort, - PointerMove, - SetShape, - HideShapeTypeWidget - ), - ShapeToolFsmState::DraggingLineEndpoints => actions!(ShapeToolMessageDiscriminant; - DragStop, - Abort, - PointerMove, - SetShape, - HideShapeTypeWidget - ), + ShapeToolFsmState::Drawing(_) + | ShapeToolFsmState::ResizingBounds + | ShapeToolFsmState::DraggingLineEndpoints + | ShapeToolFsmState::RotatingBounds + | ShapeToolFsmState::SkewingBounds { .. } => { + actions!(ShapeToolMessageDiscriminant; + DragStop, + Abort, + PointerMove, + SetShape, + HideShapeTypeWidget + ) + } } } } @@ -250,10 +243,13 @@ impl ToolTransition for ShapeTool { } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum ShapeToolFsmState { +pub enum ShapeToolFsmState { Ready(ShapeType), Drawing(ShapeType), DraggingLineEndpoints, + ResizingBounds, + RotatingBounds, + SkewingBounds { skew: Key }, } impl Default for ShapeToolFsmState { @@ -269,6 +265,28 @@ pub struct ShapeToolData { pub hide_shape_option_widget: bool, pub line_data: LineToolData, pub bounding_box_manager: Option, + layers_dragging: Vec, + snap_candidates: Vec, + cursor: MouseCursorIcon, + drag_start: DVec2, + drag_current: DVec2, + skew_edge: EdgeBool, + current_shape: ShapeType, +} + +impl ShapeToolData { + fn get_snap_candidates(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) { + self.snap_candidates.clear(); + for &layer in &self.layers_dragging { + if (self.snap_candidates.len() as f64) < document.snapping_state.tolerance { + snapping::get_layer_snap_points(layer, &SnapData::new(document, input), &mut self.snap_candidates); + } + if let Some(bounds) = document.metadata().bounding_box_with_transform(layer, DAffine2::IDENTITY) { + let quad = document.metadata().transform_to_document(layer) * Quad::from_box(bounds); + snapping::get_bbox_points(quad, &mut self.snap_candidates, snapping::BBoxSnapValues::BOUNDING_BOX, document); + } + } + } } impl Fsm for ShapeToolFsmState { @@ -297,18 +315,53 @@ impl Fsm for ShapeToolFsmState { }; match (self, event) { (_, ShapeToolMessage::Overlays(mut overlay_context)) => { - shape_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); + let is_resizing_or_rotating = matches!(self, ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::SkewingBounds { .. } | ShapeToolFsmState::RotatingBounds); + + if !is_resizing_or_rotating { + shape_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); + } + Line::overlays(document, tool_data, &mut overlay_context); - if input.keyboard.key(Key::Control) { + if input.keyboard.key(Key::Control) && matches!(self, ShapeToolFsmState::Ready(_)) { anchor_overlays(document, &mut overlay_context); } else { + if document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .all(|layer| graph_modification_utils::get_line_id(layer, &document.network_interface).is_some()) + { + return self; + } transform_cage_overlays(document, tool_data, &mut overlay_context); + + let dragging_bounds = tool_data + .bounding_box_manager + .as_mut() + .and_then(|bounding_box| bounding_box.check_selected_edges(input.mouse.position)) + .is_some(); + + if let Some(bounds) = tool_data.bounding_box_manager.as_mut() { + let edges = bounds.check_selected_edges(input.mouse.position); + let is_skewing = matches!(self, ShapeToolFsmState::SkewingBounds { .. }); + let is_near_square = edges.is_some_and(|hover_edge| bounds.over_extended_edge_midpoint(input.mouse.position, hover_edge)); + if is_skewing || (dragging_bounds && is_near_square && !is_resizing_or_rotating) { + bounds.render_skew_gizmos(&mut overlay_context, tool_data.skew_edge); + } + if !is_skewing && dragging_bounds { + if let Some(edges) = edges { + tool_data.skew_edge = bounds.get_closest_edge(edges, input.mouse.position); + } + } + } } self } (ShapeToolFsmState::Ready(_), ShapeToolMessage::DragStart) => { + tool_data.drag_start = input.mouse.position; + tool_data.drag_current = input.mouse.position; // If clicked on endpoints of a selected line, drag its endpoints if let Some((layer, _, _)) = closest_point( document, @@ -323,7 +376,24 @@ impl Fsm for ShapeToolFsmState { } } - match tool_options.shape_type { + let (resize, rotate, skew) = transforming_tranform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging); + + match (resize, rotate, skew) { + (true, false, false) => { + tool_data.get_snap_candidates(document, input); + return ShapeToolFsmState::ResizingBounds; + } + (false, true, false) => { + return ShapeToolFsmState::RotatingBounds; + } + (false, false, true) => { + tool_data.get_snap_candidates(document, input); + return ShapeToolFsmState::SkewingBounds { skew: Key::Control }; + } + _ => {} + } + + match tool_data.current_shape { ShapeType::Convex | ShapeType::Star | ShapeType::Ellipse | ShapeType::Rectangle => shape_data.start(document, input), ShapeType::Line => { let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); @@ -334,7 +404,7 @@ impl Fsm for ShapeToolFsmState { responses.add(DocumentMessage::StartTransaction); - let node = match tool_options.shape_type { + let node = match tool_data.current_shape { ShapeType::Convex => Convex::create_node(tool_options.vertices), ShapeType::Star => Star::create_node(tool_options.vertices), ShapeType::Rectangle => Rectangle::create_node(), @@ -348,7 +418,7 @@ impl Fsm for ShapeToolFsmState { responses.add(Message::StartBuffer); tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); - match tool_options.shape_type { + match tool_data.current_shape { ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Convex | ShapeType::Star => { responses.add(GraphOperationMessage::TransformSet { layer, @@ -368,20 +438,20 @@ impl Fsm for ShapeToolFsmState { shape_data.layer = Some(layer); - ShapeToolFsmState::Drawing(tool_options.shape_type) + ShapeToolFsmState::Drawing(tool_data.current_shape) } (ShapeToolFsmState::Drawing(shape), ShapeToolMessage::PointerMove(modifier)) => { let Some(layer) = shape_data.layer else { return ShapeToolFsmState::Ready(shape); }; - if match tool_options.shape_type { + if match tool_data.current_shape { ShapeType::Rectangle => Rectangle::update_shape(&document, &input, layer, tool_data, modifier, responses), ShapeType::Ellipse => Ellipse::update_shape(&document, &input, layer, tool_data, modifier, responses), ShapeType::Line => Line::update_shape(&document, &input, layer, tool_data, modifier, responses), ShapeType::Convex => Convex::update_shape(&document, &input, layer, tool_data, modifier, responses), ShapeType::Star => Star::update_shape(&document, &input, layer, tool_data, modifier, responses), } { - return if tool_options.shape_type == ShapeType::Line { ShapeToolFsmState::Ready(shape) } else { self }; + return if tool_data.current_shape == ShapeType::Line { ShapeToolFsmState::Ready(shape) } else { self }; } // Auto-panning @@ -392,7 +462,7 @@ impl Fsm for ShapeToolFsmState { } (ShapeToolFsmState::DraggingLineEndpoints, ShapeToolMessage::PointerMove(modifier)) => { let Some(layer) = line_data.editing_layer else { - return ShapeToolFsmState::Ready(tool_options.shape_type); + return ShapeToolFsmState::Ready(tool_data.current_shape); }; Line::update_shape(&document, &input, layer, tool_data, modifier, responses); @@ -402,27 +472,126 @@ impl Fsm for ShapeToolFsmState { self } + (ShapeToolFsmState::ResizingBounds, ShapeToolMessage::PointerMove(modifier)) => { + if let Some(bounds) = &mut tool_data.bounding_box_manager { + let messages = [ShapeToolMessage::PointerOutsideViewport(modifier.clone()).into(), ShapeToolMessage::PointerMove(modifier).into()]; + resize_bounds( + document, + responses, + bounds, + &mut tool_data.layers_dragging, + &mut shape_data.snap_manager, + &mut tool_data.snap_candidates, + input, + input.keyboard.key(Key::Shift), + input.keyboard.key(Key::Alt), + ToolType::Shape, + ); + tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + } + + responses.add(OverlaysMessage::Draw); + ShapeToolFsmState::ResizingBounds + } + (ShapeToolFsmState::RotatingBounds, ShapeToolMessage::PointerMove(_)) => { + if let Some(bounds) = &mut tool_data.bounding_box_manager { + rotate_bounds( + document, + responses, + bounds, + &mut tool_data.layers_dragging, + tool_data.drag_start, + input.mouse.position, + input.keyboard.key(Key::Shift), + ToolType::Shape, + ); + } + + ShapeToolFsmState::RotatingBounds + } + (ShapeToolFsmState::SkewingBounds { skew }, ShapeToolMessage::PointerMove(_)) => { + if let Some(bounds) = &mut tool_data.bounding_box_manager { + skew_bounds( + document, + responses, + bounds, + input.keyboard.key(skew), + &mut tool_data.layers_dragging, + input.mouse.position, + ToolType::Shape, + ); + } + + ShapeToolFsmState::SkewingBounds { skew } + } + (_, ShapeToolMessage::PointerMove { .. }) => { + log::info!("reaching here"); + let dragging_bounds = tool_data + .bounding_box_manager + .as_mut() + .and_then(|bounding_box| bounding_box.check_selected_edges(input.mouse.position)) + .is_some(); + + let cursor = tool_data + .bounding_box_manager + .as_ref() + .map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge))); + + // // Dragging the pivot overrules the other operations + // if tool_data.pivot.is_over(input.mouse.position) { + // cursor = MouseCursorIcon::Move; + // } + + // Generate the hover outline + responses.add(OverlaysMessage::Draw); + + if tool_data.cursor != cursor { + tool_data.cursor = cursor; + responses.add(FrontendMessage::UpdateMouseCursor { cursor }); + } + shape_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); responses.add(OverlaysMessage::Draw); self } + (ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::SkewingBounds { .. }, ShapeToolMessage::PointerOutsideViewport(_)) => { + // Auto-panning + if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) { + if let Some(bounds) = &mut tool_data.bounding_box_manager { + bounds.center_of_transformation += shift; + bounds.original_bound_transform.translation += shift; + } + } + + self + } (_, ShapeToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning let _ = tool_data.auto_panning.shift_viewport(input, responses); self } - (ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::DraggingLineEndpoints, ShapeToolMessage::DragStop) => { - input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); + ( + ShapeToolFsmState::Drawing(_) + | ShapeToolFsmState::DraggingLineEndpoints + | ShapeToolFsmState::ResizingBounds + | ShapeToolFsmState::RotatingBounds + | ShapeToolFsmState::SkewingBounds { .. }, + ShapeToolMessage::DragStop, + ) => { + input.mouse.finish_transaction(tool_data.drag_start, responses); shape_data.cleanup(responses); + if let Some(bounds) = &mut tool_data.bounding_box_manager { + bounds.original_transforms.clear(); + } - ShapeToolFsmState::Ready(tool_options.shape_type) + ShapeToolFsmState::Ready(tool_data.current_shape) } (ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::DraggingLineEndpoints, ShapeToolMessage::Abort) => { responses.add(DocumentMessage::AbortTransaction); shape_data.cleanup(responses); - ShapeToolFsmState::Ready(tool_options.shape_type) + ShapeToolFsmState::Ready(tool_data.current_shape) } (_, ShapeToolMessage::WorkingColorChanged) => { responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::WorkingColors( @@ -434,7 +603,7 @@ impl Fsm for ShapeToolFsmState { (_, ShapeToolMessage::SetShape(shape)) => { responses.add(DocumentMessage::AbortTransaction); shape_data.cleanup(responses); - responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(shape))); + tool_data.current_shape = shape; ShapeToolFsmState::Ready(shape) } @@ -498,6 +667,18 @@ impl Fsm for ShapeToolFsmState { HintInfo::keys([Key::Control], "Lock Angle"), ]), ]), + ShapeToolFsmState::ResizingBounds => HintData(vec![ + HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), + HintGroup(vec![HintInfo::keys([Key::Alt], "From Pivot"), HintInfo::keys([Key::Shift], "Preserve Aspect Ratio")]), + ]), + ShapeToolFsmState::RotatingBounds => HintData(vec![ + HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), + HintGroup(vec![HintInfo::keys([Key::Shift], "15° Increments")]), + ]), + ShapeToolFsmState::SkewingBounds { .. } => HintData(vec![ + HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), + HintGroup(vec![HintInfo::keys([Key::Control], "Unlock Slide")]), + ]), }; responses.add(FrontendMessage::UpdateInputHints { hint_data }); diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index 1733643d35..dbdf30fa2a 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -406,10 +406,10 @@ fn list_tools_in_groups() -> Vec> { ToolAvailability::Available(Box::::default()), ToolAvailability::Available(Box::::default()), ToolAvailability::Available(Box::::default()), - ToolAvailability::Available(Box::::default()), + ToolAvailability::AvailableAsShape(ShapeType::Line), ToolAvailability::AvailableAsShape(ShapeType::Rectangle), ToolAvailability::AvailableAsShape(ShapeType::Ellipse), - ToolAvailability::AvailableAsShape(ShapeType::Line), + ToolAvailability::Available(Box::::default()), ToolAvailability::Available(Box::::default()), ], vec![ diff --git a/editor/src/test_utils.rs b/editor/src/test_utils.rs index e52d74cbba..a019f2c6b9 100644 --- a/editor/src/test_utils.rs +++ b/editor/src/test_utils.rs @@ -211,7 +211,10 @@ impl EditorTestUtils { } pub async fn select_tool(&mut self, tool_type: ToolType) { - self.handle_message(Message::Tool(ToolMessage::ActivateTool { tool_type })).await; + match tool_type { + ToolType::Line => self.handle_message(Message::Tool(ToolMessage::ActivateShapeLine)).await, + _ => self.handle_message(Message::Tool(ToolMessage::ActivateTool { tool_type })).await, + } } pub async fn select_primary_color(&mut self, color: Color) { From 655bc61b9c0c5073b8e6d0eb55fce30fb655c692 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Thu, 5 Jun 2025 04:04:41 +0530 Subject: [PATCH 07/16] refactored select-tool --- .../common_functionality/utility_functions.rs | 2 +- .../tool/tool_messages/select_tool.rs | 39 +++++++++++-------- .../messages/tool/tool_messages/shape_tool.rs | 2 +- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/editor/src/messages/tool/common_functionality/utility_functions.rs b/editor/src/messages/tool/common_functionality/utility_functions.rs index 033ebc9429..d865f05cad 100644 --- a/editor/src/messages/tool/common_functionality/utility_functions.rs +++ b/editor/src/messages/tool/common_functionality/utility_functions.rs @@ -260,7 +260,7 @@ pub fn skew_bounds( } } -pub fn transforming_tranform_cage( +pub fn transforming_transform_cage( document: &DocumentMessageHandler, mut bounding_box_manager: &mut Option, input: &InputPreprocessorMessageHandler, diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 236d30fbfb..a8a0accbd1 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -2,8 +2,8 @@ use super::tool_prelude::*; use crate::consts::{ - COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COMPASS_ROSE_HOVER_RING_DIAMETER, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, RESIZE_HANDLE_SIZE, - SELECTION_DRAG_ANGLE, SELECTION_TOLERANCE, + COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COMPASS_ROSE_HOVER_RING_DIAMETER, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, RESIZE_HANDLE_SIZE, SELECTION_DRAG_ANGLE, + SELECTION_TOLERANCE, }; use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; @@ -21,7 +21,7 @@ use crate::messages::tool::common_functionality::pivot::Pivot; use crate::messages::tool::common_functionality::shape_editor::SelectionShapeType; use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager}; use crate::messages::tool::common_functionality::transformation_cage::*; -use crate::messages::tool::common_functionality::utility_functions::{resize_bounds, rotate_bounds, skew_bounds, text_bounding_box, transforming_tranform_cage}; +use crate::messages::tool::common_functionality::utility_functions::{resize_bounds, rotate_bounds, skew_bounds, text_bounding_box, transforming_transform_cage}; use bezier_rs::Subpath; use glam::DMat2; use graph_craft::document::NodeId; @@ -868,13 +868,7 @@ impl Fsm for SelectToolFsmState { let intersection_list = document.click_list(input).collect::>(); let intersection = document.find_deepest(&intersection_list); - let (resize, rotate, skew) = transforming_tranform_cage( - document, - &mut tool_data.bounding_box_manager, - input, - responses, - &mut tool_data.layers_dragging, - ); + let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging); // If the user is dragging the bounding box bounds, go into ResizingBounds mode. // If the user is dragging the rotate trigger, go into RotatingBounds mode. @@ -894,9 +888,9 @@ impl Fsm for SelectToolFsmState { let show_compass = bounds.is_some_and(|quad| quad.all_sides_at_least_width(COMPASS_ROSE_HOVER_RING_DIAMETER) && quad.contains(mouse_position)); let can_grab_compass_rose = compass_rose_state.can_grab() && (show_compass || bounds.is_none()); - let state = + let state = if is_over_pivot // Dragging the pivot - if is_over_pivot { + { responses.add(DocumentMessage::StartTransaction); // tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); @@ -905,13 +899,12 @@ impl Fsm for SelectToolFsmState { SelectToolFsmState::DraggingPivot } // Dragging one (or two, forming a corner) of the transform cage bounding box edges - else if resize { + else if resize { tool_data.get_snap_candidates(document, input); SelectToolFsmState::ResizingBounds - }else if skew{ + } else if skew { tool_data.get_snap_candidates(document, input); SelectToolFsmState::SkewingBounds { skew: Key::Control } - } // Dragging the selected layers around to transform them else if can_grab_compass_rose || intersection.is_some_and(|intersection| selected.iter().any(|selected_layer| intersection.starts_with(*selected_layer, document.metadata()))) { @@ -930,7 +923,13 @@ impl Fsm for SelectToolFsmState { let axis_state = compass_rose_state.axis_type().filter(|_| can_grab_compass_rose); (axis_state.unwrap_or_default(), axis_state.is_some()) }; - SelectToolFsmState::Dragging { axis, using_compass, has_dragged: false, deepest: input.keyboard.key(select_deepest), remove: input.keyboard.key(extend_selection) } + SelectToolFsmState::Dragging { + axis, + using_compass, + has_dragged: false, + deepest: input.keyboard.key(select_deepest), + remove: input.keyboard.key(extend_selection), + } } // Dragging near the transform cage bounding box to rotate it else if rotate { @@ -956,7 +955,13 @@ impl Fsm for SelectToolFsmState { tool_data.get_snap_candidates(document, input); responses.add(DocumentMessage::StartTransaction); - SelectToolFsmState::Dragging { axis: Axis::None, using_compass: false, has_dragged: false, deepest: input.keyboard.key(select_deepest), remove: input.keyboard.key(extend_selection) } + SelectToolFsmState::Dragging { + axis: Axis::None, + using_compass: false, + has_dragged: false, + deepest: input.keyboard.key(select_deepest), + remove: input.keyboard.key(extend_selection), + } } else { let selection_shape = if input.keyboard.key(lasso_select) { SelectionShapeType::Lasso } else { SelectionShapeType::Box }; SelectToolFsmState::Drawing { selection_shape, has_drawn: false } diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 802e5ddc08..ba6f062ffc 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -376,7 +376,7 @@ impl Fsm for ShapeToolFsmState { } } - let (resize, rotate, skew) = transforming_tranform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging); + let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging); match (resize, rotate, skew) { (true, false, false) => { From 3b57561979345551392b256cd8af6a30f051acd3 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Sun, 8 Jun 2025 00:14:56 +0530 Subject: [PATCH 08/16] add point-handle-gizmo --- .../tool/common_functionality/snapping.rs | 4 + .../snapping/alignment_snapper.rs | 4 +- editor/src/messages/tool/shapes/line_shape.rs | 37 +--- editor/src/messages/tool/shapes/star_shape.rs | 181 ++++++++++++++++++ .../src/messages/tool/tool_message_handler.rs | 3 +- .../messages/tool/tool_messages/shape_tool.rs | 145 ++++++++++---- .../src/vector/vector_data/attributes.rs | 4 + 7 files changed, 307 insertions(+), 71 deletions(-) diff --git a/editor/src/messages/tool/common_functionality/snapping.rs b/editor/src/messages/tool/common_functionality/snapping.rs index c5b7bc744e..9cb8361e0c 100644 --- a/editor/src/messages/tool/common_functionality/snapping.rs +++ b/editor/src/messages/tool/common_functionality/snapping.rs @@ -250,6 +250,10 @@ impl SnapManager { self.update_indicator(snapped); } + pub fn indicator_pos(&self) -> Option { + self.indicator.as_ref().map(|point| point.snapped_point_document) + } + fn find_best_snap(snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: SnapResults, constrained: bool, off_screen: bool, to_path: bool) -> SnappedPoint { let mut snapped_points = Vec::new(); let document = snap_data.document; diff --git a/editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs b/editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs index 47e07d46ec..94dda19722 100644 --- a/editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs +++ b/editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs @@ -70,7 +70,9 @@ impl AlignmentSnapper { if let Some(quad) = target_point.quad.map(|q| q.0) { if quad[0] == quad[3] && quad[1] == quad[2] && quad[0] == target_point.document_point { let [p1, p2, ..] = quad; - let direction = (p2 - p1).normalize(); + let Some(direction) = (p2 - p1).try_normalize() else { + return; + }; let normal = DVec2::new(-direction.y, direction.x); for endpoint in [p1, p2] { diff --git a/editor/src/messages/tool/shapes/line_shape.rs b/editor/src/messages/tool/shapes/line_shape.rs index 03e7496071..c348441862 100644 --- a/editor/src/messages/tool/shapes/line_shape.rs +++ b/editor/src/messages/tool/shapes/line_shape.rs @@ -103,34 +103,11 @@ impl Line { }) .collect::>(); } - pub fn dragging_endpoints(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, line_data: &mut LineToolData) -> bool { - for (layer, [document_start, document_end]) in line_data.selected_layers_with_position.iter() { - let transform = document.metadata().transform_to_viewport(*layer); - let viewport_x = transform.transform_vector2(DVec2::X).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD; - let viewport_y = transform.transform_vector2(DVec2::Y).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD; - let threshold_x = transform.inverse().transform_vector2(viewport_x).length(); - let threshold_y = transform.inverse().transform_vector2(viewport_y).length(); - - let drag_start = input.mouse.position; - let [start, end] = [document_start, document_end].map(|point| transform.transform_point2(*point)); - - let start_click = (drag_start.y - start.y).abs() < threshold_y && (drag_start.x - start.x).abs() < threshold_x; - let end_click = (drag_start.y - end.y).abs() < threshold_y && (drag_start.x - end.x).abs() < threshold_x; - - if start_click || end_click { - line_data.dragging_endpoint = Some(if end_click { LineEnd::End } else { LineEnd::Start }); - line_data.drag_start = if end_click { *document_start } else { *document_end }; - line_data.editing_layer = Some(*layer); - return true; - } - } - false - } } fn generate_line(tool_data: &mut ShapeToolData, snap_data: SnapData, lock_angle: bool, snap_angle: bool, center: bool) -> [DVec2; 2] { let document_to_viewport = snap_data.document.metadata().document_to_viewport; - let mut document_points = [tool_data.line_data.drag_start, document_to_viewport.inverse().transform_point2(tool_data.line_data.drag_current)]; + let mut document_points = [tool_data.data.drag_start, document_to_viewport.inverse().transform_point2(tool_data.line_data.drag_current)]; let mut angle = -(document_points[1] - document_points[0]).angle_to(DVec2::X); let mut line_length = (document_points[1] - document_points[0]).length(); @@ -154,8 +131,8 @@ fn generate_line(tool_data: &mut ShapeToolData, snap_data: SnapData, lock_angle: let constrained = snap_angle || lock_angle; let snap = &mut tool_data.data.snap_manager; - let near_point = SnapCandidatePoint::handle_neighbors(document_points[1], [tool_data.line_data.drag_start]); - let far_point = SnapCandidatePoint::handle_neighbors(2. * document_points[0] - document_points[1], [tool_data.line_data.drag_start]); + let near_point = SnapCandidatePoint::handle_neighbors(document_points[1], [tool_data.data.drag_start]); + let far_point = SnapCandidatePoint::handle_neighbors(2. * document_points[0] - document_points[1], [tool_data.data.drag_start]); let config = SnapTypeConfiguration::default(); if constrained { @@ -191,7 +168,7 @@ fn generate_line(tool_data: &mut ShapeToolData, snap_data: SnapData, lock_angle: document_points } -pub fn clicked_on_line_endpoints(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, line_data: &mut LineToolData) -> bool { +pub fn clicked_on_line_endpoints(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, shape_tool_data: &mut ShapeToolData) -> bool { let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Line") else { return false; }; @@ -213,9 +190,9 @@ pub fn clicked_on_line_endpoints(layer: LayerNodeIdentifier, document: &Document let end_click = (drag_start.y - end.y).abs() < threshold_y && (drag_start.x - end.x).abs() < threshold_x; if start_click || end_click { - line_data.dragging_endpoint = Some(if end_click { LineEnd::End } else { LineEnd::Start }); - line_data.drag_start = if end_click { document_start } else { document_end }; - line_data.editing_layer = Some(layer); + shape_tool_data.line_data.dragging_endpoint = Some(if end_click { LineEnd::End } else { LineEnd::Start }); + shape_tool_data.data.drag_start = if end_click { document_start } else { document_end }; + shape_tool_data.line_data.editing_layer = Some(layer); return true; } false diff --git a/editor/src/messages/tool/shapes/star_shape.rs b/editor/src/messages/tool/shapes/star_shape.rs index fc9d013ab2..b9267514d6 100644 --- a/editor/src/messages/tool/shapes/star_shape.rs +++ b/editor/src/messages/tool/shapes/star_shape.rs @@ -1,7 +1,9 @@ +use super::line_shape::NodeGraphLayer; use super::shape_utility::{ShapeToolModifierKey, update_radius_sign}; use super::*; 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::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::tool::common_functionality::graph_modification_utils; @@ -9,11 +11,24 @@ use crate::messages::tool::tool_messages::tool_prelude::*; use glam::DAffine2; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; +use graphene_std::vector::PointId; use std::collections::VecDeque; #[derive(Default)] pub struct Star; +#[derive(Clone, Debug, Default)] +pub struct PointRadiusHandle { + pub center: DVec2, + pub vertex: Option, + pub index: usize, +} + +#[derive(Clone, Debug, Default)] +pub struct StarShapeData { + pub point_radius_handle: PointRadiusHandle, +} + impl Star { pub fn create_node(vertices: u32) -> NodeTemplate { let node_type = resolve_document_node_type("Star").expect(" Star node does not exist"); @@ -25,6 +40,122 @@ impl Star { ]) } + pub fn set_point_radius_handle(document: &DocumentMessageHandler, mouse_pos: DVec2, shape_tool_data: &mut ShapeToolData) -> bool { + if let Some((layer, (center, _, vertex, index))) = Self::points_on_inner_circle(document, mouse_pos).iter().next() { + shape_tool_data.data.layer = Some(*layer); + shape_tool_data.star_data.point_radius_handle = PointRadiusHandle { + center: *center, + vertex: Some(*vertex), + index: *index, + }; + return true; + } + false + } + + pub fn inner_gizmo_overlays(document: &DocumentMessageHandler, shape_tool_data: &mut ShapeToolData, overlay_context: &mut OverlayContext) { + let PointRadiusHandle { center, vertex, .. } = shape_tool_data.star_data.point_radius_handle; + let layer = shape_tool_data.data.layer.unwrap(); + let vector_data = document.network_interface.compute_modified_vector(layer).unwrap(); + let viewport = document.metadata().transform_to_viewport(layer); + let vertex_pos = vector_data.point_domain.position_from_id(vertex.unwrap()).unwrap(); + Self::draw_point_radius_overlay(center, vertex_pos, viewport, overlay_context); + } + + fn draw_point_radius_overlay(center: DVec2, vertex_pos: DVec2, transform: DAffine2, overlay_context: &mut OverlayContext) { + let viewport_center = transform.transform_point2(center); + let viewport_vertex = transform.transform_point2(vertex_pos); + let extension_length = (viewport_vertex - viewport_center).length() * 0.5; + let extension = (viewport_vertex - viewport_center).normalize() * extension_length; + + overlay_context.line(viewport_center, viewport_vertex + extension, None, None); + overlay_context.manipulator_handle(viewport_vertex, true, None); + } + // when hovered + pub fn hover_point_radius_handle(document: &DocumentMessageHandler, mouse_pos: DVec2, overlay_context: &mut OverlayContext) -> bool { + for (layer, (center, vertex_pos, _, _)) in Self::points_on_inner_circle(document, mouse_pos) { + let transform = document.metadata().transform_to_viewport(layer); + Self::draw_point_radius_overlay(center, vertex_pos, transform, overlay_context); + return true; + } + + for layer in document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter(|layer| graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some()) + { + let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { + return false; + }; + + for (_, anchor_positions) in vector_data.point_domain.position_ids() { + let transform = document.metadata().transform_to_viewport(layer); + overlay_context.manipulator_handle(transform.transform_point2(*anchor_positions), false, None); + } + + return false; + } + false + } + + pub fn points_on_inner_circle(document: &DocumentMessageHandler, mouse_pos: DVec2) -> HashMap { + let mut result = HashMap::new(); + + for layer in document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter(|layer| graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some()) + { + let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { + return result; + }; + + let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { + return result; + }; + + let transform = document.network_interface.document_metadata().transform_to_viewport(layer); + let center = DVec2::ZERO; + + let (Some(&TaggedValue::F64(outer)), Some(&TaggedValue::F64(inner))) = (node_inputs[2].as_value(), node_inputs[3].as_value()) else { + return result; + }; + + let mut index = 0; + + let inner_point = vector_data.point_domain.position_ids().find(|(_, pos)| { + let transformed = transform.transform_point2(**pos); + if transformed.distance(mouse_pos) >= 5.0 { + return false; + } + + let dist = pos.distance(center); + if (dist - inner).abs() < 1e-6 { + index = 3; + + true + } else if (dist - outer).abs() < 1e-6 { + index = 2; + log::info!("dist to outer {:?}", (dist - outer).abs()); + + true + } else { + false + } + }); + + // Only insert if we found an inner point + if let Some((point_id, vertex_pos)) = inner_point { + result.insert(layer, (center, *vertex_pos, point_id, index)); + break; + } + } + + result + } + pub fn update_shape( document: &DocumentMessageHandler, ipp: &InputPreprocessorMessageHandler, @@ -74,4 +205,54 @@ impl Star { } false } + + pub fn update_inner_radius( + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + layer: LayerNodeIdentifier, + responses: &mut VecDeque, + shape_tool_data: &mut ShapeToolData, + ) { + let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface) else { + return; + }; + + let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { + return; + }; + + let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { + return; + }; + + let path = vector_data.stroke_bezier_paths().next().unwrap(); + let center = path.length_centroid(None, true).unwrap(); + let transform = document.network_interface.document_metadata().transform_to_viewport(layer); + let index = shape_tool_data.star_data.point_radius_handle.index; + + // inner radiust + let Some(&TaggedValue::F64(required_radius)) = node_inputs[index].as_value() else { + return; + }; + + // update_radius_sign(start, end, layer, document, responses); + + let delta = input.mouse.position - shape_tool_data.last_mouse_position; + let radius = document.metadata().document_to_viewport.transform_point2(shape_tool_data.data.drag_start) - transform.transform_point2(center); + let projection = delta.project_onto(radius); + let sign = radius.dot(delta).signum(); + + let net_delta = projection.length() * sign; + shape_tool_data.last_mouse_position = input.mouse.position; + + // overlay_context.line(transform.transform_point2(center), transform.transform_point2(inner + net_delta), None, None); + + // We keep the smaller dimension's scale at 1 and scale the other dimension accordingly + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, index), + input: NodeInput::value(TaggedValue::F64(required_radius + net_delta), false), + }); + responses.add(NodeGraphMessage::RunDocumentGraph); + } } diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index 4b50655149..0f1d1705fe 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -1,5 +1,5 @@ use super::common_functionality::shape_editor::ShapeState; -use super::shapes::shape_utility::ShapeType::{Ellipse, Line, Rectangle}; +use super::shapes::shape_utility::ShapeType::{self, Ellipse, Line, Rectangle}; use super::utility_types::{ToolActionHandlerData, ToolFsmState, tool_message_to_tool_type}; use crate::application::generate_uuid; use crate::messages::layout::utility_types::widget_prelude::*; @@ -65,6 +65,7 @@ impl MessageHandler> for ToolMessageHandler { self.tool_state.tool_data.active_tool_type = ToolType::Shape; } responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape }); + responses.add(ShapeToolMessage::SetShape(ShapeType::Convex)); responses.add(ShapeToolMessage::HideShapeTypeWidget(false)) } ToolMessage::ActivateToolPolygon => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Polygon }), diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index ba6f062ffc..7834ca47f0 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -9,11 +9,11 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{self use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapTypeConfiguration}; use crate::messages::tool::common_functionality::transformation_cage::{BoundingBoxManager, EdgeBool}; -use crate::messages::tool::common_functionality::utility_functions::{closest_point, resize_bounds, rotate_bounds, skew_bounds, transforming_tranform_cage}; +use crate::messages::tool::common_functionality::utility_functions::{closest_point, resize_bounds, rotate_bounds, skew_bounds, transforming_transform_cage}; use crate::messages::tool::shapes::convex_shape::Convex; use crate::messages::tool::shapes::line_shape::{LineToolData, clicked_on_line_endpoints}; use crate::messages::tool::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays}; -use crate::messages::tool::shapes::star_shape::Star; +use crate::messages::tool::shapes::star_shape::{Star, StarShapeData}; use crate::messages::tool::shapes::{Ellipse, Line, Rectangle}; use graph_craft::document::NodeId; use graphene_core::Color; @@ -206,6 +206,7 @@ impl<'a> MessageHandler> for ShapeTo | ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::DraggingLineEndpoints | ShapeToolFsmState::RotatingBounds + | ShapeToolFsmState::DraggingStarInnerRadius | ShapeToolFsmState::SkewingBounds { .. } => { actions!(ShapeToolMessageDiscriminant; DragStop, @@ -247,6 +248,7 @@ pub enum ShapeToolFsmState { Ready(ShapeType), Drawing(ShapeType), DraggingLineEndpoints, + DraggingStarInnerRadius, ResizingBounds, RotatingBounds, SkewingBounds { skew: Key }, @@ -262,15 +264,23 @@ impl Default for ShapeToolFsmState { pub struct ShapeToolData { pub data: Resize, auto_panning: AutoPanning, + + // in viewport space + pub last_mouse_position: DVec2, + + // Hide the dropdown menu when using line,rectangle or ellipse aliases pub hide_shape_option_widget: bool, pub line_data: LineToolData, + pub star_data: StarShapeData, + + // Used for by transform cage pub bounding_box_manager: Option, layers_dragging: Vec, snap_candidates: Vec, - cursor: MouseCursorIcon, - drag_start: DVec2, - drag_current: DVec2, skew_edge: EdgeBool, + cursor: MouseCursorIcon, + + // Current shape which is being drawn current_shape: ShapeType, } @@ -307,25 +317,38 @@ impl Fsm for ShapeToolFsmState { tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { - let shape_data = &mut tool_data.data; - let line_data = &mut tool_data.line_data; - let ToolMessage::Shape(event) = event else { return self; }; match (self, event) { (_, ShapeToolMessage::Overlays(mut overlay_context)) => { + let mouse_pos = tool_data + .data + .snap_manager + .indicator_pos() + .map(|pos| document.metadata().document_to_viewport.transform_point2(pos)) + .unwrap_or(input.mouse.position); let is_resizing_or_rotating = matches!(self, ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::SkewingBounds { .. } | ShapeToolFsmState::RotatingBounds); + let dragging_start_gizmos = matches!(self, Self::DraggingStarInnerRadius); - if !is_resizing_or_rotating { - shape_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); + if !is_resizing_or_rotating && !dragging_start_gizmos { + tool_data.data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); } - Line::overlays(document, tool_data, &mut overlay_context); + if matches!(self, ShapeToolFsmState::DraggingStarInnerRadius) { + Star::inner_gizmo_overlays(document, tool_data, &mut overlay_context); + } if input.keyboard.key(Key::Control) && matches!(self, ShapeToolFsmState::Ready(_)) { anchor_overlays(document, &mut overlay_context); - } else { + } else if matches!(self, ShapeToolFsmState::Ready(_)) { + Line::overlays(document, tool_data, &mut overlay_context); + + // if hovered over vertices of the star show the gizmos + if Star::hover_point_radius_handle(document, mouse_pos, &mut overlay_context) { + return self; + } + if document .network_interface .selected_nodes() @@ -360,20 +383,39 @@ impl Fsm for ShapeToolFsmState { self } (ShapeToolFsmState::Ready(_), ShapeToolMessage::DragStart) => { - tool_data.drag_start = input.mouse.position; - tool_data.drag_current = input.mouse.position; + tool_data.line_data.drag_start = input.mouse.position; + + // Snapped position in viewport-space. + let mouse_pos = tool_data + .data + .snap_manager + .indicator_pos() + .map(|pos| document.metadata().document_to_viewport.transform_point2(pos)) + .unwrap_or(input.mouse.position); + + tool_data.line_data.drag_current = mouse_pos; + + // Check if dragging the inner vertices of a star + + if Star::set_point_radius_handle(document, mouse_pos, tool_data) { + tool_data.last_mouse_position = mouse_pos; + tool_data.data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(mouse_pos); + responses.add(DocumentMessage::StartTransaction); + return ShapeToolFsmState::DraggingStarInnerRadius; + } + // If clicked on endpoints of a selected line, drag its endpoints if let Some((layer, _, _)) = closest_point( document, - input.mouse.position, + mouse_pos, SNAP_POINT_TOLERANCE, document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface), |_| false, preferences, ) { - if clicked_on_line_endpoints(layer, document, input, line_data) && !input.keyboard.key(Key::Control) { + if clicked_on_line_endpoints(layer, document, input, tool_data) && !input.keyboard.key(Key::Control) { return ShapeToolFsmState::DraggingLineEndpoints; - } + }; } let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging); @@ -394,11 +436,11 @@ impl Fsm for ShapeToolFsmState { } match tool_data.current_shape { - ShapeType::Convex | ShapeType::Star | ShapeType::Ellipse | ShapeType::Rectangle => shape_data.start(document, input), + ShapeType::Convex | ShapeType::Star | ShapeType::Ellipse | ShapeType::Rectangle => tool_data.data.start(document, input), ShapeType::Line => { let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); - let snapped = shape_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); - line_data.drag_start = snapped.snapped_point_document; + let snapped = tool_data.data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); + tool_data.data.drag_start = snapped.snapped_point_document; } } @@ -409,7 +451,7 @@ impl Fsm for ShapeToolFsmState { ShapeType::Star => Star::create_node(tool_options.vertices), ShapeType::Rectangle => Rectangle::create_node(), ShapeType::Ellipse => Ellipse::create_node(), - ShapeType::Line => Line::create_node(&document, line_data.drag_start), + ShapeType::Line => Line::create_node(&document, tool_data.data.drag_start), }; let nodes = vec![(NodeId(0), node)]; @@ -430,18 +472,18 @@ impl Fsm for ShapeToolFsmState { tool_options.fill.apply_fill(layer, responses); } ShapeType::Line => { - line_data.angle = 0.0; - line_data.weight = tool_options.line_weight; - line_data.editing_layer = Some(layer); + tool_data.line_data.angle = 0.0; + tool_data.line_data.weight = tool_options.line_weight; + tool_data.line_data.editing_layer = Some(layer); } } - shape_data.layer = Some(layer); + tool_data.data.layer = Some(layer); ShapeToolFsmState::Drawing(tool_data.current_shape) } (ShapeToolFsmState::Drawing(shape), ShapeToolMessage::PointerMove(modifier)) => { - let Some(layer) = shape_data.layer else { + let Some(layer) = tool_data.data.layer else { return ShapeToolFsmState::Ready(shape); }; if match tool_data.current_shape { @@ -461,7 +503,7 @@ impl Fsm for ShapeToolFsmState { self } (ShapeToolFsmState::DraggingLineEndpoints, ShapeToolMessage::PointerMove(modifier)) => { - let Some(layer) = line_data.editing_layer else { + let Some(layer) = tool_data.line_data.editing_layer else { return ShapeToolFsmState::Ready(tool_data.current_shape); }; @@ -472,6 +514,15 @@ impl Fsm for ShapeToolFsmState { self } + (ShapeToolFsmState::DraggingStarInnerRadius, ShapeToolMessage::PointerMove(..)) => { + if let Some(layer) = tool_data.data.layer { + Star::update_inner_radius(document, input, layer, responses, tool_data); + } + + responses.add(OverlaysMessage::Draw); + + ShapeToolFsmState::DraggingStarInnerRadius + } (ShapeToolFsmState::ResizingBounds, ShapeToolMessage::PointerMove(modifier)) => { if let Some(bounds) = &mut tool_data.bounding_box_manager { let messages = [ShapeToolMessage::PointerOutsideViewport(modifier.clone()).into(), ShapeToolMessage::PointerMove(modifier).into()]; @@ -480,7 +531,7 @@ impl Fsm for ShapeToolFsmState { responses, bounds, &mut tool_data.layers_dragging, - &mut shape_data.snap_manager, + &mut tool_data.data.snap_manager, &mut tool_data.snap_candidates, input, input.keyboard.key(Key::Shift), @@ -500,7 +551,7 @@ impl Fsm for ShapeToolFsmState { responses, bounds, &mut tool_data.layers_dragging, - tool_data.drag_start, + tool_data.data.drag_start, input.mouse.position, input.keyboard.key(Key::Shift), ToolType::Shape, @@ -526,7 +577,13 @@ impl Fsm for ShapeToolFsmState { } (_, ShapeToolMessage::PointerMove { .. }) => { - log::info!("reaching here"); + // Snapped position in viewport-space. + let mouse_pos = tool_data + .data + .snap_manager + .indicator_pos() + .map(|pos| document.metadata().document_to_viewport.transform_point2(pos)) + .unwrap_or(input.mouse.position); let dragging_bounds = tool_data .bounding_box_manager .as_mut() @@ -544,14 +601,18 @@ impl Fsm for ShapeToolFsmState { // } // Generate the hover outline - responses.add(OverlaysMessage::Draw); + let inner_gizmo_points = Star::points_on_inner_circle(document, mouse_pos).is_empty(); - if tool_data.cursor != cursor { + if tool_data.cursor != cursor && !input.keyboard.key(Key::Control) && inner_gizmo_points { tool_data.cursor = cursor; responses.add(FrontendMessage::UpdateMouseCursor { cursor }); } - shape_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); + if !inner_gizmo_points { + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); + } + + tool_data.data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); responses.add(OverlaysMessage::Draw); self } @@ -566,6 +627,7 @@ impl Fsm for ShapeToolFsmState { self } + (ShapeToolFsmState::Ready(_), ShapeToolMessage::PointerOutsideViewport(..)) => self, (_, ShapeToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning let _ = tool_data.auto_panning.shift_viewport(input, responses); @@ -576,20 +638,25 @@ impl Fsm for ShapeToolFsmState { | ShapeToolFsmState::DraggingLineEndpoints | ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::RotatingBounds - | ShapeToolFsmState::SkewingBounds { .. }, + | ShapeToolFsmState::SkewingBounds { .. } + | ShapeToolFsmState::DraggingStarInnerRadius, ShapeToolMessage::DragStop, ) => { - input.mouse.finish_transaction(tool_data.drag_start, responses); - shape_data.cleanup(responses); + input.mouse.finish_transaction(tool_data.data.drag_start, responses); + tool_data.data.cleanup(responses); + if let Some(bounds) = &mut tool_data.bounding_box_manager { bounds.original_transforms.clear(); } + tool_data.line_data.dragging_endpoint = None; + ShapeToolFsmState::Ready(tool_data.current_shape) } (ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::DraggingLineEndpoints, ShapeToolMessage::Abort) => { + tool_data.line_data.dragging_endpoint = None; responses.add(DocumentMessage::AbortTransaction); - shape_data.cleanup(responses); + tool_data.data.cleanup(responses); ShapeToolFsmState::Ready(tool_data.current_shape) } @@ -602,7 +669,7 @@ impl Fsm for ShapeToolFsmState { } (_, ShapeToolMessage::SetShape(shape)) => { responses.add(DocumentMessage::AbortTransaction); - shape_data.cleanup(responses); + tool_data.data.cleanup(responses); tool_data.current_shape = shape; ShapeToolFsmState::Ready(shape) @@ -667,7 +734,7 @@ impl Fsm for ShapeToolFsmState { HintInfo::keys([Key::Control], "Lock Angle"), ]), ]), - ShapeToolFsmState::ResizingBounds => HintData(vec![ + ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::DraggingStarInnerRadius => HintData(vec![ HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), HintGroup(vec![HintInfo::keys([Key::Alt], "From Pivot"), HintInfo::keys([Key::Shift], "Preserve Aspect Ratio")]), ]), diff --git a/node-graph/gcore/src/vector/vector_data/attributes.rs b/node-graph/gcore/src/vector/vector_data/attributes.rs index d5e1fe0241..969a449ef3 100644 --- a/node-graph/gcore/src/vector/vector_data/attributes.rs +++ b/node-graph/gcore/src/vector/vector_data/attributes.rs @@ -137,6 +137,10 @@ impl PointDomain { &self.position } + pub fn position_ids(&self) -> impl Iterator { + self.id.iter().copied().zip(self.positions()) + } + pub fn positions_mut(&mut self) -> impl Iterator { self.id.iter().copied().zip(self.position.iter_mut()) } From 5125554ff62b192c0804f49cc1e5689643a2fa8e Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Sun, 8 Jun 2025 01:35:24 +0530 Subject: [PATCH 09/16] fix rotate bug --- editor/src/messages/tool/tool_messages/shape_tool.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 7834ca47f0..2862719264 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -426,6 +426,7 @@ impl Fsm for ShapeToolFsmState { return ShapeToolFsmState::ResizingBounds; } (false, true, false) => { + tool_data.data.drag_start = mouse_pos; return ShapeToolFsmState::RotatingBounds; } (false, false, true) => { From 4acdafa144d653fc226c5d286f822d0966311101 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Thu, 12 Jun 2025 23:55:57 +0530 Subject: [PATCH 10/16] implement angle snapping gizmo , fix overlay and refactor the code --- editor/src/consts.rs | 3 + .../document/document_message_handler.rs | 15 + .../messages/tool/common_functionality/mod.rs | 1 + .../shapes/convex_shape.rs | 0 .../shapes/ellipse_shape.rs | 0 .../shapes/line_shape.rs | 0 .../{ => common_functionality}/shapes/mod.rs | 2 +- .../shapes/rectangle_shape.rs | 0 .../shapes/shape_utility.rs | 37 +- .../common_functionality/shapes/star_shape.rs | 507 ++++++++++++++++++ editor/src/messages/tool/mod.rs | 1 - editor/src/messages/tool/shapes/star_shape.rs | 258 --------- .../src/messages/tool/tool_message_handler.rs | 2 +- .../messages/tool/tool_messages/shape_tool.rs | 51 +- .../transform_layer_message_handler.rs | 3 +- editor/src/messages/tool/utility_types.rs | 2 +- 16 files changed, 586 insertions(+), 296 deletions(-) rename editor/src/messages/tool/{ => common_functionality}/shapes/convex_shape.rs (100%) rename editor/src/messages/tool/{ => common_functionality}/shapes/ellipse_shape.rs (100%) rename editor/src/messages/tool/{ => common_functionality}/shapes/line_shape.rs (100%) rename editor/src/messages/tool/{ => common_functionality}/shapes/mod.rs (80%) rename editor/src/messages/tool/{ => common_functionality}/shapes/rectangle_shape.rs (100%) rename editor/src/messages/tool/{ => common_functionality}/shapes/shape_utility.rs (81%) create mode 100644 editor/src/messages/tool/common_functionality/shapes/star_shape.rs delete mode 100644 editor/src/messages/tool/shapes/star_shape.rs diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 9b58d589bf..06440f5ddd 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -117,6 +117,9 @@ pub const LINE_ROTATE_SNAP_ANGLE: f64 = 15.; pub const BRUSH_SIZE_CHANGE_KEYBOARD: f64 = 5.; pub const DEFAULT_BRUSH_SIZE: f64 = 20.; +// STAR GIZMOS +pub const POINT_RADIUS_HANDLE_SNAP_THRESHOLD: f64 = 8.; + // SCROLLBARS pub const SCROLLBAR_SPACING: f64 = 0.1; pub const ASYMPTOTIC_EFFECT: f64 = 0.5; diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 32844ed672..343aec9aa9 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -1687,6 +1687,21 @@ impl DocumentMessageHandler { self.click_list(ipp).last() } + pub fn click_based_on_position(&self, mouse_snapped_positon: DVec2) -> Option { + ClickXRayIter::new(&self.network_interface, XRayTarget::Point(mouse_snapped_positon)) + .filter(move |&layer| !self.network_interface.is_artboard(&layer.to_node(), &[])) + .skip_while(|&layer| layer == LayerNodeIdentifier::ROOT_PARENT) + .scan(true, |last_had_children, layer| { + if *last_had_children { + *last_had_children = layer.has_children(self.network_interface.document_metadata()); + Some(layer) + } else { + None + } + }) + .last() + } + /// Get the combined bounding box of the click targets of the selected visible layers in viewport space pub fn selected_visible_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> { self.network_interface diff --git a/editor/src/messages/tool/common_functionality/mod.rs b/editor/src/messages/tool/common_functionality/mod.rs index 9bc2236061..a036e1de6e 100644 --- a/editor/src/messages/tool/common_functionality/mod.rs +++ b/editor/src/messages/tool/common_functionality/mod.rs @@ -6,6 +6,7 @@ pub mod measure; pub mod pivot; pub mod resize; pub mod shape_editor; +pub mod shapes; pub mod snapping; pub mod transformation_cage; pub mod utility_functions; diff --git a/editor/src/messages/tool/shapes/convex_shape.rs b/editor/src/messages/tool/common_functionality/shapes/convex_shape.rs similarity index 100% rename from editor/src/messages/tool/shapes/convex_shape.rs rename to editor/src/messages/tool/common_functionality/shapes/convex_shape.rs diff --git a/editor/src/messages/tool/shapes/ellipse_shape.rs b/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs similarity index 100% rename from editor/src/messages/tool/shapes/ellipse_shape.rs rename to editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs diff --git a/editor/src/messages/tool/shapes/line_shape.rs b/editor/src/messages/tool/common_functionality/shapes/line_shape.rs similarity index 100% rename from editor/src/messages/tool/shapes/line_shape.rs rename to editor/src/messages/tool/common_functionality/shapes/line_shape.rs diff --git a/editor/src/messages/tool/shapes/mod.rs b/editor/src/messages/tool/common_functionality/shapes/mod.rs similarity index 80% rename from editor/src/messages/tool/shapes/mod.rs rename to editor/src/messages/tool/common_functionality/shapes/mod.rs index 304d9c9dd5..3c9abab5f0 100644 --- a/editor/src/messages/tool/shapes/mod.rs +++ b/editor/src/messages/tool/common_functionality/shapes/mod.rs @@ -8,5 +8,5 @@ pub mod star_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::tool_messages::shape_tool::ShapeToolData; +pub use crate::messages::tool::tool_messages::shape_tool::ShapeToolData; use glam::DVec2; diff --git a/editor/src/messages/tool/shapes/rectangle_shape.rs b/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs similarity index 100% rename from editor/src/messages/tool/shapes/rectangle_shape.rs rename to editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs diff --git a/editor/src/messages/tool/shapes/shape_utility.rs b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs similarity index 81% rename from editor/src/messages/tool/shapes/shape_utility.rs rename to editor/src/messages/tool/common_functionality/shapes/shape_utility.rs index 8c0366770e..2c21ed98a4 100644 --- a/editor/src/messages/tool/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -3,7 +3,7 @@ use crate::messages::portfolio::document::overlays::utility_types::OverlayContex use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; use crate::messages::prelude::{DocumentMessageHandler, NodeGraphMessage, Responses}; -use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; +use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer}; use crate::messages::tool::common_functionality::transformation_cage::BoundingBoxManager; use crate::messages::tool::tool_messages::tool_prelude::Key; use crate::messages::tool::utility_types::*; @@ -11,6 +11,7 @@ use glam::{DMat2, DVec2}; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; use std::collections::VecDeque; +use std::f64::consts::PI; use super::ShapeToolData; @@ -149,3 +150,37 @@ pub fn anchor_overlays(document: &DocumentMessageHandler, overlay_context: &mut } } } + +pub fn points_on_inner_circle(document: &DocumentMessageHandler, mouse_position: DVec2) -> Option<(LayerNodeIdentifier, u32, usize, f64)> { + for layer in document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter(|layer| graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some()) + { + let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { + continue; + }; + + let viewport = document.network_interface.document_metadata().transform_to_viewport(layer); + + let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(outer)), Some(&TaggedValue::F64(inner))) = (node_inputs[1].as_value(), node_inputs[2].as_value(), node_inputs[3].as_value()) else { + continue; + }; + + for i in 0..(2 * n) { + let angle = i as f64 * PI / n as f64; + let (radius, index) = if i % 2 == 0 { (outer, 2) } else { (inner, 3) }; + + let point = viewport.transform_point2(DVec2 { + x: radius * angle.sin(), + y: -radius * angle.cos(), + }); + + if point.distance(mouse_position) < 5.0 { + return Some((layer, i, index, radius)); + }; + } + } + None +} diff --git a/editor/src/messages/tool/common_functionality/shapes/star_shape.rs b/editor/src/messages/tool/common_functionality/shapes/star_shape.rs new file mode 100644 index 0000000000..c4a524e1db --- /dev/null +++ b/editor/src/messages/tool/common_functionality/shapes/star_shape.rs @@ -0,0 +1,507 @@ +use super::line_shape::NodeGraphLayer; +use super::shape_utility::{ShapeToolModifierKey, update_radius_sign}; +use super::*; +use crate::consts::{COLOR_OVERLAY_RED, POINT_RADIUS_HANDLE_SNAP_THRESHOLD}; +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::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::tool::common_functionality::graph_modification_utils; +use crate::messages::tool::common_functionality::shapes::shape_utility::points_on_inner_circle; +use crate::messages::tool::tool_messages::tool_prelude::*; +use core::f64; +use glam::DAffine2; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; +use std::collections::VecDeque; +use std::f64::consts::FRAC_PI_4; +use std::f64::consts::{FRAC_1_SQRT_2, PI, SQRT_2}; + +#[derive(Default)] +pub struct Star; + +#[derive(Clone, Debug, Default, PartialEq)] +enum PointRadiusHandleState { + #[default] + Inactive, + Hover, + Dragging, + Snapped(usize), +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct PointRadiusHandle { + layer: LayerNodeIdentifier, + pub point: u32, + pub index: usize, + pub snap_radii: Vec, + initial_radii: f64, + handle_state: PointRadiusHandleState, +} + +#[derive(Clone, Debug, Default)] +enum NumberOfPointsHandleState { + #[default] + Inactive, + Dragging, +} + +#[derive(Clone, Debug, Default)] + +pub struct NumberOfPointsHandle { + initial_points: u32, + handle_state: NumberOfPointsHandleState, +} + +impl NumberOfPointsHandle { + fn overlays(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, mouse_position: DVec2, overlay_context: &mut OverlayContext) {} + fn update_state(&mut self, state: NumberOfPointsHandleState) { + self.handle_state = state; + } +} + +impl PointRadiusHandle { + pub fn cleanup(&mut self) { + self.handle_state = PointRadiusHandleState::Inactive; + self.snap_radii.clear(); + } + + pub fn is_hovered(&self) -> bool { + self.handle_state == PointRadiusHandleState::Hover + } + + fn update_state(&mut self, state: PointRadiusHandleState) { + self.handle_state = state; + } + + pub fn overlays(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, mouse_position: DVec2, overlay_context: &mut OverlayContext) { + match &self.handle_state { + PointRadiusHandleState::Inactive => { + for layer in document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter(|layer| graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some()) + { + let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { + return; + }; + + let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(outer)), Some(&TaggedValue::F64(inner))) = + (node_inputs[1].as_value(), node_inputs[2].as_value(), node_inputs[3].as_value()) + else { + return; + }; + + let viewport = document.metadata().transform_to_viewport(layer); + + for i in 0..(2 * n) { + let angle = i as f64 * PI / n as f64; + let (radius, radius_index) = if i % 2 == 0 { (outer, 2) } else { (inner, 3) }; + + let point = viewport.transform_point2(DVec2 { + x: radius * angle.sin(), + y: -radius * angle.cos(), + }); + + let center = viewport.transform_point2(DVec2::ZERO); + let viewport_diagonal = input.viewport_bounds.size().length(); + + if point.distance(mouse_position) < 5.0 { + let Some(direction) = (point - center).try_normalize() else { + continue; + }; + + self.layer = layer; + self.point = i; + self.snap_radii = Self::calculate_snap_radii(document, layer, radius_index); + self.update_state(PointRadiusHandleState::Hover); + overlay_context.manipulator_handle(point, true, None); + overlay_context.line(center, center + direction * viewport_diagonal, None, None); + + break; + } + + overlay_context.manipulator_handle(point, false, None); + } + } + } + + PointRadiusHandleState::Dragging | PointRadiusHandleState::Hover => { + let layer = self.layer; + let viewport = document.metadata().transform_to_viewport(layer); + let center = viewport.transform_point2(DVec2::ZERO); + let viewport_diagonal = input.viewport_bounds.size().length(); + + let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { + return; + }; + + let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(outer)), Some(&TaggedValue::F64(inner))) = (node_inputs[1].as_value(), node_inputs[2].as_value(), node_inputs[3].as_value()) + else { + return; + }; + let angle = self.point as f64 * PI / n as f64; + let radius = if self.point % 2 == 0 { outer } else { inner }; + + let point = viewport.transform_point2(DVec2 { + x: radius * angle.sin(), + y: -radius * angle.cos(), + }); + + if matches!(&self.handle_state, PointRadiusHandleState::Hover) { + if (mouse_position - point).length() > 5. { + self.update_state(PointRadiusHandleState::Inactive); + return; + } + } + + let Some(direction) = (point - center).try_normalize() else { return }; + + // Draws the ray from the center to the dragging point extending till the viewport + overlay_context.manipulator_handle(point, true, None); + overlay_context.line(center, center + direction * viewport_diagonal, None, None); + + // makes the ticks for snapping + + // if dragging to make radius negative don't show the + if (mouse_position - center).dot(direction) < 0. { + return; + } + + for snapped_radius in &self.snap_radii { + let Some(tick_direction) = direction.perp().try_normalize() else { return }; + // let Some(&TaggedValue::F64(radius)) = node_inputs[self.index].as_value() else { return }; + let difference = snapped_radius - self.initial_radii; + log::info!("difference {:?}", difference); + + let tick_position = viewport.transform_point2(DVec2 { + x: snapped_radius * angle.sin(), + y: -snapped_radius * angle.cos(), + }); + + // let tick_position = viewport.transform_point2(initial_point_position + difference * direction); + + // overlay_context.manipulator_handle(tick_position, false, None); + + overlay_context.line(tick_position, tick_position + tick_direction * 5., None, Some(2.)); + overlay_context.line(tick_position, tick_position - tick_direction * 5., None, Some(2.)); + } + } + PointRadiusHandleState::Snapped(snapping_index) => { + let layer = self.layer; + let viewport = document.metadata().transform_to_viewport(layer); + + let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { + return; + }; + + let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(outer)), Some(&TaggedValue::F64(inner))) = (node_inputs[1].as_value(), node_inputs[2].as_value(), node_inputs[3].as_value()) + else { + return; + }; + + let radius = |i: i32| -> f64 { if i.abs() % 2 == 0 { outer } else { inner } }; + let viewport_position = |i: i32, radius: f64| -> DVec2 { + let angle = i as f64 * PI / n as f64; + + viewport.transform_point2(DVec2 { + x: radius * angle.sin(), + y: -radius * angle.cos(), + }) + }; + + match snapping_index { + //Make a triangle with previous two points + 0 => { + let outer_radius: f64 = radius(self.point as i32 - 1); + let outer_position = viewport_position(self.point as i32 - 1, outer_radius); + + let before_outer_radius = radius(self.point as i32 - 2); + let before_outer_position = viewport_position(self.point as i32 - 2, before_outer_radius); + + let point_radius = radius(self.point as i32); + let point_position = viewport_position(self.point as i32, point_radius); + + overlay_context.line(before_outer_position, outer_position, Some(COLOR_OVERLAY_RED), Some(3.)); + overlay_context.line(outer_position, point_position, Some(COLOR_OVERLAY_RED), Some(3.)); + + let l1 = (before_outer_position - outer_position).length() * 0.2; + let Some(l1_direction) = (before_outer_position - outer_position).try_normalize() else { return }; + let l1_angle = -l1_direction.angle_to(DVec2::X); + // overlay_context.draw_angle(outer_position, l1, f64::MAX, l1_angle, FRAC_PI_2); + + let l2 = (point_position - outer_position).length() * 0.2; + let Some(l2_direction) = (point_position - outer_position).try_normalize() else { return }; + let l2_angle = -l2_direction.angle_to(DVec2::X); + + let net_angle = (l2_angle + l1_angle) / 2.; + + let new_point = SQRT_2 * l1 * DVec2::from_angle(net_angle) + outer_position; + + let before_outer_position = l1 * l1_direction + outer_position; + let point_position = l1 * l2_direction + outer_position; + + overlay_context.line(before_outer_position, new_point, Some(COLOR_OVERLAY_RED), Some(3.)); + overlay_context.line(new_point, point_position, Some(COLOR_OVERLAY_RED), Some(3.)); + } + 1 => { + let before_outer_radius = radius(self.point as i32 - 1); + let before_outer_position = viewport_position(self.point as i32 - 1, before_outer_radius); + + let after_point_radius = radius(self.point as i32 + 1); + let after_point_position = viewport_position(self.point as i32 + 1, after_point_radius); + + let point_radius = radius(self.point as i32); + let point_position = viewport_position(self.point as i32, point_radius); + + overlay_context.line(before_outer_position, point_position, Some(COLOR_OVERLAY_RED), Some(3.)); + overlay_context.line(point_position, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.)); + + let l1 = (before_outer_position - point_position).length() * 0.2; + let Some(l1_direction) = (before_outer_position - point_position).try_normalize() else { return }; + let l1_angle = -l1_direction.angle_to(DVec2::X); + + let l2 = (after_point_position - point_position).length() * 0.2; + let Some(l2_direction) = (after_point_position - point_position).try_normalize() else { return }; + let l2_angle = -l2_direction.angle_to(DVec2::X); + + let net_angle = (l2_angle + l1_angle) / 2.; + + let new_point = SQRT_2 * l1 * DVec2::from_angle(net_angle) + point_position; + + let before_outer_position = l1 * l1_direction + point_position; + let after_point_position = l1 * l2_direction + point_position; + + overlay_context.line(before_outer_position, new_point, Some(COLOR_OVERLAY_RED), Some(3.)); + overlay_context.line(new_point, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.)); + } + i => { + if i % 2 != 0 { + // flipped case + let point_radius = radius(self.point as i32); + let point_position = viewport_position(self.point as i32, point_radius); + + let target_index = *i as i32; + let target_point_radius = radius(target_index); + let target_point_position = viewport_position(target_index, target_point_radius); + + let mirrored = viewport_position(-target_index + 2, target_point_radius); + + overlay_context.line(point_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.)); + overlay_context.line(point_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.)); + } else { + let outer_radius = radius(self.point as i32 - 1); + let outer_position = viewport_position(self.point as i32 - 1, outer_radius); + + let target_index = self.point as i32 + *i as i32 - 1; + let target_point_radius = radius(target_index); + let target_point_position = viewport_position(target_index, target_point_radius); + + let mirrored = viewport_position(-target_index, target_point_radius); + + overlay_context.line(outer_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.)); + overlay_context.line(outer_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.)); + } + } + } + // 0,1 90 + } + } + } + fn calculate_snap_radii(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, index: usize) -> Vec { + let mut snap_radii = Vec::new(); + + let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { + return snap_radii; + }; + + let other_index = if index == 3 { 2 } else { 3 }; + + let Some(&TaggedValue::F64(other_radius)) = node_inputs[other_index].as_value() else { + return snap_radii; + }; + + let Some(&TaggedValue::U32(n)) = node_inputs[1].as_value() else { + return snap_radii; + }; + + // inner radius for 90 + let b = FRAC_PI_4 * (3.) - (PI / n as f64); + let angle = b.sin(); + let required_radius = (other_radius / angle) * (FRAC_1_SQRT_2); + + snap_radii.push(required_radius); + + // also push the case when the when it length increases more than the other + + let flipped = other_radius * angle * SQRT_2; + + snap_radii.push(flipped); + + for i in 1..n { + let n = n as f64; + let i = i as f64; + let denominator = 2. * (PI * (i - 1.) / n).cos() * (PI * i / n).sin(); + let numerator = (2. * PI * i / n).sin(); + let factor = numerator / denominator; + + if factor < 0. { + break; + } + + if (other_radius * factor) > 1e-6 { + snap_radii.push(other_radius * factor); + } + + snap_radii.push(other_radius * 1. / factor); + } + + snap_radii + } + + fn check_snapping(&self, new_radius: f64, original_radius: f64) -> Option<(usize, f64)> { + self.snap_radii + .iter() + .enumerate() + .filter(|(_, rad)| (**rad - new_radius).abs() < POINT_RADIUS_HANDLE_SNAP_THRESHOLD) + .min_by(|(_, a), (_, b)| (**a - new_radius).abs().partial_cmp(&(**b - new_radius).abs()).unwrap_or(std::cmp::Ordering::Equal)) + .map(|(i, rad)| (i, *rad - original_radius)) + } +} + +#[derive(Clone, Debug, Default)] +pub struct StarShapeData { + pub point_radius_handle: PointRadiusHandle, + pub number_of_points_handle: NumberOfPointsHandle, +} + +impl StarShapeData { + pub fn star_gizmos(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, mouse_position: DVec2, overlay_context: &mut OverlayContext) { + self.point_radius_handle.overlays(document, input, mouse_position, overlay_context); + self.number_of_points_handle.overlays(document, input, mouse_position, overlay_context); + } + + pub fn set_point_radius_handle(&mut self, document: &DocumentMessageHandler, mouse_pos: DVec2) -> Option { + if let Some((layer, point, index, initial_radii)) = points_on_inner_circle(document, mouse_pos) { + let snap_radii = PointRadiusHandle::calculate_snap_radii(document, layer, index); + self.point_radius_handle = PointRadiusHandle { + layer, + point, + index, + snap_radii, + initial_radii, + handle_state: PointRadiusHandleState::Dragging, + }; + return Some(layer); + } + None + } + + pub fn update_inner_radius( + &mut self, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + layer: LayerNodeIdentifier, + responses: &mut VecDeque, + drag_start: DVec2, + ) { + let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface) else { + return; + }; + + let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { + return; + }; + + let path = vector_data.stroke_bezier_paths().next().unwrap(); + let center = path.length_centroid(None, true).unwrap(); + let transform = document.network_interface.document_metadata().transform_to_viewport(layer); + let index = self.point_radius_handle.index; + + let original_radius = self.point_radius_handle.initial_radii; + + let delta = input.mouse.position - document.metadata().document_to_viewport.transform_point2(drag_start); + let radius = document.metadata().document_to_viewport.transform_point2(drag_start) - transform.transform_point2(center); + let projection = delta.project_onto(radius); + let sign = radius.dot(delta).signum(); + + let mut net_delta = projection.length() * sign; + let new_radius = original_radius + net_delta; + + self.point_radius_handle.update_state(PointRadiusHandleState::Dragging); + if let Some((index, snapped_delta)) = self.point_radius_handle.check_snapping(new_radius, original_radius) { + net_delta = snapped_delta; + self.point_radius_handle.update_state(PointRadiusHandleState::Snapped(index)); + } + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, index), + input: NodeInput::value(TaggedValue::F64(original_radius + net_delta), false), + }); + responses.add(NodeGraphMessage::RunDocumentGraph); + } +} + +impl Star { + pub fn create_node(vertices: u32) -> NodeTemplate { + let node_type = resolve_document_node_type("Star").expect(" Star node does not exist"); + node_type.node_template_input_override([ + None, + Some(NodeInput::value(TaggedValue::U32(vertices), false)), + Some(NodeInput::value(TaggedValue::F64(0.5), false)), + Some(NodeInput::value(TaggedValue::F64(0.25), false)), + ]) + } + + pub fn update_shape( + document: &DocumentMessageHandler, + ipp: &InputPreprocessorMessageHandler, + layer: LayerNodeIdentifier, + shape_tool_data: &mut ShapeToolData, + modifier: ShapeToolModifierKey, + responses: &mut VecDeque, + ) -> bool { + let (center, lock_ratio) = (modifier[0], modifier[1]); + if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) { + // TODO: We need to determine how to allow the polygon node to make irregular shapes + update_radius_sign(end, start, layer, document, responses); + + let dimensions = (start - end).abs(); + let mut scale = DVec2::ONE; + let radius: f64; + + // We keep the smaller dimension's scale at 1 and scale the other dimension accordingly + if dimensions.x > dimensions.y { + scale.x = dimensions.x / dimensions.y; + radius = dimensions.y / 2.; + } else { + scale.y = dimensions.y / dimensions.x; + radius = dimensions.x / 2.; + } + + let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface) else { + return false; + }; + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 2), + input: NodeInput::value(TaggedValue::F64(radius), false), + }); + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 3), + input: NodeInput::value(TaggedValue::F64(radius / 2.), false), + }); + + responses.add(GraphOperationMessage::TransformSet { + layer, + transform: DAffine2::from_scale_angle_translation(scale, 0., (start + end) / 2.), + transform_in: TransformIn::Viewport, + skip_rerender: false, + }); + } + false + } +} diff --git a/editor/src/messages/tool/mod.rs b/editor/src/messages/tool/mod.rs index f2c28f27dc..ca03f01e8b 100644 --- a/editor/src/messages/tool/mod.rs +++ b/editor/src/messages/tool/mod.rs @@ -2,7 +2,6 @@ mod tool_message; mod tool_message_handler; pub mod common_functionality; -pub mod shapes; pub mod tool_messages; pub mod transform_layer; pub mod utility_types; diff --git a/editor/src/messages/tool/shapes/star_shape.rs b/editor/src/messages/tool/shapes/star_shape.rs deleted file mode 100644 index b9267514d6..0000000000 --- a/editor/src/messages/tool/shapes/star_shape.rs +++ /dev/null @@ -1,258 +0,0 @@ -use super::line_shape::NodeGraphLayer; -use super::shape_utility::{ShapeToolModifierKey, update_radius_sign}; -use super::*; -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::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::tool::common_functionality::graph_modification_utils; -use crate::messages::tool::tool_messages::tool_prelude::*; -use glam::DAffine2; -use graph_craft::document::NodeInput; -use graph_craft::document::value::TaggedValue; -use graphene_std::vector::PointId; -use std::collections::VecDeque; - -#[derive(Default)] -pub struct Star; - -#[derive(Clone, Debug, Default)] -pub struct PointRadiusHandle { - pub center: DVec2, - pub vertex: Option, - pub index: usize, -} - -#[derive(Clone, Debug, Default)] -pub struct StarShapeData { - pub point_radius_handle: PointRadiusHandle, -} - -impl Star { - pub fn create_node(vertices: u32) -> NodeTemplate { - let node_type = resolve_document_node_type("Star").expect(" Star node does not exist"); - node_type.node_template_input_override([ - None, - Some(NodeInput::value(TaggedValue::U32(vertices), false)), - Some(NodeInput::value(TaggedValue::F64(0.5), false)), - Some(NodeInput::value(TaggedValue::F64(0.25), false)), - ]) - } - - pub fn set_point_radius_handle(document: &DocumentMessageHandler, mouse_pos: DVec2, shape_tool_data: &mut ShapeToolData) -> bool { - if let Some((layer, (center, _, vertex, index))) = Self::points_on_inner_circle(document, mouse_pos).iter().next() { - shape_tool_data.data.layer = Some(*layer); - shape_tool_data.star_data.point_radius_handle = PointRadiusHandle { - center: *center, - vertex: Some(*vertex), - index: *index, - }; - return true; - } - false - } - - pub fn inner_gizmo_overlays(document: &DocumentMessageHandler, shape_tool_data: &mut ShapeToolData, overlay_context: &mut OverlayContext) { - let PointRadiusHandle { center, vertex, .. } = shape_tool_data.star_data.point_radius_handle; - let layer = shape_tool_data.data.layer.unwrap(); - let vector_data = document.network_interface.compute_modified_vector(layer).unwrap(); - let viewport = document.metadata().transform_to_viewport(layer); - let vertex_pos = vector_data.point_domain.position_from_id(vertex.unwrap()).unwrap(); - Self::draw_point_radius_overlay(center, vertex_pos, viewport, overlay_context); - } - - fn draw_point_radius_overlay(center: DVec2, vertex_pos: DVec2, transform: DAffine2, overlay_context: &mut OverlayContext) { - let viewport_center = transform.transform_point2(center); - let viewport_vertex = transform.transform_point2(vertex_pos); - let extension_length = (viewport_vertex - viewport_center).length() * 0.5; - let extension = (viewport_vertex - viewport_center).normalize() * extension_length; - - overlay_context.line(viewport_center, viewport_vertex + extension, None, None); - overlay_context.manipulator_handle(viewport_vertex, true, None); - } - // when hovered - pub fn hover_point_radius_handle(document: &DocumentMessageHandler, mouse_pos: DVec2, overlay_context: &mut OverlayContext) -> bool { - for (layer, (center, vertex_pos, _, _)) in Self::points_on_inner_circle(document, mouse_pos) { - let transform = document.metadata().transform_to_viewport(layer); - Self::draw_point_radius_overlay(center, vertex_pos, transform, overlay_context); - return true; - } - - for layer in document - .network_interface - .selected_nodes() - .selected_visible_and_unlocked_layers(&document.network_interface) - .filter(|layer| graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some()) - { - let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { - return false; - }; - - for (_, anchor_positions) in vector_data.point_domain.position_ids() { - let transform = document.metadata().transform_to_viewport(layer); - overlay_context.manipulator_handle(transform.transform_point2(*anchor_positions), false, None); - } - - return false; - } - false - } - - pub fn points_on_inner_circle(document: &DocumentMessageHandler, mouse_pos: DVec2) -> HashMap { - let mut result = HashMap::new(); - - for layer in document - .network_interface - .selected_nodes() - .selected_visible_and_unlocked_layers(&document.network_interface) - .filter(|layer| graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some()) - { - let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { - return result; - }; - - let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { - return result; - }; - - let transform = document.network_interface.document_metadata().transform_to_viewport(layer); - let center = DVec2::ZERO; - - let (Some(&TaggedValue::F64(outer)), Some(&TaggedValue::F64(inner))) = (node_inputs[2].as_value(), node_inputs[3].as_value()) else { - return result; - }; - - let mut index = 0; - - let inner_point = vector_data.point_domain.position_ids().find(|(_, pos)| { - let transformed = transform.transform_point2(**pos); - if transformed.distance(mouse_pos) >= 5.0 { - return false; - } - - let dist = pos.distance(center); - if (dist - inner).abs() < 1e-6 { - index = 3; - - true - } else if (dist - outer).abs() < 1e-6 { - index = 2; - log::info!("dist to outer {:?}", (dist - outer).abs()); - - true - } else { - false - } - }); - - // Only insert if we found an inner point - if let Some((point_id, vertex_pos)) = inner_point { - result.insert(layer, (center, *vertex_pos, point_id, index)); - break; - } - } - - result - } - - pub fn update_shape( - document: &DocumentMessageHandler, - ipp: &InputPreprocessorMessageHandler, - layer: LayerNodeIdentifier, - shape_tool_data: &mut ShapeToolData, - modifier: ShapeToolModifierKey, - responses: &mut VecDeque, - ) -> bool { - let (center, lock_ratio) = (modifier[0], modifier[1]); - if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) { - // TODO: We need to determine how to allow the polygon node to make irregular shapes - update_radius_sign(end, start, layer, document, responses); - - let dimensions = (start - end).abs(); - let mut scale = DVec2::ONE; - let radius: f64; - - // We keep the smaller dimension's scale at 1 and scale the other dimension accordingly - if dimensions.x > dimensions.y { - scale.x = dimensions.x / dimensions.y; - radius = dimensions.y / 2.; - } else { - scale.y = dimensions.y / dimensions.x; - radius = dimensions.x / 2.; - } - - let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface) else { - return false; - }; - - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 2), - input: NodeInput::value(TaggedValue::F64(radius), false), - }); - - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 3), - input: NodeInput::value(TaggedValue::F64(radius / 2.), false), - }); - - responses.add(GraphOperationMessage::TransformSet { - layer, - transform: DAffine2::from_scale_angle_translation(scale, 0., (start + end) / 2.), - transform_in: TransformIn::Viewport, - skip_rerender: false, - }); - } - false - } - - pub fn update_inner_radius( - document: &DocumentMessageHandler, - input: &InputPreprocessorMessageHandler, - layer: LayerNodeIdentifier, - responses: &mut VecDeque, - shape_tool_data: &mut ShapeToolData, - ) { - let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface) else { - return; - }; - - let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { - return; - }; - - let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { - return; - }; - - let path = vector_data.stroke_bezier_paths().next().unwrap(); - let center = path.length_centroid(None, true).unwrap(); - let transform = document.network_interface.document_metadata().transform_to_viewport(layer); - let index = shape_tool_data.star_data.point_radius_handle.index; - - // inner radiust - let Some(&TaggedValue::F64(required_radius)) = node_inputs[index].as_value() else { - return; - }; - - // update_radius_sign(start, end, layer, document, responses); - - let delta = input.mouse.position - shape_tool_data.last_mouse_position; - let radius = document.metadata().document_to_viewport.transform_point2(shape_tool_data.data.drag_start) - transform.transform_point2(center); - let projection = delta.project_onto(radius); - let sign = radius.dot(delta).signum(); - - let net_delta = projection.length() * sign; - shape_tool_data.last_mouse_position = input.mouse.position; - - // overlay_context.line(transform.transform_point2(center), transform.transform_point2(inner + net_delta), None, None); - - // We keep the smaller dimension's scale at 1 and scale the other dimension accordingly - - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, index), - input: NodeInput::value(TaggedValue::F64(required_radius + net_delta), false), - }); - responses.add(NodeGraphMessage::RunDocumentGraph); - } -} diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index 0f1d1705fe..fe6e10db04 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -1,5 +1,5 @@ use super::common_functionality::shape_editor::ShapeState; -use super::shapes::shape_utility::ShapeType::{self, Ellipse, Line, Rectangle}; +use super::common_functionality::shapes::shape_utility::ShapeType::{self, Ellipse, Line, Rectangle}; use super::utility_types::{ToolActionHandlerData, ToolFsmState, tool_message_to_tool_type}; use crate::application::generate_uuid; use crate::messages::layout::utility_types::widget_prelude::*; diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 2862719264..6dfba32bc1 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -7,14 +7,14 @@ use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::graph_modification_utils::{self}; use crate::messages::tool::common_functionality::resize::Resize; +use crate::messages::tool::common_functionality::shapes::convex_shape::Convex; +use crate::messages::tool::common_functionality::shapes::line_shape::{LineToolData, clicked_on_line_endpoints}; +use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays}; +use crate::messages::tool::common_functionality::shapes::star_shape::{Star, StarShapeData}; +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}; use crate::messages::tool::common_functionality::utility_functions::{closest_point, resize_bounds, rotate_bounds, skew_bounds, transforming_transform_cage}; -use crate::messages::tool::shapes::convex_shape::Convex; -use crate::messages::tool::shapes::line_shape::{LineToolData, clicked_on_line_endpoints}; -use crate::messages::tool::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays}; -use crate::messages::tool::shapes::star_shape::{Star, StarShapeData}; -use crate::messages::tool::shapes::{Ellipse, Line, Rectangle}; use graph_craft::document::NodeId; use graphene_core::Color; use graphene_std::renderer::Quad; @@ -322,7 +322,7 @@ impl Fsm for ShapeToolFsmState { }; match (self, event) { (_, ShapeToolMessage::Overlays(mut overlay_context)) => { - let mouse_pos = tool_data + let mouse_position = tool_data .data .snap_manager .indicator_pos() @@ -335,8 +335,8 @@ impl Fsm for ShapeToolFsmState { tool_data.data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); } - if matches!(self, ShapeToolFsmState::DraggingStarInnerRadius) { - Star::inner_gizmo_overlays(document, tool_data, &mut overlay_context); + if matches!(self, ShapeToolFsmState::DraggingStarInnerRadius | Self::Ready(_)) && !input.keyboard.key(Key::Control) { + tool_data.star_data.star_gizmos(document, input, mouse_position, &mut overlay_context); } if input.keyboard.key(Key::Control) && matches!(self, ShapeToolFsmState::Ready(_)) { @@ -344,11 +344,6 @@ impl Fsm for ShapeToolFsmState { } else if matches!(self, ShapeToolFsmState::Ready(_)) { Line::overlays(document, tool_data, &mut overlay_context); - // if hovered over vertices of the star show the gizmos - if Star::hover_point_radius_handle(document, mouse_pos, &mut overlay_context) { - return self; - } - if document .network_interface .selected_nodes() @@ -397,9 +392,13 @@ impl Fsm for ShapeToolFsmState { // Check if dragging the inner vertices of a star - if Star::set_point_radius_handle(document, mouse_pos, tool_data) { + if let Some(layer) = tool_data.star_data.set_point_radius_handle(document, mouse_pos) { + tool_data.data.layer = Some(layer); tool_data.last_mouse_position = mouse_pos; + + // Always store it in document space tool_data.data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(mouse_pos); + responses.add(DocumentMessage::StartTransaction); return ShapeToolFsmState::DraggingStarInnerRadius; } @@ -517,7 +516,8 @@ impl Fsm for ShapeToolFsmState { } (ShapeToolFsmState::DraggingStarInnerRadius, ShapeToolMessage::PointerMove(..)) => { if let Some(layer) = tool_data.data.layer { - Star::update_inner_radius(document, input, layer, responses, tool_data); + tool_data.star_data.update_inner_radius(document, input, layer, responses, tool_data.data.drag_start); + tool_data.last_mouse_position = input.mouse.position; } responses.add(OverlaysMessage::Draw); @@ -578,13 +578,6 @@ impl Fsm for ShapeToolFsmState { } (_, ShapeToolMessage::PointerMove { .. }) => { - // Snapped position in viewport-space. - let mouse_pos = tool_data - .data - .snap_manager - .indicator_pos() - .map(|pos| document.metadata().document_to_viewport.transform_point2(pos)) - .unwrap_or(input.mouse.position); let dragging_bounds = tool_data .bounding_box_manager .as_mut() @@ -596,20 +589,12 @@ impl Fsm for ShapeToolFsmState { .as_ref() .map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge))); - // // Dragging the pivot overrules the other operations - // if tool_data.pivot.is_over(input.mouse.position) { - // cursor = MouseCursorIcon::Move; - // } - - // Generate the hover outline - let inner_gizmo_points = Star::points_on_inner_circle(document, mouse_pos).is_empty(); - - if tool_data.cursor != cursor && !input.keyboard.key(Key::Control) && inner_gizmo_points { + if tool_data.cursor != cursor && !input.keyboard.key(Key::Control) && !tool_data.star_data.point_radius_handle.is_hovered() { tool_data.cursor = cursor; responses.add(FrontendMessage::UpdateMouseCursor { cursor }); } - if !inner_gizmo_points { + if !tool_data.star_data.point_radius_handle.snap_radii.is_empty() { responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); } @@ -646,6 +631,8 @@ impl Fsm for ShapeToolFsmState { input.mouse.finish_transaction(tool_data.data.drag_start, responses); tool_data.data.cleanup(responses); + tool_data.star_data.point_radius_handle.cleanup(); + if let Some(bounds) = &mut tool_data.bounding_box_manager { bounds.original_transforms.clear(); } diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index bad1df0785..ce033578e3 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -139,6 +139,7 @@ impl MessageHandler> for TransformLayer let using_path_tool = tool_data.active_tool_type == ToolType::Path; let using_select_tool = tool_data.active_tool_type == ToolType::Select; let using_pen_tool = tool_data.active_tool_type == ToolType::Pen; + let using_shape_tool = tool_data.active_tool_type == ToolType::Shape; // TODO: Add support for transforming layer not in the document network let selected_layers = document @@ -390,7 +391,7 @@ impl MessageHandler> for TransformLayer TransformLayerMessage::BeginGRS { transform_type } => { let selected_points: Vec<&ManipulatorPointId> = shape_editor.selected_points().collect(); if (using_path_tool && selected_points.is_empty()) - || (!using_path_tool && !using_select_tool && !using_pen_tool) + || (!using_path_tool && !using_select_tool && !using_pen_tool && !using_shape_tool) || selected_layers.is_empty() || transform_type.equivalent_to(self.transform_operation) { diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index dbdf30fa2a..a66231eb26 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -11,7 +11,7 @@ use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::overlays::utility_types::OverlayProvider; use crate::messages::preferences::PreferencesMessageHandler; use crate::messages::prelude::*; -use crate::messages::tool::shapes::shape_utility::ShapeType; +use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeType; use crate::node_graph_executor::NodeGraphExecutor; use graphene_core::raster::color::Color; use graphene_core::text::FontCache; From efe5a25f96e93cf16f8b75268f37b2beeb5f9418 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Sun, 15 Jun 2025 20:36:41 +0530 Subject: [PATCH 11/16] implement snapping for point-handle gizmo and implement no of point gizmo need to refactor --- Cargo.lock | 1 + editor/Cargo.toml | 1 + editor/src/consts.rs | 4 + .../shapes/shape_utility.rs | 49 ++- .../common_functionality/shapes/star_shape.rs | 369 +++++++++++++++--- .../messages/tool/tool_messages/shape_tool.rs | 68 +++- 6 files changed, 391 insertions(+), 101 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c347cf8f7..4d7e09afc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2382,6 +2382,7 @@ dependencies = [ "image", "interpreted-executor", "js-sys", + "kurbo", "log", "num_enum", "once_cell", diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 2191161bcb..e951c079c0 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -47,6 +47,7 @@ serde_json = { workspace = true } bezier-rs = { workspace = true } futures = { workspace = true } glam = { workspace = true, features = ["serde", "debug-glam-assert"] } +kurbo = { workspace = true } derivative = { workspace = true } specta = { workspace = true } image = { workspace = true, features = ["bmp", "png"] } diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 06440f5ddd..a9194e5f28 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -119,6 +119,10 @@ pub const DEFAULT_BRUSH_SIZE: f64 = 20.; // STAR GIZMOS pub const POINT_RADIUS_HANDLE_SNAP_THRESHOLD: f64 = 8.; +pub const POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD: f64 = 7.9; +pub const NUMBER_OF_POINTS_HANDLE_SPOKE_EXTENSION: f64 = 1.2; +pub const NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH: f64 = 10.; +pub const GIZMO_HIDE_THRESHOLD: f64 = 20.; // SCROLLBARS pub const SCROLLBAR_SPACING: f64 = 0.1; 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 2c21ed98a4..9e6aa019e7 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -7,9 +7,11 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{self use crate::messages::tool::common_functionality::transformation_cage::BoundingBoxManager; use crate::messages::tool::tool_messages::tool_prelude::Key; use crate::messages::tool::utility_types::*; +use bezier_rs::Subpath; use glam::{DMat2, DVec2}; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; +use graphene_std::vector::PointId; use std::collections::VecDeque; use std::f64::consts::PI; @@ -151,36 +153,31 @@ pub fn anchor_overlays(document: &DocumentMessageHandler, overlay_context: &mut } } -pub fn points_on_inner_circle(document: &DocumentMessageHandler, mouse_position: DVec2) -> Option<(LayerNodeIdentifier, u32, usize, f64)> { - for layer in document - .network_interface - .selected_nodes() - .selected_visible_and_unlocked_layers(&document.network_interface) - .filter(|layer| graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some()) - { - let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { - continue; - }; +pub fn star_outline(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { + let mut anchors = Vec::new(); - let viewport = document.network_interface.document_metadata().transform_to_viewport(layer); + let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { + return; + }; - let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(outer)), Some(&TaggedValue::F64(inner))) = (node_inputs[1].as_value(), node_inputs[2].as_value(), node_inputs[3].as_value()) else { - continue; - }; + let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(radius1)), Some(&TaggedValue::F64(radius2))) = (node_inputs[1].as_value(), node_inputs[2].as_value(), node_inputs[3].as_value()) else { + return; + }; - for i in 0..(2 * n) { - let angle = i as f64 * PI / n as f64; - let (radius, index) = if i % 2 == 0 { (outer, 2) } else { (inner, 3) }; + let viewport = document.metadata().transform_to_viewport(layer); + for i in 0..(2 * n) { + let angle = i as f64 * PI / n as f64; + let radius = if i % 2 == 0 { radius1 } else { radius2 }; - let point = viewport.transform_point2(DVec2 { - x: radius * angle.sin(), - y: -radius * angle.cos(), - }); + let point = DVec2 { + x: radius * angle.sin(), + y: -radius * angle.cos(), + }; - if point.distance(mouse_position) < 5.0 { - return Some((layer, i, index, radius)); - }; - } + anchors.push(point); } - None + + let subpath: Vec> = vec![Subpath::from_anchors_linear(anchors, true)]; + + overlay_context.outline(subpath.iter(), viewport, None); } diff --git a/editor/src/messages/tool/common_functionality/shapes/star_shape.rs b/editor/src/messages/tool/common_functionality/shapes/star_shape.rs index c4a524e1db..04ab736aaa 100644 --- a/editor/src/messages/tool/common_functionality/shapes/star_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/star_shape.rs @@ -1,28 +1,33 @@ use super::line_shape::NodeGraphLayer; use super::shape_utility::{ShapeToolModifierKey, update_radius_sign}; use super::*; -use crate::consts::{COLOR_OVERLAY_RED, POINT_RADIUS_HANDLE_SNAP_THRESHOLD}; +use crate::consts::{ + COLOR_OVERLAY_RED, GIZMO_HIDE_THRESHOLD, NUMBER_OF_POINTS_HANDLE_SPOKE_EXTENSION, NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD, POINT_RADIUS_HANDLE_SNAP_THRESHOLD, +}; 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::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::tool::common_functionality::graph_modification_utils; -use crate::messages::tool::common_functionality::shapes::shape_utility::points_on_inner_circle; +use crate::messages::tool::common_functionality::shape_editor::ShapeState; +use crate::messages::tool::common_functionality::shapes::shape_utility::star_outline; use crate::messages::tool::tool_messages::tool_prelude::*; use core::f64; use glam::DAffine2; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; +use graphene_std::vector::misc::dvec2_to_point; +use kurbo::{BezPath, PathEl, Shape}; use std::collections::VecDeque; -use std::f64::consts::FRAC_PI_4; use std::f64::consts::{FRAC_1_SQRT_2, PI, SQRT_2}; +use std::f64::consts::{FRAC_PI_4, TAU}; #[derive(Default)] pub struct Star; #[derive(Clone, Debug, Default, PartialEq)] -enum PointRadiusHandleState { +pub enum PointRadiusHandleState { #[default] Inactive, Hover, @@ -32,52 +37,281 @@ enum PointRadiusHandleState { #[derive(Clone, Debug, Default, PartialEq)] pub struct PointRadiusHandle { - layer: LayerNodeIdentifier, - pub point: u32, - pub index: usize, - pub snap_radii: Vec, - initial_radii: f64, + pub layer: Option, + point: u32, + radius_index: usize, + snap_radii: Vec, + initial_radius: f64, handle_state: PointRadiusHandleState, } -#[derive(Clone, Debug, Default)] -enum NumberOfPointsHandleState { +#[derive(Clone, Debug, Default, PartialEq)] +pub enum NumberOfPointsHandleState { #[default] Inactive, + Hover, Dragging, } #[derive(Clone, Debug, Default)] pub struct NumberOfPointsHandle { + layer: Option, initial_points: u32, - handle_state: NumberOfPointsHandleState, + pub handle_state: NumberOfPointsHandleState, } impl NumberOfPointsHandle { - fn overlays(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, mouse_position: DVec2, overlay_context: &mut OverlayContext) {} - fn update_state(&mut self, state: NumberOfPointsHandleState) { + pub fn cleanup(&mut self) { + self.handle_state = NumberOfPointsHandleState::Inactive; + self.layer = None; + } + pub fn update_state(&mut self, state: NumberOfPointsHandleState) { self.handle_state = state; } + + pub fn is_hovering(&self) -> bool { + self.handle_state == NumberOfPointsHandleState::Hover + } + + pub fn is_dragging(&self) -> bool { + self.handle_state == NumberOfPointsHandleState::Dragging + } + + fn overlays( + &mut self, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + shape_editor: &mut &mut ShapeState, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + responses: &mut VecDeque, + ) { + if input.keyboard.key(Key::Control) { + return; + } + + match &self.handle_state { + NumberOfPointsHandleState::Inactive => { + for layer in document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter(|layer| graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some()) + { + let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { + return; + }; + + let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(radius1)), Some(&TaggedValue::F64(radius2))) = + (node_inputs[1].as_value(), node_inputs[2].as_value(), node_inputs[3].as_value()) + else { + return; + }; + + let viewport = document.metadata().transform_to_viewport(layer); + let center = viewport.transform_point2(DVec2::ZERO); + + let radius = radius1.max(radius2); + + if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, mouse_position, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD) { + if closest_segment.layer() == layer { + return; + } + } + + let angle: f64 = 0.; + let point = viewport.transform_point2(DVec2 { + x: radius1 * angle.sin(), + y: -radius1 * angle.cos(), + }); + + if Self::inside_star(viewport, n, radius1, radius2, mouse_position) { + self.draw_spokes(center, viewport, n, radius, overlay_context); + if mouse_position.distance(center) < NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH && point.distance(center) > GIZMO_HIDE_THRESHOLD { + self.layer = Some(layer); + self.initial_points = n; + self.update_state(NumberOfPointsHandleState::Hover); + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize }); + } + + return; + } + } + } + NumberOfPointsHandleState::Hover | NumberOfPointsHandleState::Dragging => { + let Some(layer) = self.layer else { return }; + let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { + return; + }; + + let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(outer)), Some(&TaggedValue::F64(inner))) = (node_inputs[1].as_value(), node_inputs[2].as_value(), node_inputs[3].as_value()) + else { + return; + }; + + let viewport = document.metadata().transform_to_viewport(layer); + let center = viewport.transform_point2(DVec2::ZERO); + + let radius = outer.max(inner); + + if mouse_position.distance(center) > NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH && matches!(&self.handle_state, NumberOfPointsHandleState::Hover) { + self.update_state(NumberOfPointsHandleState::Inactive); + self.layer = None; + self.draw_spokes(center, viewport, n, radius, overlay_context); + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); + + return; + } + self.draw_spokes(center, viewport, n, radius, overlay_context); + } + } + } + + fn inside_star(viewport: DAffine2, n: u32, radius1: f64, radius2: f64, mouse_position: DVec2) -> bool { + let mut paths = Vec::new(); + + for i in 0..(2 * n) { + let angle = i as f64 * PI / n as f64; + let radius = if i % 2 == 0 { radius1 } else { radius2 }; + + let point = viewport.transform_point2(DVec2 { + x: radius * angle.sin(), + y: -radius * angle.cos(), + }); + + let new_point = dvec2_to_point(point); + + if i == 0 { + paths.push(PathEl::MoveTo(new_point)); + } else { + paths.push(PathEl::LineTo(new_point)); + } + } + + paths.push(PathEl::ClosePath); + + let bez_path = BezPath::from_vec(paths); + let (shape, bbox) = (bez_path.clone(), bez_path.bounding_box()); + + if bbox.x0 > mouse_position.x || bbox.y0 > mouse_position.y || bbox.x1 < mouse_position.x || bbox.y1 < mouse_position.y { + return false; + } + + let winding = shape.winding(dvec2_to_point(mouse_position)); + + // Non-zero fill rule + winding != 0 + } + + fn draw_spokes(&mut self, center: DVec2, viewport: DAffine2, n: u32, radius: f64, overlay_context: &mut OverlayContext) { + for i in 0..n { + let angle = i as f64 * TAU / n as f64; + + let point = viewport.transform_point2(DVec2 { + x: radius * angle.sin(), + y: -radius * angle.cos(), + }); + + let Some(direction) = (point - center).try_normalize() else { continue }; + + // If the user zooms out such that shape is very small hide the gizmo + if point.distance(center) < GIZMO_HIDE_THRESHOLD { + return; + } + + let end_point = direction * NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH; + if matches!(self.handle_state, NumberOfPointsHandleState::Hover | NumberOfPointsHandleState::Dragging) { + overlay_context.line(center, end_point * NUMBER_OF_POINTS_HANDLE_SPOKE_EXTENSION + center, None, None); + } else { + overlay_context.line(center, end_point + center, None, None); + } + } + } + + pub fn update_no_of_sides(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { + let delta = input.mouse.position - document.metadata().document_to_viewport.transform_point2(drag_start); + let sign = (input.mouse.position.x - document.metadata().document_to_viewport.transform_point2(drag_start).x).signum(); + let net_delta = (delta.length() / 25.).round() * sign; + + let Some(layer) = self.layer else { return }; + + let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface) else { + return; + }; + + let new_point_count = (self.initial_points as i32 + net_delta as i32).max(3); + log::info!("Updated point count: {:?}", new_point_count); + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 1), + input: NodeInput::value(TaggedValue::U32(new_point_count as u32), false), + }); + responses.add(NodeGraphMessage::RunDocumentGraph); + } } impl PointRadiusHandle { pub fn cleanup(&mut self) { self.handle_state = PointRadiusHandleState::Inactive; self.snap_radii.clear(); + self.layer = None; + } + + pub fn is_inactive(&self) -> bool { + self.handle_state == PointRadiusHandleState::Inactive } - pub fn is_hovered(&self) -> bool { + pub fn hovered(&self) -> bool { self.handle_state == PointRadiusHandleState::Hover } - fn update_state(&mut self, state: PointRadiusHandleState) { + pub fn update_state(&mut self, state: PointRadiusHandleState) { self.handle_state = state; } - pub fn overlays(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, mouse_position: DVec2, overlay_context: &mut OverlayContext) { + pub fn overlays( + &mut self, + document: &DocumentMessageHandler, + no_of_point_gizmo: Option, + input: &InputPreprocessorMessageHandler, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ) { match &self.handle_state { PointRadiusHandleState::Inactive => { + if let Some(layer) = no_of_point_gizmo { + let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { + return; + }; + + let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(outer)), Some(&TaggedValue::F64(inner))) = + (node_inputs[1].as_value(), node_inputs[2].as_value(), node_inputs[3].as_value()) + else { + return; + }; + + let viewport = document.metadata().transform_to_viewport(layer); + let center = viewport.transform_point2(DVec2::ZERO); + + for i in 0..(2 * n) { + let angle = i as f64 * PI / n as f64; + let radius = if i % 2 == 0 { outer } else { inner }; + + let point = viewport.transform_point2(DVec2 { + x: radius * angle.sin(), + y: -radius * angle.cos(), + }); + + if point.distance(center) < GIZMO_HIDE_THRESHOLD { + return; + } + + overlay_context.manipulator_handle(point, false, None); + } + return; + } + for layer in document .network_interface .selected_nodes() @@ -108,14 +342,20 @@ impl PointRadiusHandle { let center = viewport.transform_point2(DVec2::ZERO); let viewport_diagonal = input.viewport_bounds.size().length(); + // If the user zooms out such that shape is very small hide the gizmo + if point.distance(center) < GIZMO_HIDE_THRESHOLD { + return; + } + if point.distance(mouse_position) < 5.0 { let Some(direction) = (point - center).try_normalize() else { continue; }; - - self.layer = layer; + self.radius_index = radius_index; + self.layer = Some(layer); self.point = i; self.snap_radii = Self::calculate_snap_radii(document, layer, radius_index); + self.initial_radius = radius; self.update_state(PointRadiusHandleState::Hover); overlay_context.manipulator_handle(point, true, None); overlay_context.line(center, center + direction * viewport_diagonal, None, None); @@ -129,7 +369,8 @@ impl PointRadiusHandle { } PointRadiusHandleState::Dragging | PointRadiusHandleState::Hover => { - let layer = self.layer; + let Some(layer) = self.layer else { return }; + let viewport = document.metadata().transform_to_viewport(layer); let center = viewport.transform_point2(DVec2::ZERO); let viewport_diagonal = input.viewport_bounds.size().length(); @@ -153,6 +394,7 @@ impl PointRadiusHandle { if matches!(&self.handle_state, PointRadiusHandleState::Hover) { if (mouse_position - point).length() > 5. { self.update_state(PointRadiusHandleState::Inactive); + self.layer = None; return; } } @@ -172,25 +414,18 @@ impl PointRadiusHandle { for snapped_radius in &self.snap_radii { let Some(tick_direction) = direction.perp().try_normalize() else { return }; - // let Some(&TaggedValue::F64(radius)) = node_inputs[self.index].as_value() else { return }; - let difference = snapped_radius - self.initial_radii; - log::info!("difference {:?}", difference); let tick_position = viewport.transform_point2(DVec2 { x: snapped_radius * angle.sin(), y: -snapped_radius * angle.cos(), }); - // let tick_position = viewport.transform_point2(initial_point_position + difference * direction); - - // overlay_context.manipulator_handle(tick_position, false, None); - overlay_context.line(tick_position, tick_position + tick_direction * 5., None, Some(2.)); overlay_context.line(tick_position, tick_position - tick_direction * 5., None, Some(2.)); } } PointRadiusHandleState::Snapped(snapping_index) => { - let layer = self.layer; + let Some(layer) = self.layer else { return }; let viewport = document.metadata().transform_to_viewport(layer); let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { @@ -230,9 +465,7 @@ impl PointRadiusHandle { let l1 = (before_outer_position - outer_position).length() * 0.2; let Some(l1_direction) = (before_outer_position - outer_position).try_normalize() else { return }; let l1_angle = -l1_direction.angle_to(DVec2::X); - // overlay_context.draw_angle(outer_position, l1, f64::MAX, l1_angle, FRAC_PI_2); - let l2 = (point_position - outer_position).length() * 0.2; let Some(l2_direction) = (point_position - outer_position).try_normalize() else { return }; let l2_angle = -l2_direction.angle_to(DVec2::X); @@ -263,7 +496,6 @@ impl PointRadiusHandle { let Some(l1_direction) = (before_outer_position - point_position).try_normalize() else { return }; let l1_angle = -l1_direction.angle_to(DVec2::X); - let l2 = (after_point_position - point_position).length() * 0.2; let Some(l2_direction) = (after_point_position - point_position).try_normalize() else { return }; let l2_angle = -l2_direction.angle_to(DVec2::X); @@ -278,28 +510,34 @@ impl PointRadiusHandle { overlay_context.line(new_point, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.)); } i => { + // use 'self.point' as absolute reference as it match the index of vertices of star starting from 0 if i % 2 != 0 { // flipped case let point_radius = radius(self.point as i32); let point_position = viewport_position(self.point as i32, point_radius); - let target_index = *i as i32; + let target_index = (1 - *i as i32).abs() + self.point as i32; let target_point_radius = radius(target_index); let target_point_position = viewport_position(target_index, target_point_radius); - let mirrored = viewport_position(-target_index + 2, target_point_radius); + let mirrored_index = 2 * (self.point as i32) - target_index; + let mirrored = viewport_position(mirrored_index, target_point_radius); overlay_context.line(point_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.)); overlay_context.line(point_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.)); } else { - let outer_radius = radius(self.point as i32 - 1); - let outer_position = viewport_position(self.point as i32 - 1, outer_radius); + let outer_index = self.point as i32 - 1; + let outer_radius = radius(outer_index); + let outer_position = viewport_position(outer_index, outer_radius); + // the vertex which is colinear with the point we are dragging and its previous outer vertex let target_index = self.point as i32 + *i as i32 - 1; let target_point_radius = radius(target_index); let target_point_position = viewport_position(target_index, target_point_radius); - let mirrored = viewport_position(-target_index, target_point_radius); + let mirrored_index = 2 * outer_index - target_index; + + let mirrored = viewport_position(mirrored_index, target_point_radius); overlay_context.line(outer_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.)); overlay_context.line(outer_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.)); @@ -310,14 +548,14 @@ impl PointRadiusHandle { } } } - fn calculate_snap_radii(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, index: usize) -> Vec { + fn calculate_snap_radii(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, radius_index: usize) -> Vec { let mut snap_radii = Vec::new(); let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { return snap_radii; }; - let other_index = if index == 3 { 2 } else { 3 }; + let other_index = if radius_index == 3 { 2 } else { 3 }; let Some(&TaggedValue::F64(other_radius)) = node_inputs[other_index].as_value() else { return snap_radii; @@ -378,25 +616,42 @@ pub struct StarShapeData { } impl StarShapeData { - pub fn star_gizmos(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, mouse_position: DVec2, overlay_context: &mut OverlayContext) { - self.point_radius_handle.overlays(document, input, mouse_position, overlay_context); - self.number_of_points_handle.overlays(document, input, mouse_position, overlay_context); + pub fn star_gizmos( + &mut self, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + shape_editor: &mut &mut ShapeState, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + responses: &mut VecDeque, + ) { + if !self.number_of_points_handle.is_dragging() { + self.point_radius_handle.overlays(document, self.point_radius_handle.layer, input, mouse_position, overlay_context); + } + + if self.point_radius_handle.is_inactive() { + self.number_of_points_handle.overlays(document, input, shape_editor, mouse_position, overlay_context, responses); + } + + self.star_outline_overlays(document, overlay_context); } - pub fn set_point_radius_handle(&mut self, document: &DocumentMessageHandler, mouse_pos: DVec2) -> Option { - if let Some((layer, point, index, initial_radii)) = points_on_inner_circle(document, mouse_pos) { - let snap_radii = PointRadiusHandle::calculate_snap_radii(document, layer, index); - self.point_radius_handle = PointRadiusHandle { - layer, - point, - index, - snap_radii, - initial_radii, - handle_state: PointRadiusHandleState::Dragging, - }; - return Some(layer); + pub fn star_outline_overlays(&self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { + // Prefer explicitly targeted handle layers + if let Some(layer) = self.number_of_points_handle.layer.or(self.point_radius_handle.layer) { + star_outline(layer, document, overlay_context); + return; + } + + // Fallback: apply to all selected visible & unlocked star layers + for layer in document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter(|layer| graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some()) + { + star_outline(layer, document, overlay_context); } - None } pub fn update_inner_radius( @@ -418,9 +673,9 @@ impl StarShapeData { let path = vector_data.stroke_bezier_paths().next().unwrap(); let center = path.length_centroid(None, true).unwrap(); let transform = document.network_interface.document_metadata().transform_to_viewport(layer); - let index = self.point_radius_handle.index; + let radius_index = self.point_radius_handle.radius_index; - let original_radius = self.point_radius_handle.initial_radii; + let original_radius = self.point_radius_handle.initial_radius; let delta = input.mouse.position - document.metadata().document_to_viewport.transform_point2(drag_start); let radius = document.metadata().document_to_viewport.transform_point2(drag_start) - transform.transform_point2(center); @@ -437,7 +692,7 @@ impl StarShapeData { } responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, index), + input_connector: InputConnector::node(node_id, radius_index), input: NodeInput::value(TaggedValue::F64(original_radius + net_delta), false), }); responses.add(NodeGraphMessage::RunDocumentGraph); diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 6dfba32bc1..4e8bcf7b02 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -10,7 +10,7 @@ use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::common_functionality::shapes::convex_shape::Convex; use crate::messages::tool::common_functionality::shapes::line_shape::{LineToolData, clicked_on_line_endpoints}; use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays}; -use crate::messages::tool::common_functionality::shapes::star_shape::{Star, StarShapeData}; +use crate::messages::tool::common_functionality::shapes::star_shape::{NumberOfPointsHandleState, PointRadiusHandleState, Star, StarShapeData}; 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}; @@ -207,6 +207,7 @@ impl<'a> MessageHandler> for ShapeTo | ShapeToolFsmState::DraggingLineEndpoints | ShapeToolFsmState::RotatingBounds | ShapeToolFsmState::DraggingStarInnerRadius + | ShapeToolFsmState::DraggingStarNumberPointHandle | ShapeToolFsmState::SkewingBounds { .. } => { actions!(ShapeToolMessageDiscriminant; DragStop, @@ -247,8 +248,15 @@ impl ToolTransition for ShapeTool { pub enum ShapeToolFsmState { Ready(ShapeType), Drawing(ShapeType), + + // Line Shape Specific DraggingLineEndpoints, + + // Star Shape Specific DraggingStarInnerRadius, + DraggingStarNumberPointHandle, + + // Transform Cage ResizingBounds, RotatingBounds, SkewingBounds { skew: Key }, @@ -312,6 +320,7 @@ impl Fsm for ShapeToolFsmState { global_tool_data, input, preferences, + shape_editor, .. }: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, @@ -331,12 +340,15 @@ impl Fsm for ShapeToolFsmState { let is_resizing_or_rotating = matches!(self, ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::SkewingBounds { .. } | ShapeToolFsmState::RotatingBounds); let dragging_start_gizmos = matches!(self, Self::DraggingStarInnerRadius); - if !is_resizing_or_rotating && !dragging_start_gizmos { - tool_data.data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); + if matches!(self, ShapeToolFsmState::DraggingStarInnerRadius | Self::DraggingStarNumberPointHandle | Self::Ready(_)) && !input.keyboard.key(Key::Control) { + tool_data.star_data.star_gizmos(document, input, shape_editor, mouse_position, &mut overlay_context, responses); } - if matches!(self, ShapeToolFsmState::DraggingStarInnerRadius | Self::Ready(_)) && !input.keyboard.key(Key::Control) { - tool_data.star_data.star_gizmos(document, input, mouse_position, &mut overlay_context); + let hovered = + tool_data.star_data.number_of_points_handle.is_hovering() || tool_data.star_data.number_of_points_handle.is_dragging() || tool_data.star_data.point_radius_handle.hovered(); + + if !is_resizing_or_rotating && !dragging_start_gizmos && !hovered { + tool_data.data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); } if input.keyboard.key(Key::Control) && matches!(self, ShapeToolFsmState::Ready(_)) { @@ -391,10 +403,9 @@ impl Fsm for ShapeToolFsmState { tool_data.line_data.drag_current = mouse_pos; // Check if dragging the inner vertices of a star - - if let Some(layer) = tool_data.star_data.set_point_radius_handle(document, mouse_pos) { - tool_data.data.layer = Some(layer); + if tool_data.star_data.point_radius_handle.hovered() { tool_data.last_mouse_position = mouse_pos; + tool_data.star_data.point_radius_handle.update_state(PointRadiusHandleState::Dragging); // Always store it in document space tool_data.data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(mouse_pos); @@ -403,6 +414,18 @@ impl Fsm for ShapeToolFsmState { return ShapeToolFsmState::DraggingStarInnerRadius; } + // Check if dragging the Number of Points handle of a star + if tool_data.star_data.number_of_points_handle.is_hovering() { + tool_data.last_mouse_position = mouse_pos; + tool_data.star_data.number_of_points_handle.update_state(NumberOfPointsHandleState::Dragging); + + // Always store it in document space + tool_data.data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(mouse_pos); + + responses.add(DocumentMessage::StartTransaction); + return ShapeToolFsmState::DraggingStarNumberPointHandle; + } + // If clicked on endpoints of a selected line, drag its endpoints if let Some((layer, _, _)) = closest_point( document, @@ -464,7 +487,7 @@ impl Fsm for ShapeToolFsmState { ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Convex | ShapeType::Star => { responses.add(GraphOperationMessage::TransformSet { layer, - transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0.0, input.mouse.position), + transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), transform_in: TransformIn::Viewport, skip_rerender: false, }); @@ -472,7 +495,7 @@ impl Fsm for ShapeToolFsmState { tool_options.fill.apply_fill(layer, responses); } ShapeType::Line => { - tool_data.line_data.angle = 0.0; + tool_data.line_data.angle = 0.; tool_data.line_data.weight = tool_options.line_weight; tool_data.line_data.editing_layer = Some(layer); } @@ -515,7 +538,7 @@ impl Fsm for ShapeToolFsmState { self } (ShapeToolFsmState::DraggingStarInnerRadius, ShapeToolMessage::PointerMove(..)) => { - if let Some(layer) = tool_data.data.layer { + if let Some(layer) = tool_data.star_data.point_radius_handle.layer { tool_data.star_data.update_inner_radius(document, input, layer, responses, tool_data.data.drag_start); tool_data.last_mouse_position = input.mouse.position; } @@ -524,6 +547,14 @@ impl Fsm for ShapeToolFsmState { ShapeToolFsmState::DraggingStarInnerRadius } + (ShapeToolFsmState::DraggingStarNumberPointHandle, ShapeToolMessage::PointerMove(..)) => { + tool_data.star_data.number_of_points_handle.update_no_of_sides(document, input, responses, tool_data.data.drag_start); + + tool_data.last_mouse_position = input.mouse.position; + responses.add(OverlaysMessage::Draw); + + ShapeToolFsmState::DraggingStarNumberPointHandle + } (ShapeToolFsmState::ResizingBounds, ShapeToolMessage::PointerMove(modifier)) => { if let Some(bounds) = &mut tool_data.bounding_box_manager { let messages = [ShapeToolMessage::PointerOutsideViewport(modifier.clone()).into(), ShapeToolMessage::PointerMove(modifier).into()]; @@ -589,16 +620,13 @@ impl Fsm for ShapeToolFsmState { .as_ref() .map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge))); - if tool_data.cursor != cursor && !input.keyboard.key(Key::Control) && !tool_data.star_data.point_radius_handle.is_hovered() { + if tool_data.cursor != cursor && !input.keyboard.key(Key::Control) && tool_data.star_data.point_radius_handle.is_inactive() { tool_data.cursor = cursor; responses.add(FrontendMessage::UpdateMouseCursor { cursor }); } - if !tool_data.star_data.point_radius_handle.snap_radii.is_empty() { - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); - } - tool_data.data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); + responses.add(OverlaysMessage::Draw); self } @@ -625,13 +653,15 @@ impl Fsm for ShapeToolFsmState { | ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::RotatingBounds | ShapeToolFsmState::SkewingBounds { .. } - | ShapeToolFsmState::DraggingStarInnerRadius, + | ShapeToolFsmState::DraggingStarInnerRadius + | ShapeToolFsmState::DraggingStarNumberPointHandle, ShapeToolMessage::DragStop, ) => { input.mouse.finish_transaction(tool_data.data.drag_start, responses); tool_data.data.cleanup(responses); tool_data.star_data.point_radius_handle.cleanup(); + tool_data.star_data.number_of_points_handle.cleanup(); if let Some(bounds) = &mut tool_data.bounding_box_manager { bounds.original_transforms.clear(); @@ -639,6 +669,8 @@ impl Fsm for ShapeToolFsmState { tool_data.line_data.dragging_endpoint = None; + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }); + ShapeToolFsmState::Ready(tool_data.current_shape) } (ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::DraggingLineEndpoints, ShapeToolMessage::Abort) => { @@ -722,7 +754,7 @@ impl Fsm for ShapeToolFsmState { HintInfo::keys([Key::Control], "Lock Angle"), ]), ]), - ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::DraggingStarInnerRadius => HintData(vec![ + ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::DraggingStarInnerRadius | ShapeToolFsmState::DraggingStarNumberPointHandle => HintData(vec![ HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), HintGroup(vec![HintInfo::keys([Key::Alt], "From Pivot"), HintInfo::keys([Key::Shift], "Preserve Aspect Ratio")]), ]), From c92169374aaf2091b9dd4b31027a72923e42de22 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Thu, 19 Jun 2025 12:24:08 +0530 Subject: [PATCH 12/16] implemented the gizmo for polygon, added tests , brackets to increase sides --- .../messages/input_mapper/input_mappings.rs | 30 +- .../messages/tool/common_functionality/mod.rs | 1 + .../common_functionality/shape_gizmos/mod.rs | 2 + .../shape_gizmos/number_of_points_handle.rs | 260 +++++++ .../shape_gizmos/point_radius_handle.rs | 502 +++++++++++++ .../shapes/ellipse_shape.rs | 132 +++- .../common_functionality/shapes/line_shape.rs | 188 ++++- .../tool/common_functionality/shapes/mod.rs | 4 +- .../{convex_shape.rs => polygon_shape.rs} | 11 +- .../shapes/rectangle_shape.rs | 5 +- .../shapes/shape_utility.rs | 202 ++++- .../common_functionality/shapes/star_shape.rs | 693 +----------------- .../src/messages/tool/tool_message_handler.rs | 9 +- .../messages/tool/tool_messages/shape_tool.rs | 356 +++++++-- editor/src/messages/tool/utility_types.rs | 5 +- editor/src/test_utils.rs | 4 +- 16 files changed, 1580 insertions(+), 824 deletions(-) create mode 100644 editor/src/messages/tool/common_functionality/shape_gizmos/mod.rs create mode 100644 editor/src/messages/tool/common_functionality/shape_gizmos/number_of_points_handle.rs create mode 100644 editor/src/messages/tool/common_functionality/shape_gizmos/point_radius_handle.rs rename editor/src/messages/tool/common_functionality/shapes/{convex_shape.rs => polygon_shape.rs} (94%) diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index be91df407f..8f1276ae6c 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -176,7 +176,35 @@ pub fn input_mappings() -> Mapping { entry!(KeyUp(MouseLeft); action_dispatch=ShapeToolMessage::DragStop), entry!(KeyDown(MouseRight); action_dispatch=ShapeToolMessage::Abort), entry!(KeyDown(Escape); action_dispatch=ShapeToolMessage::Abort), + entry!(KeyDown(BracketLeft); action_dispatch=ShapeToolMessage::DecreaseSides), + entry!(KeyDown(BracketRight); action_dispatch=ShapeToolMessage::IncreaseSides), entry!(PointerMove; refresh_keys=[Alt, Shift, Control], action_dispatch=ShapeToolMessage::PointerMove ([Alt, Shift, Control, Shift])), + entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowDown); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowUp], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowDown], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowUp], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowDown], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowUp); modifiers=[ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowUp); modifiers=[ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowUp); action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowDown); modifiers=[ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowDown); modifiers=[ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowDown); action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[ArrowUp], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[ArrowDown], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowLeft); action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowRight); modifiers=[ArrowDown], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowRight); action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }), + entry!(KeyDown(ArrowUp); action_dispatch=ShapeToolMessage::IncreaseSides), + entry!(KeyDown(ArrowDown); action_dispatch=ShapeToolMessage::DecreaseSides), // // ImaginateToolMessage // entry!(KeyDown(MouseLeft); action_dispatch=ImaginateToolMessage::DragStart), @@ -290,7 +318,7 @@ pub fn input_mappings() -> Mapping { entry!(KeyDown(KeyL); action_dispatch=ToolMessage::ActivateShapeLine), entry!(KeyDown(KeyM); action_dispatch=ToolMessage::ActivateShapeRectangle), entry!(KeyDown(KeyE); action_dispatch=ToolMessage::ActivateShapeEllipse), - entry!(KeyDown(KeyY); action_dispatch=ToolMessage::ActivateToolPolygon), + entry!(KeyDown(KeyY); action_dispatch=ToolMessage::ActivateToolShape), entry!(KeyDown(KeyB); action_dispatch=ToolMessage::ActivateToolBrush), entry!(KeyDown(KeyX); modifiers=[Accel, Shift], action_dispatch=ToolMessage::ResetColors), entry!(KeyDown(KeyX); modifiers=[Shift], action_dispatch=ToolMessage::SwapColors), diff --git a/editor/src/messages/tool/common_functionality/mod.rs b/editor/src/messages/tool/common_functionality/mod.rs index a036e1de6e..4a003d0254 100644 --- a/editor/src/messages/tool/common_functionality/mod.rs +++ b/editor/src/messages/tool/common_functionality/mod.rs @@ -6,6 +6,7 @@ pub mod measure; pub mod pivot; pub mod resize; pub mod shape_editor; +pub mod shape_gizmos; pub mod shapes; pub mod snapping; pub mod transformation_cage; diff --git a/editor/src/messages/tool/common_functionality/shape_gizmos/mod.rs b/editor/src/messages/tool/common_functionality/shape_gizmos/mod.rs new file mode 100644 index 0000000000..03951cd2d7 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/shape_gizmos/mod.rs @@ -0,0 +1,2 @@ +pub mod number_of_points_handle; +pub mod point_radius_handle; diff --git a/editor/src/messages/tool/common_functionality/shape_gizmos/number_of_points_handle.rs b/editor/src/messages/tool/common_functionality/shape_gizmos/number_of_points_handle.rs new file mode 100644 index 0000000000..6ded0b0857 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/shape_gizmos/number_of_points_handle.rs @@ -0,0 +1,260 @@ +use crate::messages::tool::{ + common_functionality::shapes::shape_utility::{calculate_polygon_vertex_position, extract_polygon_parameters, inside_polygon, inside_star}, + tool_messages::tool_prelude::Key, +}; +use std::{collections::VecDeque, f64::consts::TAU}; + +use crate::messages::{portfolio::document::utility_types::document_metadata::LayerNodeIdentifier, prelude::Responses}; +use glam::{DAffine2, DVec2}; +use graph_craft::document::{NodeInput, value::TaggedValue}; + +use crate::{ + consts::{GIZMO_HIDE_THRESHOLD, NUMBER_OF_POINTS_HANDLE_SPOKE_EXTENSION, NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD}, + messages::{ + frontend::utility_types::MouseCursorIcon, + message::Message, + portfolio::document::{overlays::utility_types::OverlayContext, utility_types::network_interface::InputConnector}, + prelude::{DocumentMessageHandler, FrontendMessage, InputPreprocessorMessageHandler, NodeGraphMessage}, + tool::common_functionality::{ + graph_modification_utils, + shape_editor::ShapeState, + shapes::shape_utility::{calculate_star_vertex_position, extract_star_parameters}, + }, + }, +}; + +#[derive(Clone, Debug, Default, PartialEq)] +pub enum NumberOfPointsHandleState { + #[default] + Inactive, + Hover, + Dragging, +} + +#[derive(Clone, Debug, Default)] +pub struct NumberOfPointsHandle { + pub layer: Option, + pub initial_points: u32, + pub handle_state: NumberOfPointsHandleState, +} + +impl NumberOfPointsHandle { + pub fn cleanup(&mut self) { + self.handle_state = NumberOfPointsHandleState::Inactive; + self.layer = None; + } + pub fn update_state(&mut self, state: NumberOfPointsHandleState) { + self.handle_state = state; + } + + pub fn is_hovering(&self) -> bool { + self.handle_state == NumberOfPointsHandleState::Hover + } + + pub fn is_dragging(&self) -> bool { + self.handle_state == NumberOfPointsHandleState::Dragging + } + + pub fn handle_actions( + &mut self, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + responses: &mut VecDeque, + ) { + if input.keyboard.key(Key::Control) { + return; + } + + match &self.handle_state { + NumberOfPointsHandleState::Inactive => { + for layer in document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter(|layer| { + graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some() + }) { + if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) { + let viewport = document.metadata().transform_to_viewport(layer); + let center = viewport.transform_point2(DVec2::ZERO); + + let point_on_max_radius = calculate_star_vertex_position(viewport, 0, n, radius1, radius2); + + if mouse_position.distance(center) < NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD { + self.layer = Some(layer); + self.initial_points = n; + self.update_state(NumberOfPointsHandleState::Hover); + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize }); + } + } + + if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) { + let viewport = document.metadata().transform_to_viewport(layer); + let center = viewport.transform_point2(DVec2::ZERO); + + let point_on_max_radius = calculate_polygon_vertex_position(viewport, 0, n, radius); + + if mouse_position.distance(center) < NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD { + self.layer = Some(layer); + self.initial_points = n; + self.update_state(NumberOfPointsHandleState::Hover); + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize }); + } + } + } + } + NumberOfPointsHandleState::Hover | NumberOfPointsHandleState::Dragging => { + let Some(layer) = self.layer else { + return; + }; + + let Some((n, radius)) = extract_star_parameters(Some(layer), document) + .map(|(n, r1, r2)| (n, r1.max(r2))) + .or_else(|| extract_polygon_parameters(Some(layer), document).map(|(n, r)| (n, r))) + else { + return; + }; + + let viewport = document.metadata().transform_to_viewport(layer); + let center = viewport.transform_point2(DVec2::ZERO); + + if mouse_position.distance(center) > NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH && matches!(&self.handle_state, NumberOfPointsHandleState::Hover) { + self.update_state(NumberOfPointsHandleState::Inactive); + self.layer = None; + self.draw_spokes(center, viewport, n, radius, overlay_context); + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); + + return; + } + } + } + } + + pub fn overlays( + &mut self, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + shape_editor: &mut &mut ShapeState, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ) { + if input.keyboard.key(Key::Control) { + return; + } + + match &self.handle_state { + NumberOfPointsHandleState::Inactive => { + for layer in document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter(|layer| { + graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some() + }) { + if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) { + let radius = radius1.max(radius2); + let viewport = document.metadata().transform_to_viewport(layer); + let center = viewport.transform_point2(DVec2::ZERO); + + if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, mouse_position, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD) { + if closest_segment.layer() == layer { + return; + } + } + let point_on_max_radius = calculate_star_vertex_position(viewport, 0, n, radius1, radius2); + + if inside_star(viewport, n, radius1, radius2, mouse_position) && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD { + self.draw_spokes(center, viewport, n, radius, overlay_context); + return; + } + } + + if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) { + let viewport = document.metadata().transform_to_viewport(layer); + let center = viewport.transform_point2(DVec2::ZERO); + + if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, mouse_position, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD) { + if closest_segment.layer() == layer { + return; + } + } + let point_on_max_radius = calculate_polygon_vertex_position(viewport, 0, n, radius); + + if inside_polygon(viewport, n, radius, mouse_position) && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD { + self.draw_spokes(center, viewport, n, radius, overlay_context); + return; + } + } + } + } + NumberOfPointsHandleState::Hover | NumberOfPointsHandleState::Dragging => { + let Some(layer) = self.layer else { + return; + }; + + let Some((n, radius)) = extract_star_parameters(Some(layer), document) + .map(|(n, r1, r2)| (n, r1.max(r2))) + .or_else(|| extract_polygon_parameters(Some(layer), document).map(|(n, r)| (n, r))) + else { + return; + }; + + let viewport = document.metadata().transform_to_viewport(layer); + let center = viewport.transform_point2(DVec2::ZERO); + + self.draw_spokes(center, viewport, n, radius, overlay_context); + } + } + } + + fn draw_spokes(&self, center: DVec2, viewport: DAffine2, n: u32, radius: f64, overlay_context: &mut OverlayContext) { + for i in 0..n { + let angle = ((i as f64) * TAU) / (n as f64); + + let point = viewport.transform_point2(DVec2 { + x: radius * angle.sin(), + y: -radius * angle.cos(), + }); + + let Some(direction) = (point - center).try_normalize() else { + continue; + }; + + // If the user zooms out such that shape is very small hide the gizmo + if point.distance(center) < GIZMO_HIDE_THRESHOLD { + return; + } + + let end_point = direction * NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH; + if matches!(self.handle_state, NumberOfPointsHandleState::Hover | NumberOfPointsHandleState::Dragging) { + overlay_context.line(center, end_point * NUMBER_OF_POINTS_HANDLE_SPOKE_EXTENSION + center, None, None); + } else { + overlay_context.line(center, end_point + center, None, None); + } + } + } + + pub fn update_no_of_sides(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { + let delta = input.mouse.position - document.metadata().document_to_viewport.transform_point2(drag_start); + let sign = (input.mouse.position.x - document.metadata().document_to_viewport.transform_point2(drag_start).x).signum(); + let net_delta = (delta.length() / 25.0).round() * sign; + + let Some(layer) = self.layer else { + return; + }; + + let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else { + return; + }; + + let new_point_count = ((self.initial_points as i32) + (net_delta as i32)).max(3); + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 1), + input: NodeInput::value(TaggedValue::U32(new_point_count as u32), false), + }); + responses.add(NodeGraphMessage::RunDocumentGraph); + } +} diff --git a/editor/src/messages/tool/common_functionality/shape_gizmos/point_radius_handle.rs b/editor/src/messages/tool/common_functionality/shape_gizmos/point_radius_handle.rs new file mode 100644 index 0000000000..d718979e40 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/shape_gizmos/point_radius_handle.rs @@ -0,0 +1,502 @@ +use crate::{ + consts::{COLOR_OVERLAY_RED, POINT_RADIUS_HANDLE_SNAP_THRESHOLD}, + messages::tool::common_functionality::shapes::shape_utility::{calculate_polygon_vertex_position, draw_snapping_ticks, extract_polygon_parameters}, +}; +use std::{ + collections::VecDeque, + f64::consts::{FRAC_1_SQRT_2, FRAC_PI_4, PI, SQRT_2}, +}; + +use crate::messages::{portfolio::document::utility_types::document_metadata::LayerNodeIdentifier, prelude::Responses}; +use glam::DVec2; +use graph_craft::document::{NodeInput, value::TaggedValue}; + +use crate::{ + consts::GIZMO_HIDE_THRESHOLD, + messages::{ + message::Message, + portfolio::document::{overlays::utility_types::OverlayContext, utility_types::network_interface::InputConnector}, + prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage}, + tool::common_functionality::{ + graph_modification_utils, + graph_modification_utils::NodeGraphLayer, + shapes::shape_utility::{calculate_star_vertex_position, extract_star_parameters}, + }, + }, +}; + +#[derive(Clone, Debug, Default, PartialEq)] +pub enum PointRadiusHandleState { + #[default] + Inactive, + Hover, + Dragging, + Snapped(usize), +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct PointRadiusHandle { + pub layer: Option, + point: u32, + radius_index: usize, + snap_radii: Vec, + initial_radius: f64, + handle_state: PointRadiusHandleState, +} + +impl PointRadiusHandle { + pub fn cleanup(&mut self) { + self.handle_state = PointRadiusHandleState::Inactive; + self.snap_radii.clear(); + self.layer = None; + } + + pub fn is_inactive(&self) -> bool { + self.handle_state == PointRadiusHandleState::Inactive + } + + pub fn hovered(&self) -> bool { + self.handle_state == PointRadiusHandleState::Hover + } + + pub fn update_state(&mut self, state: PointRadiusHandleState) { + self.handle_state = state; + } + + pub fn handle_actions(&mut self, document: &DocumentMessageHandler, mouse_position: DVec2) { + match &self.handle_state { + PointRadiusHandleState::Inactive => { + for layer in document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter(|layer| { + graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some() + }) { + // Draw the point handle gizmo for the star shape + if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) { + let viewport = document.metadata().transform_to_viewport(layer); + + for i in 0..2 * n { + let (radius, radius_index) = if i % 2 == 0 { (radius1, 2) } else { (radius2, 3) }; + let point = calculate_star_vertex_position(viewport, i as i32, n, radius1, radius2); + let center = viewport.transform_point2(DVec2::ZERO); + + // If the user zooms out such that shape is very small hide the gizmo + if point.distance(center) < GIZMO_HIDE_THRESHOLD { + return; + } + + if point.distance(mouse_position) < 5. { + self.radius_index = radius_index; + self.layer = Some(layer); + self.point = i; + self.snap_radii = Self::calculate_snap_radii(document, layer, radius_index); + self.initial_radius = radius; + self.update_state(PointRadiusHandleState::Hover); + + return; + } + } + } + + // Draw the point handle gizmo for the Polygon shape + if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) { + let viewport = document.metadata().transform_to_viewport(layer); + + for i in 0..n { + let point = calculate_polygon_vertex_position(viewport, i as i32, n, radius); + let center = viewport.transform_point2(DVec2::ZERO); + + // If the user zooms out such that shape is very small hide the gizmo + if point.distance(center) < GIZMO_HIDE_THRESHOLD { + return; + } + + if point.distance(mouse_position) < 5.0 { + self.radius_index = 2; + self.layer = Some(layer); + self.point = i; + self.snap_radii.clear(); + self.initial_radius = radius; + self.update_state(PointRadiusHandleState::Hover); + + return; + } + } + } + } + } + + PointRadiusHandleState::Dragging | PointRadiusHandleState::Hover => { + let Some(layer) = self.layer else { + return; + }; + + let viewport = document.metadata().transform_to_viewport(layer); + + if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) { + let point = calculate_star_vertex_position(viewport, self.point as i32, n, radius1, radius2); + + if matches!(&self.handle_state, PointRadiusHandleState::Hover) { + if (mouse_position - point).length() > 5.0 { + self.update_state(PointRadiusHandleState::Inactive); + self.layer = None; + return; + } + } + } + + if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) { + let point = calculate_polygon_vertex_position(viewport, self.point as i32, n, radius); + + if matches!(&self.handle_state, PointRadiusHandleState::Hover) { + if (mouse_position - point).length() > 5. { + self.update_state(PointRadiusHandleState::Inactive); + self.layer = None; + return; + } + } + } + } + PointRadiusHandleState::Snapped(_) => {} + } + } + + pub fn overlays(&mut self, other_gizmo_active: bool, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, mouse_position: DVec2, overlay_context: &mut OverlayContext) { + match &self.handle_state { + PointRadiusHandleState::Inactive => { + for layer in document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter(|layer| { + graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some() + }) { + if other_gizmo_active { + return; + } + // Draw the point handle gizmo for the star shape + if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) { + let viewport = document.metadata().transform_to_viewport(layer); + + for i in 0..2 * n { + let point = calculate_star_vertex_position(viewport, i as i32, n, radius1, radius2); + let center = viewport.transform_point2(DVec2::ZERO); + let viewport_diagonal = input.viewport_bounds.size().length(); + + // If the user zooms out such that shape is very small hide the gizmo + if point.distance(center) < GIZMO_HIDE_THRESHOLD { + return; + } + + if point.distance(mouse_position) < 5. { + let Some(direction) = (point - center).try_normalize() else { + continue; + }; + + overlay_context.manipulator_handle(point, true, None); + let angle = ((i as f64) * PI) / (n as f64); + overlay_context.line(center, center + direction * viewport_diagonal, None, None); + + draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context); + + return; + } + + overlay_context.manipulator_handle(point, false, None); + } + } + + // Draw the point handle gizmo for the Polygon shape + if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) { + let viewport = document.metadata().transform_to_viewport(layer); + + for i in 0..n { + let point = calculate_polygon_vertex_position(viewport, i as i32, n, radius); + let center = viewport.transform_point2(DVec2::ZERO); + let viewport_diagonal = input.viewport_bounds.size().length(); + + // If the user zooms out such that shape is very small hide the gizmo + if point.distance(center) < GIZMO_HIDE_THRESHOLD { + return; + } + + if point.distance(mouse_position) < 5. { + let Some(direction) = (point - center).try_normalize() else { + continue; + }; + + overlay_context.manipulator_handle(point, true, None); + overlay_context.line(center, center + direction * viewport_diagonal, None, None); + + return; + } + + overlay_context.manipulator_handle(point, false, None); + } + } + } + } + + PointRadiusHandleState::Dragging | PointRadiusHandleState::Hover => { + let Some(layer) = self.layer else { + return; + }; + + let viewport = document.metadata().transform_to_viewport(layer); + let center = viewport.transform_point2(DVec2::ZERO); + let viewport_diagonal = input.viewport_bounds.size().length(); + + if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) { + let angle = ((self.point as f64) * PI) / (n as f64); + let point = calculate_star_vertex_position(viewport, self.point as i32, n, radius1, radius2); + + let Some(direction) = (point - center).try_normalize() else { + return; + }; + + // Draws the ray from the center to the dragging point extending till the viewport + overlay_context.manipulator_handle(point, true, None); + overlay_context.line(center, center + direction * viewport_diagonal, None, None); + + // makes the ticks for snapping + + // if dragging to make radius negative don't show the + if (mouse_position - center).dot(direction) < 0.0 { + return; + } + draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context); + + return; + } + + if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) { + let point = calculate_polygon_vertex_position(viewport, self.point as i32, n, radius); + + let Some(direction) = (point - center).try_normalize() else { + return; + }; + + // Draws the ray from the center to the dragging point extending till the viewport + overlay_context.manipulator_handle(point, true, None); + overlay_context.line(center, center + direction * viewport_diagonal, None, None); + } + } + PointRadiusHandleState::Snapped(snapping_index) => { + let Some(layer) = self.layer else { + return; + }; + + let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) else { + return; + }; + + let viewport = document.metadata().transform_to_viewport(layer); + let center = viewport.transform_point2(DVec2::ZERO); + + match snapping_index { + //Make a triangle with previous two points + 0 => { + let before_outer_position = calculate_star_vertex_position(viewport, (self.point as i32) - 2, n, radius1, radius2); + let outer_position = calculate_star_vertex_position(viewport, (self.point as i32) - 1, n, radius1, radius2); + let point_position = calculate_star_vertex_position(viewport, self.point as i32, n, radius1, radius2); + + overlay_context.line(before_outer_position, outer_position, Some(COLOR_OVERLAY_RED), Some(3.0)); + overlay_context.line(outer_position, point_position, Some(COLOR_OVERLAY_RED), Some(3.0)); + + let l1 = (before_outer_position - outer_position).length() * 0.2; + let Some(l1_direction) = (before_outer_position - outer_position).try_normalize() else { + return; + }; + + let Some(l2_direction) = (point_position - outer_position).try_normalize() else { + return; + }; + + let Some(direction) = (center - outer_position).try_normalize() else { + return; + }; + + let new_point = SQRT_2 * l1 * direction + outer_position; + + let before_outer_position = l1 * l1_direction + outer_position; + let point_position = l1 * l2_direction + outer_position; + + overlay_context.line(before_outer_position, new_point, Some(COLOR_OVERLAY_RED), Some(3.0)); + overlay_context.line(new_point, point_position, Some(COLOR_OVERLAY_RED), Some(3.0)); + } + 1 => { + let before_outer_position = calculate_star_vertex_position(viewport, (self.point as i32) - 1, n, radius1, radius2); + + let after_point_position = calculate_star_vertex_position(viewport, (self.point as i32) + 1, n, radius1, radius2); + + let point_position = calculate_star_vertex_position(viewport, self.point as i32, n, radius1, radius2); + + overlay_context.line(before_outer_position, point_position, Some(COLOR_OVERLAY_RED), Some(3.0)); + overlay_context.line(point_position, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.0)); + + let l1 = (before_outer_position - point_position).length() * 0.2; + let Some(l1_direction) = (before_outer_position - point_position).try_normalize() else { + return; + }; + + let Some(l2_direction) = (after_point_position - point_position).try_normalize() else { + return; + }; + + let Some(direction) = (center - point_position).try_normalize() else { + return; + }; + + let new_point = SQRT_2 * l1 * direction + point_position; + + let before_outer_position = l1 * l1_direction + point_position; + let after_point_position = l1 * l2_direction + point_position; + + overlay_context.line(before_outer_position, new_point, Some(COLOR_OVERLAY_RED), Some(3.0)); + overlay_context.line(new_point, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.0)); + } + i => { + // use 'self.point' as absolute reference as it match the index of vertices of star starting from 0 + if i % 2 != 0 { + // flipped case + let point_position = calculate_star_vertex_position(viewport, self.point as i32, n, radius1, radius2); + let target_index = (1 - (*i as i32)).abs() + (self.point as i32); + let target_point_position = calculate_star_vertex_position(viewport, target_index, n, radius1, radius2); + + let mirrored_index = 2 * (self.point as i32) - target_index; + let mirrored = calculate_star_vertex_position(viewport, mirrored_index, n, radius1, radius2); + + overlay_context.line(point_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.0)); + overlay_context.line(point_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.0)); + } else { + let outer_index = (self.point as i32) - 1; + let outer_position = calculate_star_vertex_position(viewport, outer_index, n, radius1, radius2); + + // the vertex which is colinear with the point we are dragging and its previous outer vertex + let target_index = (self.point as i32) + (*i as i32) - 1; + let target_point_position = calculate_star_vertex_position(viewport, target_index, n, radius1, radius2); + + let mirrored_index = 2 * outer_index - target_index; + + let mirrored = calculate_star_vertex_position(viewport, mirrored_index, n, radius1, radius2); + + overlay_context.line(outer_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.0)); + overlay_context.line(outer_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.0)); + } + } + } + // 0,1 90 + } + } + } + fn calculate_snap_radii(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, radius_index: usize) -> Vec { + let mut snap_radii = Vec::new(); + + let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { + return snap_radii; + }; + + let other_index = if radius_index == 3 { 2 } else { 3 }; + + let Some(&TaggedValue::F64(other_radius)) = node_inputs[other_index].as_value() else { + return snap_radii; + }; + + let Some(&TaggedValue::U32(n)) = node_inputs[1].as_value() else { + return snap_radii; + }; + + // inner radius for 90 + let b = FRAC_PI_4 * 3. - PI / (n as f64); + let angle = b.sin(); + let required_radius = (other_radius / angle) * FRAC_1_SQRT_2; + + snap_radii.push(required_radius); + + // also push the case when the when it length increases more than the other + + let flipped = other_radius * angle * SQRT_2; + + snap_radii.push(flipped); + + for i in 1..n { + let n = n as f64; + let i = i as f64; + let denominator = 2. * ((PI * (i - 1.0)) / n).cos() * ((PI * i) / n).sin(); + let numerator = ((2. * PI * i) / n).sin(); + let factor = numerator / denominator; + + if factor < 0.0 { + break; + } + + if other_radius * factor > 1e-6 { + snap_radii.push(other_radius * factor); + } + + snap_radii.push((other_radius * 1.0) / factor); + } + + snap_radii + } + + fn check_snapping(&self, new_radius: f64, original_radius: f64) -> Option<(usize, f64)> { + self.snap_radii + .iter() + .enumerate() + .filter(|(_, rad)| (**rad - new_radius).abs() < POINT_RADIUS_HANDLE_SNAP_THRESHOLD) + .min_by(|(i_a, a), (i_b, b)| { + let dist_a = (**a - new_radius).abs(); + let dist_b = (**b - new_radius).abs(); + + // Check if either index is 0 or 1 and prioritize them + match (*i_a == 0 || *i_a == 1, *i_b == 0 || *i_b == 1) { + (true, false) => std::cmp::Ordering::Less, // a is priority index, b is not + (false, true) => std::cmp::Ordering::Greater, // b is priority index, a is not + _ => dist_a.partial_cmp(&dist_b).unwrap_or(std::cmp::Ordering::Equal), // normal comparison + } + }) + .map(|(i, rad)| (i, *rad - original_radius)) + } + + pub fn update_inner_radius( + &mut self, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + layer: LayerNodeIdentifier, + responses: &mut VecDeque, + drag_start: DVec2, + ) { + let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else { + return; + }; + + let transform = document.network_interface.document_metadata().transform_to_viewport(layer); + let center = transform.transform_point2(DVec2::ZERO); + let radius_index = self.radius_index; + + let original_radius = self.initial_radius; + + let delta = input.mouse.position - document.metadata().document_to_viewport.transform_point2(drag_start); + let radius = document.metadata().document_to_viewport.transform_point2(drag_start) - center; + let projection = delta.project_onto(radius); + let sign = radius.dot(delta).signum(); + + let mut net_delta = projection.length() * sign; + let new_radius = original_radius + net_delta; + + self.update_state(PointRadiusHandleState::Dragging); + if let Some((index, snapped_delta)) = self.check_snapping(new_radius, original_radius) { + net_delta = snapped_delta; + self.update_state(PointRadiusHandleState::Snapped(index)); + } + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, radius_index), + input: NodeInput::value(TaggedValue::F64(original_radius + net_delta), false), + }); + responses.add(NodeGraphMessage::RunDocumentGraph); + } +} diff --git a/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs b/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs index d662466833..3ba433955c 100644 --- a/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs @@ -27,11 +27,11 @@ impl Ellipse { shape_tool_data: &mut ShapeToolData, modifier: ShapeToolModifierKey, responses: &mut VecDeque, - ) -> bool { + ) { let (center, lock_ratio) = (modifier[0], modifier[1]); if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) { let Some(node_id) = graph_modification_utils::get_ellipse_id(layer, &document.network_interface) else { - return true; + return; }; responses.add(NodeGraphMessage::SetInput { @@ -49,6 +49,132 @@ impl Ellipse { skip_rerender: false, }); } - false + } +} + +#[cfg(test)] +mod test_ellipse { + pub use crate::test_utils::test_prelude::*; + use glam::DAffine2; + use graphene_core::vector::generator_nodes::ellipse; + + #[derive(Debug, PartialEq)] + struct ResolvedEllipse { + radius_x: f64, + radius_y: f64, + transform: DAffine2, + } + + async fn get_ellipse(editor: &mut EditorTestUtils) -> Vec { + let instrumented = match editor.eval_graph().await { + Ok(instrumented) => instrumented, + Err(e) => panic!("Failed to evaluate graph: {e}"), + }; + + let document = editor.active_document(); + let layers = document.metadata().all_layers(); + layers + .filter_map(|layer| { + let node_graph_layer = NodeGraphLayer::new(layer, &document.network_interface); + let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::protonode_identifier())?; + Some(ResolvedEllipse { + radius_x: instrumented.grab_protonode_input::(&vec![ellipse_node], &editor.runtime).unwrap(), + radius_y: instrumented.grab_protonode_input::(&vec![ellipse_node], &editor.runtime).unwrap(), + transform: document.metadata().transform_to_document(layer), + }) + }) + .collect() + } + + #[tokio::test] + async fn ellipse_draw_simple() { + let mut editor = EditorTestUtils::create(); + editor.new_document().await; + editor.drag_tool(ToolType::Ellipse, 10., 10., 19., 0., ModifierKeys::empty()).await; + + assert_eq!(editor.active_document().metadata().all_layers().count(), 1); + + let ellipse = get_ellipse(&mut editor).await; + assert_eq!(ellipse.len(), 1); + assert_eq!( + ellipse[0], + ResolvedEllipse { + radius_x: 4.5, + radius_y: 5., + transform: DAffine2::from_translation(DVec2::new(14.5, 5.)) // Uses center + } + ); + } + + #[tokio::test] + async fn ellipse_draw_circle() { + let mut editor = EditorTestUtils::create(); + editor.new_document().await; + editor.drag_tool(ToolType::Ellipse, 10., 10., -10., 11., ModifierKeys::SHIFT).await; + + let ellipse = get_ellipse(&mut editor).await; + assert_eq!(ellipse.len(), 1); + assert_eq!( + ellipse[0], + ResolvedEllipse { + radius_x: 10., + radius_y: 10., + transform: DAffine2::from_translation(DVec2::new(0., 20.)) // Uses center + } + ); + } + + #[tokio::test] + async fn ellipse_draw_square_rotated() { + let mut editor = EditorTestUtils::create(); + editor.new_document().await; + editor + .handle_message(NavigationMessage::CanvasTiltSet { + // 45 degree rotation of content clockwise + angle_radians: f64::consts::FRAC_PI_4, + }) + .await; + editor.drag_tool(ToolType::Ellipse, 0., 0., 1., 10., ModifierKeys::SHIFT).await; // Viewport coordinates + + let ellipse = get_ellipse(&mut editor).await; + assert_eq!(ellipse.len(), 1); + println!("{ellipse:?}"); + assert_eq!(ellipse[0].radius_x, 5.); + assert_eq!(ellipse[0].radius_y, 5.); + + assert!( + ellipse[0] + .transform + .abs_diff_eq(DAffine2::from_angle_translation(-f64::consts::FRAC_PI_4, DVec2::X * f64::consts::FRAC_1_SQRT_2 * 10.), 0.001) + ); + } + + #[tokio::test] + async fn ellipse_draw_center_square_rotated() { + let mut editor = EditorTestUtils::create(); + editor.new_document().await; + editor + .handle_message(NavigationMessage::CanvasTiltSet { + // 45 degree rotation of content clockwise + angle_radians: f64::consts::FRAC_PI_4, + }) + .await; + editor.drag_tool(ToolType::Ellipse, 0., 0., 1., 10., ModifierKeys::SHIFT | ModifierKeys::ALT).await; // Viewport coordinates + + let ellipse = get_ellipse(&mut editor).await; + assert_eq!(ellipse.len(), 1); + assert_eq!(ellipse[0].radius_x, 10.); + assert_eq!(ellipse[0].radius_y, 10.); + assert!(ellipse[0].transform.abs_diff_eq(DAffine2::from_angle(-f64::consts::FRAC_PI_4), 0.001)); + } + + #[tokio::test] + async fn ellipse_cancel() { + let mut editor = EditorTestUtils::create(); + editor.new_document().await; + editor.drag_tool_cancel_rmb(ToolType::Ellipse).await; + + let ellipse = get_ellipse(&mut editor).await; + assert_eq!(ellipse.len(), 0); } } diff --git a/editor/src/messages/tool/common_functionality/shapes/line_shape.rs b/editor/src/messages/tool/common_functionality/shapes/line_shape.rs index c348441862..1812e56033 100644 --- a/editor/src/messages/tool/common_functionality/shapes/line_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/line_shape.rs @@ -52,7 +52,7 @@ impl Line { shape_tool_data: &mut ShapeToolData, modifier: ShapeToolModifierKey, responses: &mut VecDeque, - ) -> bool { + ) { let (center, snap_angle, lock_angle) = (modifier[0], modifier[3], modifier[2]); shape_tool_data.line_data.drag_current = ipp.mouse.position; let keyboard = &ipp.keyboard; @@ -65,7 +65,7 @@ impl Line { } let Some(node_id) = graph_modification_utils::get_line_id(layer, &document.network_interface) else { - return true; + return; }; responses.add(NodeGraphMessage::SetInput { @@ -77,7 +77,6 @@ impl Line { input: NodeInput::value(TaggedValue::DVec2(document_points[1]), false), }); responses.add(NodeGraphMessage::RunDocumentGraph); - false } pub fn overlays(document: &DocumentMessageHandler, shape_tool_data: &mut ShapeToolData, overlay_context: &mut OverlayContext) { @@ -197,3 +196,186 @@ pub fn clicked_on_line_endpoints(layer: LayerNodeIdentifier, document: &Document } false } + +#[cfg(test)] +mod test_line_tool { + use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; + use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; + use crate::test_utils::test_prelude::*; + use glam::DAffine2; + use graph_craft::document::value::TaggedValue; + + async fn get_line_node_inputs(editor: &mut EditorTestUtils) -> Option<(DVec2, DVec2)> { + let document = editor.active_document(); + let network_interface = &document.network_interface; + let node_id = network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(network_interface) + .filter_map(|layer| { + let node_inputs = NodeGraphLayer::new(layer, &network_interface).find_node_inputs("Line")?; + let (Some(&TaggedValue::DVec2(start)), Some(&TaggedValue::DVec2(end))) = (node_inputs[1].as_value(), node_inputs[2].as_value()) else { + return None; + }; + Some((start, end)) + }) + .next(); + node_id + } + + #[tokio::test] + async fn test_line_tool_basicdraw() { + let mut editor = EditorTestUtils::create(); + editor.new_document().await; + editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::empty()).await; + if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { + match (start_input, end_input) { + (start_input, end_input) => { + assert!((start_input - DVec2::ZERO).length() < 1., "Start point should be near (0,0)"); + assert!((end_input - DVec2::new(100., 100.)).length() < 1., "End point should be near (100,100)"); + } + } + } + } + + #[tokio::test] + async fn test_line_tool_with_transformed_viewport() { + let mut editor = EditorTestUtils::create(); + editor.new_document().await; + editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2. }).await; + editor.handle_message(NavigationMessage::CanvasPan { delta: DVec2::new(100., 50.) }).await; + editor + .handle_message(NavigationMessage::CanvasTiltSet { + angle_radians: (30. as f64).to_radians(), + }) + .await; + editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::empty()).await; + if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { + let document = editor.active_document(); + let document_to_viewport = document.metadata().document_to_viewport; + let viewport_to_document = document_to_viewport.inverse(); + + let expected_start = viewport_to_document.transform_point2(DVec2::ZERO); + let expected_end = viewport_to_document.transform_point2(DVec2::new(100., 100.)); + + assert!( + (start_input - expected_start).length() < 1., + "Start point should match expected document coordinates. Got {:?}, expected {:?}", + start_input, + expected_start + ); + assert!( + (end_input - expected_end).length() < 1., + "End point should match expected document coordinates. Got {:?}, expected {:?}", + end_input, + expected_end + ); + } else { + panic!("Line was not created successfully with transformed viewport"); + } + } + + #[tokio::test] + async fn test_line_tool_ctrl_anglelock() { + let mut editor = EditorTestUtils::create(); + editor.new_document().await; + editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::CONTROL).await; + if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { + match (start_input, end_input) { + (start_input, end_input) => { + let line_vec = end_input - start_input; + let original_angle = line_vec.angle_to(DVec2::X); + editor.drag_tool(ToolType::Line, 0., 0., 200., 50., ModifierKeys::CONTROL).await; + if let Some((updated_start, updated_end)) = get_line_node_inputs(&mut editor).await { + match (updated_start, updated_end) { + (updated_start, updated_end) => { + let updated_line_vec = updated_end - updated_start; + let updated_angle = updated_line_vec.angle_to(DVec2::X); + print!("{:?}", original_angle); + print!("{:?}", updated_angle); + assert!( + line_vec.normalize().dot(updated_line_vec.normalize()).abs() - 1. < 1e-6, + "Line angle should be locked when Ctrl is kept pressed" + ); + assert!((updated_start - updated_end).length() > 1., "Line should be able to change length when Ctrl is kept pressed"); + } + } + } + } + } + } + } + + #[tokio::test] + async fn test_line_tool_alt() { + let mut editor = EditorTestUtils::create(); + editor.new_document().await; + editor.drag_tool(ToolType::Line, 100., 100., 200., 100., ModifierKeys::ALT).await; + if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { + match (start_input, end_input) { + (start_input, end_input) => { + let expected_start = DVec2::new(0., 100.); + let expected_end = DVec2::new(200., 100.); + assert!((start_input - expected_start).length() < 1., "start point should be near (0,100)"); + assert!((end_input - expected_end).length() < 1., "end point should be near (200,100)"); + } + } + } + } + + #[tokio::test] + async fn test_line_tool_alt_shift_drag() { + let mut editor = EditorTestUtils::create(); + editor.new_document().await; + editor.drag_tool(ToolType::Line, 100., 100., 150., 120., ModifierKeys::ALT | ModifierKeys::SHIFT).await; + if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { + match (start_input, end_input) { + (start_input, end_input) => { + let line_vec = end_input - start_input; + let angle_radians = line_vec.angle_to(DVec2::X); + let angle_degrees = angle_radians.to_degrees(); + let nearest_angle = (angle_degrees / 15.).round() * 15.; + + assert!((angle_degrees - nearest_angle).abs() < 1., "Angle should snap to the nearest 15 degrees"); + } + } + } + } + + #[tokio::test] + async fn test_line_tool_with_transformed_artboard() { + let mut editor = EditorTestUtils::create(); + editor.new_document().await; + editor.drag_tool(ToolType::Artboard, 0., 0., 200., 200., ModifierKeys::empty()).await; + + let artboard_id = editor.get_selected_layer().await.expect("Should have selected the artboard"); + + editor + .handle_message(GraphOperationMessage::TransformChange { + layer: artboard_id, + transform: DAffine2::from_angle(45.0_f64.to_radians()), + transform_in: TransformIn::Local, + skip_rerender: false, + }) + .await; + + editor.drag_tool(ToolType::Line, 50., 50., 150., 150., ModifierKeys::empty()).await; + + let (start_input, end_input) = get_line_node_inputs(&mut editor).await.expect("Line was not created successfully within transformed artboard"); + // The line should still be diagonal with equal change in x and y + let line_vector = end_input - start_input; + // Verifying the line is approximately 100*sqrt(2) units in length (diagonal of 100x100 square) + let line_length = line_vector.length(); + assert!( + (line_length - 141.42).abs() < 1.0, // 100 * sqrt(2) ~= 141.42 + "Line length should be approximately 141.42 units. Got: {line_length}" + ); + assert!((line_vector.x - 100.0).abs() < 1.0, "X-component of line vector should be approximately 100. Got: {}", line_vector.x); + assert!( + (line_vector.y.abs() - 100.0).abs() < 1.0, + "Absolute Y-component of line vector should be approximately 100. Got: {}", + line_vector.y.abs() + ); + let angle_degrees = line_vector.angle_to(DVec2::X).to_degrees(); + assert!((angle_degrees - (-45.0)).abs() < 1.0, "Line angle should be close to -45 degrees. Got: {angle_degrees}"); + } +} diff --git a/editor/src/messages/tool/common_functionality/shapes/mod.rs b/editor/src/messages/tool/common_functionality/shapes/mod.rs index 3c9abab5f0..d2f83518ec 100644 --- a/editor/src/messages/tool/common_functionality/shapes/mod.rs +++ b/editor/src/messages/tool/common_functionality/shapes/mod.rs @@ -1,4 +1,4 @@ -pub mod convex_shape; +pub mod polygon_shape; pub mod ellipse_shape; pub mod line_shape; pub mod rectangle_shape; @@ -6,7 +6,7 @@ pub mod shape_utility; pub mod star_shape; pub use super::shapes::ellipse_shape::Ellipse; -pub use super::shapes::line_shape::{Line, LineEnd}; +pub use super::shapes::line_shape::{ Line, LineEnd }; pub use super::shapes::rectangle_shape::Rectangle; pub use crate::messages::tool::tool_messages::shape_tool::ShapeToolData; use glam::DVec2; diff --git a/editor/src/messages/tool/common_functionality/shapes/convex_shape.rs b/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs similarity index 94% rename from editor/src/messages/tool/common_functionality/shapes/convex_shape.rs rename to editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs index ea83972a92..457650f1a1 100644 --- a/editor/src/messages/tool/common_functionality/shapes/convex_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs @@ -13,9 +13,9 @@ use graph_craft::document::value::TaggedValue; use std::collections::VecDeque; #[derive(Default)] -pub struct Convex; +pub struct Polygon; -impl Convex { +impl Polygon { pub fn create_node(vertices: u32) -> NodeTemplate { let node_type = resolve_document_node_type("Regular Polygon").expect("Regular Polygon does not exist"); node_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::U32(vertices), false)), Some(NodeInput::value(TaggedValue::F64(0.5), false))]) @@ -28,7 +28,7 @@ impl Convex { shape_tool_data: &mut ShapeToolData, modifier: ShapeToolModifierKey, responses: &mut VecDeque, - ) -> bool { + ) { let (center, lock_ratio) = (modifier[0], modifier[1]); if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) { // TODO: We need to determine how to allow the polygon node to make irregular shapes @@ -48,7 +48,7 @@ impl Convex { } let Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface) else { - return false; + return; }; responses.add(NodeGraphMessage::SetInput { @@ -58,11 +58,10 @@ impl Convex { responses.add(GraphOperationMessage::TransformSet { layer, - transform: DAffine2::from_scale_angle_translation(scale, 0., (start + end) / 2.), + transform: DAffine2::from_scale_angle_translation(scale, 0.0, (start + end) / 2.0), transform_in: TransformIn::Viewport, skip_rerender: false, }); } - false } } diff --git a/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs b/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs index 04e0791a03..6ee1dead12 100644 --- a/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs @@ -27,11 +27,11 @@ impl Rectangle { shape_tool_data: &mut ShapeToolData, modifier: ShapeToolModifierKey, responses: &mut VecDeque, - ) -> bool { + ) { let (center, lock_ratio) = (modifier[0], modifier[1]); if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) { let Some(node_id) = graph_modification_utils::get_rectangle_id(layer, &document.network_interface) else { - return true; + return; }; responses.add(NodeGraphMessage::SetInput { @@ -49,6 +49,5 @@ impl Rectangle { skip_rerender: false, }); } - false } } 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 9e6aa019e7..c181228cc3 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -8,19 +8,22 @@ use crate::messages::tool::common_functionality::transformation_cage::BoundingBo use crate::messages::tool::tool_messages::tool_prelude::Key; use crate::messages::tool::utility_types::*; use bezier_rs::Subpath; -use glam::{DMat2, DVec2}; +use glam::{DAffine2, DMat2, DVec2}; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; +use graphene_std::renderer::ClickTargetType; use graphene_std::vector::PointId; +use graphene_std::vector::misc::dvec2_to_point; +use kurbo::{BezPath, PathEl, Shape}; use std::collections::VecDeque; -use std::f64::consts::PI; +use std::f64::consts::{PI, TAU}; use super::ShapeToolData; #[derive(Debug, Clone, Copy, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)] pub enum ShapeType { #[default] - Convex = 0, + Polygon = 0, Star = 1, Rectangle = 2, Ellipse = 3, @@ -29,37 +32,37 @@ pub enum ShapeType { impl ShapeType { pub fn name(&self) -> String { - match self { - Self::Convex => "Convex", + (match self { + Self::Polygon => "Polygon", Self::Star => "Star", Self::Rectangle => "Rectangle", Self::Ellipse => "Ellipse", Self::Line => "Line", - } + }) .into() } pub fn tooltip(&self) -> String { - match self { + (match self { Self::Line => "Line tool", Self::Rectangle => "Rectangle tool", Self::Ellipse => "Ellipse tool", _ => "", - } + }) .into() } pub fn icon_name(&self) -> String { - match self { + (match self { Self::Line => "VectorLineTool", Self::Rectangle => "VectorRectangleTool", Self::Ellipse => "VectorEllipseTool", _ => "", - } + }) .into() } - pub fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { + pub fn tool_type(&self) -> ToolType { match self { Self::Line => ToolType::Line, Self::Rectangle => ToolType::Rectangle, @@ -69,15 +72,17 @@ impl ShapeType { } } -// Center, Lock Ratio, Lock Angle, Snap Angle +// Center, Lock Ratio, Lock Angle, Snap Angle, Increase/Decrease Side pub type ShapeToolModifierKey = [Key; 4]; pub fn update_radius_sign(end: DVec2, start: DVec2, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, responses: &mut VecDeque) { - let sign_num = if end[1] > start[1] { 1. } else { -1. }; + let sign_num = if end[1] > start[1] { 1.0 } else { -1.0 }; let new_layer = NodeGraphLayer::new(layer, &document.network_interface); if new_layer.find_input("Regular Polygon", 1).unwrap_or(&TaggedValue::U32(0)).to_u32() % 2 == 1 { - let Some(polygon_node_id) = new_layer.upstream_node_id_from_name("Regular Polygon") else { return }; + let Some(polygon_node_id) = new_layer.upstream_node_id_from_name("Regular Polygon") else { + return; + }; responses.add(NodeGraphMessage::SetInput { input_connector: InputConnector::node(polygon_node_id, 2), @@ -87,7 +92,9 @@ pub fn update_radius_sign(end: DVec2, start: DVec2, layer: LayerNodeIdentifier, } if new_layer.find_input("Star", 1).unwrap_or(&TaggedValue::U32(0)).to_u32() % 2 == 1 { - let Some(star_node_id) = new_layer.upstream_node_id_from_name("Star") else { return }; + let Some(star_node_id) = new_layer.upstream_node_id_from_name("Star") else { + return; + }; responses.add(NodeGraphMessage::SetInput { input_connector: InputConnector::node(star_node_id, 2), @@ -111,7 +118,7 @@ pub fn transform_cage_overlays(document: &DocumentMessageHandler, tool_data: &mu // Check if the matrix is not invertible let mut transform_tampered = false; - if transform.matrix2.determinant() == 0. { + if transform.matrix2.determinant() == 0.0 { transform.matrix2 += DMat2::IDENTITY * 1e-4; // TODO: Is this the cleanest way to handle this? transform_tampered = true; } @@ -142,7 +149,9 @@ pub fn transform_cage_overlays(document: &DocumentMessageHandler, tool_data: &mu pub fn anchor_overlays(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) { - let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue }; + let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { + continue; + }; let transform = document.metadata().transform_to_viewport(layer); overlay_context.outline_vector(&vector_data, transform); @@ -153,20 +162,66 @@ pub fn anchor_overlays(document: &DocumentMessageHandler, overlay_context: &mut } } -pub fn star_outline(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { - let mut anchors = Vec::new(); +/// Extract the node input values of Star +pub fn extract_star_parameters(layer: Option, document: &DocumentMessageHandler) -> Option<(u32, f64, f64)> { + let Some(layer) = layer else { + return None; + }; + let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star")?; - let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { - return; + let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(outer)), Some(&TaggedValue::F64(inner))) = (node_inputs.get(1)?.as_value(), node_inputs.get(2)?.as_value(), node_inputs.get(3)?.as_value()) + else { + return None; }; - let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(radius1)), Some(&TaggedValue::F64(radius2))) = (node_inputs[1].as_value(), node_inputs[2].as_value(), node_inputs[3].as_value()) else { + Some((n, outer, inner)) +} + +/// Extract the node input values of Polygon +pub fn extract_polygon_parameters(layer: Option, document: &DocumentMessageHandler) -> Option<(u32, f64)> { + let Some(layer) = layer else { + return None; + }; + let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Regular Polygon")?; + + let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(radius))) = (node_inputs.get(1)?.as_value(), node_inputs.get(2)?.as_value()) else { + return None; + }; + + Some((n, radius)) +} + +/// Calculate the viewport position of as a star vertex given its index +pub fn calculate_star_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, radius1: f64, radius2: f64) -> DVec2 { + let angle = ((vertex_index as f64) * PI) / (n as f64); + let radius = if vertex_index % 2 == 0 { radius1 } else { radius2 }; + + viewport.transform_point2(DVec2 { + x: radius * angle.sin(), + y: -radius * angle.cos(), + }) +} + +/// Calculate the viewport position of as a polygon vertex given its index +pub fn calculate_polygon_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, radius: f64) -> DVec2 { + let angle = ((vertex_index as f64) * TAU) / (n as f64); + + viewport.transform_point2(DVec2 { + x: radius * angle.sin(), + y: -radius * angle.cos(), + }) +} + +/// Outlines the geometric shape made by star-node +pub fn star_outline(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { + let mut anchors = Vec::new(); + let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) else { return; }; let viewport = document.metadata().transform_to_viewport(layer); - for i in 0..(2 * n) { - let angle = i as f64 * PI / n as f64; + for i in 0..2 * n { + let angle = ((i as f64) * PI) / (n as f64); let radius = if i % 2 == 0 { radius1 } else { radius2 }; let point = DVec2 { @@ -181,3 +236,102 @@ pub fn star_outline(layer: LayerNodeIdentifier, document: &DocumentMessageHandle overlay_context.outline(subpath.iter(), viewport, None); } + +/// Outlines the geometric shape made by polygon-node +pub fn polygon_outline(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { + let mut anchors = Vec::new(); + + let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) else { + return; + }; + + let viewport = document.metadata().transform_to_viewport(layer); + for i in 0..2 * n { + let angle = ((i as f64) * TAU) / (n as f64); + + let point = DVec2 { + x: radius * angle.sin(), + y: -radius * angle.cos(), + }; + + anchors.push(point); + } + + let subpath: Vec = vec![ClickTargetType::Subpath(Subpath::from_anchors_linear(anchors, true))]; + + overlay_context.outline(subpath.iter(), viewport, None); +} + +/// Check if the the cursor is inside the geometric star-shape made by star-node without any upstream node modifications +pub fn inside_star(viewport: DAffine2, n: u32, radius1: f64, radius2: f64, mouse_position: DVec2) -> bool { + let mut paths = Vec::new(); + + for i in 0..2 * n { + let new_point = dvec2_to_point(calculate_star_vertex_position(viewport, i as i32, n, radius1, radius2)); + + if i == 0 { + paths.push(PathEl::MoveTo(new_point)); + } else { + paths.push(PathEl::LineTo(new_point)); + } + } + + paths.push(PathEl::ClosePath); + + let bez_path = BezPath::from_vec(paths); + let (shape, bbox) = (bez_path.clone(), bez_path.bounding_box()); + + if bbox.x0 > mouse_position.x || bbox.y0 > mouse_position.y || bbox.x1 < mouse_position.x || bbox.y1 < mouse_position.y { + return false; + } + + let winding = shape.winding(dvec2_to_point(mouse_position)); + + // Non-zero fill rule + winding != 0 +} + +/// Check if the the cursor is inside the geometric star-shape made by star-node without any upstream node modifications +pub fn inside_polygon(viewport: DAffine2, n: u32, radius: f64, mouse_position: DVec2) -> bool { + let mut paths = Vec::new(); + + for i in 0..n { + let new_point = dvec2_to_point(calculate_polygon_vertex_position(viewport, i as i32, n, radius)); + + if i == 0 { + paths.push(PathEl::MoveTo(new_point)); + } else { + paths.push(PathEl::LineTo(new_point)); + } + } + + paths.push(PathEl::ClosePath); + + let bez_path = BezPath::from_vec(paths); + let (shape, bbox) = (bez_path.clone(), bez_path.bounding_box()); + + if bbox.x0 > mouse_position.x || bbox.y0 > mouse_position.y || bbox.x1 < mouse_position.x || bbox.y1 < mouse_position.y { + return false; + } + + let winding = shape.winding(dvec2_to_point(mouse_position)); + + // Non-zero fill rule + winding != 0 +} + +pub fn draw_snapping_ticks(snap_radii: &[f64], direction: DVec2, viewport: DAffine2, angle: f64, overlay_context: &mut OverlayContext) { + for &snapped_radius in snap_radii { + let Some(tick_direction) = direction.perp().try_normalize() else { + return; + }; + + let tick_position = viewport.transform_point2(DVec2 { + x: snapped_radius * angle.sin(), + y: -snapped_radius * angle.cos(), + }); + + overlay_context.line(tick_position, tick_position + tick_direction * 5., None, Some(2.)); + overlay_context.line(tick_position, tick_position - tick_direction * 5., None, Some(2.)); + } +} diff --git a/editor/src/messages/tool/common_functionality/shapes/star_shape.rs b/editor/src/messages/tool/common_functionality/shapes/star_shape.rs index 04ab736aaa..e933dc2694 100644 --- a/editor/src/messages/tool/common_functionality/shapes/star_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/star_shape.rs @@ -1,704 +1,20 @@ -use super::line_shape::NodeGraphLayer; use super::shape_utility::{ShapeToolModifierKey, update_radius_sign}; use super::*; -use crate::consts::{ - COLOR_OVERLAY_RED, GIZMO_HIDE_THRESHOLD, NUMBER_OF_POINTS_HANDLE_SPOKE_EXTENSION, NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD, POINT_RADIUS_HANDLE_SNAP_THRESHOLD, -}; 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::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::tool::common_functionality::graph_modification_utils; -use crate::messages::tool::common_functionality::shape_editor::ShapeState; -use crate::messages::tool::common_functionality::shapes::shape_utility::star_outline; use crate::messages::tool::tool_messages::tool_prelude::*; use core::f64; use glam::DAffine2; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; -use graphene_std::vector::misc::dvec2_to_point; -use kurbo::{BezPath, PathEl, Shape}; use std::collections::VecDeque; -use std::f64::consts::{FRAC_1_SQRT_2, PI, SQRT_2}; -use std::f64::consts::{FRAC_PI_4, TAU}; #[derive(Default)] pub struct Star; -#[derive(Clone, Debug, Default, PartialEq)] -pub enum PointRadiusHandleState { - #[default] - Inactive, - Hover, - Dragging, - Snapped(usize), -} - -#[derive(Clone, Debug, Default, PartialEq)] -pub struct PointRadiusHandle { - pub layer: Option, - point: u32, - radius_index: usize, - snap_radii: Vec, - initial_radius: f64, - handle_state: PointRadiusHandleState, -} - -#[derive(Clone, Debug, Default, PartialEq)] -pub enum NumberOfPointsHandleState { - #[default] - Inactive, - Hover, - Dragging, -} - -#[derive(Clone, Debug, Default)] - -pub struct NumberOfPointsHandle { - layer: Option, - initial_points: u32, - pub handle_state: NumberOfPointsHandleState, -} - -impl NumberOfPointsHandle { - pub fn cleanup(&mut self) { - self.handle_state = NumberOfPointsHandleState::Inactive; - self.layer = None; - } - pub fn update_state(&mut self, state: NumberOfPointsHandleState) { - self.handle_state = state; - } - - pub fn is_hovering(&self) -> bool { - self.handle_state == NumberOfPointsHandleState::Hover - } - - pub fn is_dragging(&self) -> bool { - self.handle_state == NumberOfPointsHandleState::Dragging - } - - fn overlays( - &mut self, - document: &DocumentMessageHandler, - input: &InputPreprocessorMessageHandler, - shape_editor: &mut &mut ShapeState, - mouse_position: DVec2, - overlay_context: &mut OverlayContext, - responses: &mut VecDeque, - ) { - if input.keyboard.key(Key::Control) { - return; - } - - match &self.handle_state { - NumberOfPointsHandleState::Inactive => { - for layer in document - .network_interface - .selected_nodes() - .selected_visible_and_unlocked_layers(&document.network_interface) - .filter(|layer| graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some()) - { - let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { - return; - }; - - let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(radius1)), Some(&TaggedValue::F64(radius2))) = - (node_inputs[1].as_value(), node_inputs[2].as_value(), node_inputs[3].as_value()) - else { - return; - }; - - let viewport = document.metadata().transform_to_viewport(layer); - let center = viewport.transform_point2(DVec2::ZERO); - - let radius = radius1.max(radius2); - - if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, mouse_position, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD) { - if closest_segment.layer() == layer { - return; - } - } - - let angle: f64 = 0.; - let point = viewport.transform_point2(DVec2 { - x: radius1 * angle.sin(), - y: -radius1 * angle.cos(), - }); - - if Self::inside_star(viewport, n, radius1, radius2, mouse_position) { - self.draw_spokes(center, viewport, n, radius, overlay_context); - if mouse_position.distance(center) < NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH && point.distance(center) > GIZMO_HIDE_THRESHOLD { - self.layer = Some(layer); - self.initial_points = n; - self.update_state(NumberOfPointsHandleState::Hover); - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize }); - } - - return; - } - } - } - NumberOfPointsHandleState::Hover | NumberOfPointsHandleState::Dragging => { - let Some(layer) = self.layer else { return }; - let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { - return; - }; - - let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(outer)), Some(&TaggedValue::F64(inner))) = (node_inputs[1].as_value(), node_inputs[2].as_value(), node_inputs[3].as_value()) - else { - return; - }; - - let viewport = document.metadata().transform_to_viewport(layer); - let center = viewport.transform_point2(DVec2::ZERO); - - let radius = outer.max(inner); - - if mouse_position.distance(center) > NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH && matches!(&self.handle_state, NumberOfPointsHandleState::Hover) { - self.update_state(NumberOfPointsHandleState::Inactive); - self.layer = None; - self.draw_spokes(center, viewport, n, radius, overlay_context); - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); - - return; - } - self.draw_spokes(center, viewport, n, radius, overlay_context); - } - } - } - - fn inside_star(viewport: DAffine2, n: u32, radius1: f64, radius2: f64, mouse_position: DVec2) -> bool { - let mut paths = Vec::new(); - - for i in 0..(2 * n) { - let angle = i as f64 * PI / n as f64; - let radius = if i % 2 == 0 { radius1 } else { radius2 }; - - let point = viewport.transform_point2(DVec2 { - x: radius * angle.sin(), - y: -radius * angle.cos(), - }); - - let new_point = dvec2_to_point(point); - - if i == 0 { - paths.push(PathEl::MoveTo(new_point)); - } else { - paths.push(PathEl::LineTo(new_point)); - } - } - - paths.push(PathEl::ClosePath); - - let bez_path = BezPath::from_vec(paths); - let (shape, bbox) = (bez_path.clone(), bez_path.bounding_box()); - - if bbox.x0 > mouse_position.x || bbox.y0 > mouse_position.y || bbox.x1 < mouse_position.x || bbox.y1 < mouse_position.y { - return false; - } - - let winding = shape.winding(dvec2_to_point(mouse_position)); - - // Non-zero fill rule - winding != 0 - } - - fn draw_spokes(&mut self, center: DVec2, viewport: DAffine2, n: u32, radius: f64, overlay_context: &mut OverlayContext) { - for i in 0..n { - let angle = i as f64 * TAU / n as f64; - - let point = viewport.transform_point2(DVec2 { - x: radius * angle.sin(), - y: -radius * angle.cos(), - }); - - let Some(direction) = (point - center).try_normalize() else { continue }; - - // If the user zooms out such that shape is very small hide the gizmo - if point.distance(center) < GIZMO_HIDE_THRESHOLD { - return; - } - - let end_point = direction * NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH; - if matches!(self.handle_state, NumberOfPointsHandleState::Hover | NumberOfPointsHandleState::Dragging) { - overlay_context.line(center, end_point * NUMBER_OF_POINTS_HANDLE_SPOKE_EXTENSION + center, None, None); - } else { - overlay_context.line(center, end_point + center, None, None); - } - } - } - - pub fn update_no_of_sides(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { - let delta = input.mouse.position - document.metadata().document_to_viewport.transform_point2(drag_start); - let sign = (input.mouse.position.x - document.metadata().document_to_viewport.transform_point2(drag_start).x).signum(); - let net_delta = (delta.length() / 25.).round() * sign; - - let Some(layer) = self.layer else { return }; - - let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface) else { - return; - }; - - let new_point_count = (self.initial_points as i32 + net_delta as i32).max(3); - log::info!("Updated point count: {:?}", new_point_count); - - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 1), - input: NodeInput::value(TaggedValue::U32(new_point_count as u32), false), - }); - responses.add(NodeGraphMessage::RunDocumentGraph); - } -} - -impl PointRadiusHandle { - pub fn cleanup(&mut self) { - self.handle_state = PointRadiusHandleState::Inactive; - self.snap_radii.clear(); - self.layer = None; - } - - pub fn is_inactive(&self) -> bool { - self.handle_state == PointRadiusHandleState::Inactive - } - - pub fn hovered(&self) -> bool { - self.handle_state == PointRadiusHandleState::Hover - } - - pub fn update_state(&mut self, state: PointRadiusHandleState) { - self.handle_state = state; - } - - pub fn overlays( - &mut self, - document: &DocumentMessageHandler, - no_of_point_gizmo: Option, - input: &InputPreprocessorMessageHandler, - mouse_position: DVec2, - overlay_context: &mut OverlayContext, - ) { - match &self.handle_state { - PointRadiusHandleState::Inactive => { - if let Some(layer) = no_of_point_gizmo { - let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { - return; - }; - - let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(outer)), Some(&TaggedValue::F64(inner))) = - (node_inputs[1].as_value(), node_inputs[2].as_value(), node_inputs[3].as_value()) - else { - return; - }; - - let viewport = document.metadata().transform_to_viewport(layer); - let center = viewport.transform_point2(DVec2::ZERO); - - for i in 0..(2 * n) { - let angle = i as f64 * PI / n as f64; - let radius = if i % 2 == 0 { outer } else { inner }; - - let point = viewport.transform_point2(DVec2 { - x: radius * angle.sin(), - y: -radius * angle.cos(), - }); - - if point.distance(center) < GIZMO_HIDE_THRESHOLD { - return; - } - - overlay_context.manipulator_handle(point, false, None); - } - return; - } - - for layer in document - .network_interface - .selected_nodes() - .selected_visible_and_unlocked_layers(&document.network_interface) - .filter(|layer| graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some()) - { - let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { - return; - }; - - let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(outer)), Some(&TaggedValue::F64(inner))) = - (node_inputs[1].as_value(), node_inputs[2].as_value(), node_inputs[3].as_value()) - else { - return; - }; - - let viewport = document.metadata().transform_to_viewport(layer); - - for i in 0..(2 * n) { - let angle = i as f64 * PI / n as f64; - let (radius, radius_index) = if i % 2 == 0 { (outer, 2) } else { (inner, 3) }; - - let point = viewport.transform_point2(DVec2 { - x: radius * angle.sin(), - y: -radius * angle.cos(), - }); - - let center = viewport.transform_point2(DVec2::ZERO); - let viewport_diagonal = input.viewport_bounds.size().length(); - - // If the user zooms out such that shape is very small hide the gizmo - if point.distance(center) < GIZMO_HIDE_THRESHOLD { - return; - } - - if point.distance(mouse_position) < 5.0 { - let Some(direction) = (point - center).try_normalize() else { - continue; - }; - self.radius_index = radius_index; - self.layer = Some(layer); - self.point = i; - self.snap_radii = Self::calculate_snap_radii(document, layer, radius_index); - self.initial_radius = radius; - self.update_state(PointRadiusHandleState::Hover); - overlay_context.manipulator_handle(point, true, None); - overlay_context.line(center, center + direction * viewport_diagonal, None, None); - - break; - } - - overlay_context.manipulator_handle(point, false, None); - } - } - } - - PointRadiusHandleState::Dragging | PointRadiusHandleState::Hover => { - let Some(layer) = self.layer else { return }; - - let viewport = document.metadata().transform_to_viewport(layer); - let center = viewport.transform_point2(DVec2::ZERO); - let viewport_diagonal = input.viewport_bounds.size().length(); - - let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { - return; - }; - - let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(outer)), Some(&TaggedValue::F64(inner))) = (node_inputs[1].as_value(), node_inputs[2].as_value(), node_inputs[3].as_value()) - else { - return; - }; - let angle = self.point as f64 * PI / n as f64; - let radius = if self.point % 2 == 0 { outer } else { inner }; - - let point = viewport.transform_point2(DVec2 { - x: radius * angle.sin(), - y: -radius * angle.cos(), - }); - - if matches!(&self.handle_state, PointRadiusHandleState::Hover) { - if (mouse_position - point).length() > 5. { - self.update_state(PointRadiusHandleState::Inactive); - self.layer = None; - return; - } - } - - let Some(direction) = (point - center).try_normalize() else { return }; - - // Draws the ray from the center to the dragging point extending till the viewport - overlay_context.manipulator_handle(point, true, None); - overlay_context.line(center, center + direction * viewport_diagonal, None, None); - - // makes the ticks for snapping - - // if dragging to make radius negative don't show the - if (mouse_position - center).dot(direction) < 0. { - return; - } - - for snapped_radius in &self.snap_radii { - let Some(tick_direction) = direction.perp().try_normalize() else { return }; - - let tick_position = viewport.transform_point2(DVec2 { - x: snapped_radius * angle.sin(), - y: -snapped_radius * angle.cos(), - }); - - overlay_context.line(tick_position, tick_position + tick_direction * 5., None, Some(2.)); - overlay_context.line(tick_position, tick_position - tick_direction * 5., None, Some(2.)); - } - } - PointRadiusHandleState::Snapped(snapping_index) => { - let Some(layer) = self.layer else { return }; - let viewport = document.metadata().transform_to_viewport(layer); - - let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { - return; - }; - - let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(outer)), Some(&TaggedValue::F64(inner))) = (node_inputs[1].as_value(), node_inputs[2].as_value(), node_inputs[3].as_value()) - else { - return; - }; - - let radius = |i: i32| -> f64 { if i.abs() % 2 == 0 { outer } else { inner } }; - let viewport_position = |i: i32, radius: f64| -> DVec2 { - let angle = i as f64 * PI / n as f64; - - viewport.transform_point2(DVec2 { - x: radius * angle.sin(), - y: -radius * angle.cos(), - }) - }; - - match snapping_index { - //Make a triangle with previous two points - 0 => { - let outer_radius: f64 = radius(self.point as i32 - 1); - let outer_position = viewport_position(self.point as i32 - 1, outer_radius); - - let before_outer_radius = radius(self.point as i32 - 2); - let before_outer_position = viewport_position(self.point as i32 - 2, before_outer_radius); - - let point_radius = radius(self.point as i32); - let point_position = viewport_position(self.point as i32, point_radius); - - overlay_context.line(before_outer_position, outer_position, Some(COLOR_OVERLAY_RED), Some(3.)); - overlay_context.line(outer_position, point_position, Some(COLOR_OVERLAY_RED), Some(3.)); - - let l1 = (before_outer_position - outer_position).length() * 0.2; - let Some(l1_direction) = (before_outer_position - outer_position).try_normalize() else { return }; - let l1_angle = -l1_direction.angle_to(DVec2::X); - - let Some(l2_direction) = (point_position - outer_position).try_normalize() else { return }; - let l2_angle = -l2_direction.angle_to(DVec2::X); - - let net_angle = (l2_angle + l1_angle) / 2.; - - let new_point = SQRT_2 * l1 * DVec2::from_angle(net_angle) + outer_position; - - let before_outer_position = l1 * l1_direction + outer_position; - let point_position = l1 * l2_direction + outer_position; - - overlay_context.line(before_outer_position, new_point, Some(COLOR_OVERLAY_RED), Some(3.)); - overlay_context.line(new_point, point_position, Some(COLOR_OVERLAY_RED), Some(3.)); - } - 1 => { - let before_outer_radius = radius(self.point as i32 - 1); - let before_outer_position = viewport_position(self.point as i32 - 1, before_outer_radius); - - let after_point_radius = radius(self.point as i32 + 1); - let after_point_position = viewport_position(self.point as i32 + 1, after_point_radius); - - let point_radius = radius(self.point as i32); - let point_position = viewport_position(self.point as i32, point_radius); - - overlay_context.line(before_outer_position, point_position, Some(COLOR_OVERLAY_RED), Some(3.)); - overlay_context.line(point_position, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.)); - - let l1 = (before_outer_position - point_position).length() * 0.2; - let Some(l1_direction) = (before_outer_position - point_position).try_normalize() else { return }; - let l1_angle = -l1_direction.angle_to(DVec2::X); - - let Some(l2_direction) = (after_point_position - point_position).try_normalize() else { return }; - let l2_angle = -l2_direction.angle_to(DVec2::X); - - let net_angle = (l2_angle + l1_angle) / 2.; - - let new_point = SQRT_2 * l1 * DVec2::from_angle(net_angle) + point_position; - - let before_outer_position = l1 * l1_direction + point_position; - let after_point_position = l1 * l2_direction + point_position; - - overlay_context.line(before_outer_position, new_point, Some(COLOR_OVERLAY_RED), Some(3.)); - overlay_context.line(new_point, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.)); - } - i => { - // use 'self.point' as absolute reference as it match the index of vertices of star starting from 0 - if i % 2 != 0 { - // flipped case - let point_radius = radius(self.point as i32); - let point_position = viewport_position(self.point as i32, point_radius); - - let target_index = (1 - *i as i32).abs() + self.point as i32; - let target_point_radius = radius(target_index); - let target_point_position = viewport_position(target_index, target_point_radius); - - let mirrored_index = 2 * (self.point as i32) - target_index; - let mirrored = viewport_position(mirrored_index, target_point_radius); - - overlay_context.line(point_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.)); - overlay_context.line(point_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.)); - } else { - let outer_index = self.point as i32 - 1; - let outer_radius = radius(outer_index); - let outer_position = viewport_position(outer_index, outer_radius); - - // the vertex which is colinear with the point we are dragging and its previous outer vertex - let target_index = self.point as i32 + *i as i32 - 1; - let target_point_radius = radius(target_index); - let target_point_position = viewport_position(target_index, target_point_radius); - - let mirrored_index = 2 * outer_index - target_index; - - let mirrored = viewport_position(mirrored_index, target_point_radius); - - overlay_context.line(outer_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.)); - overlay_context.line(outer_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.)); - } - } - } - // 0,1 90 - } - } - } - fn calculate_snap_radii(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, radius_index: usize) -> Vec { - let mut snap_radii = Vec::new(); - - let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else { - return snap_radii; - }; - - let other_index = if radius_index == 3 { 2 } else { 3 }; - - let Some(&TaggedValue::F64(other_radius)) = node_inputs[other_index].as_value() else { - return snap_radii; - }; - - let Some(&TaggedValue::U32(n)) = node_inputs[1].as_value() else { - return snap_radii; - }; - - // inner radius for 90 - let b = FRAC_PI_4 * (3.) - (PI / n as f64); - let angle = b.sin(); - let required_radius = (other_radius / angle) * (FRAC_1_SQRT_2); - - snap_radii.push(required_radius); - - // also push the case when the when it length increases more than the other - - let flipped = other_radius * angle * SQRT_2; - - snap_radii.push(flipped); - - for i in 1..n { - let n = n as f64; - let i = i as f64; - let denominator = 2. * (PI * (i - 1.) / n).cos() * (PI * i / n).sin(); - let numerator = (2. * PI * i / n).sin(); - let factor = numerator / denominator; - - if factor < 0. { - break; - } - - if (other_radius * factor) > 1e-6 { - snap_radii.push(other_radius * factor); - } - - snap_radii.push(other_radius * 1. / factor); - } - - snap_radii - } - - fn check_snapping(&self, new_radius: f64, original_radius: f64) -> Option<(usize, f64)> { - self.snap_radii - .iter() - .enumerate() - .filter(|(_, rad)| (**rad - new_radius).abs() < POINT_RADIUS_HANDLE_SNAP_THRESHOLD) - .min_by(|(_, a), (_, b)| (**a - new_radius).abs().partial_cmp(&(**b - new_radius).abs()).unwrap_or(std::cmp::Ordering::Equal)) - .map(|(i, rad)| (i, *rad - original_radius)) - } -} - -#[derive(Clone, Debug, Default)] -pub struct StarShapeData { - pub point_radius_handle: PointRadiusHandle, - pub number_of_points_handle: NumberOfPointsHandle, -} - -impl StarShapeData { - pub fn star_gizmos( - &mut self, - document: &DocumentMessageHandler, - input: &InputPreprocessorMessageHandler, - shape_editor: &mut &mut ShapeState, - mouse_position: DVec2, - overlay_context: &mut OverlayContext, - responses: &mut VecDeque, - ) { - if !self.number_of_points_handle.is_dragging() { - self.point_radius_handle.overlays(document, self.point_radius_handle.layer, input, mouse_position, overlay_context); - } - - if self.point_radius_handle.is_inactive() { - self.number_of_points_handle.overlays(document, input, shape_editor, mouse_position, overlay_context, responses); - } - - self.star_outline_overlays(document, overlay_context); - } - - pub fn star_outline_overlays(&self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { - // Prefer explicitly targeted handle layers - if let Some(layer) = self.number_of_points_handle.layer.or(self.point_radius_handle.layer) { - star_outline(layer, document, overlay_context); - return; - } - - // Fallback: apply to all selected visible & unlocked star layers - for layer in document - .network_interface - .selected_nodes() - .selected_visible_and_unlocked_layers(&document.network_interface) - .filter(|layer| graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some()) - { - star_outline(layer, document, overlay_context); - } - } - - pub fn update_inner_radius( - &mut self, - document: &DocumentMessageHandler, - input: &InputPreprocessorMessageHandler, - layer: LayerNodeIdentifier, - responses: &mut VecDeque, - drag_start: DVec2, - ) { - let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface) else { - return; - }; - - let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { - return; - }; - - let path = vector_data.stroke_bezier_paths().next().unwrap(); - let center = path.length_centroid(None, true).unwrap(); - let transform = document.network_interface.document_metadata().transform_to_viewport(layer); - let radius_index = self.point_radius_handle.radius_index; - - let original_radius = self.point_radius_handle.initial_radius; - - let delta = input.mouse.position - document.metadata().document_to_viewport.transform_point2(drag_start); - let radius = document.metadata().document_to_viewport.transform_point2(drag_start) - transform.transform_point2(center); - let projection = delta.project_onto(radius); - let sign = radius.dot(delta).signum(); - - let mut net_delta = projection.length() * sign; - let new_radius = original_radius + net_delta; - - self.point_radius_handle.update_state(PointRadiusHandleState::Dragging); - if let Some((index, snapped_delta)) = self.point_radius_handle.check_snapping(new_radius, original_radius) { - net_delta = snapped_delta; - self.point_radius_handle.update_state(PointRadiusHandleState::Snapped(index)); - } - - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, radius_index), - input: NodeInput::value(TaggedValue::F64(original_radius + net_delta), false), - }); - responses.add(NodeGraphMessage::RunDocumentGraph); - } -} - impl Star { pub fn create_node(vertices: u32) -> NodeTemplate { let node_type = resolve_document_node_type("Star").expect(" Star node does not exist"); @@ -717,7 +33,7 @@ impl Star { shape_tool_data: &mut ShapeToolData, modifier: ShapeToolModifierKey, responses: &mut VecDeque, - ) -> bool { + ) { let (center, lock_ratio) = (modifier[0], modifier[1]); if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) { // TODO: We need to determine how to allow the polygon node to make irregular shapes @@ -737,7 +53,7 @@ impl Star { } let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface) else { - return false; + return; }; responses.add(NodeGraphMessage::SetInput { @@ -747,16 +63,15 @@ impl Star { responses.add(NodeGraphMessage::SetInput { input_connector: InputConnector::node(node_id, 3), - input: NodeInput::value(TaggedValue::F64(radius / 2.), false), + input: NodeInput::value(TaggedValue::F64(radius / 2.0), false), }); responses.add(GraphOperationMessage::TransformSet { layer, - transform: DAffine2::from_scale_angle_translation(scale, 0., (start + end) / 2.), + transform: DAffine2::from_scale_angle_translation(scale, 0.0, (start + end) / 2.0), transform_in: TransformIn::Viewport, skip_rerender: false, }); } - false } } diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index fe6e10db04..b19de5c259 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -65,11 +65,9 @@ impl MessageHandler> for ToolMessageHandler { self.tool_state.tool_data.active_tool_type = ToolType::Shape; } responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape }); - responses.add(ShapeToolMessage::SetShape(ShapeType::Convex)); + responses.add(ShapeToolMessage::SetShape(ShapeType::Polygon)); responses.add(ShapeToolMessage::HideShapeTypeWidget(false)) } - ToolMessage::ActivateToolPolygon => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Polygon }), - ToolMessage::ActivateToolBrush => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Brush }), ToolMessage::ActivateShapeRectangle | ToolMessage::ActivateShapeEllipse | ToolMessage::ActivateShapeLine => { responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape }); @@ -80,8 +78,6 @@ impl MessageHandler> for ToolMessageHandler { _ => unreachable!(), }; self.tool_state.tool_data.active_shape_type = Some(shape.tool_type()); - responses.add(ToolMessage::RefreshToolOptions); - self.tool_state.tool_data.send_layout(responses, LayoutTarget::ToolShelf); responses.add(ShapeToolMessage::HideShapeTypeWidget(true)); responses.add(ShapeToolMessage::SetShape(shape)); } @@ -90,12 +86,9 @@ impl MessageHandler> for ToolMessageHandler { let tool_data = &mut self.tool_state.tool_data; let old_tool = tool_data.active_tool_type; - // let shape = tool_type; let tool_type = tool_type.get_tool(); let old_tool = old_tool.get_tool(); - // tool_data.active_shape_type = if tool_type != ToolType::Shape { None } else { old_shape }; - responses.add(ToolMessage::RefreshToolOptions); tool_data.send_layout(responses, LayoutTarget::ToolShelf); // Do nothing if switching to the same tool diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 4e8bcf7b02..ad9f500645 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -3,19 +3,24 @@ use crate::consts::{DEFAULT_STROKE_WIDTH, SNAP_POINT_TOLERANCE}; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; 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; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; +use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; use crate::messages::tool::common_functionality::graph_modification_utils::{self}; use crate::messages::tool::common_functionality::resize::Resize; -use crate::messages::tool::common_functionality::shapes::convex_shape::Convex; +use crate::messages::tool::common_functionality::shape_gizmos::number_of_points_handle::{NumberOfPointsHandle, NumberOfPointsHandleState}; +use crate::messages::tool::common_functionality::shape_gizmos::point_radius_handle::{PointRadiusHandle, PointRadiusHandleState}; use crate::messages::tool::common_functionality::shapes::line_shape::{LineToolData, clicked_on_line_endpoints}; -use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays}; -use crate::messages::tool::common_functionality::shapes::star_shape::{NumberOfPointsHandleState, PointRadiusHandleState, Star, StarShapeData}; +use crate::messages::tool::common_functionality::shapes::polygon_shape::Polygon; +use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, polygon_outline, star_outline, transform_cage_overlays}; +use crate::messages::tool::common_functionality::shapes::star_shape::Star; 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}; use crate::messages::tool::common_functionality::utility_functions::{closest_point, resize_bounds, rotate_bounds, skew_bounds, transforming_transform_cage}; -use graph_craft::document::NodeId; +use graph_craft::document::value::TaggedValue; +use graph_craft::document::{NodeId, NodeInput}; use graphene_core::Color; use graphene_std::renderer::Quad; use std::vec; @@ -41,7 +46,7 @@ impl Default for ShapeToolOptions { line_weight: DEFAULT_STROKE_WIDTH, fill: ToolColorOptions::new_secondary(), stroke: ToolColorOptions::new_primary(), - shape_type: ShapeType::Convex, + shape_type: ShapeType::Polygon, vertices: 5, } } @@ -75,6 +80,11 @@ pub enum ShapeToolMessage { PointerOutsideViewport(ShapeToolModifierKey), UpdateOptions(ShapeOptionsUpdate), SetShape(ShapeType), + + IncreaseSides, + DecreaseSides, + + NudgeSelectedLayers { delta_x: f64, delta_y: f64, resize: Key, resize_opposite_corner: Key }, } fn create_sides_widget(vertices: u32) -> WidgetHolder { @@ -89,11 +99,12 @@ fn create_sides_widget(vertices: u32) -> WidgetHolder { } fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder { + log::info!("shape_type {:?}", shape_type); let entries = vec![vec![ - MenuListEntry::new("convex") - .label("Convex") - .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Convex)).into()), - MenuListEntry::new("star") + MenuListEntry::new("Polygon") + .label("Polygon") + .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Polygon)).into()), + MenuListEntry::new("Star") .label("Star") .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Star)).into()), ]]; @@ -118,7 +129,7 @@ impl LayoutHolder for ShapeTool { widgets.push(create_shape_option_widget(self.options.shape_type)); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); - if self.options.shape_type == ShapeType::Convex || self.options.shape_type == ShapeType::Star { + if self.options.shape_type == ShapeType::Polygon || self.options.shape_type == ShapeType::Star { widgets.push(create_sides_widget(self.options.vertices)); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); } @@ -200,7 +211,10 @@ impl<'a> MessageHandler> for ShapeTo PointerMove, SetShape, Abort, - HideShapeTypeWidget + HideShapeTypeWidget, + IncreaseSides, + DecreaseSides, + NudgeSelectedLayers, ), ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::ResizingBounds @@ -214,7 +228,10 @@ impl<'a> MessageHandler> for ShapeTo Abort, PointerMove, SetShape, - HideShapeTypeWidget + HideShapeTypeWidget, + IncreaseSides, + DecreaseSides, + NudgeSelectedLayers, ) } } @@ -228,7 +245,7 @@ impl ToolMetadata for ShapeTool { fn tooltip(&self) -> String { "Shape Tool".into() } - fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { + fn tool_type(&self) -> ToolType { ToolType::Shape } } @@ -273,13 +290,14 @@ pub struct ShapeToolData { pub data: Resize, auto_panning: AutoPanning, - // in viewport space + // In viewport space pub last_mouse_position: DVec2, // Hide the dropdown menu when using line,rectangle or ellipse aliases pub hide_shape_option_widget: bool, + + // Shape-specific data pub line_data: LineToolData, - pub star_data: StarShapeData, // Used for by transform cage pub bounding_box_manager: Option, @@ -290,6 +308,10 @@ pub struct ShapeToolData { // Current shape which is being drawn current_shape: ShapeType, + + // Gizmo-data + pub point_radius_handle: PointRadiusHandle, + pub number_of_points_handle: NumberOfPointsHandle, } impl ShapeToolData { @@ -305,6 +327,26 @@ impl ShapeToolData { } } } + + fn outlines(&self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { + if let Some(layer) = self.number_of_points_handle.layer.or(self.point_radius_handle.layer) { + star_outline(layer, document, overlay_context); + polygon_outline(layer, document, overlay_context); + return; + } + + // Fallback: apply to all selected visible & unlocked star layers + for layer in document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter(|layer| { + graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some() + }) { + star_outline(layer, document, overlay_context); + polygon_outline(layer, document, overlay_context); + } + } } impl Fsm for ShapeToolFsmState { @@ -326,6 +368,12 @@ impl Fsm for ShapeToolFsmState { tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { + let all_selected_layers_line = document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .all(|layer| graph_modification_utils::get_line_id(layer, &document.network_interface).is_some()); + let ToolMessage::Shape(event) = event else { return self; }; @@ -341,29 +389,39 @@ impl Fsm for ShapeToolFsmState { let dragging_start_gizmos = matches!(self, Self::DraggingStarInnerRadius); if matches!(self, ShapeToolFsmState::DraggingStarInnerRadius | Self::DraggingStarNumberPointHandle | Self::Ready(_)) && !input.keyboard.key(Key::Control) { - tool_data.star_data.star_gizmos(document, input, shape_editor, mouse_position, &mut overlay_context, responses); + // Manage state handling of Number of Point Gizmo + tool_data.number_of_points_handle.handle_actions(document, input, mouse_position, &mut overlay_context, responses); + + // Manage state handling of Point-Radius-Handle Gizmo + tool_data.point_radius_handle.handle_actions(document, mouse_position); + + tool_data.number_of_points_handle.overlays(document, input, shape_editor, mouse_position, &mut overlay_context); + tool_data + .point_radius_handle + .overlays(tool_data.number_of_points_handle.layer.is_some(), document, input, mouse_position, &mut overlay_context); + tool_data.outlines(document, &mut overlay_context); } - let hovered = - tool_data.star_data.number_of_points_handle.is_hovering() || tool_data.star_data.number_of_points_handle.is_dragging() || tool_data.star_data.point_radius_handle.hovered(); + let hovered = tool_data.number_of_points_handle.is_hovering() || tool_data.number_of_points_handle.is_dragging() || !tool_data.point_radius_handle.is_inactive(); + let modifying_transform_cage = matches!(self, ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::RotatingBounds | ShapeToolFsmState::SkewingBounds { .. }); - if !is_resizing_or_rotating && !dragging_start_gizmos && !hovered { + if !is_resizing_or_rotating && !dragging_start_gizmos && !hovered && !modifying_transform_cage { tool_data.data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); } + if modifying_transform_cage { + transform_cage_overlays(document, tool_data, &mut overlay_context); + } + if input.keyboard.key(Key::Control) && matches!(self, ShapeToolFsmState::Ready(_)) { anchor_overlays(document, &mut overlay_context); } else if matches!(self, ShapeToolFsmState::Ready(_)) { Line::overlays(document, tool_data, &mut overlay_context); - if document - .network_interface - .selected_nodes() - .selected_visible_and_unlocked_layers(&document.network_interface) - .all(|layer| graph_modification_utils::get_line_id(layer, &document.network_interface).is_some()) - { + if all_selected_layers_line { return self; } + transform_cage_overlays(document, tool_data, &mut overlay_context); let dragging_bounds = tool_data @@ -387,6 +445,109 @@ impl Fsm for ShapeToolFsmState { } } + if matches!(self, ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::DraggingLineEndpoints) { + Line::overlays(document, tool_data, &mut overlay_context); + } + + self + } + (ShapeToolFsmState::Ready(_), ShapeToolMessage::IncreaseSides) => { + responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(tool_options.vertices + 1))); + self + } + (ShapeToolFsmState::Ready(_), ShapeToolMessage::DecreaseSides) => { + responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices((tool_options.vertices - 1).max(3)))); + self + } + ( + ShapeToolFsmState::Ready(_), + ShapeToolMessage::NudgeSelectedLayers { + delta_x, + delta_y, + resize, + resize_opposite_corner, + }, + ) => { + responses.add(DocumentMessage::NudgeSelectedLayers { + delta_x, + delta_y, + resize, + resize_opposite_corner, + }); + + self + } + (ShapeToolFsmState::Drawing(_), ShapeToolMessage::NudgeSelectedLayers { .. }) => { + let increase = input.keyboard.key(Key::ArrowUp); + let decrease = input.keyboard.key(Key::ArrowDown); + + if increase { + responses.add(ShapeToolMessage::IncreaseSides); + return self; + } + + if decrease { + responses.add(ShapeToolMessage::DecreaseSides); + return self; + } + self + } + (ShapeToolFsmState::Drawing(_), ShapeToolMessage::IncreaseSides) => { + if let Some(layer) = tool_data.data.layer { + let Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) + else { + return self; + }; + + let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface) + .find_node_inputs("Regular Polygon") + .or(NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star")) + else { + return self; + }; + + let Some(&TaggedValue::U32(n)) = node_inputs.get(1).unwrap().as_value() else { + return self; + }; + + responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(n + 1))); + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 1), + input: NodeInput::value(TaggedValue::U32(n + 1), false), + }); + responses.add(NodeGraphMessage::RunDocumentGraph); + } + + self + } + (ShapeToolFsmState::Drawing(_), ShapeToolMessage::DecreaseSides) => { + if let Some(layer) = tool_data.data.layer { + let Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) + else { + return self; + }; + + let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface) + .find_node_inputs("Regular Polygon") + .or(NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star")) + else { + return self; + }; + + let Some(&TaggedValue::U32(n)) = node_inputs.get(1).unwrap().as_value() else { + return self; + }; + + responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices((n - 1).max(3)))); + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 1), + input: NodeInput::value(TaggedValue::U32((n - 1).max(3)), false), + }); + responses.add(NodeGraphMessage::RunDocumentGraph); + } + self } (ShapeToolFsmState::Ready(_), ShapeToolMessage::DragStart) => { @@ -403,9 +564,10 @@ impl Fsm for ShapeToolFsmState { tool_data.line_data.drag_current = mouse_pos; // Check if dragging the inner vertices of a star - if tool_data.star_data.point_radius_handle.hovered() { + + if tool_data.point_radius_handle.hovered() { tool_data.last_mouse_position = mouse_pos; - tool_data.star_data.point_radius_handle.update_state(PointRadiusHandleState::Dragging); + tool_data.point_radius_handle.update_state(PointRadiusHandleState::Dragging); // Always store it in document space tool_data.data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(mouse_pos); @@ -414,10 +576,10 @@ impl Fsm for ShapeToolFsmState { return ShapeToolFsmState::DraggingStarInnerRadius; } - // Check if dragging the Number of Points handle of a star - if tool_data.star_data.number_of_points_handle.is_hovering() { + // Check if dragging the Number of Points handle of a star or polygon + if tool_data.number_of_points_handle.is_hovering() { tool_data.last_mouse_position = mouse_pos; - tool_data.star_data.number_of_points_handle.update_state(NumberOfPointsHandleState::Dragging); + tool_data.number_of_points_handle.update_state(NumberOfPointsHandleState::Dragging); // Always store it in document space tool_data.data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(mouse_pos); @@ -437,29 +599,31 @@ impl Fsm for ShapeToolFsmState { ) { if clicked_on_line_endpoints(layer, document, input, tool_data) && !input.keyboard.key(Key::Control) { return ShapeToolFsmState::DraggingLineEndpoints; - }; + } } let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging); - match (resize, rotate, skew) { - (true, false, false) => { - tool_data.get_snap_candidates(document, input); - return ShapeToolFsmState::ResizingBounds; - } - (false, true, false) => { - tool_data.data.drag_start = mouse_pos; - return ShapeToolFsmState::RotatingBounds; - } - (false, false, true) => { - tool_data.get_snap_candidates(document, input); - return ShapeToolFsmState::SkewingBounds { skew: Key::Control }; + if !input.keyboard.key(Key::Control) { + match (resize, rotate, skew) { + (true, false, false) => { + tool_data.get_snap_candidates(document, input); + return ShapeToolFsmState::ResizingBounds; + } + (false, true, false) => { + tool_data.data.drag_start = mouse_pos; + return ShapeToolFsmState::RotatingBounds; + } + (false, false, true) => { + tool_data.get_snap_candidates(document, input); + return ShapeToolFsmState::SkewingBounds { skew: Key::Control }; + } + _ => {} } - _ => {} - } + }; match tool_data.current_shape { - ShapeType::Convex | ShapeType::Star | ShapeType::Ellipse | ShapeType::Rectangle => tool_data.data.start(document, input), + ShapeType::Polygon | ShapeType::Star | ShapeType::Ellipse | ShapeType::Rectangle => tool_data.data.start(document, input), ShapeType::Line => { let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); let snapped = tool_data.data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); @@ -470,7 +634,7 @@ impl Fsm for ShapeToolFsmState { responses.add(DocumentMessage::StartTransaction); let node = match tool_data.current_shape { - ShapeType::Convex => Convex::create_node(tool_options.vertices), + ShapeType::Polygon => Polygon::create_node(tool_options.vertices), ShapeType::Star => Star::create_node(tool_options.vertices), ShapeType::Rectangle => Rectangle::create_node(), ShapeType::Ellipse => Ellipse::create_node(), @@ -484,10 +648,10 @@ impl Fsm for ShapeToolFsmState { tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); match tool_data.current_shape { - ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Convex | ShapeType::Star => { + ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Polygon | ShapeType::Star => { responses.add(GraphOperationMessage::TransformSet { layer, - transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), + transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0.0, input.mouse.position), transform_in: TransformIn::Viewport, skip_rerender: false, }); @@ -495,7 +659,7 @@ impl Fsm for ShapeToolFsmState { tool_options.fill.apply_fill(layer, responses); } ShapeType::Line => { - tool_data.line_data.angle = 0.; + tool_data.line_data.angle = 0.0; tool_data.line_data.weight = tool_options.line_weight; tool_data.line_data.editing_layer = Some(layer); } @@ -509,14 +673,13 @@ impl Fsm for ShapeToolFsmState { let Some(layer) = tool_data.data.layer else { return ShapeToolFsmState::Ready(shape); }; - if match tool_data.current_shape { + + match tool_data.current_shape { ShapeType::Rectangle => Rectangle::update_shape(&document, &input, layer, tool_data, modifier, responses), ShapeType::Ellipse => Ellipse::update_shape(&document, &input, layer, tool_data, modifier, responses), ShapeType::Line => Line::update_shape(&document, &input, layer, tool_data, modifier, responses), - ShapeType::Convex => Convex::update_shape(&document, &input, layer, tool_data, modifier, responses), + ShapeType::Polygon => Polygon::update_shape(&document, &input, layer, tool_data, modifier, responses), ShapeType::Star => Star::update_shape(&document, &input, layer, tool_data, modifier, responses), - } { - return if tool_data.current_shape == ShapeType::Line { ShapeToolFsmState::Ready(shape) } else { self }; } // Auto-panning @@ -538,8 +701,8 @@ impl Fsm for ShapeToolFsmState { self } (ShapeToolFsmState::DraggingStarInnerRadius, ShapeToolMessage::PointerMove(..)) => { - if let Some(layer) = tool_data.star_data.point_radius_handle.layer { - tool_data.star_data.update_inner_radius(document, input, layer, responses, tool_data.data.drag_start); + if let Some(layer) = tool_data.point_radius_handle.layer { + tool_data.point_radius_handle.update_inner_radius(document, input, layer, responses, tool_data.data.drag_start); tool_data.last_mouse_position = input.mouse.position; } @@ -548,7 +711,7 @@ impl Fsm for ShapeToolFsmState { ShapeToolFsmState::DraggingStarInnerRadius } (ShapeToolFsmState::DraggingStarNumberPointHandle, ShapeToolMessage::PointerMove(..)) => { - tool_data.star_data.number_of_points_handle.update_no_of_sides(document, input, responses, tool_data.data.drag_start); + tool_data.number_of_points_handle.update_no_of_sides(document, input, responses, tool_data.data.drag_start); tool_data.last_mouse_position = input.mouse.position; responses.add(OverlaysMessage::Draw); @@ -566,8 +729,8 @@ impl Fsm for ShapeToolFsmState { &mut tool_data.data.snap_manager, &mut tool_data.snap_candidates, input, - input.keyboard.key(Key::Shift), - input.keyboard.key(Key::Alt), + input.keyboard.key(modifier[0]), + input.keyboard.key(modifier[1]), ToolType::Shape, ); tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); @@ -576,7 +739,7 @@ impl Fsm for ShapeToolFsmState { responses.add(OverlaysMessage::Draw); ShapeToolFsmState::ResizingBounds } - (ShapeToolFsmState::RotatingBounds, ShapeToolMessage::PointerMove(_)) => { + (ShapeToolFsmState::RotatingBounds, ShapeToolMessage::PointerMove(modifier)) => { if let Some(bounds) = &mut tool_data.bounding_box_manager { rotate_bounds( document, @@ -585,7 +748,7 @@ impl Fsm for ShapeToolFsmState { &mut tool_data.layers_dragging, tool_data.data.drag_start, input.mouse.position, - input.keyboard.key(Key::Shift), + input.keyboard.key(modifier[1]), ToolType::Shape, ); } @@ -608,7 +771,7 @@ impl Fsm for ShapeToolFsmState { ShapeToolFsmState::SkewingBounds { skew } } - (_, ShapeToolMessage::PointerMove { .. }) => { + (_, ShapeToolMessage::PointerMove(_)) => { let dragging_bounds = tool_data .bounding_box_manager .as_mut() @@ -620,7 +783,7 @@ impl Fsm for ShapeToolFsmState { .as_ref() .map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge))); - if tool_data.cursor != cursor && !input.keyboard.key(Key::Control) && tool_data.star_data.point_radius_handle.is_inactive() { + if tool_data.cursor != cursor && !input.keyboard.key(Key::Control) && tool_data.point_radius_handle.is_inactive() && !all_selected_layers_line { tool_data.cursor = cursor; responses.add(FrontendMessage::UpdateMouseCursor { cursor }); } @@ -660,8 +823,8 @@ impl Fsm for ShapeToolFsmState { input.mouse.finish_transaction(tool_data.data.drag_start, responses); tool_data.data.cleanup(responses); - tool_data.star_data.point_radius_handle.cleanup(); - tool_data.star_data.number_of_points_handle.cleanup(); + tool_data.number_of_points_handle.cleanup(); + tool_data.point_radius_handle.cleanup(); if let Some(bounds) = &mut tool_data.bounding_box_manager { bounds.original_transforms.clear(); @@ -673,10 +836,29 @@ impl Fsm for ShapeToolFsmState { ShapeToolFsmState::Ready(tool_data.current_shape) } - (ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::DraggingLineEndpoints, ShapeToolMessage::Abort) => { - tool_data.line_data.dragging_endpoint = None; + ( + ShapeToolFsmState::Drawing(_) + | ShapeToolFsmState::DraggingLineEndpoints + | ShapeToolFsmState::ResizingBounds + | ShapeToolFsmState::RotatingBounds + | ShapeToolFsmState::SkewingBounds { .. } + | ShapeToolFsmState::DraggingStarInnerRadius + | ShapeToolFsmState::DraggingStarNumberPointHandle, + ShapeToolMessage::Abort, + ) => { responses.add(DocumentMessage::AbortTransaction); tool_data.data.cleanup(responses); + tool_data.line_data.dragging_endpoint = None; + + // Reset gizmo state + tool_data.number_of_points_handle.cleanup(); + tool_data.point_radius_handle.cleanup(); + + if let Some(bounds) = &mut tool_data.bounding_box_manager { + bounds.original_transforms.clear(); + } + + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }); ShapeToolFsmState::Ready(tool_data.current_shape) } @@ -706,35 +888,38 @@ impl Fsm for ShapeToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { ShapeToolFsmState::Ready(shape) => { - let hint_infos = match shape { - ShapeType::Convex | ShapeType::Star => vec![ - HintInfo::mouse(MouseMotion::LmbDrag, "Draw Polygon"), - HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(), - HintInfo::keys([Key::Alt], "From Center").prepend_plus(), + let hint_groups = match shape { + ShapeType::Polygon | ShapeType::Star => vec![ + HintGroup(vec![ + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Polygon"), + HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(), + HintInfo::keys([Key::Alt], "From Center").prepend_plus(), + ]), + HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Sides")]), ], - ShapeType::Ellipse => vec![ + ShapeType::Ellipse => vec![HintGroup(vec![ HintInfo::mouse(MouseMotion::LmbDrag, "Draw Ellipse"), HintInfo::keys([Key::Shift], "Constrain Circular").prepend_plus(), HintInfo::keys([Key::Alt], "From Center").prepend_plus(), - ], - ShapeType::Line => vec![ + ])], + ShapeType::Line => vec![HintGroup(vec![ HintInfo::mouse(MouseMotion::LmbDrag, "Draw Line"), HintInfo::keys([Key::Shift], "15° Increments").prepend_plus(), HintInfo::keys([Key::Alt], "From Center").prepend_plus(), HintInfo::keys([Key::Control], "Lock Angle").prepend_plus(), - ], - ShapeType::Rectangle => vec![ + ])], + ShapeType::Rectangle => vec![HintGroup(vec![ HintInfo::mouse(MouseMotion::LmbDrag, "Draw Rectangle"), HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(), HintInfo::keys([Key::Alt], "From Center").prepend_plus(), - ], + ])], }; - HintData(vec![HintGroup(hint_infos)]) + HintData(hint_groups) } 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::Convex | ShapeType::Star => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]), + ShapeType::Polygon | ShapeType::Star => 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::Line => HintGroup(vec![ @@ -743,7 +928,13 @@ impl Fsm for ShapeToolFsmState { HintInfo::keys([Key::Control], "Lock Angle"), ]), }; + common_hint_group.push(tool_hint_group); + + if matches!(shape, ShapeType::Polygon | ShapeType::Star) { + common_hint_group.push(HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Sides")])); + } + HintData(common_hint_group) } ShapeToolFsmState::DraggingLineEndpoints => HintData(vec![ @@ -754,7 +945,7 @@ impl Fsm for ShapeToolFsmState { HintInfo::keys([Key::Control], "Lock Angle"), ]), ]), - ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::DraggingStarInnerRadius | ShapeToolFsmState::DraggingStarNumberPointHandle => HintData(vec![ + ShapeToolFsmState::ResizingBounds => HintData(vec![ HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), HintGroup(vec![HintInfo::keys([Key::Alt], "From Pivot"), HintInfo::keys([Key::Shift], "Preserve Aspect Ratio")]), ]), @@ -766,6 +957,9 @@ impl Fsm for ShapeToolFsmState { HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), HintGroup(vec![HintInfo::keys([Key::Control], "Unlock Slide")]), ]), + ShapeToolFsmState::DraggingStarInnerRadius | ShapeToolFsmState::DraggingStarNumberPointHandle => { + HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]) + } }; responses.add(FrontendMessage::UpdateInputHints { hint_data }); diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index a66231eb26..4b89abd3c4 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -255,7 +255,7 @@ impl LayoutHolder for ToolData { IconButton::new(icon_name, 32) .disabled(false) .active(match tool_type { - ToolType::Line | ToolType::Ellipse | ToolType::Rectangle => {self.active_shape_type.is_some() && active_tool == tool_type}, + ToolType::Line | ToolType::Ellipse | ToolType::Rectangle => { self.active_shape_type.is_some() && active_tool == tool_type } _ => active_tool == tool_type, }) .tooltip(tooltip.clone()) @@ -265,7 +265,7 @@ impl LayoutHolder for ToolData { ToolType::Line => ToolMessage::ActivateShapeLine.into(), ToolType::Ellipse => ToolMessage::ActivateShapeEllipse.into(), ToolType::Rectangle => ToolMessage::ActivateShapeRectangle.into(), - ToolType::Shape => ToolMessage::ActivateToolShape .into(), + ToolType::Shape => ToolMessage::ActivateToolShape.into(), _ => { if !tooltip.contains("Coming Soon") { (ToolMessage::ActivateTool { tool_type }).into() } else { (DialogMessage::RequestComingSoonDialog { issue: None }).into() } } @@ -351,7 +351,6 @@ pub enum ToolType { Freehand, Spline, Shape, - Polygon, Text, // Shape group diff --git a/editor/src/test_utils.rs b/editor/src/test_utils.rs index 48bade4b6f..70e61726e4 100644 --- a/editor/src/test_utils.rs +++ b/editor/src/test_utils.rs @@ -89,7 +89,7 @@ impl EditorTestUtils { } pub async fn draw_polygon(&mut self, x1: f64, y1: f64, x2: f64, y2: f64) { - self.drag_tool(ToolType::Polygon, x1, y1, x2, y2, ModifierKeys::default()).await; + self.drag_tool(ToolType::Shape, x1, y1, x2, y2, ModifierKeys::default()).await; } pub async fn draw_ellipse(&mut self, x1: f64, y1: f64, x2: f64, y2: f64) { @@ -220,6 +220,8 @@ impl EditorTestUtils { pub async fn select_tool(&mut self, tool_type: ToolType) { match tool_type { ToolType::Line => self.handle_message(Message::Tool(ToolMessage::ActivateShapeLine)).await, + ToolType::Rectangle => self.handle_message(Message::Tool(ToolMessage::ActivateShapeRectangle)).await, + ToolType::Ellipse => self.handle_message(Message::Tool(ToolMessage::ActivateShapeEllipse)).await, _ => self.handle_message(Message::Tool(ToolMessage::ActivateTool { tool_type })).await, } } From 7c9a018b88922ccd11613300e8f0df8569274be3 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Thu, 19 Jun 2025 12:32:43 +0530 Subject: [PATCH 13/16] formatting-fix --- editor/src/messages/tool/common_functionality/shapes/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/editor/src/messages/tool/common_functionality/shapes/mod.rs b/editor/src/messages/tool/common_functionality/shapes/mod.rs index d2f83518ec..d4d0b8a0c2 100644 --- a/editor/src/messages/tool/common_functionality/shapes/mod.rs +++ b/editor/src/messages/tool/common_functionality/shapes/mod.rs @@ -1,12 +1,12 @@ -pub mod polygon_shape; pub mod ellipse_shape; pub mod line_shape; +pub mod polygon_shape; pub mod rectangle_shape; pub mod shape_utility; pub mod star_shape; pub use super::shapes::ellipse_shape::Ellipse; -pub use super::shapes::line_shape::{ Line, LineEnd }; +pub use super::shapes::line_shape::{Line, LineEnd}; pub use super::shapes::rectangle_shape::Rectangle; pub use crate::messages::tool::tool_messages::shape_tool::ShapeToolData; use glam::DVec2; From d961cf5097ad6189283a72d033c4fb82a79fc870 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Fri, 20 Jun 2025 01:02:57 +0530 Subject: [PATCH 14/16] small nit-picks --- editor/src/consts.rs | 2 +- editor/src/messages/tool/tool_messages/shape_tool.rs | 4 ++-- node-graph/gcore/src/vector/vector_data/attributes.rs | 4 ---- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 6747a80ab1..19edd48afd 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -119,7 +119,7 @@ pub const LINE_ROTATE_SNAP_ANGLE: f64 = 15.; pub const BRUSH_SIZE_CHANGE_KEYBOARD: f64 = 5.; pub const DEFAULT_BRUSH_SIZE: f64 = 20.; -// STAR GIZMOS +// GIZMOS pub const POINT_RADIUS_HANDLE_SNAP_THRESHOLD: f64 = 8.; pub const POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD: f64 = 7.9; pub const NUMBER_OF_POINTS_HANDLE_SPOKE_EXTENSION: f64 = 1.2; diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index ad9f500645..890a65f4c2 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -494,7 +494,7 @@ impl Fsm for ShapeToolFsmState { } (ShapeToolFsmState::Drawing(_), ShapeToolMessage::IncreaseSides) => { if let Some(layer) = tool_data.data.layer { - let Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) + let Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface).or(graph_modification_utils::get_star_id(layer, &document.network_interface)) else { return self; }; @@ -523,7 +523,7 @@ impl Fsm for ShapeToolFsmState { } (ShapeToolFsmState::Drawing(_), ShapeToolMessage::DecreaseSides) => { if let Some(layer) = tool_data.data.layer { - let Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) + let Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface).or(graph_modification_utils::get_star_id(layer, &document.network_interface)) else { return self; }; diff --git a/node-graph/gcore/src/vector/vector_data/attributes.rs b/node-graph/gcore/src/vector/vector_data/attributes.rs index dcf5a99a08..8f71996c3e 100644 --- a/node-graph/gcore/src/vector/vector_data/attributes.rs +++ b/node-graph/gcore/src/vector/vector_data/attributes.rs @@ -137,10 +137,6 @@ impl PointDomain { &self.position } - pub fn position_ids(&self) -> impl Iterator { - self.id.iter().copied().zip(self.positions()) - } - pub fn positions_mut(&mut self) -> impl Iterator { self.id.iter().copied().zip(self.position.iter_mut()) } From fd9d35d357fa1dd2876fda9c895bf3e1af091130 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Thu, 19 Jun 2025 23:42:04 -0700 Subject: [PATCH 15/16] Make it compile --- .../tool/common_functionality/shapes/shape_utility.rs | 11 ++++------- editor/src/messages/tool/tool_messages/shape_tool.rs | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) 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 c181228cc3..afe0bfc772 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -1,9 +1,10 @@ +use super::ShapeToolData; use crate::messages::message::Message; 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; use crate::messages::prelude::{DocumentMessageHandler, NodeGraphMessage, Responses}; -use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer}; +use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; use crate::messages::tool::common_functionality::transformation_cage::BoundingBoxManager; use crate::messages::tool::tool_messages::tool_prelude::Key; use crate::messages::tool::utility_types::*; @@ -12,14 +13,11 @@ use glam::{DAffine2, DMat2, DVec2}; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; use graphene_std::renderer::ClickTargetType; -use graphene_std::vector::PointId; use graphene_std::vector::misc::dvec2_to_point; use kurbo::{BezPath, PathEl, Shape}; use std::collections::VecDeque; use std::f64::consts::{PI, TAU}; -use super::ShapeToolData; - #[derive(Debug, Clone, Copy, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)] pub enum ShapeType { #[default] @@ -133,7 +131,7 @@ pub fn transform_cage_overlays(document: &DocumentMessageHandler, tool_data: &mu .metadata() .bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer)) }) - .reduce(graphene_core::renderer::Quad::combine_bounds); + .reduce(graphene_std::renderer::Quad::combine_bounds); if let Some(bounds) = bounds { let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default()); @@ -232,8 +230,7 @@ pub fn star_outline(layer: LayerNodeIdentifier, document: &DocumentMessageHandle anchors.push(point); } - let subpath: Vec> = vec![Subpath::from_anchors_linear(anchors, true)]; - + let subpath = vec![ClickTargetType::Subpath(Subpath::from_anchors_linear(anchors, true))]; overlay_context.outline(subpath.iter(), viewport, None); } diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 890a65f4c2..36212cf3e0 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -21,7 +21,7 @@ use crate::messages::tool::common_functionality::transformation_cage::{BoundingB use crate::messages::tool::common_functionality::utility_functions::{closest_point, resize_bounds, rotate_bounds, skew_bounds, transforming_transform_cage}; use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput}; -use graphene_core::Color; +use graphene_std::Color; use graphene_std::renderer::Quad; use std::vec; From 0813643b6a28b2692fdd4ea8b37877b203994515 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sun, 22 Jun 2025 00:15:19 -0700 Subject: [PATCH 16/16] Code review --- .../messages/input_mapper/input_mappings.rs | 8 +- .../shape_gizmos/number_of_points_handle.rs | 99 +++---- .../shape_gizmos/point_radius_handle.rs | 245 +++++++----------- .../shapes/ellipse_shape.rs | 7 +- .../common_functionality/shapes/line_shape.rs | 22 +- .../tool/common_functionality/shapes/mod.rs | 1 - .../shapes/polygon_shape.rs | 11 +- .../shapes/rectangle_shape.rs | 5 +- .../shapes/shape_utility.rs | 56 ++-- .../common_functionality/shapes/star_shape.rs | 13 +- .../snapping/alignment_snapper.rs | 4 +- .../common_functionality/utility_functions.rs | 23 +- editor/src/messages/tool/tool_message.rs | 9 +- .../src/messages/tool/tool_message_handler.rs | 30 +-- .../tool/tool_messages/gradient_tool.rs | 3 +- .../tool/tool_messages/select_tool.rs | 5 +- .../messages/tool/tool_messages/shape_tool.rs | 59 ++--- .../transform_layer_message_handler.rs | 3 +- editor/src/messages/tool/utility_types.rs | 23 +- editor/src/test_utils.rs | 6 +- 20 files changed, 268 insertions(+), 364 deletions(-) diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index 8f1276ae6c..46854dc760 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -178,7 +178,7 @@ pub fn input_mappings() -> Mapping { entry!(KeyDown(Escape); action_dispatch=ShapeToolMessage::Abort), entry!(KeyDown(BracketLeft); action_dispatch=ShapeToolMessage::DecreaseSides), entry!(KeyDown(BracketRight); action_dispatch=ShapeToolMessage::IncreaseSides), - entry!(PointerMove; refresh_keys=[Alt, Shift, Control], action_dispatch=ShapeToolMessage::PointerMove ([Alt, Shift, Control, Shift])), + entry!(PointerMove; refresh_keys=[Alt, Shift, Control], action_dispatch=ShapeToolMessage::PointerMove([Alt, Shift, Control, Shift])), entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), @@ -315,9 +315,9 @@ pub fn input_mappings() -> Mapping { entry!(KeyDown(KeyA); action_dispatch=ToolMessage::ActivateToolPath), entry!(KeyDown(KeyP); action_dispatch=ToolMessage::ActivateToolPen), entry!(KeyDown(KeyN); action_dispatch=ToolMessage::ActivateToolFreehand), - entry!(KeyDown(KeyL); action_dispatch=ToolMessage::ActivateShapeLine), - entry!(KeyDown(KeyM); action_dispatch=ToolMessage::ActivateShapeRectangle), - entry!(KeyDown(KeyE); action_dispatch=ToolMessage::ActivateShapeEllipse), + entry!(KeyDown(KeyL); action_dispatch=ToolMessage::ActivateToolShapeLine), + entry!(KeyDown(KeyM); action_dispatch=ToolMessage::ActivateToolShapeRectangle), + entry!(KeyDown(KeyE); action_dispatch=ToolMessage::ActivateToolShapeEllipse), entry!(KeyDown(KeyY); action_dispatch=ToolMessage::ActivateToolShape), entry!(KeyDown(KeyB); action_dispatch=ToolMessage::ActivateToolBrush), entry!(KeyDown(KeyX); modifiers=[Accel, Shift], action_dispatch=ToolMessage::ResetColors), diff --git a/editor/src/messages/tool/common_functionality/shape_gizmos/number_of_points_handle.rs b/editor/src/messages/tool/common_functionality/shape_gizmos/number_of_points_handle.rs index 6ded0b0857..d698292ea4 100644 --- a/editor/src/messages/tool/common_functionality/shape_gizmos/number_of_points_handle.rs +++ b/editor/src/messages/tool/common_functionality/shape_gizmos/number_of_points_handle.rs @@ -1,27 +1,22 @@ -use crate::messages::tool::{ - common_functionality::shapes::shape_utility::{calculate_polygon_vertex_position, extract_polygon_parameters, inside_polygon, inside_star}, - tool_messages::tool_prelude::Key, +use crate::consts::{GIZMO_HIDE_THRESHOLD, NUMBER_OF_POINTS_HANDLE_SPOKE_EXTENSION, NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD}; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::message::Message; +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; +use crate::messages::prelude::Responses; +use crate::messages::prelude::{DocumentMessageHandler, FrontendMessage, InputPreprocessorMessageHandler, NodeGraphMessage}; +use crate::messages::tool::common_functionality::graph_modification_utils; +use crate::messages::tool::common_functionality::shape_editor::ShapeState; +use crate::messages::tool::common_functionality::shapes::shape_utility::{ + extract_polygon_parameters, extract_star_parameters, inside_polygon, inside_star, polygon_vertex_position, star_vertex_position, }; -use std::{collections::VecDeque, f64::consts::TAU}; - -use crate::messages::{portfolio::document::utility_types::document_metadata::LayerNodeIdentifier, prelude::Responses}; +use crate::messages::tool::tool_messages::tool_prelude::Key; use glam::{DAffine2, DVec2}; -use graph_craft::document::{NodeInput, value::TaggedValue}; - -use crate::{ - consts::{GIZMO_HIDE_THRESHOLD, NUMBER_OF_POINTS_HANDLE_SPOKE_EXTENSION, NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD}, - messages::{ - frontend::utility_types::MouseCursorIcon, - message::Message, - portfolio::document::{overlays::utility_types::OverlayContext, utility_types::network_interface::InputConnector}, - prelude::{DocumentMessageHandler, FrontendMessage, InputPreprocessorMessageHandler, NodeGraphMessage}, - tool::common_functionality::{ - graph_modification_utils, - shape_editor::ShapeState, - shapes::shape_utility::{calculate_star_vertex_position, extract_star_parameters}, - }, - }, -}; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; +use std::collections::VecDeque; +use std::f64::consts::TAU; #[derive(Clone, Debug, Default, PartialEq)] pub enum NumberOfPointsHandleState { @@ -43,6 +38,7 @@ impl NumberOfPointsHandle { self.handle_state = NumberOfPointsHandleState::Inactive; self.layer = None; } + pub fn update_state(&mut self, state: NumberOfPointsHandleState) { self.handle_state = state; } @@ -69,18 +65,16 @@ impl NumberOfPointsHandle { match &self.handle_state { NumberOfPointsHandleState::Inactive => { - for layer in document - .network_interface - .selected_nodes() - .selected_visible_and_unlocked_layers(&document.network_interface) - .filter(|layer| { - graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some() - }) { + let selected_nodes = document.network_interface.selected_nodes(); + let layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface).filter(|layer| { + graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some() + }); + for layer in layers { if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) { let viewport = document.metadata().transform_to_viewport(layer); let center = viewport.transform_point2(DVec2::ZERO); - let point_on_max_radius = calculate_star_vertex_position(viewport, 0, n, radius1, radius2); + let point_on_max_radius = star_vertex_position(viewport, 0, n, radius1, radius2); if mouse_position.distance(center) < NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD { self.layer = Some(layer); @@ -94,7 +88,7 @@ impl NumberOfPointsHandle { let viewport = document.metadata().transform_to_viewport(layer); let center = viewport.transform_point2(DVec2::ZERO); - let point_on_max_radius = calculate_polygon_vertex_position(viewport, 0, n, radius); + let point_on_max_radius = polygon_vertex_position(viewport, 0, n, radius); if mouse_position.distance(center) < NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD { self.layer = Some(layer); @@ -106,13 +100,11 @@ impl NumberOfPointsHandle { } } NumberOfPointsHandleState::Hover | NumberOfPointsHandleState::Dragging => { - let Some(layer) = self.layer else { - return; - }; + let Some(layer) = self.layer else { return }; let Some((n, radius)) = extract_star_parameters(Some(layer), document) .map(|(n, r1, r2)| (n, r1.max(r2))) - .or_else(|| extract_polygon_parameters(Some(layer), document).map(|(n, r)| (n, r))) + .or_else(|| extract_polygon_parameters(Some(layer), document)) else { return; }; @@ -125,8 +117,6 @@ impl NumberOfPointsHandle { self.layer = None; self.draw_spokes(center, viewport, n, radius, overlay_context); responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); - - return; } } } @@ -146,13 +136,11 @@ impl NumberOfPointsHandle { match &self.handle_state { NumberOfPointsHandleState::Inactive => { - for layer in document - .network_interface - .selected_nodes() - .selected_visible_and_unlocked_layers(&document.network_interface) - .filter(|layer| { - graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some() - }) { + let selected_nodes = document.network_interface.selected_nodes(); + let layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface).filter(|layer| { + graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some() + }); + for layer in layers { if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) { let radius = radius1.max(radius2); let viewport = document.metadata().transform_to_viewport(layer); @@ -163,7 +151,7 @@ impl NumberOfPointsHandle { return; } } - let point_on_max_radius = calculate_star_vertex_position(viewport, 0, n, radius1, radius2); + let point_on_max_radius = star_vertex_position(viewport, 0, n, radius1, radius2); if inside_star(viewport, n, radius1, radius2, mouse_position) && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD { self.draw_spokes(center, viewport, n, radius, overlay_context); @@ -180,7 +168,7 @@ impl NumberOfPointsHandle { return; } } - let point_on_max_radius = calculate_polygon_vertex_position(viewport, 0, n, radius); + let point_on_max_radius = polygon_vertex_position(viewport, 0, n, radius); if inside_polygon(viewport, n, radius, mouse_position) && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD { self.draw_spokes(center, viewport, n, radius, overlay_context); @@ -190,13 +178,11 @@ impl NumberOfPointsHandle { } } NumberOfPointsHandleState::Hover | NumberOfPointsHandleState::Dragging => { - let Some(layer) = self.layer else { - return; - }; + let Some(layer) = self.layer else { return }; let Some((n, radius)) = extract_star_parameters(Some(layer), document) .map(|(n, r1, r2)| (n, r1.max(r2))) - .or_else(|| extract_polygon_parameters(Some(layer), document).map(|(n, r)| (n, r))) + .or_else(|| extract_polygon_parameters(Some(layer), document)) else { return; }; @@ -218,9 +204,7 @@ impl NumberOfPointsHandle { y: -radius * angle.cos(), }); - let Some(direction) = (point - center).try_normalize() else { - continue; - }; + let Some(direction) = (point - center).try_normalize() else { continue }; // If the user zooms out such that shape is very small hide the gizmo if point.distance(center) < GIZMO_HIDE_THRESHOLD { @@ -236,15 +220,12 @@ impl NumberOfPointsHandle { } } - pub fn update_no_of_sides(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { + pub fn update_number_of_sides(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { let delta = input.mouse.position - document.metadata().document_to_viewport.transform_point2(drag_start); let sign = (input.mouse.position.x - document.metadata().document_to_viewport.transform_point2(drag_start).x).signum(); - let net_delta = (delta.length() / 25.0).round() * sign; - - let Some(layer) = self.layer else { - return; - }; + let net_delta = (delta.length() / 25.).round() * sign; + let Some(layer) = self.layer else { return }; let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else { return; }; diff --git a/editor/src/messages/tool/common_functionality/shape_gizmos/point_radius_handle.rs b/editor/src/messages/tool/common_functionality/shape_gizmos/point_radius_handle.rs index d718979e40..4a72de0319 100644 --- a/editor/src/messages/tool/common_functionality/shape_gizmos/point_radius_handle.rs +++ b/editor/src/messages/tool/common_functionality/shape_gizmos/point_radius_handle.rs @@ -1,29 +1,17 @@ -use crate::{ - consts::{COLOR_OVERLAY_RED, POINT_RADIUS_HANDLE_SNAP_THRESHOLD}, - messages::tool::common_functionality::shapes::shape_utility::{calculate_polygon_vertex_position, draw_snapping_ticks, extract_polygon_parameters}, -}; -use std::{ - collections::VecDeque, - f64::consts::{FRAC_1_SQRT_2, FRAC_PI_4, PI, SQRT_2}, -}; - -use crate::messages::{portfolio::document::utility_types::document_metadata::LayerNodeIdentifier, prelude::Responses}; +use crate::consts::{COLOR_OVERLAY_RED, GIZMO_HIDE_THRESHOLD, POINT_RADIUS_HANDLE_SNAP_THRESHOLD}; +use crate::messages::message::Message; +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; +use crate::messages::prelude::Responses; +use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage}; +use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer}; +use crate::messages::tool::common_functionality::shapes::shape_utility::{draw_snapping_ticks, extract_polygon_parameters, extract_star_parameters, polygon_vertex_position, star_vertex_position}; use glam::DVec2; -use graph_craft::document::{NodeInput, value::TaggedValue}; - -use crate::{ - consts::GIZMO_HIDE_THRESHOLD, - messages::{ - message::Message, - portfolio::document::{overlays::utility_types::OverlayContext, utility_types::network_interface::InputConnector}, - prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage}, - tool::common_functionality::{ - graph_modification_utils, - graph_modification_utils::NodeGraphLayer, - shapes::shape_utility::{calculate_star_vertex_position, extract_star_parameters}, - }, - }, -}; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; +use std::collections::VecDeque; +use std::f64::consts::{FRAC_1_SQRT_2, FRAC_PI_4, PI, SQRT_2}; #[derive(Clone, Debug, Default, PartialEq)] pub enum PointRadiusHandleState { @@ -79,7 +67,7 @@ impl PointRadiusHandle { for i in 0..2 * n { let (radius, radius_index) = if i % 2 == 0 { (radius1, 2) } else { (radius2, 3) }; - let point = calculate_star_vertex_position(viewport, i as i32, n, radius1, radius2); + let point = star_vertex_position(viewport, i as i32, n, radius1, radius2); let center = viewport.transform_point2(DVec2::ZERO); // If the user zooms out such that shape is very small hide the gizmo @@ -100,20 +88,20 @@ impl PointRadiusHandle { } } - // Draw the point handle gizmo for the Polygon shape + // Draw the point handle gizmo for the polygon shape if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) { let viewport = document.metadata().transform_to_viewport(layer); for i in 0..n { - let point = calculate_polygon_vertex_position(viewport, i as i32, n, radius); + let point = polygon_vertex_position(viewport, i as i32, n, radius); let center = viewport.transform_point2(DVec2::ZERO); - // If the user zooms out such that shape is very small hide the gizmo + // If the user zooms out so the shape is very small, hide the gizmo if point.distance(center) < GIZMO_HIDE_THRESHOLD { return; } - if point.distance(mouse_position) < 5.0 { + if point.distance(mouse_position) < 5. { self.radius_index = 2; self.layer = Some(layer); self.point = i; @@ -129,33 +117,26 @@ impl PointRadiusHandle { } PointRadiusHandleState::Dragging | PointRadiusHandleState::Hover => { - let Some(layer) = self.layer else { - return; - }; + let Some(layer) = self.layer else { return }; let viewport = document.metadata().transform_to_viewport(layer); if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) { - let point = calculate_star_vertex_position(viewport, self.point as i32, n, radius1, radius2); + let point = star_vertex_position(viewport, self.point as i32, n, radius1, radius2); - if matches!(&self.handle_state, PointRadiusHandleState::Hover) { - if (mouse_position - point).length() > 5.0 { - self.update_state(PointRadiusHandleState::Inactive); - self.layer = None; - return; - } + if matches!(&self.handle_state, PointRadiusHandleState::Hover) && (mouse_position - point).length() > 5. { + self.update_state(PointRadiusHandleState::Inactive); + self.layer = None; + return; } } if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) { - let point = calculate_polygon_vertex_position(viewport, self.point as i32, n, radius); + let point = polygon_vertex_position(viewport, self.point as i32, n, radius); - if matches!(&self.handle_state, PointRadiusHandleState::Hover) { - if (mouse_position - point).length() > 5. { - self.update_state(PointRadiusHandleState::Inactive); - self.layer = None; - return; - } + if matches!(&self.handle_state, PointRadiusHandleState::Hover) && (mouse_position - point).length() > 5. { + self.update_state(PointRadiusHandleState::Inactive); + self.layer = None; } } } @@ -166,13 +147,11 @@ impl PointRadiusHandle { pub fn overlays(&mut self, other_gizmo_active: bool, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, mouse_position: DVec2, overlay_context: &mut OverlayContext) { match &self.handle_state { PointRadiusHandleState::Inactive => { - for layer in document - .network_interface - .selected_nodes() - .selected_visible_and_unlocked_layers(&document.network_interface) - .filter(|layer| { - graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some() - }) { + let selected_nodes = document.network_interface.selected_nodes(); + let layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface).filter(|layer| { + graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some() + }); + for layer in layers { if other_gizmo_active { return; } @@ -180,8 +159,8 @@ impl PointRadiusHandle { if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) { let viewport = document.metadata().transform_to_viewport(layer); - for i in 0..2 * n { - let point = calculate_star_vertex_position(viewport, i as i32, n, radius1, radius2); + for i in 0..(2 * n) { + let point = star_vertex_position(viewport, i as i32, n, radius1, radius2); let center = viewport.transform_point2(DVec2::ZERO); let viewport_diagonal = input.viewport_bounds.size().length(); @@ -191,9 +170,7 @@ impl PointRadiusHandle { } if point.distance(mouse_position) < 5. { - let Some(direction) = (point - center).try_normalize() else { - continue; - }; + let Some(direction) = (point - center).try_normalize() else { continue }; overlay_context.manipulator_handle(point, true, None); let angle = ((i as f64) * PI) / (n as f64); @@ -213,7 +190,7 @@ impl PointRadiusHandle { let viewport = document.metadata().transform_to_viewport(layer); for i in 0..n { - let point = calculate_polygon_vertex_position(viewport, i as i32, n, radius); + let point = polygon_vertex_position(viewport, i as i32, n, radius); let center = viewport.transform_point2(DVec2::ZERO); let viewport_diagonal = input.viewport_bounds.size().length(); @@ -223,9 +200,7 @@ impl PointRadiusHandle { } if point.distance(mouse_position) < 5. { - let Some(direction) = (point - center).try_normalize() else { - continue; - }; + let Some(direction) = (point - center).try_normalize() else { continue }; overlay_context.manipulator_handle(point, true, None); overlay_context.line(center, center + direction * viewport_diagonal, None, None); @@ -240,157 +215,125 @@ impl PointRadiusHandle { } PointRadiusHandleState::Dragging | PointRadiusHandleState::Hover => { - let Some(layer) = self.layer else { - return; - }; - + let Some(layer) = self.layer else { return }; let viewport = document.metadata().transform_to_viewport(layer); let center = viewport.transform_point2(DVec2::ZERO); let viewport_diagonal = input.viewport_bounds.size().length(); if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) { let angle = ((self.point as f64) * PI) / (n as f64); - let point = calculate_star_vertex_position(viewport, self.point as i32, n, radius1, radius2); + let point = star_vertex_position(viewport, self.point as i32, n, radius1, radius2); - let Some(direction) = (point - center).try_normalize() else { - return; - }; + let Some(direction) = (point - center).try_normalize() else { return }; // Draws the ray from the center to the dragging point extending till the viewport overlay_context.manipulator_handle(point, true, None); overlay_context.line(center, center + direction * viewport_diagonal, None, None); - // makes the ticks for snapping + // Makes the tick marks for snapping - // if dragging to make radius negative don't show the - if (mouse_position - center).dot(direction) < 0.0 { - return; + // Only show the snapping ticks if the radius is positive + if (mouse_position - center).dot(direction) >= 0. { + draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context); } - draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context); return; } if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) { - let point = calculate_polygon_vertex_position(viewport, self.point as i32, n, radius); + let point = polygon_vertex_position(viewport, self.point as i32, n, radius); - let Some(direction) = (point - center).try_normalize() else { - return; - }; + let Some(direction) = (point - center).try_normalize() else { return }; - // Draws the ray from the center to the dragging point extending till the viewport + // Draws the ray from the center to the dragging point and extending until the viewport edge is reached overlay_context.manipulator_handle(point, true, None); overlay_context.line(center, center + direction * viewport_diagonal, None, None); } } PointRadiusHandleState::Snapped(snapping_index) => { - let Some(layer) = self.layer else { - return; - }; - - let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) else { - return; - }; - + let Some(layer) = self.layer else { return }; + let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) else { return }; let viewport = document.metadata().transform_to_viewport(layer); let center = viewport.transform_point2(DVec2::ZERO); match snapping_index { - //Make a triangle with previous two points + // Make a triangle with the previous two points 0 => { - let before_outer_position = calculate_star_vertex_position(viewport, (self.point as i32) - 2, n, radius1, radius2); - let outer_position = calculate_star_vertex_position(viewport, (self.point as i32) - 1, n, radius1, radius2); - let point_position = calculate_star_vertex_position(viewport, self.point as i32, n, radius1, radius2); - - overlay_context.line(before_outer_position, outer_position, Some(COLOR_OVERLAY_RED), Some(3.0)); - overlay_context.line(outer_position, point_position, Some(COLOR_OVERLAY_RED), Some(3.0)); - - let l1 = (before_outer_position - outer_position).length() * 0.2; - let Some(l1_direction) = (before_outer_position - outer_position).try_normalize() else { - return; - }; + let before_outer_position = star_vertex_position(viewport, (self.point as i32) - 2, n, radius1, radius2); + let outer_position = star_vertex_position(viewport, (self.point as i32) - 1, n, radius1, radius2); + let point_position = star_vertex_position(viewport, self.point as i32, n, radius1, radius2); - let Some(l2_direction) = (point_position - outer_position).try_normalize() else { - return; - }; + overlay_context.line(before_outer_position, outer_position, Some(COLOR_OVERLAY_RED), Some(3.)); + overlay_context.line(outer_position, point_position, Some(COLOR_OVERLAY_RED), Some(3.)); - let Some(direction) = (center - outer_position).try_normalize() else { - return; - }; + let Some(l1_direction) = (before_outer_position - outer_position).try_normalize() else { return }; + let Some(l2_direction) = (point_position - outer_position).try_normalize() else { return }; + let Some(direction) = (center - outer_position).try_normalize() else { return }; + let l1 = 0.2 * (before_outer_position - outer_position).length(); let new_point = SQRT_2 * l1 * direction + outer_position; let before_outer_position = l1 * l1_direction + outer_position; let point_position = l1 * l2_direction + outer_position; - overlay_context.line(before_outer_position, new_point, Some(COLOR_OVERLAY_RED), Some(3.0)); - overlay_context.line(new_point, point_position, Some(COLOR_OVERLAY_RED), Some(3.0)); + overlay_context.line(before_outer_position, new_point, Some(COLOR_OVERLAY_RED), Some(3.)); + overlay_context.line(new_point, point_position, Some(COLOR_OVERLAY_RED), Some(3.)); } 1 => { - let before_outer_position = calculate_star_vertex_position(viewport, (self.point as i32) - 1, n, radius1, radius2); + let before_outer_position = star_vertex_position(viewport, (self.point as i32) - 1, n, radius1, radius2); + let after_point_position = star_vertex_position(viewport, (self.point as i32) + 1, n, radius1, radius2); + let point_position = star_vertex_position(viewport, self.point as i32, n, radius1, radius2); - let after_point_position = calculate_star_vertex_position(viewport, (self.point as i32) + 1, n, radius1, radius2); + overlay_context.line(before_outer_position, point_position, Some(COLOR_OVERLAY_RED), Some(3.)); + overlay_context.line(point_position, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.)); - let point_position = calculate_star_vertex_position(viewport, self.point as i32, n, radius1, radius2); - - overlay_context.line(before_outer_position, point_position, Some(COLOR_OVERLAY_RED), Some(3.0)); - overlay_context.line(point_position, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.0)); - - let l1 = (before_outer_position - point_position).length() * 0.2; - let Some(l1_direction) = (before_outer_position - point_position).try_normalize() else { - return; - }; - - let Some(l2_direction) = (after_point_position - point_position).try_normalize() else { - return; - }; - - let Some(direction) = (center - point_position).try_normalize() else { - return; - }; + let Some(l1_direction) = (before_outer_position - point_position).try_normalize() else { return }; + let Some(l2_direction) = (after_point_position - point_position).try_normalize() else { return }; + let Some(direction) = (center - point_position).try_normalize() else { return }; + let l1 = 0.2 * (before_outer_position - point_position).length(); let new_point = SQRT_2 * l1 * direction + point_position; let before_outer_position = l1 * l1_direction + point_position; let after_point_position = l1 * l2_direction + point_position; - overlay_context.line(before_outer_position, new_point, Some(COLOR_OVERLAY_RED), Some(3.0)); - overlay_context.line(new_point, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.0)); + overlay_context.line(before_outer_position, new_point, Some(COLOR_OVERLAY_RED), Some(3.)); + overlay_context.line(new_point, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.)); } i => { - // use 'self.point' as absolute reference as it match the index of vertices of star starting from 0 + // Use `self.point` as an absolute reference, as it matches the index of the star's vertices starting from 0 if i % 2 != 0 { - // flipped case - let point_position = calculate_star_vertex_position(viewport, self.point as i32, n, radius1, radius2); + // Flipped case + let point_position = star_vertex_position(viewport, self.point as i32, n, radius1, radius2); let target_index = (1 - (*i as i32)).abs() + (self.point as i32); - let target_point_position = calculate_star_vertex_position(viewport, target_index, n, radius1, radius2); + let target_point_position = star_vertex_position(viewport, target_index, n, radius1, radius2); let mirrored_index = 2 * (self.point as i32) - target_index; - let mirrored = calculate_star_vertex_position(viewport, mirrored_index, n, radius1, radius2); + let mirrored = star_vertex_position(viewport, mirrored_index, n, radius1, radius2); - overlay_context.line(point_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.0)); - overlay_context.line(point_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.0)); + overlay_context.line(point_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.)); + overlay_context.line(point_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.)); } else { let outer_index = (self.point as i32) - 1; - let outer_position = calculate_star_vertex_position(viewport, outer_index, n, radius1, radius2); + let outer_position = star_vertex_position(viewport, outer_index, n, radius1, radius2); - // the vertex which is colinear with the point we are dragging and its previous outer vertex + // The vertex which is colinear with the point we are dragging and its previous outer vertex let target_index = (self.point as i32) + (*i as i32) - 1; - let target_point_position = calculate_star_vertex_position(viewport, target_index, n, radius1, radius2); + let target_point_position = star_vertex_position(viewport, target_index, n, radius1, radius2); let mirrored_index = 2 * outer_index - target_index; - let mirrored = calculate_star_vertex_position(viewport, mirrored_index, n, radius1, radius2); + let mirrored = star_vertex_position(viewport, mirrored_index, n, radius1, radius2); - overlay_context.line(outer_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.0)); - overlay_context.line(outer_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.0)); + overlay_context.line(outer_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.)); + overlay_context.line(outer_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.)); } } } - // 0,1 90 } } } + fn calculate_snap_radii(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, radius_index: usize) -> Vec { let mut snap_radii = Vec::new(); @@ -399,36 +342,32 @@ impl PointRadiusHandle { }; let other_index = if radius_index == 3 { 2 } else { 3 }; - let Some(&TaggedValue::F64(other_radius)) = node_inputs[other_index].as_value() else { return snap_radii; }; - let Some(&TaggedValue::U32(n)) = node_inputs[1].as_value() else { return snap_radii; }; - // inner radius for 90 + // Inner radius for 90° let b = FRAC_PI_4 * 3. - PI / (n as f64); let angle = b.sin(); let required_radius = (other_radius / angle) * FRAC_1_SQRT_2; snap_radii.push(required_radius); - // also push the case when the when it length increases more than the other - + // Also add the case where the radius exceeds the other radius (the "flipped" case) let flipped = other_radius * angle * SQRT_2; - snap_radii.push(flipped); for i in 1..n { let n = n as f64; let i = i as f64; - let denominator = 2. * ((PI * (i - 1.0)) / n).cos() * ((PI * i) / n).sin(); + let denominator = 2. * ((PI * (i - 1.)) / n).cos() * ((PI * i) / n).sin(); let numerator = ((2. * PI * i) / n).sin(); let factor = numerator / denominator; - if factor < 0.0 { + if factor < 0. { break; } @@ -436,7 +375,7 @@ impl PointRadiusHandle { snap_radii.push(other_radius * factor); } - snap_radii.push((other_radius * 1.0) / factor); + snap_radii.push((other_radius * 1.) / factor); } snap_radii diff --git a/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs b/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs index 3ba433955c..fe97c22318 100644 --- a/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs @@ -16,7 +16,7 @@ pub struct Ellipse; impl Ellipse { pub fn create_node() -> NodeTemplate { - let node_type = resolve_document_node_type("Ellipse").expect("Ellipse node does not exist"); + let node_type = resolve_document_node_type("Ellipse").expect("Ellipse 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))]) } @@ -28,7 +28,8 @@ impl Ellipse { modifier: ShapeToolModifierKey, responses: &mut VecDeque, ) { - let (center, lock_ratio) = (modifier[0], modifier[1]); + let [center, lock_ratio, _, _] = modifier; + if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) { let Some(node_id) = graph_modification_utils::get_ellipse_id(layer, &document.network_interface) else { return; @@ -56,7 +57,7 @@ impl Ellipse { mod test_ellipse { pub use crate::test_utils::test_prelude::*; use glam::DAffine2; - use graphene_core::vector::generator_nodes::ellipse; + use graphene_std::vector::generator_nodes::ellipse; #[derive(Debug, PartialEq)] struct ResolvedEllipse { diff --git a/editor/src/messages/tool/common_functionality/shapes/line_shape.rs b/editor/src/messages/tool/common_functionality/shapes/line_shape.rs index 1812e56033..985457c208 100644 --- a/editor/src/messages/tool/common_functionality/shapes/line_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/line_shape.rs @@ -37,7 +37,7 @@ pub struct Line; impl Line { pub fn create_node(document: &DocumentMessageHandler, drag_start: DVec2) -> NodeTemplate { - let node_type = resolve_document_node_type("Line").expect("Line node does not exist"); + let node_type = resolve_document_node_type("Line").expect("Line node can't be found"); node_type.node_template_input_override([ None, Some(NodeInput::value(TaggedValue::DVec2(document.metadata().document_to_viewport.transform_point2(drag_start)), false)), @@ -53,10 +53,12 @@ impl Line { modifier: ShapeToolModifierKey, responses: &mut VecDeque, ) { - let (center, snap_angle, lock_angle) = (modifier[0], modifier[3], modifier[2]); + let [center, _, lock_angle, snap_angle] = modifier; + shape_tool_data.line_data.drag_current = ipp.mouse.position; + let keyboard = &ipp.keyboard; - let ignore = vec![layer]; + let ignore = [layer]; let snap_data = SnapData::ignore(document, ipp, &ignore); let mut document_points = generate_line(shape_tool_data, snap_data, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center)); @@ -315,8 +317,8 @@ mod test_line_tool { (start_input, end_input) => { let expected_start = DVec2::new(0., 100.); let expected_end = DVec2::new(200., 100.); - assert!((start_input - expected_start).length() < 1., "start point should be near (0,100)"); - assert!((end_input - expected_end).length() < 1., "end point should be near (200,100)"); + assert!((start_input - expected_start).length() < 1., "Start point should be near (0, 100)"); + assert!((end_input - expected_end).length() < 1., "End point should be near (200, 100)"); } } } @@ -352,7 +354,7 @@ mod test_line_tool { editor .handle_message(GraphOperationMessage::TransformChange { layer: artboard_id, - transform: DAffine2::from_angle(45.0_f64.to_radians()), + transform: DAffine2::from_angle(45_f64.to_radians()), transform_in: TransformIn::Local, skip_rerender: false, }) @@ -366,16 +368,16 @@ mod test_line_tool { // Verifying the line is approximately 100*sqrt(2) units in length (diagonal of 100x100 square) let line_length = line_vector.length(); assert!( - (line_length - 141.42).abs() < 1.0, // 100 * sqrt(2) ~= 141.42 + (line_length - 141.42).abs() < 1., // 100 * sqrt(2) ~= 141.42 "Line length should be approximately 141.42 units. Got: {line_length}" ); - assert!((line_vector.x - 100.0).abs() < 1.0, "X-component of line vector should be approximately 100. Got: {}", line_vector.x); + assert!((line_vector.x - 100.).abs() < 1., "X-component of line vector should be approximately 100. Got: {}", line_vector.x); assert!( - (line_vector.y.abs() - 100.0).abs() < 1.0, + (line_vector.y.abs() - 100.).abs() < 1., "Absolute Y-component of line vector should be approximately 100. Got: {}", line_vector.y.abs() ); let angle_degrees = line_vector.angle_to(DVec2::X).to_degrees(); - assert!((angle_degrees - (-45.0)).abs() < 1.0, "Line angle should be close to -45 degrees. Got: {angle_degrees}"); + assert!((angle_degrees - (-45.)).abs() < 1., "Line angle should be close to -45 degrees. Got: {angle_degrees}"); } } diff --git a/editor/src/messages/tool/common_functionality/shapes/mod.rs b/editor/src/messages/tool/common_functionality/shapes/mod.rs index d4d0b8a0c2..44f40b5982 100644 --- a/editor/src/messages/tool/common_functionality/shapes/mod.rs +++ b/editor/src/messages/tool/common_functionality/shapes/mod.rs @@ -9,4 +9,3 @@ pub use super::shapes::ellipse_shape::Ellipse; pub use super::shapes::line_shape::{Line, LineEnd}; pub use super::shapes::rectangle_shape::Rectangle; pub use crate::messages::tool::tool_messages::shape_tool::ShapeToolData; -use glam::DVec2; diff --git a/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs b/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs index 457650f1a1..871cf2c0b2 100644 --- a/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs @@ -17,7 +17,7 @@ pub struct Polygon; impl Polygon { pub fn create_node(vertices: u32) -> NodeTemplate { - let node_type = resolve_document_node_type("Regular Polygon").expect("Regular Polygon does not exist"); + let node_type = resolve_document_node_type("Regular Polygon").expect("Regular Polygon can't be found"); node_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::U32(vertices), false)), Some(NodeInput::value(TaggedValue::F64(0.5), false))]) } @@ -29,16 +29,17 @@ impl Polygon { modifier: ShapeToolModifierKey, responses: &mut VecDeque, ) { - let (center, lock_ratio) = (modifier[0], modifier[1]); + let [center, lock_ratio, _, _] = modifier; + if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) { // TODO: We need to determine how to allow the polygon node to make irregular shapes update_radius_sign(end, start, layer, document, responses); let dimensions = (start - end).abs(); - let mut scale = DVec2::ONE; - let radius: f64; // We keep the smaller dimension's scale at 1 and scale the other dimension accordingly + let mut scale = DVec2::ONE; + let radius; if dimensions.x > dimensions.y { scale.x = dimensions.x / dimensions.y; radius = dimensions.y / 2.; @@ -58,7 +59,7 @@ impl Polygon { responses.add(GraphOperationMessage::TransformSet { layer, - transform: DAffine2::from_scale_angle_translation(scale, 0.0, (start + end) / 2.0), + transform: DAffine2::from_scale_angle_translation(scale, 0., (start + end) / 2.), transform_in: TransformIn::Viewport, skip_rerender: false, }); diff --git a/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs b/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs index 6ee1dead12..cbd6722960 100644 --- a/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs @@ -16,7 +16,7 @@ pub struct Rectangle; impl Rectangle { pub fn create_node() -> NodeTemplate { - let node_type = resolve_document_node_type("Rectangle").expect("Rectangle node does not exist"); + let node_type = resolve_document_node_type("Rectangle").expect("Rectangle node can't be found"); node_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(1.), false)), Some(NodeInput::value(TaggedValue::F64(1.), false))]) } @@ -28,7 +28,8 @@ impl Rectangle { modifier: ShapeToolModifierKey, responses: &mut VecDeque, ) { - let (center, lock_ratio) = (modifier[0], modifier[1]); + let [center, lock_ratio, _, _] = modifier; + if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) { let Some(node_id) = graph_modification_utils::get_rectangle_id(layer, &document.network_interface) else { return; 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 afe0bfc772..772991be4b 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -42,9 +42,9 @@ impl ShapeType { pub fn tooltip(&self) -> String { (match self { - Self::Line => "Line tool", - Self::Rectangle => "Rectangle tool", - Self::Ellipse => "Ellipse tool", + Self::Line => "Line Tool", + Self::Rectangle => "Rectangle Tool", + Self::Ellipse => "Ellipse Tool", _ => "", }) .into() @@ -70,17 +70,15 @@ impl ShapeType { } } -// Center, Lock Ratio, Lock Angle, Snap Angle, Increase/Decrease Side +/// Center, Lock Ratio, Lock Angle, Snap Angle, Increase/Decrease Side pub type ShapeToolModifierKey = [Key; 4]; pub fn update_radius_sign(end: DVec2, start: DVec2, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, responses: &mut VecDeque) { - let sign_num = if end[1] > start[1] { 1.0 } else { -1.0 }; + let sign_num = if end[1] > start[1] { 1. } else { -1. }; let new_layer = NodeGraphLayer::new(layer, &document.network_interface); if new_layer.find_input("Regular Polygon", 1).unwrap_or(&TaggedValue::U32(0)).to_u32() % 2 == 1 { - let Some(polygon_node_id) = new_layer.upstream_node_id_from_name("Regular Polygon") else { - return; - }; + let Some(polygon_node_id) = new_layer.upstream_node_id_from_name("Regular Polygon") else { return }; responses.add(NodeGraphMessage::SetInput { input_connector: InputConnector::node(polygon_node_id, 2), @@ -90,9 +88,7 @@ pub fn update_radius_sign(end: DVec2, start: DVec2, layer: LayerNodeIdentifier, } if new_layer.find_input("Star", 1).unwrap_or(&TaggedValue::U32(0)).to_u32() % 2 == 1 { - let Some(star_node_id) = new_layer.upstream_node_id_from_name("Star") else { - return; - }; + let Some(star_node_id) = new_layer.upstream_node_id_from_name("Star") else { return }; responses.add(NodeGraphMessage::SetInput { input_connector: InputConnector::node(star_node_id, 2), @@ -116,7 +112,7 @@ pub fn transform_cage_overlays(document: &DocumentMessageHandler, tool_data: &mu // Check if the matrix is not invertible let mut transform_tampered = false; - if transform.matrix2.determinant() == 0.0 { + if transform.matrix2.determinant() == 0. { transform.matrix2 += DMat2::IDENTITY * 1e-4; // TODO: Is this the cleanest way to handle this? transform_tampered = true; } @@ -147,9 +143,7 @@ pub fn transform_cage_overlays(document: &DocumentMessageHandler, tool_data: &mu pub fn anchor_overlays(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) { - let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { - continue; - }; + let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue }; let transform = document.metadata().transform_to_viewport(layer); overlay_context.outline_vector(&vector_data, transform); @@ -162,10 +156,7 @@ pub fn anchor_overlays(document: &DocumentMessageHandler, overlay_context: &mut /// Extract the node input values of Star pub fn extract_star_parameters(layer: Option, document: &DocumentMessageHandler) -> Option<(u32, f64, f64)> { - let Some(layer) = layer else { - return None; - }; - let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star")?; + let node_inputs = NodeGraphLayer::new(layer?, &document.network_interface).find_node_inputs("Star")?; let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(outer)), Some(&TaggedValue::F64(inner))) = (node_inputs.get(1)?.as_value(), node_inputs.get(2)?.as_value(), node_inputs.get(3)?.as_value()) else { @@ -177,10 +168,7 @@ pub fn extract_star_parameters(layer: Option, document: &Do /// Extract the node input values of Polygon pub fn extract_polygon_parameters(layer: Option, document: &DocumentMessageHandler) -> Option<(u32, f64)> { - let Some(layer) = layer else { - return None; - }; - let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Regular Polygon")?; + let node_inputs = NodeGraphLayer::new(layer?, &document.network_interface).find_node_inputs("Regular Polygon")?; let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(radius))) = (node_inputs.get(1)?.as_value(), node_inputs.get(2)?.as_value()) else { return None; @@ -190,7 +178,7 @@ pub fn extract_polygon_parameters(layer: Option, document: } /// Calculate the viewport position of as a star vertex given its index -pub fn calculate_star_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, radius1: f64, radius2: f64) -> DVec2 { +pub fn star_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, radius1: f64, radius2: f64) -> DVec2 { let angle = ((vertex_index as f64) * PI) / (n as f64); let radius = if vertex_index % 2 == 0 { radius1 } else { radius2 }; @@ -201,7 +189,7 @@ pub fn calculate_star_vertex_position(viewport: DAffine2, vertex_index: i32, n: } /// Calculate the viewport position of as a polygon vertex given its index -pub fn calculate_polygon_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, radius: f64) -> DVec2 { +pub fn polygon_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, radius: f64) -> DVec2 { let angle = ((vertex_index as f64) * TAU) / (n as f64); viewport.transform_point2(DVec2 { @@ -210,12 +198,10 @@ pub fn calculate_polygon_vertex_position(viewport: DAffine2, vertex_index: i32, }) } -/// Outlines the geometric shape made by star-node +/// Outlines the geometric shape made by the Star node pub fn star_outline(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { let mut anchors = Vec::new(); - let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) else { - return; - }; + let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) else { return }; let viewport = document.metadata().transform_to_viewport(layer); for i in 0..2 * n { @@ -230,11 +216,11 @@ pub fn star_outline(layer: LayerNodeIdentifier, document: &DocumentMessageHandle anchors.push(point); } - let subpath = vec![ClickTargetType::Subpath(Subpath::from_anchors_linear(anchors, true))]; + let subpath = [ClickTargetType::Subpath(Subpath::from_anchors_linear(anchors, true))]; overlay_context.outline(subpath.iter(), viewport, None); } -/// Outlines the geometric shape made by polygon-node +/// Outlines the geometric shape made by the Polygon node pub fn polygon_outline(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { let mut anchors = Vec::new(); @@ -259,12 +245,12 @@ pub fn polygon_outline(layer: LayerNodeIdentifier, document: &DocumentMessageHan overlay_context.outline(subpath.iter(), viewport, None); } -/// Check if the the cursor is inside the geometric star-shape made by star-node without any upstream node modifications +/// Check if the the cursor is inside the geometric star shape made by the Star node without any upstream node modifications pub fn inside_star(viewport: DAffine2, n: u32, radius1: f64, radius2: f64, mouse_position: DVec2) -> bool { let mut paths = Vec::new(); for i in 0..2 * n { - let new_point = dvec2_to_point(calculate_star_vertex_position(viewport, i as i32, n, radius1, radius2)); + let new_point = dvec2_to_point(star_vertex_position(viewport, i as i32, n, radius1, radius2)); if i == 0 { paths.push(PathEl::MoveTo(new_point)); @@ -288,12 +274,12 @@ pub fn inside_star(viewport: DAffine2, n: u32, radius1: f64, radius2: f64, mouse winding != 0 } -/// Check if the the cursor is inside the geometric star-shape made by star-node without any upstream node modifications +/// Check if the the cursor is inside the geometric polygon shape made by the Polygon node without any upstream node modifications pub fn inside_polygon(viewport: DAffine2, n: u32, radius: f64, mouse_position: DVec2) -> bool { let mut paths = Vec::new(); for i in 0..n { - let new_point = dvec2_to_point(calculate_polygon_vertex_position(viewport, i as i32, n, radius)); + let new_point = dvec2_to_point(polygon_vertex_position(viewport, i as i32, n, radius)); if i == 0 { paths.push(PathEl::MoveTo(new_point)); diff --git a/editor/src/messages/tool/common_functionality/shapes/star_shape.rs b/editor/src/messages/tool/common_functionality/shapes/star_shape.rs index e933dc2694..21069620d8 100644 --- a/editor/src/messages/tool/common_functionality/shapes/star_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/star_shape.rs @@ -17,7 +17,7 @@ pub struct Star; impl Star { pub fn create_node(vertices: u32) -> NodeTemplate { - let node_type = resolve_document_node_type("Star").expect(" Star node does not exist"); + let node_type = resolve_document_node_type("Star").expect("Star node can't be found"); node_type.node_template_input_override([ None, Some(NodeInput::value(TaggedValue::U32(vertices), false)), @@ -34,16 +34,17 @@ impl Star { modifier: ShapeToolModifierKey, responses: &mut VecDeque, ) { - let (center, lock_ratio) = (modifier[0], modifier[1]); + let [center, lock_ratio, _, _] = modifier; + if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) { // TODO: We need to determine how to allow the polygon node to make irregular shapes update_radius_sign(end, start, layer, document, responses); let dimensions = (start - end).abs(); - let mut scale = DVec2::ONE; - let radius: f64; // We keep the smaller dimension's scale at 1 and scale the other dimension accordingly + let mut scale = DVec2::ONE; + let radius: f64; if dimensions.x > dimensions.y { scale.x = dimensions.x / dimensions.y; radius = dimensions.y / 2.; @@ -63,12 +64,12 @@ impl Star { responses.add(NodeGraphMessage::SetInput { input_connector: InputConnector::node(node_id, 3), - input: NodeInput::value(TaggedValue::F64(radius / 2.0), false), + input: NodeInput::value(TaggedValue::F64(radius / 2.), false), }); responses.add(GraphOperationMessage::TransformSet { layer, - transform: DAffine2::from_scale_angle_translation(scale, 0.0, (start + end) / 2.0), + transform: DAffine2::from_scale_angle_translation(scale, 0., (start + end) / 2.), transform_in: TransformIn::Viewport, skip_rerender: false, }); diff --git a/editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs b/editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs index 424f28e226..4d5645e539 100644 --- a/editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs +++ b/editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs @@ -70,9 +70,7 @@ impl AlignmentSnapper { if let Some(quad) = target_point.quad.map(|q| q.0) { if quad[0] == quad[3] && quad[1] == quad[2] && quad[0] == target_point.document_point { let [p1, p2, ..] = quad; - let Some(direction) = (p2 - p1).try_normalize() else { - return; - }; + let Some(direction) = (p2 - p1).try_normalize() else { return }; let normal = DVec2::new(-direction.y, direction.x); for endpoint in [p1, p2] { diff --git a/editor/src/messages/tool/common_functionality/utility_functions.rs b/editor/src/messages/tool/common_functionality/utility_functions.rs index 5fef219777..c6974d5009 100644 --- a/editor/src/messages/tool/common_functionality/utility_functions.rs +++ b/editor/src/messages/tool/common_functionality/utility_functions.rs @@ -1,3 +1,5 @@ +use super::snapping::{SnapCandidatePoint, SnapData, SnapManager}; +use super::transformation_cage::{BoundingBoxManager, SizeSnapData}; use crate::consts::ROTATE_INCREMENT; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::transformation::Selected; @@ -12,9 +14,6 @@ use graphene_std::renderer::Quad; use graphene_std::text::{FontCache, load_face}; use graphene_std::vector::{HandleId, ManipulatorPointId, PointId, SegmentId, VectorData, VectorModificationType}; -use super::snapping::{SnapCandidatePoint, SnapData, SnapManager}; -use super::transformation_cage::{BoundingBoxManager, SizeSnapData}; - /// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable. pub fn should_extend( document: &DocumentMessageHandler, @@ -238,9 +237,8 @@ pub fn resize_bounds( false } }); - let selected = &dragging_layers; - let mut selected = Selected::new(&mut bounds.original_transforms, &mut pivot, selected, responses, &document.network_interface, None, &tool, None); + let mut selected = Selected::new(&mut bounds.original_transforms, &mut pivot, &dragging_layers, responses, &document.network_interface, None, &tool, None); selected.apply_transformation(bounds.original_bound_transform * transformation * bounds.original_bound_transform.inverse(), None); } } @@ -278,6 +276,7 @@ pub fn rotate_bounds( false } }); + let mut selected = Selected::new( &mut bounds.original_transforms, &mut bounds.center_of_transformation, @@ -288,7 +287,6 @@ pub fn rotate_bounds( &tool, None, ); - selected.update_transforms(delta, None, None); } @@ -302,6 +300,8 @@ pub fn skew_bounds( tool: ToolType, ) { if let Some(movement) = &mut bounds.selected_edges { + let mut pivot = DVec2::ZERO; + let transformation = movement.skew_transform(mouse_position, bounds.original_bound_transform, free_movement); layers.retain(|layer| { @@ -312,14 +312,14 @@ pub fn skew_bounds( false } }); - let selected = &layers; - let mut pivot = DVec2::ZERO; - let mut selected = Selected::new(&mut bounds.original_transforms, &mut pivot, selected, responses, &document.network_interface, None, &tool, None); + let mut selected = Selected::new(&mut bounds.original_transforms, &mut pivot, &layers, responses, &document.network_interface, None, &tool, None); selected.apply_transformation(bounds.original_bound_transform * transformation * bounds.original_bound_transform.inverse(), None); } } +// TODO: Replace returned tuple (where at most 1 element is true at a time) with an enum. +/// Returns the tuple (resize, rotate, skew). pub fn transforming_transform_cage( document: &DocumentMessageHandler, mut bounding_box_manager: &mut Option, @@ -379,10 +379,13 @@ pub fn transforming_transform_cage( if let Some(edges) = edges { let closest_edge = bounds.get_closest_edge(edges, input.mouse.position); if bounds.check_skew_handle(input.mouse.position, closest_edge) { + // No resize or rotate, just skew return (false, false, true); } } } + + // Just resize, no rotate or skew return (true, false, false); } @@ -415,9 +418,11 @@ pub fn transforming_transform_cage( *layers_dragging = selected; + // No resize or skew, just rotate return (false, true, false); } + // No resize, rotate, or skew return (false, false, false); } diff --git a/editor/src/messages/tool/tool_message.rs b/editor/src/messages/tool/tool_message.rs index 8c5c91e5a7..6a5d893e38 100644 --- a/editor/src/messages/tool/tool_message.rs +++ b/editor/src/messages/tool/tool_message.rs @@ -56,7 +56,6 @@ pub enum ToolMessage { ActivateToolArtboard, ActivateToolNavigate, ActivateToolEyedropper, - ActivateToolText, ActivateToolFill, ActivateToolGradient, @@ -64,11 +63,11 @@ pub enum ToolMessage { ActivateToolPen, ActivateToolFreehand, ActivateToolSpline, + ActivateToolShapeLine, + ActivateToolShapeRectangle, + ActivateToolShapeEllipse, ActivateToolShape, - ActivateShapeRectangle, - ActivateShapeEllipse, - ActivateShapeLine, - ActivateToolPolygon, + ActivateToolText, ActivateToolBrush, // ActivateToolImaginate, diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index 32f0ff7563..badf9ac5f3 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -69,28 +69,28 @@ impl MessageHandler> for ToolMessageHandler { responses.add(ShapeToolMessage::HideShapeTypeWidget(false)) } ToolMessage::ActivateToolBrush => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Brush }), - ToolMessage::ActivateShapeRectangle | ToolMessage::ActivateShapeEllipse | ToolMessage::ActivateShapeLine => { - responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape }); + ToolMessage::ActivateToolShapeLine | ToolMessage::ActivateToolShapeRectangle | ToolMessage::ActivateToolShapeEllipse => { let shape = match message { - ToolMessage::ActivateShapeLine => Line, - ToolMessage::ActivateShapeEllipse => Ellipse, - ToolMessage::ActivateShapeRectangle => Rectangle, + ToolMessage::ActivateToolShapeLine => Line, + ToolMessage::ActivateToolShapeRectangle => Rectangle, + ToolMessage::ActivateToolShapeEllipse => Ellipse, _ => unreachable!(), }; + self.tool_state.tool_data.active_shape_type = Some(shape.tool_type()); + responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape }); responses.add(ShapeToolMessage::HideShapeTypeWidget(true)); responses.add(ShapeToolMessage::SetShape(shape)); } // ToolMessage::ActivateToolImaginate => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Imaginate }), ToolMessage::ActivateTool { tool_type } => { let tool_data = &mut self.tool_state.tool_data; - let old_tool = tool_data.active_tool_type; - + let old_tool = tool_data.active_tool_type.get_tool(); let tool_type = tool_type.get_tool(); - let old_tool = old_tool.get_tool(); responses.add(ToolMessage::RefreshToolOptions); tool_data.send_layout(responses, LayoutTarget::ToolShelf); + // Do nothing if switching to the same tool if self.tool_is_active && tool_type == old_tool { return; @@ -99,6 +99,7 @@ impl MessageHandler> for ToolMessageHandler { if tool_type != ToolType::Shape { tool_data.active_shape_type = None; } + self.tool_is_active = true; // Send the old and new tools a transition to their FSM Abort states @@ -325,7 +326,6 @@ impl MessageHandler> for ToolMessageHandler { ActivateToolArtboard, ActivateToolNavigate, ActivateToolEyedropper, - ActivateToolText, ActivateToolFill, ActivateToolGradient, @@ -333,17 +333,15 @@ impl MessageHandler> for ToolMessageHandler { ActivateToolPen, ActivateToolFreehand, ActivateToolSpline, - ActivateToolShape, - - ActivateToolPolygon, + ActivateToolShapeLine, + ActivateToolShapeRectangle, + ActivateToolShapeEllipse, + ActivateToolShape, + ActivateToolText, ActivateToolBrush, // ActivateToolImaginate, - ActivateShapeRectangle, - ActivateShapeEllipse, - ActivateShapeLine, - SelectRandomPrimaryColor, ResetColors, SwapColors, diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index 519db67d96..6265c64b1e 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -535,7 +535,8 @@ impl Fsm for GradientToolFsmState { mod test_gradient { use crate::messages::input_mapper::utility_types::input_mouse::EditorMouseState; use crate::messages::input_mapper::utility_types::input_mouse::ScrollDelta; - use crate::messages::portfolio::document::{graph_operation::utility_types::TransformIn, utility_types::misc::GroupFolderType}; + use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; + use crate::messages::portfolio::document::utility_types::misc::GroupFolderType; pub use crate::test_utils::test_prelude::*; use glam::DAffine2; use graphene_std::vector::fill; diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index a69f3f73b3..d468c4dc75 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -1,10 +1,7 @@ #![allow(clippy::too_many_arguments)] use super::tool_prelude::*; -use crate::consts::{ - COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COMPASS_ROSE_HOVER_RING_DIAMETER, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, RESIZE_HANDLE_SIZE, SELECTION_DRAG_ANGLE, - SELECTION_TOLERANCE, -}; +use crate::consts::*; use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 36212cf3e0..a6b79a1139 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -6,8 +6,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; -use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; -use crate::messages::tool::common_functionality::graph_modification_utils::{self}; +use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer}; use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::common_functionality::shape_gizmos::number_of_points_handle::{NumberOfPointsHandle, NumberOfPointsHandleState}; use crate::messages::tool::common_functionality::shape_gizmos::point_radius_handle::{PointRadiusHandle, PointRadiusHandleState}; @@ -23,7 +22,6 @@ use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput}; use graphene_std::Color; use graphene_std::renderer::Quad; -use std::vec; #[derive(Default)] pub struct ShapeTool { @@ -91,15 +89,14 @@ fn create_sides_widget(vertices: u32) -> WidgetHolder { NumberInput::new(Some(vertices as f64)) .label("Sides") .int() - .min(3.0) - .max(1000.0) + .min(3.) + .max(1000.) .mode(NumberInputMode::Increment) .on_update(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(number_input.value.unwrap() as u32)).into()) .widget_holder() } fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder { - log::info!("shape_type {:?}", shape_type); let entries = vec![vec![ MenuListEntry::new("Polygon") .label("Polygon") @@ -115,7 +112,7 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder { NumberInput::new(Some(line_weight)) .unit(" px") .label("Weight") - .min(0.0) + .min(0.) .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .on_update(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) .widget_holder() @@ -266,14 +263,14 @@ pub enum ShapeToolFsmState { Ready(ShapeType), Drawing(ShapeType), - // Line Shape Specific + // Line shape-specific DraggingLineEndpoints, - // Star Shape Specific + // Star shape-specific DraggingStarInnerRadius, DraggingStarNumberPointHandle, - // Transform Cage + // Transform cage ResizingBounds, RotatingBounds, SkewingBounds { skew: Key }, @@ -293,7 +290,7 @@ pub struct ShapeToolData { // In viewport space pub last_mouse_position: DVec2, - // Hide the dropdown menu when using line,rectangle or ellipse aliases + // Hide the dropdown menu when using Line, Rectangle, or Ellipse aliases pub hide_shape_option_widget: bool, // Shape-specific data @@ -309,7 +306,7 @@ pub struct ShapeToolData { // Current shape which is being drawn current_shape: ShapeType, - // Gizmo-data + // Gizmo data pub point_radius_handle: PointRadiusHandle, pub number_of_points_handle: NumberOfPointsHandle, } @@ -374,9 +371,8 @@ impl Fsm for ShapeToolFsmState { .selected_visible_and_unlocked_layers(&document.network_interface) .all(|layer| graph_modification_utils::get_line_id(layer, &document.network_interface).is_some()); - let ToolMessage::Shape(event) = event else { - return self; - }; + let ToolMessage::Shape(event) = event else { return self }; + match (self, event) { (_, ShapeToolMessage::Overlays(mut overlay_context)) => { let mouse_position = tool_data @@ -389,10 +385,10 @@ impl Fsm for ShapeToolFsmState { let dragging_start_gizmos = matches!(self, Self::DraggingStarInnerRadius); if matches!(self, ShapeToolFsmState::DraggingStarInnerRadius | Self::DraggingStarNumberPointHandle | Self::Ready(_)) && !input.keyboard.key(Key::Control) { - // Manage state handling of Number of Point Gizmo + // Manage state handling of the number of point gizmos tool_data.number_of_points_handle.handle_actions(document, input, mouse_position, &mut overlay_context, responses); - // Manage state handling of Point-Radius-Handle Gizmo + // Manage state handling of point radius handle gizmo tool_data.point_radius_handle.handle_actions(document, mouse_position); tool_data.number_of_points_handle.overlays(document, input, shape_editor, mouse_position, &mut overlay_context); @@ -553,7 +549,7 @@ impl Fsm for ShapeToolFsmState { (ShapeToolFsmState::Ready(_), ShapeToolMessage::DragStart) => { tool_data.line_data.drag_start = input.mouse.position; - // Snapped position in viewport-space. + // Snapped position in viewport space let mouse_pos = tool_data .data .snap_manager @@ -564,7 +560,6 @@ impl Fsm for ShapeToolFsmState { tool_data.line_data.drag_current = mouse_pos; // Check if dragging the inner vertices of a star - if tool_data.point_radius_handle.hovered() { tool_data.last_mouse_position = mouse_pos; tool_data.point_radius_handle.update_state(PointRadiusHandleState::Dragging); @@ -576,7 +571,7 @@ impl Fsm for ShapeToolFsmState { return ShapeToolFsmState::DraggingStarInnerRadius; } - // Check if dragging the Number of Points handle of a star or polygon + // Check if dragging the number of points handle of a star or polygon if tool_data.number_of_points_handle.is_hovering() { tool_data.last_mouse_position = mouse_pos; tool_data.number_of_points_handle.update_state(NumberOfPointsHandleState::Dragging); @@ -638,7 +633,7 @@ impl Fsm for ShapeToolFsmState { ShapeType::Star => Star::create_node(tool_options.vertices), ShapeType::Rectangle => Rectangle::create_node(), ShapeType::Ellipse => Ellipse::create_node(), - ShapeType::Line => Line::create_node(&document, tool_data.data.drag_start), + ShapeType::Line => Line::create_node(document, tool_data.data.drag_start), }; let nodes = vec![(NodeId(0), node)]; @@ -646,12 +641,11 @@ impl Fsm for ShapeToolFsmState { responses.add(Message::StartBuffer); - tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); match tool_data.current_shape { ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Polygon | ShapeType::Star => { responses.add(GraphOperationMessage::TransformSet { layer, - transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0.0, input.mouse.position), + transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), transform_in: TransformIn::Viewport, skip_rerender: false, }); @@ -659,11 +653,12 @@ impl Fsm for ShapeToolFsmState { tool_options.fill.apply_fill(layer, responses); } ShapeType::Line => { - tool_data.line_data.angle = 0.0; + tool_data.line_data.angle = 0.; tool_data.line_data.weight = tool_options.line_weight; tool_data.line_data.editing_layer = Some(layer); } } + tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); tool_data.data.layer = Some(layer); @@ -675,11 +670,11 @@ impl Fsm for ShapeToolFsmState { }; match tool_data.current_shape { - ShapeType::Rectangle => Rectangle::update_shape(&document, &input, layer, tool_data, modifier, responses), - ShapeType::Ellipse => Ellipse::update_shape(&document, &input, layer, tool_data, modifier, responses), - ShapeType::Line => Line::update_shape(&document, &input, layer, tool_data, modifier, responses), - ShapeType::Polygon => Polygon::update_shape(&document, &input, layer, tool_data, modifier, responses), - ShapeType::Star => Star::update_shape(&document, &input, layer, tool_data, modifier, responses), + ShapeType::Rectangle => Rectangle::update_shape(document, input, layer, tool_data, modifier, responses), + ShapeType::Ellipse => Ellipse::update_shape(document, input, layer, tool_data, modifier, responses), + ShapeType::Line => Line::update_shape(document, input, layer, tool_data, modifier, responses), + ShapeType::Polygon => Polygon::update_shape(document, input, layer, tool_data, modifier, responses), + ShapeType::Star => Star::update_shape(document, input, layer, tool_data, modifier, responses), } // Auto-panning @@ -693,7 +688,7 @@ impl Fsm for ShapeToolFsmState { return ShapeToolFsmState::Ready(tool_data.current_shape); }; - Line::update_shape(&document, &input, layer, tool_data, modifier, responses); + Line::update_shape(document, input, layer, tool_data, modifier, responses); // Auto-panning let messages = [ShapeToolMessage::PointerOutsideViewport(modifier).into(), ShapeToolMessage::PointerMove(modifier).into()]; tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); @@ -711,7 +706,7 @@ impl Fsm for ShapeToolFsmState { ShapeToolFsmState::DraggingStarInnerRadius } (ShapeToolFsmState::DraggingStarNumberPointHandle, ShapeToolMessage::PointerMove(..)) => { - tool_data.number_of_points_handle.update_no_of_sides(document, input, responses, tool_data.data.drag_start); + tool_data.number_of_points_handle.update_number_of_sides(document, input, responses, tool_data.data.drag_start); tool_data.last_mouse_position = input.mouse.position; responses.add(OverlaysMessage::Draw); @@ -720,7 +715,7 @@ impl Fsm for ShapeToolFsmState { } (ShapeToolFsmState::ResizingBounds, ShapeToolMessage::PointerMove(modifier)) => { if let Some(bounds) = &mut tool_data.bounding_box_manager { - let messages = [ShapeToolMessage::PointerOutsideViewport(modifier.clone()).into(), ShapeToolMessage::PointerMove(modifier).into()]; + let messages = [ShapeToolMessage::PointerOutsideViewport(modifier).into(), ShapeToolMessage::PointerMove(modifier).into()]; resize_bounds( document, responses, diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index 85cb2e92a1..eab68b8866 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -716,7 +716,8 @@ impl MessageHandler> for TransformLayer #[cfg(test)] mod test_transform_layer { - use crate::messages::portfolio::document::graph_operation::{transform_utils, utility_types::ModifyInputsContext}; + use crate::messages::portfolio::document::graph_operation::transform_utils; + use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; use crate::messages::portfolio::document::utility_types::misc::GroupFolderType; use crate::messages::prelude::Message; use crate::messages::tool::transform_layer::transform_layer_message_handler::VectorModificationType; diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index 93ec2d8848..f2b441331e 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -262,9 +262,9 @@ impl LayoutHolder for ToolData { .tooltip_shortcut(tooltip_shortcut) .on_update(move |_| { match tool_type { - ToolType::Line => ToolMessage::ActivateShapeLine.into(), - ToolType::Ellipse => ToolMessage::ActivateShapeEllipse.into(), - ToolType::Rectangle => ToolMessage::ActivateShapeRectangle.into(), + ToolType::Line => ToolMessage::ActivateToolShapeLine.into(), + ToolType::Rectangle => ToolMessage::ActivateToolShapeRectangle.into(), + ToolType::Ellipse => ToolMessage::ActivateToolShapeEllipse.into(), ToolType::Shape => ToolMessage::ActivateToolShape.into(), _ => { if !tooltip.contains("Coming Soon") { (ToolMessage::ActivateTool { tool_type }).into() } else { (DialogMessage::RequestComingSoonDialog { issue: None }).into() } @@ -351,13 +351,11 @@ pub enum ToolType { Freehand, Spline, Shape, + Line, // Shape tool alias + Rectangle, // Shape tool alias + Ellipse, // Shape tool alias Text, - // Shape group - Rectangle, - Ellipse, - Line, - // Raster tool group Brush, Heal, @@ -381,6 +379,7 @@ impl ToolType { if self.get_shape().is_some() { ToolType::Shape } else { self } } } + enum ToolAvailability { Available(Box), AvailableAsShape(ShapeType), @@ -441,7 +440,7 @@ pub fn tool_message_to_tool_type(tool_message: &ToolMessage) -> ToolType { ToolMessage::Pen(_) => ToolType::Pen, ToolMessage::Freehand(_) => ToolType::Freehand, ToolMessage::Spline(_) => ToolType::Spline, - ToolMessage::Shape(_) => ToolType::Shape, + ToolMessage::Shape(_) => ToolType::Shape, // Includes the Line, Rectangle, and Ellipse aliases ToolMessage::Text(_) => ToolType::Text, // Raster tool group @@ -471,11 +470,11 @@ pub fn tool_type_to_activate_tool_message(tool_type: ToolType) -> ToolMessageDis ToolType::Pen => ToolMessageDiscriminant::ActivateToolPen, ToolType::Freehand => ToolMessageDiscriminant::ActivateToolFreehand, ToolType::Spline => ToolMessageDiscriminant::ActivateToolSpline, + ToolType::Line => ToolMessageDiscriminant::ActivateToolShapeLine, // Shape tool alias + ToolType::Rectangle => ToolMessageDiscriminant::ActivateToolShapeRectangle, // Shape tool alias + ToolType::Ellipse => ToolMessageDiscriminant::ActivateToolShapeEllipse, // Shape tool alias ToolType::Shape => ToolMessageDiscriminant::ActivateToolShape, ToolType::Text => ToolMessageDiscriminant::ActivateToolText, - ToolType::Rectangle => ToolMessageDiscriminant::ActivateShapeRectangle, - ToolType::Ellipse => ToolMessageDiscriminant::ActivateShapeEllipse, - ToolType::Line => ToolMessageDiscriminant::ActivateShapeLine, // Raster tool group ToolType::Brush => ToolMessageDiscriminant::ActivateToolBrush, diff --git a/editor/src/test_utils.rs b/editor/src/test_utils.rs index 620a44a687..683315c767 100644 --- a/editor/src/test_utils.rs +++ b/editor/src/test_utils.rs @@ -219,9 +219,9 @@ impl EditorTestUtils { pub async fn select_tool(&mut self, tool_type: ToolType) { match tool_type { - ToolType::Line => self.handle_message(Message::Tool(ToolMessage::ActivateShapeLine)).await, - ToolType::Rectangle => self.handle_message(Message::Tool(ToolMessage::ActivateShapeRectangle)).await, - ToolType::Ellipse => self.handle_message(Message::Tool(ToolMessage::ActivateShapeEllipse)).await, + ToolType::Line => self.handle_message(Message::Tool(ToolMessage::ActivateToolShapeLine)).await, + ToolType::Rectangle => self.handle_message(Message::Tool(ToolMessage::ActivateToolShapeRectangle)).await, + ToolType::Ellipse => self.handle_message(Message::Tool(ToolMessage::ActivateToolShapeEllipse)).await, _ => self.handle_message(Message::Tool(ToolMessage::ActivateTool { tool_type })).await, } }