diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 19edd48af..c943efe24 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -122,8 +122,8 @@ pub const DEFAULT_BRUSH_SIZE: f64 = 20.; // 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 NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION: f64 = 1.2; +pub const NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH: f64 = 10.; pub const GIZMO_HIDE_THRESHOLD: f64 = 20.; // SCROLLBARS diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 0d5bd7597..a4550d4ce 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -1624,7 +1624,7 @@ impl DocumentMessageHandler { subpath.is_inside_subpath(&viewport_polygon, None, None) } ClickTargetType::FreePoint(point) => { - let mut point = point.clone(); + let mut point = *point; point.apply_transform(layer_transform); viewport_polygon.contains_point(point.position) } @@ -3346,9 +3346,9 @@ mod document_message_handler_tests { let rect_bbox_after = document.metadata().bounding_box_viewport(rect_layer).unwrap(); // Verifing the rectangle maintains approximately the same position in viewport space - let before_center = (rect_bbox_before[0] + rect_bbox_before[1]) / 2.; // TODO: Should be: DVec2(0.0, -25.0), regression (#2688) causes it to be: DVec2(100.0, 25.0) - let after_center = (rect_bbox_after[0] + rect_bbox_after[1]) / 2.; // TODO: Should be: DVec2(0.0, -25.0), regression (#2688) causes it to be: DVec2(200.0, 75.0) - let distance = before_center.distance(after_center); // TODO: Should be: 0.0, regression (#2688) causes it to be: 111.80339887498948 + let before_center = (rect_bbox_before[0] + rect_bbox_before[1]) / 2.; // TODO: Should be: DVec2(0., -25.), regression (#2688) causes it to be: DVec2(100., 25.) + let after_center = (rect_bbox_after[0] + rect_bbox_after[1]) / 2.; // TODO: Should be: DVec2(0., -25.), regression (#2688) causes it to be: DVec2(200., 75.) + let distance = before_center.distance(after_center); // TODO: Should be: 0., regression (#2688) causes it to be: 111.80339887498948 assert!( distance < 1., diff --git a/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs b/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs new file mode 100644 index 000000000..703c85e14 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs @@ -0,0 +1,246 @@ +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::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler}; +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::polygon_shape::PolygonGizmoHandler; +use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler; +use crate::messages::tool::common_functionality::shapes::star_shape::StarGizmoHandler; +use glam::DVec2; +use std::collections::VecDeque; + +/// A unified enum wrapper around all available shape-specific gizmo handlers. +/// +/// This abstraction allows `GizmoManager` to interact with different shape gizmos (like Star or Polygon) +/// using a common interface without needing to know the specific shape type at compile time. +/// +/// Each variant stores a concrete handler (e.g., `StarGizmoHandler`, `PolygonGizmoHandler`) that implements +/// the shape-specific logic for rendering overlays, responding to input, and modifying shape parameters. +#[derive(Clone, Debug, Default)] +pub enum ShapeGizmoHandlers { + #[default] + None, + Star(StarGizmoHandler), + Polygon(PolygonGizmoHandler), +} + +impl ShapeGizmoHandlers { + /// Returns the kind of shape the handler is managing, such as `"star"` or `"polygon"`. + /// Used for grouping logic and distinguishing between handler types at runtime. + pub fn kind(&self) -> &'static str { + match self { + Self::Star(_) => "star", + Self::Polygon(_) => "polygon", + Self::None => "none", + } + } + + /// 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) { + 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::None => {} + } + } + + /// Checks if any interactive part of the gizmo is currently hovered. + pub fn is_any_gizmo_hovered(&self) -> bool { + match self { + Self::Star(h) => h.is_any_gizmo_hovered(), + Self::Polygon(h) => h.is_any_gizmo_hovered(), + Self::None => false, + } + } + + /// Passes the click interaction to the appropriate gizmo handler if one is hovered. + pub fn handle_click(&mut self) { + match self { + Self::Star(h) => h.handle_click(), + Self::Polygon(h) => h.handle_click(), + Self::None => {} + } + } + + /// 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) { + 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::None => {} + } + } + + /// Cleans up any state used by the gizmo handler. + pub fn cleanup(&mut self) { + match self { + Self::Star(h) => h.cleanup(), + Self::Polygon(h) => h.cleanup(), + Self::None => {} + } + } + + /// Draws overlays like control points or outlines for the shape handled by this gizmo. + pub fn overlays( + &self, + document: &DocumentMessageHandler, + layer: Option, + input: &InputPreprocessorMessageHandler, + shape_editor: &mut &mut ShapeState, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ) { + match self { + 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::None => {} + } + } + + /// Draws live-updating overlays during drag interactions for the shape handled by this gizmo. + pub fn dragging_overlays( + &self, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + shape_editor: &mut &mut ShapeState, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ) { + match self { + 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::None => {} + } + } +} + +/// Central manager that coordinates shape gizmo handlers for interactive editing on the canvas. +/// +/// The `GizmoManager` is responsible for detecting which shapes are selected, activating the appropriate +/// shape-specific gizmo, and routing user interactions (hover, click, drag) to the correct handler. +/// It allows editing multiple shapes of the same type or focusing on a single active shape when a gizmo is hovered. +/// +/// ## Responsibilities: +/// - Detect which selected layers support shape gizmos (e.g., stars, polygons) +/// - Activate the correct handler and manage state between frames +/// - Route click, hover, and drag events to the proper shape gizmo +/// - Render overlays and dragging visuals +#[derive(Clone, Debug, Default)] +pub struct GizmoManager { + active_shape_handler: Option, + layers_handlers: Vec<(ShapeGizmoHandlers, Vec)>, +} + +impl GizmoManager { + /// Detects and returns a shape gizmo handler based on the layer type (e.g., star, polygon). + /// + /// 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 { + // Star + if graph_modification_utils::get_star_id(layer, &document.network_interface).is_some() { + return Some(ShapeGizmoHandlers::Star(StarGizmoHandler::default())); + } + + // Polygon + if graph_modification_utils::get_polygon_id(layer, &document.network_interface).is_some() { + return Some(ShapeGizmoHandlers::Polygon(PolygonGizmoHandler::default())); + } + + None + } + + /// Returns `true` if a gizmo is currently active (hovered or being interacted with). + pub fn hovering_over_gizmo(&self) -> bool { + self.active_shape_handler.is_some() + } + + /// 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) { + 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); + let is_hovered = handler.is_any_gizmo_hovered(); + + if is_hovered { + self.layers_handlers.clear(); + self.active_shape_handler = Some(handler); + return; + } + + // Try to group this handler with others of the same type + if let Some((_, layers)) = handlers_layer.iter_mut().find(|(existing_handler, _)| existing_handler.kind() == handler.kind()) { + layers.push(layer); + } else { + handlers_layer.push((handler, vec![layer])); + } + } + } + + self.layers_handlers = handlers_layer; + self.active_shape_handler = None; + } + + /// Handles click interactions if a gizmo is active. Returns `true` if a gizmo handled the click. + pub fn handle_click(&mut self) -> bool { + if let Some(handle) = &mut self.active_shape_handler { + handle.handle_click(); + return true; + } + false + } + + pub fn handle_cleanup(&mut self) { + if let Some(handle) = &mut self.active_shape_handler { + handle.cleanup(); + } + } + + /// 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) { + if let Some(handle) = &mut self.active_shape_handler { + handle.handle_update(drag_start, document, input, responses); + } + } + + /// Draws overlays for the currently active shape gizmo during a drag interaction. + pub fn dragging_overlays( + &self, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + shape_editor: &mut &mut ShapeState, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ) { + if let Some(handle) = &self.active_shape_handler { + handle.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context); + } + } + + /// Draws overlays for either the active gizmo (if hovered) or all grouped selected gizmos. + /// + /// If no single gizmo is active, it renders overlays for all grouped layers with associated handlers. + pub fn overlays( + &self, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + shape_editor: &mut &mut ShapeState, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ) { + if let Some(handler) = &self.active_shape_handler { + handler.overlays(document, None, input, shape_editor, mouse_position, overlay_context); + return; + } + + for (handler, selected_layers) in &self.layers_handlers { + for layer in selected_layers { + handler.overlays(document, Some(*layer), input, shape_editor, mouse_position, overlay_context); + } + } + } +} diff --git a/editor/src/messages/tool/common_functionality/gizmos/mod.rs b/editor/src/messages/tool/common_functionality/gizmos/mod.rs new file mode 100644 index 000000000..108c45d6a --- /dev/null +++ b/editor/src/messages/tool/common_functionality/gizmos/mod.rs @@ -0,0 +1,2 @@ +pub mod gizmo_manager; +pub mod shape_gizmos; 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 new file mode 100644 index 000000000..2b88dddd5 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs @@ -0,0 +1,2 @@ +pub mod number_of_points_dial; +pub mod point_radius_handle; 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 new file mode 100644 index 000000000..3995a1f40 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs @@ -0,0 +1,209 @@ +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::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}; +use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_star_parameters, star_vertex_position}; +use glam::{DAffine2, DVec2}; +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 NumberOfPointsDialState { + #[default] + Inactive, + Hover, + Dragging, +} + +#[derive(Clone, Debug, Default)] +pub struct NumberOfPointsDial { + pub layer: Option, + pub initial_points: u32, + pub handle_state: NumberOfPointsDialState, +} + +impl NumberOfPointsDial { + pub fn cleanup(&mut self) { + self.handle_state = NumberOfPointsDialState::Inactive; + self.layer = None; + } + + pub fn update_state(&mut self, state: NumberOfPointsDialState) { + self.handle_state = state; + } + + pub fn is_hovering(&self) -> bool { + self.handle_state == NumberOfPointsDialState::Hover + } + + pub fn is_dragging(&self) -> bool { + self.handle_state == NumberOfPointsDialState::Dragging + } + + pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { + match &self.handle_state { + NumberOfPointsDialState::Inactive => { + // Star + if let Some((sides, 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 = star_vertex_position(viewport, 0, sides, radius1, radius2); + + if mouse_position.distance(center) < NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD { + self.layer = Some(layer); + self.initial_points = sides; + self.update_state(NumberOfPointsDialState::Hover); + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize }); + } + } + + // Polygon + if let Some((sides, 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 = polygon_vertex_position(viewport, 0, sides, radius); + + if mouse_position.distance(center) < NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD { + self.layer = Some(layer); + self.initial_points = sides; + self.update_state(NumberOfPointsDialState::Hover); + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize }); + } + } + } + NumberOfPointsDialState::Hover | NumberOfPointsDialState::Dragging => { + let Some(layer) = self.layer 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_DIAL_SPOKE_LENGTH && matches!(&self.handle_state, NumberOfPointsDialState::Hover) { + self.update_state(NumberOfPointsDialState::Inactive); + self.layer = None; + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); + } + } + } + } + + pub fn overlays(&self, document: &DocumentMessageHandler, layer: Option, shape_editor: &mut &mut ShapeState, mouse_position: DVec2, overlay_context: &mut OverlayContext) { + match &self.handle_state { + NumberOfPointsDialState::Inactive => { + let Some(layer) = layer else { return }; + + // Star + if let Some((sides, 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 = star_vertex_position(viewport, 0, sides, radius1, radius2); + + if inside_star(viewport, sides, radius1, radius2, mouse_position) && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD { + self.draw_spokes(center, viewport, sides, radius, overlay_context); + return; + } + } + + // Polygon + if let Some((sides, 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 = polygon_vertex_position(viewport, 0, sides, radius); + + if inside_polygon(viewport, sides, radius, mouse_position) && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD { + self.draw_spokes(center, viewport, sides, radius, overlay_context); + } + } + } + NumberOfPointsDialState::Hover | NumberOfPointsDialState::Dragging => { + let Some(layer) = self.layer else { + return; + }; + + // Get the star's greater radius or polygon's radius, as well as the number of sides + let Some((sides, radius)) = extract_star_parameters(Some(layer), document) + .map(|(sides, r1, r2)| (sides, r1.max(r2))) + .or_else(|| extract_polygon_parameters(Some(layer), document)) + else { + return; + }; + + let viewport = document.metadata().transform_to_viewport(layer); + let center = viewport.transform_point2(DVec2::ZERO); + + // Draw either the star or polygon outline + star_outline(Some(layer), document, overlay_context); + polygon_outline(Some(layer), document, overlay_context); + + self.draw_spokes(center, viewport, sides, radius, overlay_context); + } + } + } + + fn draw_spokes(&self, center: DVec2, viewport: DAffine2, sides: u32, radius: f64, overlay_context: &mut OverlayContext) { + for i in 0..sides { + let angle = ((i as f64) * TAU) / (sides 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_DIAL_SPOKE_LENGTH; + if matches!(self.handle_state, NumberOfPointsDialState::Hover | NumberOfPointsDialState::Dragging) { + overlay_context.line(center, end_point * NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION + center, None, None); + } else { + overlay_context.line(center, end_point + center, None, None); + } + } + } + + 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.).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/gizmos/shape_gizmos/point_radius_handle.rs similarity index 54% rename from editor/src/messages/tool/common_functionality/shape_gizmos/point_radius_handle.rs rename to editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs index 4a72de031..fc00e078c 100644 --- a/editor/src/messages/tool/common_functionality/shape_gizmos/point_radius_handle.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs @@ -1,12 +1,15 @@ -use crate::consts::{COLOR_OVERLAY_RED, GIZMO_HIDE_THRESHOLD, POINT_RADIUS_HANDLE_SNAP_THRESHOLD}; +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::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::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}; -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 crate::messages::tool::common_functionality::shapes::shape_utility::{draw_snapping_ticks, extract_polygon_parameters, polygon_outline, polygon_vertex_position, star_outline}; +use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_star_parameters, star_vertex_position}; use glam::DVec2; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; @@ -39,78 +42,71 @@ impl PointRadiusHandle { 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 is_dragging_or_snapped(&self) -> bool { + self.handle_state == PointRadiusHandleState::Dragging || matches!(self.handle_state, PointRadiusHandleState::Snapped(_)) + } + pub fn update_state(&mut self, state: PointRadiusHandleState) { self.handle_state = state; } - pub fn handle_actions(&mut self, document: &DocumentMessageHandler, mouse_position: DVec2) { + pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque) { 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); + // Draw the point handle gizmo for the star shape + if let Some((sides, 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 = star_vertex_position(viewport, i as i32, n, radius1, radius2); - let center = viewport.transform_point2(DVec2::ZERO); + for i in 0..2 * sides { + let (radius, radius_index) = if i % 2 == 0 { (radius1, 2) } else { (radius2, 3) }; + let point = star_vertex_position(viewport, i as i32, sides, 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 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); + 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; + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); + self.update_state(PointRadiusHandleState::Hover); - return; - } + 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); + // Draw the point handle gizmo for the polygon shape + if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) { + let viewport = document.metadata().transform_to_viewport(layer); - for i in 0..n { - let point = polygon_vertex_position(viewport, i as i32, n, radius); - let center = viewport.transform_point2(DVec2::ZERO); + for i in 0..sides { + let point = polygon_vertex_position(viewport, i as i32, sides, radius); + let center = viewport.transform_point2(DVec2::ZERO); - // If the user zooms out so the shape is very small, hide the gizmo - if point.distance(center) < GIZMO_HIDE_THRESHOLD { - return; - } + // 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 = 2; - self.layer = Some(layer); - self.point = i; - self.snap_radii.clear(); - self.initial_radius = radius; - self.update_state(PointRadiusHandleState::Hover); - - return; - } + if point.distance(mouse_position) < 5. { + self.radius_index = 2; + self.layer = Some(layer); + self.point = i; + self.snap_radii.clear(); + self.initial_radius = radius; + self.update_state(PointRadiusHandleState::Hover); + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); + return; } } } @@ -121,8 +117,9 @@ impl PointRadiusHandle { let viewport = document.metadata().transform_to_viewport(layer); - if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) { - let point = star_vertex_position(viewport, self.point as i32, n, radius1, radius2); + // Star + if let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) { + let point = star_vertex_position(viewport, self.point as i32, sides, radius1, radius2); if matches!(&self.handle_state, PointRadiusHandleState::Hover) && (mouse_position - point).length() > 5. { self.update_state(PointRadiusHandleState::Inactive); @@ -131,8 +128,9 @@ impl PointRadiusHandle { } } - if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) { - let point = polygon_vertex_position(viewport, self.point as i32, n, radius); + // Polygon + if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) { + let point = polygon_vertex_position(viewport, self.point as i32, sides, radius); if matches!(&self.handle_state, PointRadiusHandleState::Hover) && (mouse_position - point).length() > 5. { self.update_state(PointRadiusHandleState::Inactive); @@ -144,85 +142,109 @@ impl PointRadiusHandle { } } - pub fn overlays(&mut self, other_gizmo_active: bool, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, mouse_position: DVec2, overlay_context: &mut OverlayContext) { + pub fn overlays( + &self, + selected_star_layer: Option, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ) { match &self.handle_state { PointRadiusHandleState::Inactive => { - 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; - } - // 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); + let Some(layer) = selected_star_layer else { return }; - 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(); + // Draw the point handle gizmo for the star shape + if let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) { + let viewport = document.metadata().transform_to_viewport(layer); - // If the user zooms out such that shape is very small hide the gizmo - if point.distance(center) < GIZMO_HIDE_THRESHOLD { - return; - } + for i in 0..(2 * sides) { + let point = star_vertex_position(viewport, i as i32, sides, radius1, radius2); + let center = viewport.transform_point2(DVec2::ZERO); + let viewport_diagonal = input.viewport_bounds.size().length(); - 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); + // If the user zooms out such that shape is very small hide the gizmo + if point.distance(center) < GIZMO_HIDE_THRESHOLD { + 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); + if point.distance(mouse_position) < 5. { + let Some(direction) = (point - center).try_normalize() else { continue }; - for i in 0..n { - 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(); + overlay_context.manipulator_handle(point, true, None); + let angle = ((i as f64) * PI) / (sides as f64); + overlay_context.line(center, center + direction * viewport_diagonal, None, None); - // If the user zooms out such that shape is very small hide the gizmo - if point.distance(center) < GIZMO_HIDE_THRESHOLD { - return; - } + draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context); - 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); + return; } + + overlay_context.manipulator_handle(point, false, None); + } + } + + // Draw the point handle gizmo for the Polygon shape + if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) { + let viewport = document.metadata().transform_to_viewport(layer); + + for i in 0..sides { + let point = polygon_vertex_position(viewport, i as i32, sides, 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 = star_vertex_position(viewport, self.point as i32, n, radius1, radius2); + // Star + if let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) { + let angle = ((self.point as f64) * PI) / (sides as f64); + let point = star_vertex_position(viewport, self.point as i32, sides, 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); + star_outline(Some(layer), document, overlay_context); + + // Make the ticks for snapping + + // If dragging to make radius negative don't show the + if (mouse_position - center).dot(direction) < 0. { + return; + } + draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context); + + return; + } + + // Polygon + if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) { + let point = polygon_vertex_position(viewport, self.point as i32, sides, radius); let Some(direction) = (point - center).try_normalize() else { return }; @@ -230,47 +252,33 @@ impl PointRadiusHandle { overlay_context.manipulator_handle(point, true, None); overlay_context.line(center, center + direction * viewport_diagonal, None, None); - // Makes the tick marks for snapping - - // 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); - } - - return; - } - - if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) { - let point = 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 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); + polygon_outline(Some(layer), document, overlay_context); } } 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((sides, 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 the previous two points + // Make a triangle with previous two points 0 => { - 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 before_outer_position = star_vertex_position(viewport, (self.point as i32) - 2, sides, radius1, radius2); + let outer_position = star_vertex_position(viewport, (self.point as i32) - 1, sides, radius1, radius2); + let point_position = star_vertex_position(viewport, self.point as i32, sides, radius1, radius2); 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 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; @@ -280,18 +288,20 @@ impl PointRadiusHandle { overlay_context.line(new_point, point_position, Some(COLOR_OVERLAY_RED), Some(3.)); } 1 => { - 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 before_outer_position = star_vertex_position(viewport, (self.point as i32) - 1, sides, radius1, radius2); + + let after_point_position = star_vertex_position(viewport, (self.point as i32) + 1, sides, radius1, radius2); + + let point_position = star_vertex_position(viewport, self.point as i32, sides, 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 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 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; @@ -301,35 +311,37 @@ impl PointRadiusHandle { overlay_context.line(new_point, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.)); } i => { - // Use `self.point` as an absolute reference, as it matches the index of the star's vertices starting from 0 + // Use `self.point` as absolute reference as it matches the index of vertices of the star starting from 0 if i % 2 != 0 { // Flipped case - let point_position = star_vertex_position(viewport, self.point as i32, n, radius1, radius2); + let point_position = star_vertex_position(viewport, self.point as i32, sides, radius1, radius2); let target_index = (1 - (*i as i32)).abs() + (self.point as i32); - let target_point_position = star_vertex_position(viewport, target_index, n, radius1, radius2); + let target_point_position = star_vertex_position(viewport, target_index, sides, radius1, radius2); let mirrored_index = 2 * (self.point as i32) - target_index; - let mirrored = star_vertex_position(viewport, mirrored_index, n, radius1, radius2); + let mirrored = star_vertex_position(viewport, mirrored_index, sides, radius1, radius2); 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 = star_vertex_position(viewport, outer_index, n, radius1, radius2); + let outer_position = star_vertex_position(viewport, outer_index, sides, 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 = star_vertex_position(viewport, target_index, n, radius1, radius2); + let target_point_position = star_vertex_position(viewport, target_index, sides, radius1, radius2); let mirrored_index = 2 * outer_index - target_index; - let mirrored = star_vertex_position(viewport, mirrored_index, n, radius1, radius2); + let mirrored = star_vertex_position(viewport, mirrored_index, sides, radius1, radius2); 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.)); } } } + + star_outline(Some(layer), document, overlay_context); } } } @@ -342,29 +354,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 { + let Some(&TaggedValue::U32(sides)) = node_inputs[1].as_value() else { return snap_radii; }; // Inner radius for 90° - let b = FRAC_PI_4 * 3. - PI / (n as f64); + let b = FRAC_PI_4 * 3. - PI / (sides as f64); let angle = b.sin(); let required_radius = (other_radius / angle) * FRAC_1_SQRT_2; snap_radii.push(required_radius); - // Also add the case where the radius exceeds the other radius (the "flipped" case) + // 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; + for i in 1..sides { + let sides = sides 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 denominator = 2. * ((PI * (i - 1.)) / sides).cos() * ((PI * i) / sides).sin(); + let numerator = ((2. * PI * i) / sides).sin(); let factor = numerator / denominator; if factor < 0. { @@ -392,33 +407,32 @@ impl PointRadiusHandle { // 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 + // `a` is priority index, `b` is not + (true, false) => std::cmp::Ordering::Less, + // `b` is priority index, `a` is not + (false, true) => std::cmp::Ordering::Greater, + // Normal comparison + _ => dist_a.partial_cmp(&dist_b).unwrap_or(std::cmp::Ordering::Equal), } }) .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, - ) { + pub fn update_inner_radius(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { + 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 transform = document.network_interface.document_metadata().transform_to_viewport(layer); - let center = transform.transform_point2(DVec2::ZERO); + let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer); + let document_transform = document.network_interface.document_metadata().transform_to_document(layer); + let center = viewport_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 delta = viewport_transform.inverse().transform_point2(input.mouse.position) - document_transform.inverse().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(); diff --git a/editor/src/messages/tool/common_functionality/mod.rs b/editor/src/messages/tool/common_functionality/mod.rs index 4a003d025..ca3e629e1 100644 --- a/editor/src/messages/tool/common_functionality/mod.rs +++ b/editor/src/messages/tool/common_functionality/mod.rs @@ -1,12 +1,12 @@ pub mod auto_panning; pub mod color_selector; pub mod compass_rose; +pub mod gizmos; pub mod graph_modification_utils; 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 deleted file mode 100644 index 03951cd2d..000000000 --- a/editor/src/messages/tool/common_functionality/shape_gizmos/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -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 deleted file mode 100644 index d698292ea..000000000 --- a/editor/src/messages/tool/common_functionality/shape_gizmos/number_of_points_handle.rs +++ /dev/null @@ -1,241 +0,0 @@ -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 crate::messages::tool::tool_messages::tool_prelude::Key; -use glam::{DAffine2, DVec2}; -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 { - #[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 => { - 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 = 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 = 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)) - 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 }); - } - } - } - } - - 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 => { - 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); - 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 = 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 = 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)) - 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_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.).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/shapes/polygon_shape.rs b/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs index 871cf2c0b..82dcf10cf 100644 --- a/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs @@ -3,15 +3,98 @@ 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::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::number_of_points_dial::NumberOfPointsDial; +use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::NumberOfPointsDialState; +use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::PointRadiusHandle; +use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::PointRadiusHandleState; 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::tool_messages::tool_prelude::*; use glam::DAffine2; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; use std::collections::VecDeque; +#[derive(Clone, Debug, Default)] +pub struct PolygonGizmoHandler { + number_of_points_dial: NumberOfPointsDial, + point_radius_handle: PointRadiusHandle, +} + +impl ShapeGizmoHandler for PolygonGizmoHandler { + fn is_any_gizmo_hovered(&self) -> bool { + 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_click(&mut self) { + if self.number_of_points_dial.is_hovering() { + self.number_of_points_dial.update_state(NumberOfPointsDialState::Dragging); + return; + } + + if self.point_radius_handle.hovered() { + self.point_radius_handle.update_state(PointRadiusHandleState::Dragging); + } + } + + fn handle_update(&mut self, drag_start: DVec2, 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); + } + + if self.point_radius_handle.is_dragging_or_snapped() { + self.point_radius_handle.update_inner_radius(document, input, responses, drag_start); + } + } + + fn overlays( + &self, + document: &DocumentMessageHandler, + selected_polygon_layer: Option, + input: &InputPreprocessorMessageHandler, + shape_editor: &mut &mut ShapeState, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ) { + self.number_of_points_dial.overlays(document, selected_polygon_layer, shape_editor, mouse_position, overlay_context); + self.point_radius_handle.overlays(selected_polygon_layer, document, input, mouse_position, overlay_context); + + polygon_outline(selected_polygon_layer, document, overlay_context); + } + + fn dragging_overlays( + &self, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + shape_editor: &mut &mut ShapeState, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ) { + if self.number_of_points_dial.is_dragging() { + self.number_of_points_dial.overlays(document, None, shape_editor, mouse_position, overlay_context); + } + + if self.point_radius_handle.is_dragging_or_snapped() { + self.point_radius_handle.overlays(None, document, input, mouse_position, overlay_context); + } + } + + fn cleanup(&mut self) { + self.number_of_points_dial.cleanup(); + self.point_radius_handle.cleanup(); + } +} + #[derive(Default)] pub struct Polygon; 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 772991be4..be61c9764 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -3,8 +3,9 @@ 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::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::transformation_cage::BoundingBoxManager; use crate::messages::tool::tool_messages::tool_prelude::Key; use crate::messages::tool::utility_types::*; @@ -70,9 +71,62 @@ impl ShapeType { } } -/// Center, Lock Ratio, Lock Angle, Snap Angle, Increase/Decrease Side pub type ShapeToolModifierKey = [Key; 4]; +/// The `ShapeGizmoHandler` trait defines the interactive behavior and overlay logic for shape-specific tools in the editor. +/// A gizmo is a visual handle or control point used to manipulate a shape's properties (e.g., number of sides, radius, angle). +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); + + /// Called when a mouse click occurs over the canvas and a gizmo handle is hovered. + /// + /// Used to initiate drag interactions or toggle states on the handle, depending on the tool. + /// For example, a hovered "number of points" handle might enter a "Dragging" state. + fn handle_click(&mut self); + + /// 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); + + /// Draws the static or hover-dependent overlays associated with the gizmo. + /// + /// These overlays include visual indicators like shape outlines, control points, and hover highlights. + fn overlays( + &self, + document: &DocumentMessageHandler, + selected_shape_layers: Option, + input: &InputPreprocessorMessageHandler, + shape_editor: &mut &mut ShapeState, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ); + + /// Draws overlays specifically during a drag operation. + /// + /// Used to give real-time visual feedback based on drag progress, such as showing the updated shape preview or snapping guides. + fn dragging_overlays( + &self, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + shape_editor: &mut &mut ShapeState, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ); + + /// Returns `true` if any handle or control point in the gizmo is currently being hovered. + fn is_any_gizmo_hovered(&self) -> bool; + + /// Resets or clears any internal state maintained by the gizmo when it is no longer active. + /// + /// For example, dragging states or hover flags should be cleared to avoid visual glitches when switching tools or shapes. + fn cleanup(&mut self); +} + +/// Center, Lock Ratio, Lock Angle, Snap Angle, Increase/Decrease Side 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); @@ -154,19 +208,22 @@ pub fn anchor_overlays(document: &DocumentMessageHandler, overlay_context: &mut } } -/// Extract the node input values of Star +/// Extract the node input values of Star. +/// Returns an option of (sides, radius1, radius2). pub fn extract_star_parameters(layer: Option, document: &DocumentMessageHandler) -> Option<(u32, f64, f64)> { 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()) + let (Some(&TaggedValue::U32(sides)), Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) = + (node_inputs.get(1)?.as_value(), node_inputs.get(2)?.as_value(), node_inputs.get(3)?.as_value()) else { return None; }; - Some((n, outer, inner)) + Some((sides, radius_1, radius_2)) } -/// Extract the node input values of Polygon +/// Extract the node input values of Polygon. +/// Returns an option of (sides, radius). pub fn extract_polygon_parameters(layer: Option, document: &DocumentMessageHandler) -> Option<(u32, f64)> { let node_inputs = NodeGraphLayer::new(layer?, &document.network_interface).find_node_inputs("Regular Polygon")?; @@ -188,7 +245,7 @@ pub fn star_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, radiu }) } -/// Calculate the viewport position of as a polygon vertex given its index +/// Calculate the viewport position of a polygon vertex given its index 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); @@ -198,49 +255,37 @@ pub fn polygon_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, ra }) } -/// 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 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 = DVec2 { - x: radius * angle.sin(), - y: -radius * angle.cos(), - }; - - anchors.push(point); - } - - let subpath = [ClickTargetType::Subpath(Subpath::from_anchors_linear(anchors, true))]; - overlay_context.outline(subpath.iter(), viewport, None); -} - -/// 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(); - - let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) else { +/// Outlines the geometric shape made by star-node +pub fn star_outline(layer: Option, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { + let Some(layer) = layer else { return }; + let Some((sides, 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) * TAU) / (n as f64); - let point = DVec2 { - x: radius * angle.sin(), - y: -radius * angle.cos(), - }; + let points = sides as u64; + let diameter: f64 = radius1 * 2.; + let inner_diameter = radius2 * 2.; - anchors.push(point); - } + let subpath: Vec = vec![ClickTargetType::Subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter))]; - let subpath: Vec = vec![ClickTargetType::Subpath(Subpath::from_anchors_linear(anchors, true))]; + overlay_context.outline(subpath.iter(), viewport, None); +} + +/// Outlines the geometric shape made by polygon-node +pub fn polygon_outline(layer: Option, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { + let Some(layer) = layer else { return }; + let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) else { + return; + }; + + let viewport = document.metadata().transform_to_viewport(layer); + + let points = sides as u64; + let radius: f64 = radius * 2.; + + let subpath: Vec = vec![ClickTargetType::Subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius))]; 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 21069620d..653b22f3b 100644 --- a/editor/src/messages/tool/common_functionality/shapes/star_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/star_shape.rs @@ -2,9 +2,14 @@ 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::gizmos::shape_gizmos::number_of_points_dial::{NumberOfPointsDial, NumberOfPointsDialState}; +use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::{PointRadiusHandle, PointRadiusHandleState}; 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::tool_messages::tool_prelude::*; use core::f64; use glam::DAffine2; @@ -12,6 +17,81 @@ use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; use std::collections::VecDeque; +#[derive(Clone, Debug, Default)] +pub struct StarGizmoHandler { + number_of_points_dial: NumberOfPointsDial, + point_radius_handle: PointRadiusHandle, +} + +impl ShapeGizmoHandler for StarGizmoHandler { + fn is_any_gizmo_hovered(&self) -> bool { + 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_click(&mut self) { + if self.number_of_points_dial.is_hovering() { + self.number_of_points_dial.update_state(NumberOfPointsDialState::Dragging); + return; + } + + if self.point_radius_handle.hovered() { + self.point_radius_handle.update_state(PointRadiusHandleState::Dragging); + } + } + + fn handle_update(&mut self, drag_start: DVec2, 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); + } + + if self.point_radius_handle.is_dragging_or_snapped() { + self.point_radius_handle.update_inner_radius(document, input, responses, drag_start); + } + } + + fn overlays( + &self, + document: &DocumentMessageHandler, + selected_star_layer: Option, + input: &InputPreprocessorMessageHandler, + shape_editor: &mut &mut ShapeState, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ) { + self.number_of_points_dial.overlays(document, selected_star_layer, shape_editor, mouse_position, overlay_context); + self.point_radius_handle.overlays(selected_star_layer, document, input, mouse_position, overlay_context); + + star_outline(selected_star_layer, document, overlay_context); + } + + fn dragging_overlays( + &self, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + shape_editor: &mut &mut ShapeState, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ) { + if self.number_of_points_dial.is_dragging() { + self.number_of_points_dial.overlays(document, None, shape_editor, mouse_position, overlay_context); + } + + if self.point_radius_handle.is_dragging_or_snapped() { + self.point_radius_handle.overlays(None, document, input, mouse_position, overlay_context); + } + } + + fn cleanup(&mut self) { + self.number_of_points_dial.cleanup(); + self.point_radius_handle.cleanup(); + } +} + #[derive(Default)] pub struct Star; diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs index 1245fb430..e2abddef2 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -416,7 +416,7 @@ mod test_freehand { editor .handle_message(GraphOperationMessage::TransformSet { layer: artboard, - transform: DAffine2::from_scale_angle_translation(DVec2::new(1.5, 0.8), 0.3, DVec2::new(10.0, -5.0)), + transform: DAffine2::from_scale_angle_translation(DVec2::new(1.5, 0.8), 0.3, DVec2::new(10., -5.)), transform_in: TransformIn::Local, skip_rerender: false, }) @@ -424,14 +424,14 @@ mod test_freehand { editor.select_tool(ToolType::Freehand).await; - let mouse_points = [DVec2::new(150.0, 100.0), DVec2::new(200.0, 150.0), DVec2::new(250.0, 130.0), DVec2::new(300.0, 170.0)]; + let mouse_points = [DVec2::new(150., 100.), DVec2::new(200., 150.), DVec2::new(250., 130.), DVec2::new(300., 170.)]; // Expected points that will actually be captured by the tool let expected_captured_points = &mouse_points[1..]; editor.drag_path(&mouse_points, ModifierKeys::empty()).await; let vector_data_list = get_vector_data(&mut editor).await; - verify_path_points(&vector_data_list, expected_captured_points, 1.0).expect("Path points verification failed"); + verify_path_points(&vector_data_list, expected_captured_points, 1.).expect("Path points verification failed"); } #[tokio::test] @@ -439,7 +439,7 @@ mod test_freehand { let mut editor = EditorTestUtils::create(); editor.new_document().await; - let initial_points = [DVec2::new(100.0, 100.0), DVec2::new(200.0, 200.0), DVec2::new(300.0, 100.0)]; + let initial_points = [DVec2::new(100., 100.), DVec2::new(200., 200.), DVec2::new(300., 100.)]; editor.select_tool(ToolType::Freehand).await; @@ -491,7 +491,7 @@ mod test_freehand { assert!(endpoint_viewport_pos.is_finite(), "Endpoint position is not finite"); - let extension_points = [DVec2::new(400.0, 200.0), DVec2::new(500.0, 100.0)]; + let extension_points = [DVec2::new(400., 200.), DVec2::new(500., 100.)]; let layer_node_id = { let document = editor.active_document(); @@ -558,7 +558,7 @@ mod test_freehand { editor.select_tool(ToolType::Freehand).await; - let initial_points = [DVec2::new(100.0, 100.0), DVec2::new(200.0, 200.0), DVec2::new(300.0, 100.0)]; + let initial_points = [DVec2::new(100., 100.), DVec2::new(200., 200.), DVec2::new(300., 100.)]; let first_point = initial_points[0]; editor.move_mouse(first_point.x, first_point.y, ModifierKeys::empty(), MouseKeys::empty()).await; @@ -599,7 +599,7 @@ mod test_freehand { }) .await; - let second_path_points = [DVec2::new(400.0, 100.0), DVec2::new(500.0, 200.0), DVec2::new(600.0, 100.0)]; + let second_path_points = [DVec2::new(400., 100.), DVec2::new(500., 200.), DVec2::new(600., 100.)]; let first_second_point = second_path_points[0]; editor.move_mouse(first_second_point.x, first_second_point.y, ModifierKeys::SHIFT, MouseKeys::empty()).await; @@ -677,12 +677,12 @@ mod test_freehand { editor.select_tool(ToolType::Freehand).await; - let custom_line_weight = 5.0; + let custom_line_weight = 5.; editor .handle_message(ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::LineWeight(custom_line_weight)))) .await; - let points = [DVec2::new(100.0, 100.0), DVec2::new(200.0, 200.0), DVec2::new(300.0, 100.0)]; + let points = [DVec2::new(100., 100.), DVec2::new(200., 200.), DVec2::new(300., 100.)]; let first_point = points[0]; editor.move_mouse(first_point.x, first_point.y, ModifierKeys::empty(), MouseKeys::empty()).await; diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index 6265c64b1..030da8ec1 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -718,7 +718,7 @@ mod test_gradient { let mut editor = EditorTestUtils::create(); editor.new_document().await; - editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2.0 }).await; + editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2. }).await; editor.drag_tool(ToolType::Rectangle, -5., -3., 100., 100., ModifierKeys::empty()).await; @@ -727,7 +727,7 @@ mod test_gradient { editor .handle_message(GraphOperationMessage::TransformSet { layer: selected_layer, - transform: DAffine2::from_scale_angle_translation(DVec2::new(1.5, 0.8), 0.3, DVec2::new(10.0, -5.0)), + transform: DAffine2::from_scale_angle_translation(DVec2::new(1.5, 0.8), 0.3, DVec2::new(10., -5.)), transform_in: TransformIn::Local, skip_rerender: false, }) @@ -803,7 +803,7 @@ mod test_gradient { stops.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); let positions: Vec = stops.iter().map(|(pos, _)| *pos).collect(); - assert_stops_at_positions(&positions, &[0.0, 0.5, 1.0], 0.1); + assert_stops_at_positions(&positions, &[0., 0.5, 1.], 0.1); let middle_color = stops[1].1.to_rgba8_srgb(); @@ -843,7 +843,7 @@ mod test_gradient { // Check positions are now correctly ordered let updated_positions: Vec = updated_stops.iter().map(|(pos, _)| *pos).collect(); - assert_stops_at_positions(&updated_positions, &[0.0, 0.8, 1.0], 0.1); + assert_stops_at_positions(&updated_positions, &[0., 0.8, 1.], 0.1); // Colors should maintain their associations with the stop points assert_eq!(updated_stops[0].1.to_rgba8_srgb(), Color::BLUE.to_rgba8_srgb()); @@ -877,7 +877,7 @@ mod test_gradient { let positions: Vec = updated_gradient.stops.iter().map(|(pos, _)| *pos).collect(); // Use helper function to verify positions - assert_stops_at_positions(&positions, &[0.0, 0.25, 0.75, 1.0], 0.05); + assert_stops_at_positions(&positions, &[0., 0.25, 0.75, 1.], 0.05); // Select the stop at position 0.75 and delete it let position2 = DVec2::new(75., 0.); @@ -903,7 +903,7 @@ mod test_gradient { let final_positions: Vec = final_gradient.stops.iter().map(|(pos, _)| *pos).collect(); // Verify final positions with helper function - assert_stops_at_positions(&final_positions, &[0.0, 0.25, 1.0], 0.05); + assert_stops_at_positions(&final_positions, &[0., 0.25, 1.], 0.05); // Additional verification that 0.75 stop is gone assert!(!final_positions.iter().any(|pos| (pos - 0.75).abs() < 0.05), "Stop at position 0.75 should have been deleted"); diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index a6b79a113..96dbc5ab0 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -6,13 +6,13 @@ 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::{self, NodeGraphLayer}; +use crate::messages::tool::common_functionality::gizmos::gizmo_manager::GizmoManager; +use crate::messages::tool::common_functionality::graph_modification_utils; +use crate::messages::tool::common_functionality::graph_modification_utils::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}; use crate::messages::tool::common_functionality::shapes::line_shape::{LineToolData, clicked_on_line_endpoints}; 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::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}; @@ -22,6 +22,8 @@ use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput}; use graphene_std::Color; use graphene_std::renderer::Quad; +use graphene_std::vector::misc::ArcType; +use std::vec; #[derive(Default)] pub struct ShapeTool { @@ -36,6 +38,7 @@ pub struct ShapeToolOptions { stroke: ToolColorOptions, vertices: u32, shape_type: ShapeType, + arc_type: ArcType, } impl Default for ShapeToolOptions { @@ -44,8 +47,9 @@ impl Default for ShapeToolOptions { line_weight: DEFAULT_STROKE_WIDTH, fill: ToolColorOptions::new_secondary(), stroke: ToolColorOptions::new_primary(), - shape_type: ShapeType::Polygon, vertices: 5, + shape_type: ShapeType::Polygon, + arc_type: ArcType::Open, } } } @@ -60,6 +64,7 @@ pub enum ShapeOptionsUpdate { WorkingColors(Option, Option), Vertices(u32), ShapeType(ShapeType), + ArcType(ArcType), } #[impl_message(Message, ToolMessage, Shape)] @@ -195,6 +200,9 @@ impl<'a> MessageHandler> for ShapeTo ShapeOptionsUpdate::Vertices(vertices) => { self.options.vertices = vertices; } + ShapeOptionsUpdate::ArcType(arc_type) => { + self.options.arc_type = arc_type; + } } self.fsm_state.update_hints(responses); @@ -217,8 +225,7 @@ impl<'a> MessageHandler> for ShapeTo | ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::DraggingLineEndpoints | ShapeToolFsmState::RotatingBounds - | ShapeToolFsmState::DraggingStarInnerRadius - | ShapeToolFsmState::DraggingStarNumberPointHandle + | ShapeToolFsmState::ModifyingGizmo | ShapeToolFsmState::SkewingBounds { .. } => { actions!(ShapeToolMessageDiscriminant; DragStop, @@ -263,12 +270,9 @@ pub enum ShapeToolFsmState { Ready(ShapeType), Drawing(ShapeType), - // Line shape-specific + // Gizmos DraggingLineEndpoints, - - // Star shape-specific - DraggingStarInnerRadius, - DraggingStarNumberPointHandle, + ModifyingGizmo, // Transform cage ResizingBounds, @@ -306,9 +310,8 @@ 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, + // Gizmos + gizmo_manger: GizmoManager, } impl ShapeToolData { @@ -324,26 +327,6 @@ 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 { @@ -382,30 +365,24 @@ impl Fsm for ShapeToolFsmState { .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 matches!(self, ShapeToolFsmState::DraggingStarInnerRadius | Self::DraggingStarNumberPointHandle | Self::Ready(_)) && !input.keyboard.key(Key::Control) { - // 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 - 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); + if matches!(self, Self::Ready(_)) && !input.keyboard.key(Key::Control) { + tool_data.gizmo_manger.handle_actions(mouse_position, document, responses); + tool_data.gizmo_manger.overlays(document, input, shape_editor, mouse_position, &mut overlay_context); } - 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 matches!(self, ShapeToolFsmState::ModifyingGizmo) && !input.keyboard.key(Key::Control) { + tool_data.gizmo_manger.dragging_overlays(document, input, shape_editor, mouse_position, &mut overlay_context); + } - if !is_resizing_or_rotating && !dragging_start_gizmos && !hovered && !modifying_transform_cage { + let modifying_transform_cage = matches!(self, ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::RotatingBounds | ShapeToolFsmState::SkewingBounds { .. }); + let hovering_over_gizmo = tool_data.gizmo_manger.hovering_over_gizmo(); + + if !is_resizing_or_rotating && !matches!(self, ShapeToolFsmState::ModifyingGizmo) && !modifying_transform_cage && !hovering_over_gizmo { tool_data.data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); } - if modifying_transform_cage { + if modifying_transform_cage && !matches!(self, ShapeToolFsmState::ModifyingGizmo) { transform_cage_overlays(document, tool_data, &mut overlay_context); } @@ -418,7 +395,9 @@ impl Fsm for ShapeToolFsmState { return self; } - transform_cage_overlays(document, tool_data, &mut overlay_context); + if !hovering_over_gizmo { + transform_cage_overlays(document, tool_data, &mut overlay_context); + } let dragging_bounds = tool_data .bounding_box_manager @@ -430,10 +409,10 @@ impl Fsm for ShapeToolFsmState { 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) { + if is_skewing || (dragging_bounds && is_near_square && !is_resizing_or_rotating && !hovering_over_gizmo) { bounds.render_skew_gizmos(&mut overlay_context, tool_data.skew_edge); } - if !is_skewing && dragging_bounds { + if !is_skewing && dragging_bounds && !hovering_over_gizmo { if let Some(edges) = edges { tool_data.skew_edge = bounds.get_closest_edge(edges, input.mouse.position); } @@ -559,28 +538,9 @@ 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); - - // Always store it in document space + if tool_data.gizmo_manger.handle_click() { tool_data.data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(mouse_pos); - - responses.add(DocumentMessage::StartTransaction); - return ShapeToolFsmState::DraggingStarInnerRadius; - } - - // 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); - - // 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; + return ShapeToolFsmState::ModifyingGizmo; } // If clicked on endpoints of a selected line, drag its endpoints @@ -653,13 +613,13 @@ impl Fsm for ShapeToolFsmState { tool_options.fill.apply_fill(layer, responses); } ShapeType::Line => { - 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_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); tool_data.data.layer = Some(layer); ShapeToolFsmState::Drawing(tool_data.current_shape) @@ -695,23 +655,13 @@ impl Fsm for ShapeToolFsmState { self } - (ShapeToolFsmState::DraggingStarInnerRadius, ShapeToolMessage::PointerMove(..)) => { - 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; - } + (ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::PointerMove(..)) => { + responses.add(DocumentMessage::StartTransaction); + tool_data.gizmo_manger.handle_update(tool_data.data.drag_start, document, input, responses); responses.add(OverlaysMessage::Draw); - ShapeToolFsmState::DraggingStarInnerRadius - } - (ShapeToolFsmState::DraggingStarNumberPointHandle, ShapeToolMessage::PointerMove(..)) => { - 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); - - ShapeToolFsmState::DraggingStarNumberPointHandle + ShapeToolFsmState::ModifyingGizmo } (ShapeToolFsmState::ResizingBounds, ShapeToolMessage::PointerMove(modifier)) => { if let Some(bounds) = &mut tool_data.bounding_box_manager { @@ -773,12 +723,12 @@ impl Fsm for ShapeToolFsmState { .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))); + let cursor = tool_data.bounding_box_manager.as_ref().map_or(MouseCursorIcon::Crosshair, |bounds| { + let cursor = bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge)); + if cursor == MouseCursorIcon::Default { MouseCursorIcon::Crosshair } else { cursor } + }); - if tool_data.cursor != cursor && !input.keyboard.key(Key::Control) && tool_data.point_radius_handle.is_inactive() && !all_selected_layers_line { + if tool_data.cursor != cursor && !input.keyboard.key(Key::Control) && !all_selected_layers_line { tool_data.cursor = cursor; responses.add(FrontendMessage::UpdateMouseCursor { cursor }); } @@ -811,15 +761,13 @@ impl Fsm for ShapeToolFsmState { | ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::RotatingBounds | ShapeToolFsmState::SkewingBounds { .. } - | ShapeToolFsmState::DraggingStarInnerRadius - | ShapeToolFsmState::DraggingStarNumberPointHandle, + | ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::DragStop, ) => { input.mouse.finish_transaction(tool_data.data.drag_start, responses); tool_data.data.cleanup(responses); - tool_data.number_of_points_handle.cleanup(); - tool_data.point_radius_handle.cleanup(); + tool_data.gizmo_manger.handle_cleanup(); if let Some(bounds) = &mut tool_data.bounding_box_manager { bounds.original_transforms.clear(); @@ -837,17 +785,14 @@ impl Fsm for ShapeToolFsmState { | ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::RotatingBounds | ShapeToolFsmState::SkewingBounds { .. } - | ShapeToolFsmState::DraggingStarInnerRadius - | ShapeToolFsmState::DraggingStarNumberPointHandle, + | ShapeToolFsmState::ModifyingGizmo, 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(); + tool_data.gizmo_manger.handle_cleanup(); if let Some(bounds) = &mut tool_data.bounding_box_manager { bounds.original_transforms.clear(); @@ -952,9 +897,7 @@ 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()])]) - } + ShapeToolFsmState::ModifyingGizmo => 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/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 94c8ee2da..2ac541d70 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -656,15 +656,15 @@ mod test_spline_tool { editor.new_document().await; // Zooming the viewport - editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2.0 }).await; + editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2. }).await; // Selecting the spline tool editor.select_tool(ToolType::Spline).await; // Adding points by clicking at different positions - editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50.0, 50.0), ModifierKeys::empty()).await; - editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100.0, 50.0), ModifierKeys::empty()).await; - editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150.0, 100.0), ModifierKeys::empty()).await; + editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50., 50.), ModifierKeys::empty()).await; + editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100., 50.), ModifierKeys::empty()).await; + editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150., 100.), ModifierKeys::empty()).await; // Finish the spline editor.handle_message(SplineToolMessage::Confirm).await; @@ -686,7 +686,7 @@ mod test_spline_tool { let layer_to_viewport = document.metadata().transform_to_viewport(layer); // Expected points in viewport coordinates - let expected_points = vec![DVec2::new(50.0, 50.0), DVec2::new(100.0, 50.0), DVec2::new(150.0, 100.0)]; + let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)]; // Assert all points are correctly positioned assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10); @@ -697,15 +697,15 @@ mod test_spline_tool { let mut editor = EditorTestUtils::create(); editor.new_document().await; - let pan_amount = DVec2::new(200.0, 150.0); + let pan_amount = DVec2::new(200., 150.); editor.handle_message(NavigationMessage::CanvasPan { delta: pan_amount }).await; editor.select_tool(ToolType::Spline).await; // Add points by clicking at different positions - editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50.0, 50.0), ModifierKeys::empty()).await; - editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100.0, 50.0), ModifierKeys::empty()).await; - editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150.0, 100.0), ModifierKeys::empty()).await; + editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50., 50.), ModifierKeys::empty()).await; + editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100., 50.), ModifierKeys::empty()).await; + editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150., 100.), ModifierKeys::empty()).await; editor.handle_message(SplineToolMessage::Confirm).await; @@ -726,7 +726,7 @@ mod test_spline_tool { let layer_to_viewport = document.metadata().transform_to_viewport(layer); // Expected points in viewport coordinates - let expected_points = vec![DVec2::new(50.0, 50.0), DVec2::new(100.0, 50.0), DVec2::new(150.0, 100.0)]; + let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)]; // Assert all points are correctly positioned assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10); @@ -738,12 +738,12 @@ mod test_spline_tool { editor.new_document().await; // Tilt/rotate the viewport (45 degrees) - editor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 45.0_f64.to_radians() }).await; + editor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 45_f64.to_radians() }).await; editor.select_tool(ToolType::Spline).await; - editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50.0, 50.0), ModifierKeys::empty()).await; - editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100.0, 50.0), ModifierKeys::empty()).await; - editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150.0, 100.0), ModifierKeys::empty()).await; + editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50., 50.), ModifierKeys::empty()).await; + editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100., 50.), ModifierKeys::empty()).await; + editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150., 100.), ModifierKeys::empty()).await; editor.handle_message(SplineToolMessage::Confirm).await; @@ -764,7 +764,7 @@ mod test_spline_tool { let layer_to_viewport = document.metadata().transform_to_viewport(layer); // Expected points in viewport coordinates - let expected_points = vec![DVec2::new(50.0, 50.0), DVec2::new(100.0, 50.0), DVec2::new(150.0, 100.0)]; + let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)]; // Assert all points are correctly positioned assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10); @@ -777,14 +777,14 @@ mod test_spline_tool { // Applying multiple transformations editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 1.5 }).await; - editor.handle_message(NavigationMessage::CanvasPan { delta: DVec2::new(100.0, 75.0) }).await; - editor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 30.0_f64.to_radians() }).await; + editor.handle_message(NavigationMessage::CanvasPan { delta: DVec2::new(100., 75.) }).await; + editor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 30_f64.to_radians() }).await; editor.select_tool(ToolType::Spline).await; - editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50.0, 50.0), ModifierKeys::empty()).await; - editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100.0, 50.0), ModifierKeys::empty()).await; - editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150.0, 100.0), ModifierKeys::empty()).await; + editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50., 50.), ModifierKeys::empty()).await; + editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100., 50.), ModifierKeys::empty()).await; + editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150., 100.), ModifierKeys::empty()).await; editor.handle_message(SplineToolMessage::Confirm).await; if let Err(e) = editor.eval_graph().await { @@ -803,7 +803,7 @@ mod test_spline_tool { let layer_to_viewport = document.metadata().transform_to_viewport(layer); // Expected points in viewport coordinates - let expected_points = vec![DVec2::new(50.0, 50.0), DVec2::new(100.0, 50.0), DVec2::new(150.0, 100.0)]; + let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)]; // Assert all points are correctly positioned assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10); 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 eab68b886..287faf00d 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 @@ -1021,8 +1021,8 @@ mod test_transform_layer { let scale_x = final_transform.matrix2.x_axis.length() / original_transform.matrix2.x_axis.length(); let scale_y = final_transform.matrix2.y_axis.length() / original_transform.matrix2.y_axis.length(); - assert!((scale_x - 2.).abs() < 0.1, "Expected scale factor X of 2.0, got: {}", scale_x); - assert!((scale_y - 2.).abs() < 0.1, "Expected scale factor Y of 2.0, got: {}", scale_y); + assert!((scale_x - 2.).abs() < 0.1, "Expected scale factor X of 2, got: {}", scale_x); + assert!((scale_y - 2.).abs() < 0.1, "Expected scale factor Y of 2, got: {}", scale_y); } #[tokio::test] @@ -1047,8 +1047,8 @@ mod test_transform_layer { let scale_x = final_transform.matrix2.x_axis.length() / original_transform.matrix2.x_axis.length(); let scale_y = final_transform.matrix2.y_axis.length() / original_transform.matrix2.y_axis.length(); - assert!((scale_x - 2.).abs() < 0.1, "Expected scale factor X of 2.0, got: {}", scale_x); - assert!((scale_y - 2.).abs() < 0.1, "Expected scale factor Y of 2.0, got: {}", scale_y); + assert!((scale_x - 2.).abs() < 0.1, "Expected scale factor X of 2, got: {}", scale_x); + assert!((scale_y - 2.).abs() < 0.1, "Expected scale factor Y of 2, got: {}", scale_y); } #[tokio::test] diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index 75cdec10e..adab1ce53 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -119,12 +119,12 @@ fn star( #[hard_min(2.)] #[implementations(u32, u64, f64)] sides: T, - #[default(50)] radius: f64, - #[default(25)] inner_radius: f64, + #[default(50)] radius_1: f64, + #[default(25)] radius_2: f64, ) -> VectorDataTable { let points = sides.as_u64(); - let diameter: f64 = radius * 2.; - let inner_diameter = inner_radius * 2.; + let diameter: f64 = radius_1 * 2.; + let inner_diameter = radius_2 * 2.; VectorDataTable::new(VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter))) }