diff --git a/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs b/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs index 57cbb4ed33..49af7fc030 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs @@ -7,9 +7,11 @@ 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::arc_shape::ArcGizmoHandler; use crate::messages::tool::common_functionality::shapes::circle_shape::CircleGizmoHandler; +use crate::messages::tool::common_functionality::shapes::line_shape::LineGizmoHandler; use crate::messages::tool::common_functionality::shapes::polygon_shape::PolygonGizmoHandler; use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler; use crate::messages::tool::common_functionality::shapes::star_shape::StarGizmoHandler; +use crate::messages::tool::common_functionality::snapping::SnapManager; use glam::DVec2; use std::collections::VecDeque; @@ -24,6 +26,7 @@ use std::collections::VecDeque; pub enum ShapeGizmoHandlers { #[default] None, + Line(LineGizmoHandler), Star(StarGizmoHandler), Polygon(PolygonGizmoHandler), Arc(ArcGizmoHandler), @@ -35,6 +38,7 @@ impl ShapeGizmoHandlers { /// Used for grouping logic and distinguishing between handler types at runtime. pub fn kind(&self) -> &'static str { match self { + Self::Line(_) => "Line", Self::Star(_) => "star", Self::Polygon(_) => "polygon", Self::Arc(_) => "arc", @@ -44,12 +48,13 @@ impl ShapeGizmoHandlers { } /// Dispatches interaction state updates to the corresponding shape-specific handler. - pub fn handle_state(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { + pub fn handle_state(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler) { match self { - Self::Star(h) => h.handle_state(layer, mouse_position, document, responses), - Self::Polygon(h) => h.handle_state(layer, mouse_position, document, responses), - Self::Arc(h) => h.handle_state(layer, mouse_position, document, responses), - Self::Circle(h) => h.handle_state(layer, mouse_position, document, responses), + Self::Line(h) => h.handle_state(layer, mouse_position, document), + Self::Star(h) => h.handle_state(layer, mouse_position, document), + Self::Polygon(h) => h.handle_state(layer, mouse_position, document), + Self::Arc(h) => h.handle_state(layer, mouse_position, document), + Self::Circle(h) => h.handle_state(layer, mouse_position, document), Self::None => {} } } @@ -57,6 +62,7 @@ impl ShapeGizmoHandlers { /// Checks if any interactive part of the gizmo is currently hovered. pub fn is_any_gizmo_hovered(&self) -> bool { match self { + Self::Line(h) => h.is_any_gizmo_hovered(), Self::Star(h) => h.is_any_gizmo_hovered(), Self::Polygon(h) => h.is_any_gizmo_hovered(), Self::Arc(h) => h.is_any_gizmo_hovered(), @@ -68,6 +74,7 @@ impl ShapeGizmoHandlers { /// Passes the click interaction to the appropriate gizmo handler if one is hovered. pub fn handle_click(&mut self) { match self { + Self::Line(h) => h.handle_click(), Self::Star(h) => h.handle_click(), Self::Polygon(h) => h.handle_click(), Self::Arc(h) => h.handle_click(), @@ -77,12 +84,13 @@ impl ShapeGizmoHandlers { } /// Updates the gizmo state while the user is dragging a handle (e.g., adjusting radius). - pub fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + pub fn handle_update(&mut self, drag_start: DVec2, snap_manager: &mut SnapManager, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { match self { - Self::Star(h) => h.handle_update(drag_start, document, input, responses), - Self::Polygon(h) => h.handle_update(drag_start, document, input, responses), - Self::Arc(h) => h.handle_update(drag_start, document, input, responses), - Self::Circle(h) => h.handle_update(drag_start, document, input, responses), + Self::Line(h) => h.handle_update(drag_start, snap_manager, document, input, responses), + Self::Star(h) => h.handle_update(drag_start, snap_manager, document, input, responses), + Self::Polygon(h) => h.handle_update(drag_start, snap_manager, document, input, responses), + Self::Arc(h) => h.handle_update(drag_start, snap_manager, document, input, responses), + Self::Circle(h) => h.handle_update(drag_start, snap_manager, document, input, responses), Self::None => {} } } @@ -90,6 +98,7 @@ impl ShapeGizmoHandlers { /// Cleans up any state used by the gizmo handler. pub fn cleanup(&mut self) { match self { + Self::Line(h) => h.cleanup(), Self::Star(h) => h.cleanup(), Self::Polygon(h) => h.cleanup(), Self::Arc(h) => h.cleanup(), @@ -109,6 +118,7 @@ impl ShapeGizmoHandlers { overlay_context: &mut OverlayContext, ) { match self { + Self::Line(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context), Self::Star(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context), Self::Polygon(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context), Self::Arc(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context), @@ -127,6 +137,7 @@ impl ShapeGizmoHandlers { overlay_context: &mut OverlayContext, ) { match self { + Self::Line(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context), Self::Star(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context), Self::Polygon(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context), Self::Arc(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context), @@ -137,6 +148,7 @@ impl ShapeGizmoHandlers { pub fn gizmo_cursor_icon(&self) -> Option { match self { + Self::Line(h) => h.mouse_cursor_icon(), Self::Star(h) => h.mouse_cursor_icon(), Self::Polygon(h) => h.mouse_cursor_icon(), Self::Arc(h) => h.mouse_cursor_icon(), @@ -168,6 +180,10 @@ impl GizmoManager { /// /// Returns `None` if the given layer does not represent a shape with a registered gizmo. pub fn detect_shape_handler(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option { + // Line + if graph_modification_utils::get_line_id(layer, &document.network_interface).is_some() { + return Some(ShapeGizmoHandlers::Line(LineGizmoHandler::default())); + } // Star if graph_modification_utils::get_star_id(layer, &document.network_interface).is_some() { return Some(ShapeGizmoHandlers::Star(StarGizmoHandler::default())); @@ -196,12 +212,12 @@ impl GizmoManager { /// Called every frame to check selected layers and update the active shape gizmo, if hovered. /// /// Also groups all shape layers with the same kind of gizmo to support overlays for multi-shape editing. - pub fn handle_actions(&mut self, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { + pub fn handle_actions(&mut self, mouse_position: DVec2, document: &DocumentMessageHandler) { let mut handlers_layer: Vec<(ShapeGizmoHandlers, Vec)> = Vec::new(); for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) { if let Some(mut handler) = Self::detect_shape_handler(layer, document) { - handler.handle_state(layer, mouse_position, document, responses); + handler.handle_state(layer, mouse_position, document); let is_hovered = handler.is_any_gizmo_hovered(); if is_hovered { @@ -239,9 +255,9 @@ impl GizmoManager { } /// Passes drag update data to the active gizmo to update shape parameters live. - pub fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + pub fn handle_update(&mut self, drag_start: DVec2, snap_manager: &mut SnapManager, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { if let Some(handle) = &mut self.active_shape_handler { - handle.handle_update(drag_start, document, input, responses); + handle.handle_update(drag_start, snap_manager, document, input, responses); } } diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/circle_arc_radius_handle.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/circle_arc_radius_handle.rs index 2cbf1c4509..cc0d7f8e41 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/circle_arc_radius_handle.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/circle_arc_radius_handle.rs @@ -1,11 +1,10 @@ use crate::consts::GIZMO_HIDE_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, InputPreprocessorMessageHandler, NodeGraphMessage}; -use crate::messages::prelude::{FrontendMessage, Responses}; use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_arc_id, get_stroke_width}; use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_arc_parameters, extract_circle_radius}; use glam::{DAffine2, DVec2}; @@ -77,7 +76,7 @@ impl RadiusHandle { stroke_width + extra_spacing } - pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque) { + pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2) { match &self.handle_state { RadiusHandleState::Inactive => { let Some(radius) = extract_circle_radius(layer, document).or(extract_arc_parameters(Some(layer), document).map(|(r, _, _, _)| r)) else { @@ -99,8 +98,6 @@ impl RadiusHandle { self.angle = angle; self.update_state(RadiusHandleState::Hover); - - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize }); } } RadiusHandleState::Dragging | RadiusHandleState::Hover => {} diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/line_endpoint_handle.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/line_endpoint_handle.rs new file mode 100644 index 0000000000..a3cb36dfa7 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/line_endpoint_handle.rs @@ -0,0 +1,139 @@ +use crate::consts::BOUNDS_SELECT_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}; +use crate::messages::tool::common_functionality::shapes::LineEnd; +use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_line_parameters, generate_line}; +use crate::messages::tool::common_functionality::snapping::{SnapData, SnapManager}; +use crate::messages::tool::tool_messages::tool_prelude::Key; +use glam::DVec2; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; +use std::collections::VecDeque; + +#[derive(Clone, Debug, Default, PartialEq)] +pub enum LineEndPointHandleState { + #[default] + Inactive, + Hover, + Dragging, +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct LineEndPointHandle { + pub layer: Option, + pub handle_state: LineEndPointHandleState, + end_point: LineEnd, + drag_start: DVec2, + drag_current: DVec2, + angle: f64, +} + +impl LineEndPointHandle { + pub fn cleanup(&mut self) { + self.handle_state = LineEndPointHandleState::Inactive; + self.layer = None; + } + + pub fn hovered(&self) -> bool { + self.handle_state == LineEndPointHandleState::Hover + } + + pub fn is_dragging(&self) -> bool { + self.handle_state == LineEndPointHandleState::Dragging + } + + pub fn update_state(&mut self, state: LineEndPointHandleState) { + self.handle_state = state; + } + + pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2) { + match &self.handle_state { + LineEndPointHandleState::Inactive => { + if self.clicked_on_line_endpoints(layer, document, mouse_position) { + self.drag_current = mouse_position; + self.update_state(LineEndPointHandleState::Hover); + } + } + _ => {} + } + } + + pub fn overlays(&self, selected_line_layer: Option, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { + let Some(layer) = selected_line_layer.or(self.layer) else { return }; + let Some((start, end)) = extract_line_parameters(Some(layer), document) else { return }; + + let [viewport_start, viewport_end] = [start, end].map(|point| document.metadata().transform_to_viewport(layer).transform_point2(point)); + overlay_context.line(viewport_start, viewport_end, None, None); + if !start.abs_diff_eq(end, f64::EPSILON * 1000.) { + overlay_context.square(viewport_start, Some(6.), None, None); + overlay_context.square(viewport_end, Some(6.), None, None); + } + } + + pub fn update_endpoint_position(&mut self, document: &DocumentMessageHandler, snap: &mut SnapManager, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + let Some(layer) = self.layer else { return }; + + self.drag_current = input.mouse.position; + let ignore = [layer]; + let snap_data = SnapData::ignore(document, input, &ignore); + let to_document = document.metadata().transform_to_document(layer); + let (mut document_points, angle) = generate_line( + self.angle, + to_document.transform_point2(self.drag_start), + input.mouse.position, + snap, + snap_data, + input.keyboard.key(Key::Shift), + input.keyboard.key(Key::Control), + input.keyboard.key(Key::Alt), + ); + + self.angle = angle; + + if self.end_point == LineEnd::Start { + document_points.swap(0, 1); + } + let Some(node_id) = graph_modification_utils::get_line_id(layer, &document.network_interface) else { + return; + }; + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 1), + input: NodeInput::value(TaggedValue::DVec2(to_document.inverse().transform_point2(document_points[0])), false), + }); + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 2), + input: NodeInput::value(TaggedValue::DVec2(to_document.inverse().transform_point2(document_points[1])), false), + }); + responses.add(NodeGraphMessage::RunDocumentGraph); + } + + pub fn clicked_on_line_endpoints(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, drag_start: DVec2) -> bool { + let Some((document_start, document_end)) = extract_line_parameters(Some(layer), document) 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 [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 { + self.end_point = if end_click { LineEnd::End } else { LineEnd::Start }; + self.drag_start = if end_click { document_start } else { document_end }; + self.layer = Some(layer); + return true; + } + false + } +} diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs index 710584f471..5ddceb149d 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs @@ -1,4 +1,5 @@ pub mod circle_arc_radius_handle; +pub mod line_endpoint_handle; pub mod number_of_points_dial; pub mod point_radius_handle; pub mod sweep_angle_gizmo; diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs index fa4dbe2ed8..84e68dad06 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs @@ -1,11 +1,10 @@ use crate::consts::{GIZMO_HIDE_THRESHOLD, NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION, NUMBER_OF_POINTS_DIAL_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::prelude::{DocumentMessageHandler, 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, inside_polygon, inside_star, polygon_outline, polygon_vertex_position, star_outline}; @@ -49,7 +48,7 @@ impl NumberOfPointsDial { self.handle_state == NumberOfPointsDialState::Dragging } - pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { + pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler) { match &self.handle_state { NumberOfPointsDialState::Inactive => { // Star @@ -63,7 +62,6 @@ impl NumberOfPointsDial { self.layer = Some(layer); self.initial_points = sides; self.update_state(NumberOfPointsDialState::Hover); - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize }); } } @@ -78,7 +76,6 @@ impl NumberOfPointsDial { self.layer = Some(layer); self.initial_points = sides; self.update_state(NumberOfPointsDialState::Hover); - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize }); } } } @@ -91,7 +88,6 @@ impl NumberOfPointsDial { if mouse_position.distance(center) > NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH && matches!(&self.handle_state, NumberOfPointsDialState::Hover) { self.update_state(NumberOfPointsDialState::Inactive); self.layer = None; - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); } } } diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs index 25516284bd..55c2d9e837 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs @@ -1,10 +1,8 @@ use crate::consts::GIZMO_HIDE_THRESHOLD; use crate::consts::{COLOR_OVERLAY_RED, POINT_RADIUS_HANDLE_SNAP_THRESHOLD}; -use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::message::Message; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::{overlays::utility_types::OverlayContext, utility_types::network_interface::InputConnector}; -use crate::messages::prelude::FrontendMessage; use crate::messages::prelude::Responses; use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage}; use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer}; @@ -54,7 +52,7 @@ impl PointRadiusHandle { self.handle_state = state; } - pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque) { + pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2) { match &self.handle_state { PointRadiusHandleState::Inactive => { // Draw the point handle gizmo for the star shape @@ -77,7 +75,6 @@ impl PointRadiusHandle { self.point = i; self.snap_radii = Self::calculate_snap_radii(document, layer, radius_index); self.initial_radius = radius; - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); self.update_state(PointRadiusHandleState::Hover); return; @@ -105,7 +102,6 @@ impl PointRadiusHandle { self.snap_radii.clear(); self.initial_radius = radius; self.update_state(PointRadiusHandleState::Hover); - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); return; } } diff --git a/editor/src/messages/tool/common_functionality/shapes/arc_shape.rs b/editor/src/messages/tool/common_functionality/shapes/arc_shape.rs index 68803598de..ed61636f55 100644 --- a/editor/src/messages/tool/common_functionality/shapes/arc_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/arc_shape.rs @@ -8,6 +8,7 @@ use crate::messages::tool::common_functionality::gizmos::shape_gizmos::circle_ar use crate::messages::tool::common_functionality::gizmos::shape_gizmos::sweep_angle_gizmo::{SweepAngleGizmo, SweepAngleGizmoState}; use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, arc_outline}; +use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::tool_messages::tool_prelude::*; use glam::DAffine2; use graph_craft::document::NodeInput; @@ -28,9 +29,9 @@ impl ArcGizmoHandler { } impl ShapeGizmoHandler for ArcGizmoHandler { - fn handle_state(&mut self, selected_shape_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { + fn handle_state(&mut self, selected_shape_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler) { self.sweep_angle_gizmo.handle_actions(selected_shape_layer, document, mouse_position); - self.arc_radius_handle.handle_actions(selected_shape_layer, document, mouse_position, responses); + self.arc_radius_handle.handle_actions(selected_shape_layer, document, mouse_position); } fn is_any_gizmo_hovered(&self) -> bool { @@ -54,7 +55,7 @@ impl ShapeGizmoHandler for ArcGizmoHandler { } } - fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + fn handle_update(&mut self, drag_start: DVec2, _snap_manager: &mut SnapManager, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { if self.sweep_angle_gizmo.is_dragging_or_snapped() { self.sweep_angle_gizmo.update_arc(document, input, responses); } diff --git a/editor/src/messages/tool/common_functionality/shapes/circle_shape.rs b/editor/src/messages/tool/common_functionality/shapes/circle_shape.rs index 1e58f872cc..c346cdeced 100644 --- a/editor/src/messages/tool/common_functionality/shapes/circle_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/circle_shape.rs @@ -7,6 +7,7 @@ use crate::messages::tool::common_functionality::gizmos::shape_gizmos::circle_ar 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::{ShapeGizmoHandler, ShapeToolModifierKey}; +use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::tool_messages::shape_tool::ShapeToolData; use crate::messages::tool::tool_messages::tool_prelude::*; use glam::DAffine2; @@ -23,8 +24,8 @@ impl ShapeGizmoHandler for CircleGizmoHandler { self.circle_radius_handle.hovered() } - fn handle_state(&mut self, selected_circle_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { - self.circle_radius_handle.handle_actions(selected_circle_layer, document, mouse_position, responses); + fn handle_state(&mut self, selected_circle_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler) { + self.circle_radius_handle.handle_actions(selected_circle_layer, document, mouse_position); } fn handle_click(&mut self) { @@ -33,7 +34,7 @@ impl ShapeGizmoHandler for CircleGizmoHandler { } } - fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + fn handle_update(&mut self, drag_start: DVec2, _snap_manager: &mut SnapManager, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { if self.circle_radius_handle.is_dragging() { self.circle_radius_handle.update_inner_radius(document, input, responses, drag_start); } 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 8bd22b0b16..cb50a03ccc 100644 --- a/editor/src/messages/tool/common_functionality/shapes/line_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/line_shape.rs @@ -1,12 +1,14 @@ 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::gizmos::shape_gizmos::line_endpoint_handle::{LineEndPointHandle, LineEndPointHandleState}; 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::common_functionality::shape_editor::ShapeState; +use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, generate_line}; +use crate::messages::tool::common_functionality::snapping::{SnapData, SnapManager}; use crate::messages::tool::tool_messages::shape_tool::ShapeToolData; use crate::messages::tool::tool_messages::tool_prelude::*; use glam::DVec2; @@ -14,6 +16,77 @@ use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; use std::collections::VecDeque; +#[derive(Clone, Debug, Default)] +pub struct LineGizmoHandler { + line_endpoint_handle: LineEndPointHandle, +} + +impl LineGizmoHandler { + pub fn new() -> Self { + Self { ..Default::default() } + } +} + +impl ShapeGizmoHandler for LineGizmoHandler { + fn handle_state(&mut self, selected_shape_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler) { + self.line_endpoint_handle.handle_actions(selected_shape_layer, document, mouse_position); + } + + fn is_any_gizmo_hovered(&self) -> bool { + self.line_endpoint_handle.hovered() + } + + fn handle_click(&mut self) { + // If hovering over both the gizmos give priority to sweep angle gizmo + if self.line_endpoint_handle.hovered() { + self.line_endpoint_handle.update_state(LineEndPointHandleState::Dragging); + return; + } + } + + fn handle_update(&mut self, _drag_start: DVec2, snap_manager: &mut SnapManager, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + if self.line_endpoint_handle.is_dragging() { + self.line_endpoint_handle.update_endpoint_position(document, snap_manager, input, responses); + } + } + + fn dragging_overlays( + &self, + document: &DocumentMessageHandler, + _input: &InputPreprocessorMessageHandler, + _shape_editor: &mut &mut crate::messages::tool::common_functionality::shape_editor::ShapeState, + _mouse_position: DVec2, + overlay_context: &mut crate::messages::portfolio::document::overlays::utility_types::OverlayContext, + ) { + if self.line_endpoint_handle.is_dragging() { + self.line_endpoint_handle.overlays(None, document, overlay_context); + } + } + + fn overlays( + &self, + document: &DocumentMessageHandler, + selected_shape_layer: Option, + _input: &InputPreprocessorMessageHandler, + _shape_editor: &mut &mut ShapeState, + _mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ) { + self.line_endpoint_handle.overlays(selected_shape_layer, document, overlay_context); + } + + fn mouse_cursor_icon(&self) -> Option { + if self.line_endpoint_handle.hovered() { + return Some(MouseCursorIcon::Default); + } + + None + } + + fn cleanup(&mut self) { + self.line_endpoint_handle.cleanup(); + } +} #[derive(Clone, PartialEq, Debug, Default)] pub enum LineEnd { #[default] @@ -24,12 +97,8 @@ pub enum LineEnd { #[derive(Clone, Debug, Default)] pub struct LineToolData { 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)] @@ -55,16 +124,23 @@ impl Line { ) { let [center, snap_angle, lock_angle] = modifier; - shape_tool_data.line_data.drag_current = ipp.mouse.position; - let keyboard = &ipp.keyboard; 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)); + let (document_points, angle) = generate_line( + shape_tool_data.line_data.angle, + shape_tool_data.data.drag_start, + ipp.mouse.position, + &mut shape_tool_data.data.snap_manager, + 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); - } + shape_tool_data.line_data.angle = angle; + + let to_document = document.metadata().transform_to_document(layer); let Some(node_id) = graph_modification_utils::get_line_id(layer, &document.network_interface) else { return; @@ -72,131 +148,14 @@ impl Line { responses.add(NodeGraphMessage::SetInput { input_connector: InputConnector::node(node_id, 1), - input: NodeInput::value(TaggedValue::DVec2(document_points[0]), false), + input: NodeInput::value(TaggedValue::DVec2(to_document.inverse().transform_point2(document_points[0])), false), }); responses.add(NodeGraphMessage::SetInput { input_connector: InputConnector::node(node_id, 2), - input: NodeInput::value(TaggedValue::DVec2(document_points[1]), false), + input: NodeInput::value(TaggedValue::DVec2(to_document.inverse().transform_point2(document_points[1])), false), }); responses.add(NodeGraphMessage::RunDocumentGraph); } - - 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::>(); - } -} - -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.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.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.line_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 -} - -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; - }; - - 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 { - 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 } #[cfg(test)] 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 ef90e7a4fa..4fe5bb135c 100644 --- a/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs @@ -14,6 +14,7 @@ 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::ShapeGizmoHandler; use crate::messages::tool::common_functionality::shapes::shape_utility::polygon_outline; +use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::tool_messages::tool_prelude::*; use glam::DAffine2; use graph_craft::document::NodeInput; @@ -31,9 +32,9 @@ impl ShapeGizmoHandler for PolygonGizmoHandler { self.number_of_points_dial.is_hovering() || self.point_radius_handle.hovered() } - fn handle_state(&mut self, selected_star_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { - self.number_of_points_dial.handle_actions(selected_star_layer, mouse_position, document, responses); - self.point_radius_handle.handle_actions(selected_star_layer, document, mouse_position, responses); + fn handle_state(&mut self, selected_star_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler) { + self.number_of_points_dial.handle_actions(selected_star_layer, mouse_position, document); + self.point_radius_handle.handle_actions(selected_star_layer, document, mouse_position); } fn handle_click(&mut self) { @@ -47,7 +48,7 @@ impl ShapeGizmoHandler for PolygonGizmoHandler { } } - fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + fn handle_update(&mut self, drag_start: DVec2, _snap_manager: &mut SnapManager, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { if self.number_of_points_dial.is_dragging() { self.number_of_points_dial.update_number_of_sides(document, input, responses, drag_start); } 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 17c7f8e574..d2cc243c8b 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -1,5 +1,5 @@ use super::ShapeToolData; -use crate::consts::{ARC_SWEEP_GIZMO_RADIUS, ARC_SWEEP_GIZMO_TEXT_HEIGHT}; +use crate::consts::{ARC_SWEEP_GIZMO_RADIUS, ARC_SWEEP_GIZMO_TEXT_HEIGHT, LINE_ROTATE_SNAP_ANGLE}; use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::message::Message; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; @@ -8,6 +8,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::Inpu use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage, Responses}; use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; use crate::messages::tool::common_functionality::shape_editor::ShapeState; +use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration}; use crate::messages::tool::common_functionality::transformation_cage::BoundingBoxManager; use crate::messages::tool::tool_messages::tool_prelude::Key; use crate::messages::tool::utility_types::*; @@ -85,7 +86,7 @@ pub trait ShapeGizmoHandler { /// Called every frame to update the gizmo's interaction state based on the mouse position and selection. /// /// This includes detecting hover states and preparing interaction flags or visual feedback (e.g., highlighting a hovered handle). - fn handle_state(&mut self, selected_shape_layers: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque); + fn handle_state(&mut self, selected_shape_layers: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler); /// Called when a mouse click occurs over the canvas and a gizmo handle is hovered. /// @@ -96,7 +97,7 @@ pub trait ShapeGizmoHandler { /// Called during a drag interaction to update the shape's parameters in real time. /// /// For example, a handle might calculate the distance from the drag start to determine a new radius or update the number of points. - fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque); + fn handle_update(&mut self, drag_start: DVec2, snap_manager: &mut SnapManager, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque); /// Draws the static or hover-dependent overlays associated with the gizmo. /// @@ -259,6 +260,18 @@ pub fn extract_arc_parameters(layer: Option, document: &Doc Some((radius, start_angle, sweep_angle, arc_type)) } +// Extract the node input values of an Line. +/// Returns an option of (start point, end point). +pub fn extract_line_parameters(layer: Option, document: &DocumentMessageHandler) -> Option<(DVec2, DVec2)> { + let node_inputs = NodeGraphLayer::new(layer?, &document.network_interface).find_node_inputs("Line")?; + + let (Some(&TaggedValue::DVec2(start)), Some(&TaggedValue::DVec2(end))) = (node_inputs.get(1)?.as_value(), node_inputs.get(2)?.as_value()) else { + return None; + }; + + Some((start, end)) +} + /// Calculate the viewport positions of arc endpoints pub fn arc_end_points(layer: Option, document: &DocumentMessageHandler) -> Option<(DVec2, DVec2)> { let (radius, start_angle, sweep_angle, _) = extract_arc_parameters(Some(layer?), document)?; @@ -475,3 +488,72 @@ pub fn calculate_arc_text_transform(angle: f64, offset_angle: f64, center: DVec2 ); DAffine2::from_translation(text_texture_position + center) } + +pub fn generate_line( + previous_angle: f64, + drag_start: DVec2, + current_position: DVec2, + snap: &mut SnapManager, + snap_data: SnapData, + lock_angle: bool, + snap_angle: bool, + center: bool, +) -> ([DVec2; 2], f64) { + let document_to_viewport = snap_data.document.metadata().document_to_viewport; + let mut document_points = [drag_start, document_to_viewport.inverse().transform_point2(current_position)]; + + 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 = previous_angle; + } else if snap_angle { + let snap_resolution = LINE_ROTATE_SNAP_ANGLE.to_radians(); + angle = (angle / snap_resolution).round() * snap_resolution; + } + + 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 near_point = SnapCandidatePoint::handle_neighbors(document_points[1], [drag_start]); + let far_point = SnapCandidatePoint::handle_neighbors(2. * document_points[0] - document_points[1], [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[0], document_points[1]], angle) +} 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 4effa98957..9a9fe93347 100644 --- a/editor/src/messages/tool/common_functionality/shapes/star_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/star_shape.rs @@ -10,6 +10,7 @@ use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_rad 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::{ShapeGizmoHandler, star_outline}; +use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::tool_messages::tool_prelude::*; use core::f64; use glam::DAffine2; @@ -28,9 +29,9 @@ impl ShapeGizmoHandler for StarGizmoHandler { self.number_of_points_dial.is_hovering() || self.point_radius_handle.hovered() } - fn handle_state(&mut self, selected_star_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { - self.number_of_points_dial.handle_actions(selected_star_layer, mouse_position, document, responses); - self.point_radius_handle.handle_actions(selected_star_layer, document, mouse_position, responses); + fn handle_state(&mut self, selected_star_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler) { + self.number_of_points_dial.handle_actions(selected_star_layer, mouse_position, document); + self.point_radius_handle.handle_actions(selected_star_layer, document, mouse_position); } fn handle_click(&mut self) { @@ -44,7 +45,7 @@ impl ShapeGizmoHandler for StarGizmoHandler { } } - fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + fn handle_update(&mut self, drag_start: DVec2, _snap_manager: &mut SnapManager, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { if self.number_of_points_dial.is_dragging() { self.number_of_points_dial.update_number_of_sides(document, input, responses, drag_start); } diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 2c6eea957b..f4cab964d7 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -1,5 +1,5 @@ use super::tool_prelude::*; -use crate::consts::{DEFAULT_STROKE_WIDTH, SNAP_POINT_TOLERANCE}; +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; @@ -12,14 +12,14 @@ use crate::messages::tool::common_functionality::graph_modification_utils::NodeG use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::common_functionality::shapes::arc_shape::Arc; use crate::messages::tool::common_functionality::shapes::circle_shape::Circle; -use crate::messages::tool::common_functionality::shapes::line_shape::{LineToolData, clicked_on_line_endpoints}; +use crate::messages::tool::common_functionality::shapes::line_shape::LineToolData; use crate::messages::tool::common_functionality::shapes::polygon_shape::Polygon; use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays}; use crate::messages::tool::common_functionality::shapes::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 crate::messages::tool::common_functionality::utility_functions::{resize_bounds, rotate_bounds, skew_bounds, transforming_transform_cage}; use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput}; use graphene_std::Color; @@ -315,12 +315,7 @@ impl<'a> MessageHandler> for Shap DecreaseSides, NudgeSelectedLayers, ), - ShapeToolFsmState::Drawing(_) - | ShapeToolFsmState::ResizingBounds - | ShapeToolFsmState::DraggingLineEndpoints - | ShapeToolFsmState::RotatingBounds - | ShapeToolFsmState::ModifyingGizmo - | ShapeToolFsmState::SkewingBounds { .. } => { + ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::RotatingBounds | ShapeToolFsmState::ModifyingGizmo | ShapeToolFsmState::SkewingBounds { .. } => { actions!(ShapeToolMessageDiscriminant; DragStop, Abort, @@ -365,7 +360,6 @@ pub enum ShapeToolFsmState { Drawing(ShapeType), // Gizmos - DraggingLineEndpoints, ModifyingGizmo, // Transform cage @@ -452,7 +446,6 @@ impl Fsm for ShapeToolFsmState { document, global_tool_data, input, - preferences, shape_editor, .. }: &mut ToolActionMessageContext, @@ -477,7 +470,7 @@ impl Fsm for ShapeToolFsmState { .unwrap_or(input.mouse.position); if matches!(self, Self::Ready(_)) && !input.keyboard.key(Key::Control) { - tool_data.gizmo_manager.handle_actions(mouse_position, document, responses); + tool_data.gizmo_manager.handle_actions(mouse_position, document); tool_data.gizmo_manager.overlays(document, input, shape_editor, mouse_position, &mut overlay_context); } @@ -504,8 +497,6 @@ impl Fsm for ShapeToolFsmState { anchor_overlays(document, &mut overlay_context); responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }); } else if matches!(self, ShapeToolFsmState::Ready(_)) { - Line::overlays(document, tool_data, &mut overlay_context); - if all_selected_layers_line { return self; } @@ -540,11 +531,8 @@ impl Fsm for ShapeToolFsmState { responses.add(FrontendMessage::UpdateMouseCursor { cursor }); } - if matches!(self, ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::DraggingLineEndpoints) { - Line::overlays(document, tool_data, &mut overlay_context); - if tool_options.shape_type == ShapeType::Circle { - tool_data.gizmo_manager.overlays(document, input, shape_editor, mouse_position, &mut overlay_context); - } + if matches!(self, ShapeToolFsmState::Drawing(_)) { + tool_data.gizmo_manager.overlays(document, input, shape_editor, mouse_position, &mut overlay_context); } self @@ -657,8 +645,6 @@ impl Fsm for ShapeToolFsmState { self } (ShapeToolFsmState::Ready(_), ShapeToolMessage::DragStart) => { - tool_data.line_data.drag_start = input.mouse.position; - // Snapped position in viewport space let mouse_pos = tool_data .data @@ -667,8 +653,6 @@ impl Fsm for ShapeToolFsmState { .map(|pos| document.metadata().document_to_viewport.transform_point2(pos)) .unwrap_or(input.mouse.position); - tool_data.line_data.drag_current = mouse_pos; - if tool_data.gizmo_manager.handle_click() && !input.keyboard.key(Key::Accel) { tool_data.data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(mouse_pos); responses.add(DocumentMessage::StartTransaction); @@ -684,20 +668,6 @@ impl Fsm for ShapeToolFsmState { return ShapeToolFsmState::ModifyingGizmo; } - // If clicked on endpoints of a selected line, drag its endpoints - if let Some((layer, _, _)) = closest_point( - document, - 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, 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, None); if !input.keyboard.key(Key::Control) { @@ -773,10 +743,8 @@ impl Fsm for ShapeToolFsmState { } ShapeType::Line => { 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, defered_responses); tool_options.stroke.apply_stroke(tool_options.line_weight, layer, defered_responses); tool_data.data.layer = Some(layer); @@ -809,21 +777,15 @@ impl Fsm for ShapeToolFsmState { self } - (ShapeToolFsmState::DraggingLineEndpoints, ShapeToolMessage::PointerMove { modifier }) => { - let Some(layer) = tool_data.line_data.editing_layer else { - return ShapeToolFsmState::Ready(tool_data.current_shape); - }; - Line::update_shape(document, input, layer, tool_data, modifier, responses); - // Auto-panning + (ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::PointerMove { modifier }) => { + tool_data + .gizmo_manager + .handle_update(tool_data.data.viewport_drag_start(document), &mut tool_data.data.snap_manager, document, input, responses); + let messages = [ShapeToolMessage::PointerOutsideViewport { modifier }.into(), ShapeToolMessage::PointerMove { modifier }.into()]; tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); - self - } - (ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::PointerMove { .. }) => { - tool_data.gizmo_manager.handle_update(tool_data.data.viewport_drag_start(document), document, input, responses); - responses.add(OverlaysMessage::Draw); ShapeToolFsmState::ModifyingGizmo @@ -921,12 +883,7 @@ impl Fsm for ShapeToolFsmState { self } ( - ShapeToolFsmState::Drawing(_) - | ShapeToolFsmState::DraggingLineEndpoints - | ShapeToolFsmState::ResizingBounds - | ShapeToolFsmState::RotatingBounds - | ShapeToolFsmState::SkewingBounds { .. } - | ShapeToolFsmState::ModifyingGizmo, + ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::RotatingBounds | ShapeToolFsmState::SkewingBounds { .. } | ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::DragStop, ) => { input.mouse.finish_transaction(tool_data.data.drag_start, responses); @@ -938,24 +895,16 @@ impl Fsm for ShapeToolFsmState { bounds.original_transforms.clear(); } - tool_data.line_data.dragging_endpoint = None; - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }); ShapeToolFsmState::Ready(tool_data.current_shape) } ( - ShapeToolFsmState::Drawing(_) - | ShapeToolFsmState::DraggingLineEndpoints - | ShapeToolFsmState::ResizingBounds - | ShapeToolFsmState::RotatingBounds - | ShapeToolFsmState::SkewingBounds { .. } - | ShapeToolFsmState::ModifyingGizmo, + ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::RotatingBounds | ShapeToolFsmState::SkewingBounds { .. } | ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::Abort, ) => { responses.add(DocumentMessage::AbortTransaction); tool_data.data.cleanup(responses); - tool_data.line_data.dragging_endpoint = None; tool_data.gizmo_manager.handle_cleanup(); @@ -1066,14 +1015,6 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque 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"), - ]), - ]), 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")]),