From 0ec46dbd1232628e976547414e9567055d078eef Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Thu, 10 Jul 2025 14:11:30 +0530 Subject: [PATCH] impl inner outer gizmos --- editor/src/consts.rs | 1 + .../document/overlays/utility_types.rs | 58 +++- .../arc_spiral_inner_radius_handle.rs | 171 ++++++++++ .../gizmos/shape_gizmos/mod.rs | 2 + .../shape_gizmos/spiral_tightness_gizmo.rs | 292 ++++++++++++++++++ .../shapes/shape_utility.rs | 2 +- .../shapes/spiral_shape.rs | 86 ++++-- .../messages/tool/tool_messages/shape_tool.rs | 1 + 8 files changed, 585 insertions(+), 28 deletions(-) create mode 100644 editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/arc_spiral_inner_radius_handle.rs create mode 100644 editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_tightness_gizmo.rs diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 1a95aa201..f05f156b8 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -125,6 +125,7 @@ pub const POINT_RADIUS_HANDLE_SNAP_THRESHOLD: f64 = 8.; pub const POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD: f64 = 7.9; pub const NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION: f64 = 1.2; pub const NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH: f64 = 10.; +pub const SPIRAL_INNER_RADIUS_GIZMO_THRESHOLD: f64 = 10.; pub const GIZMO_HIDE_THRESHOLD: f64 = 20.; // SCROLLBARS diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index 3d12ba496..7c26b166b 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -372,23 +372,73 @@ impl OverlayContext { self.end_dpi_aware_transform(); } - pub fn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) { - let color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE); + pub fn dashed_circle( + &mut self, + position: DVec2, + radius: f64, + color_fill: Option<&str>, + color_stroke: Option<&str>, + dash_width: Option, + dash_gap_width: Option, + dash_offset: Option, + transform: Option, + ) { let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE); let position = position.round(); self.start_dpi_aware_transform(); + if let Some(transform) = transform { + let [a, b, c, d, e, f] = transform.to_cols_array(); + self.render_context.transform(a, b, c, d, e, f); + } + + if let Some(dash_width) = dash_width { + let dash_gap_width = dash_gap_width.unwrap_or(1.); + let array = js_sys::Array::new(); + array.push(&JsValue::from(dash_width)); + array.push(&JsValue::from(dash_gap_width)); + + if let Some(dash_offset) = dash_offset { + if dash_offset != 0. { + self.render_context.set_line_dash_offset(dash_offset); + } + } + + self.render_context + .set_line_dash(&JsValue::from(array)) + .map_err(|error| log::warn!("Error drawing dashed line: {:?}", error)) + .ok(); + } + self.render_context.begin_path(); self.render_context.arc(position.x, position.y, radius, 0., TAU).expect("Failed to draw the circle"); - self.render_context.set_fill_style_str(color_fill); self.render_context.set_stroke_style_str(color_stroke); - self.render_context.fill(); + + if let Some(fill_color) = color_fill { + self.render_context.set_fill_style_str(fill_color); + self.render_context.fill(); + } self.render_context.stroke(); + // Reset the dash pattern back to solid + if dash_width.is_some() { + self.render_context + .set_line_dash(&JsValue::from(js_sys::Array::new())) + .map_err(|error| log::warn!("Error drawing dashed line: {:?}", error)) + .ok(); + } + if dash_offset.is_some() && dash_offset != Some(0.) { + self.render_context.set_line_dash_offset(0.); + } + self.end_dpi_aware_transform(); } + pub fn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) { + self.dashed_circle(position, radius, color_fill, color_stroke, None, None, None, None); + } + pub fn draw_arc(&mut self, center: DVec2, radius: f64, start_from: f64, end_at: f64) { let segments = ((end_at - start_from).abs() / (std::f64::consts::PI / 4.)).ceil() as usize; let step = (end_at - start_from) / segments as f64; diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/arc_spiral_inner_radius_handle.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/arc_spiral_inner_radius_handle.rs new file mode 100644 index 000000000..247027a83 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/arc_spiral_inner_radius_handle.rs @@ -0,0 +1,171 @@ +use crate::consts::ARCHIMEDEAN_INNER_RADIUS_INDEX; +use crate::consts::COLOR_OVERLAY_RED; +use crate::consts::GIZMO_HIDE_THRESHOLD; +use crate::consts::LOGARITHMIC_START_RADIUS_INDEX; +use crate::consts::NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH; +use crate::consts::SPIRAL_INNER_RADIUS_GIZMO_THRESHOLD; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::message::Message; +use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; +use crate::messages::portfolio::document::{overlays::utility_types::OverlayContext, utility_types::network_interface::InputConnector}; +use crate::messages::prelude::FrontendMessage; +use crate::messages::prelude::Responses; +use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage}; +use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer}; +use crate::messages::tool::common_functionality::shapes::shape_utility::archimedean_spiral_point; +use crate::messages::tool::common_functionality::shapes::shape_utility::calculate_b; +use crate::messages::tool::common_functionality::shapes::shape_utility::extract_arc_spiral_parameters; +use crate::messages::tool::common_functionality::shapes::shape_utility::extract_log_spiral_parameters; +use crate::messages::tool::common_functionality::shapes::shape_utility::get_arc_spiral_end_point; +use crate::messages::tool::common_functionality::shapes::shape_utility::get_log_spiral_end_point; +use glam::DVec2; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; +use graphene_std::num_traits::sign; +use graphene_std::vector::misc::SpiralType; +use graphene_std::vector::misc::dvec2_to_point; +use kurbo::BezPath; +use kurbo::Circle; +use kurbo::Line; +use kurbo::ParamCurveNearest; +use kurbo::Point; +use std::collections::VecDeque; + +#[derive(Clone, Debug, Default, PartialEq)] +pub enum RadiusGizmoState { + #[default] + Inactive, + Hover, + Dragging, +} + +#[derive(Clone, Debug, Default)] +pub struct RadiusGizmo { + pub layer: Option, + pub handle_state: RadiusGizmoState, + pub spiral_type: SpiralType, + initial_radius: f64, + previous_mouse: DVec2, +} + +impl RadiusGizmo { + pub fn cleanup(&mut self) { + self.layer = None; + self.handle_state = RadiusGizmoState::Inactive; + self.initial_radius = 0.; + self.previous_mouse = DVec2::ZERO; + } + + pub fn hovered(&self) -> bool { + self.handle_state == RadiusGizmoState::Hover + } + + pub fn is_dragging(&self) -> bool { + self.handle_state == RadiusGizmoState::Dragging + } + + pub fn update_state(&mut self, state: RadiusGizmoState) { + self.handle_state = state; + } + + pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque) { + match &self.handle_state { + RadiusGizmoState::Inactive => { + // Archimedean + if let Some((a, outer_radius, turns)) = extract_arc_spiral_parameters(layer, document) { + let viewport = document.metadata().transform_to_viewport(layer); + let layer_mouse = viewport.inverse().transform_point2(mouse_position); + + let center = viewport.transform_point2(DVec2::ZERO); + + if (DVec2::ZERO.distance(layer_mouse) - a).abs() < 5. { + self.layer = Some(layer); + self.initial_radius = a; + self.spiral_type = SpiralType::Archimedean; + self.update_state(RadiusGizmoState::Hover); + self.previous_mouse = mouse_position; + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize }); + } + } + + // Logarithmic + if let Some((a, outer_radius, turns)) = extract_log_spiral_parameters(layer, document) { + let viewport = document.metadata().transform_to_viewport(layer); + let layer_mouse = viewport.inverse().transform_point2(mouse_position); + + if (DVec2::ZERO.distance(layer_mouse) - a).abs() < 5. { + self.layer = Some(layer); + self.initial_radius = a; + self.spiral_type = SpiralType::Logarithmic; + self.update_state(RadiusGizmoState::Hover); + self.previous_mouse = mouse_position; + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize }); + } + } + } + RadiusGizmoState::Hover | RadiusGizmoState::Dragging => {} + } + } + + pub fn overlays( + &self, + document: &DocumentMessageHandler, + selected_spiral_layer: Option, + input: &InputPreprocessorMessageHandler, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ) { + match &self.handle_state { + _ => { + let Some(layer) = selected_spiral_layer.or(self.layer) else { return }; + + let viewport = document.metadata().transform_to_viewport(layer); + if let Some((radius, _, _)) = extract_arc_spiral_parameters(layer, document).or(extract_log_spiral_parameters(layer, document)) { + overlay_context.dashed_circle(DVec2::ZERO, radius.max(5.), None, None, Some(4.), Some(4.), Some(0.5), Some(viewport)); + } + } + } + } + + 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_spiral_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else { + return; + }; + + let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer); + let center = viewport_transform.transform_point2(DVec2::ZERO); + let current_mouse_layer = viewport_transform.inverse().transform_point2(input.mouse.position); + let previous_mouse_layer = viewport_transform.inverse().transform_point2(self.previous_mouse); + let drag_start = viewport_transform.inverse().transform_point2(drag_start); + let center_layer = DVec2::ZERO; + + let delta_vector = current_mouse_layer - previous_mouse_layer; + let sign = (current_mouse_layer - previous_mouse_layer).dot(drag_start - center_layer).signum(); + let delta = delta_vector.length() * sign; + + self.previous_mouse = input.mouse.position; + + let (net_radius, index) = match self.spiral_type { + SpiralType::Archimedean => { + let current_radius = extract_arc_spiral_parameters(layer, document) + .map(|(a, _, _)| a) + .expect("Failed to get archimedean spiral inner radius"); + ((current_radius + delta).max(0.), ARCHIMEDEAN_INNER_RADIUS_INDEX) + } + SpiralType::Logarithmic => { + let current_radius = extract_log_spiral_parameters(layer, document) + .map(|(a, _, _)| a) + .expect("Failed to get logarithmic spiral inner radius"); + ((current_radius + delta).max(0.001), LOGARITHMIC_START_RADIUS_INDEX) + } + }; + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, index), + input: NodeInput::value(TaggedValue::F64(net_radius), false), + }); + responses.add(NodeGraphMessage::RunDocumentGraph); + } +} diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs index 857d8d9e2..b604ec1ee 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs @@ -1,3 +1,5 @@ +pub mod arc_spiral_inner_radius_handle; pub mod number_of_points_dial; pub mod point_radius_handle; +pub mod spiral_tightness_gizmo; pub mod spiral_turns_handle; diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_tightness_gizmo.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_tightness_gizmo.rs new file mode 100644 index 000000000..dbf8882a3 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_tightness_gizmo.rs @@ -0,0 +1,292 @@ +use crate::consts::{COLOR_OVERLAY_RED, SPIRAL_INNER_RADIUS_GIZMO_THRESHOLD, SPIRAL_OUTER_RADIUS_INDEX, SPIRAL_TURNS_INDEX}; +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::{self, get_stroke_width}; +use crate::messages::tool::common_functionality::shape_editor::ShapeState; +use crate::messages::tool::common_functionality::shapes::shape_utility::{ + archimedean_spiral_point, calculate_b, extract_arc_spiral_parameters, extract_log_spiral_parameters, get_arc_spiral_end_point, get_log_spiral_end_point, +}; +use glam::{DAffine2, DVec2}; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; +use graphene_std::vector::misc::{SpiralType, dvec2_to_point}; +use kurbo::{Line, ParamCurveNearest}; +use std::collections::VecDeque; +use std::f64::consts::TAU; + +#[derive(Clone, Debug, Default, PartialEq)] +pub enum TightnessGizmoState { + #[default] + Inactive, + Hover, + Dragging, +} + +#[derive(Clone, Debug, Default)] +pub struct TightnessGizmo { + pub layer: Option, + pub handle_state: TightnessGizmoState, + initial_outer_radius: f64, + spiral_type: SpiralType, + gizmo_line_points: Option<(DVec2, DVec2)>, + previous_mouse: DVec2, +} + +impl TightnessGizmo { + pub fn cleanup(&mut self) { + self.handle_state = TightnessGizmoState::Inactive; + self.layer = None; + self.gizmo_line_points = None; + } + + pub fn update_state(&mut self, state: TightnessGizmoState) { + self.handle_state = state; + } + + pub fn hovered(&self) -> bool { + self.handle_state == TightnessGizmoState::Hover + } + + pub fn is_dragging(&self) -> bool { + self.handle_state == TightnessGizmoState::Dragging + } + + pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { + let viewport = document.metadata().transform_to_viewport(layer); + + match &self.handle_state { + TightnessGizmoState::Inactive => { + // Archimedean + if let Some((a, outer_radius, turns)) = extract_arc_spiral_parameters(layer, document) { + let b = calculate_b(a, turns, outer_radius, SpiralType::Archimedean); + if let Some((start, end)) = Self::check_which_inter_segment(viewport.inverse().transform_point2(mouse_position), outer_radius, turns, a, viewport) { + let line = Line::new(dvec2_to_point(start), dvec2_to_point(end)); + if line.nearest(dvec2_to_point(mouse_position), 1e-6).distance_sq < SPIRAL_INNER_RADIUS_GIZMO_THRESHOLD { + self.layer = Some(layer); + self.initial_outer_radius = outer_radius; + self.previous_mouse = mouse_position; + self.gizmo_line_points = Some((start, end)); + self.spiral_type = SpiralType::Archimedean; + self.update_state(TightnessGizmoState::Hover); + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); + } + return; + }; + + let layer_mouse = viewport.inverse().transform_point2(mouse_position); + + let center = viewport.transform_point2(DVec2::ZERO); + + let Some(endpoint) = get_arc_spiral_end_point(layer, document, viewport, TAU) else { return }; + + let close_to_circle = (DVec2::ZERO.distance(layer_mouse) - outer_radius).abs() < 5.; + + if close_to_circle { + self.layer = Some(layer); + self.initial_outer_radius = outer_radius; + self.previous_mouse = mouse_position; + // self.gizmo_line_points = Some((start, end)); + self.spiral_type = SpiralType::Archimedean; + self.update_state(TightnessGizmoState::Hover); + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); + } + } + + // // Logarithmic + // if let Some(((_, _, turns), end_point)) = extract_log_spiral_parameters(layer, document).zip(get_log_spiral_end_point(layer, document, viewport)) { + // if mouse_position.distance(end_point) < POINT_RADIUS_HANDLE_SNAP_THRESHOLD { + // self.layer = Some(layer); + // self.initial_turns = turns; + // self.previous_mouse_position = mouse_position; + // self.spiral_type = SpiralType::Logarithmic; + // self.update_state(SpiralTurnsState::Hover); + // responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); + // } + // } + } + TightnessGizmoState::Hover | TightnessGizmoState::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, + selected_spiral_layer: Option, + _shape_editor: &mut &mut ShapeState, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ) { + let Some(layer) = selected_spiral_layer.or(self.layer) else { + return; + }; + let viewport = document.metadata().transform_to_viewport(layer); + + match &self.handle_state { + TightnessGizmoState::Hover | TightnessGizmoState::Dragging => { + let Some(layer) = selected_spiral_layer.or(self.layer) else { + return; + }; + + if let Some((start, end)) = self.gizmo_line_points { + overlay_context.dashed_line(start, end, Some(COLOR_OVERLAY_RED), None, Some(4.0), Some(4.0), Some(0.5)); + }; + + let viewport = document.metadata().transform_to_viewport(layer); + if let Some((_, outer_radius, _)) = extract_arc_spiral_parameters(layer, document).or(extract_log_spiral_parameters(layer, document)) { + overlay_context.dashed_circle(DVec2::ZERO, outer_radius.max(5.), None, Some(COLOR_OVERLAY_RED), Some(8.), Some(4.), Some(0.5), Some(viewport)); + } + + // let viewport = document.metadata().transform_to_viewport(layer); + // if let Some((a, outer_radius, turns)) = extract_arc_spiral_parameters(layer, document) { + // let b = calculate_b(a, turns, outer_radius, SpiralType::Archimedean); + // let Some((start, end)) = Self::check_which_inter_segment(viewport.inverse().transform_point2(mouse_position), outer_radius, turns, a, b, viewport) else { + // return; + // }; + // overlay_context.dashed_line(start, end, None, None, Some(4.), Some(4.), Some(0.5)); + // } + } + TightnessGizmoState::Inactive => { + if let Some((_, outer_radius, _)) = extract_arc_spiral_parameters(layer, document).or(extract_log_spiral_parameters(layer, document)) { + overlay_context.dashed_circle(DVec2::ZERO, outer_radius.max(5.), None, Some(COLOR_OVERLAY_RED), Some(8.), Some(4.), Some(0.5), Some(viewport)); + } + } + } + } + + fn check_which_inter_segment(mouse_position: DVec2, outer_radius: f64, turns: f64, a: f64, transform: DAffine2) -> Option<(DVec2, DVec2)> { + let b = calculate_b(a, turns, outer_radius, SpiralType::Archimedean); + let center = DVec2::ZERO; + let angle = mouse_position.angle_to(DVec2::X).rem_euclid(TAU); + + let viewport_mouse = transform.transform_point2(mouse_position); + let viewport_center = transform.transform_point2(center); + + let max_theta = turns * TAU; + let spiral_outer = archimedean_spiral_point(max_theta, a, b); + let viewport_outer = transform.transform_point2(spiral_outer); + + if viewport_mouse.distance(viewport_center) > viewport_outer.distance(viewport_center) { + return None; + } + + let mouse_distance = viewport_mouse.distance(viewport_center); + + let mut segment_index = 0; + + // ---- First segment: from center to spiral at θ = angle + { + let start = viewport_center; + let spiral_end = archimedean_spiral_point(angle, a, b); + let end = transform.transform_point2(spiral_end); + + let r_end = end.distance(viewport_center); + + if mouse_distance <= r_end { + return Some(Self::calculate_gizmo_line_points(viewport_center, end)); + } + + segment_index += 1; + } + + // ---- Remaining segments: each full turn outward along the ray + let mut base_theta = angle; + + while base_theta <= max_theta { + let theta_start = base_theta; + let theta_end = base_theta + TAU; + + if theta_end > max_theta { + break; + } + + let spiral_start = archimedean_spiral_point(theta_start, a, b); + let spiral_end = archimedean_spiral_point(theta_end, a, b); + + let viewport_start = transform.transform_point2(spiral_start); + let viewport_end = transform.transform_point2(spiral_end); + + let r_start = viewport_start.distance(viewport_center); + let r_end = viewport_end.distance(viewport_center); + + if mouse_distance >= r_start && mouse_distance <= r_end { + return Some(Self::calculate_gizmo_line_points(viewport_start, viewport_end)); + } + + base_theta += TAU; + segment_index += 1; + } + + None + } + + // (start_point,end_point) + fn calculate_gizmo_line_points(start_point: DVec2, end_point: DVec2) -> (DVec2, DVec2) { + let length = start_point.distance(end_point); + let factor = 0.25 * length; + + let direction = (end_point - start_point).normalize(); + + let new_endpoint = end_point - direction * factor; + let new_start_point = start_point + direction * factor; + + (new_start_point, new_endpoint) + } + + pub fn update_number_of_turns(&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_spiral_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else { + return; + }; + + let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer); + let center = viewport_transform.transform_point2(DVec2::ZERO); + let current_mouse_layer = viewport_transform.inverse().transform_point2(input.mouse.position); + let previous_mouse_layer = viewport_transform.inverse().transform_point2(self.previous_mouse); + let drag_start = viewport_transform.inverse().transform_point2(drag_start); + let center_layer = DVec2::ZERO; + + let delta_vector = current_mouse_layer - previous_mouse_layer; + let sign = (current_mouse_layer - previous_mouse_layer).dot(drag_start - center_layer).signum(); + let delta = delta_vector.length() * sign; + + self.previous_mouse = input.mouse.position; + + let (a, turns, net_radius) = match self.spiral_type { + SpiralType::Archimedean => { + let (a, outer_radius, turns) = extract_arc_spiral_parameters(layer, document).expect("Failed to get archimedean spiral inner radius"); + (a, turns, (outer_radius + delta).max(0.0)) + } + SpiralType::Logarithmic => { + let (a, outer_radius, turns) = extract_log_spiral_parameters(layer, document).expect("Failed to get logarithmic spiral inner radius"); + (a, turns, (outer_radius + delta).max(0.001)) + } + }; + + self.gizmo_line_points = Self::check_which_inter_segment(current_mouse_layer, net_radius, turns, a, viewport_transform); + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, SPIRAL_OUTER_RADIUS_INDEX), + input: NodeInput::value(TaggedValue::F64(net_radius), false), + }); + responses.add(NodeGraphMessage::RunDocumentGraph); + } +} 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 aa75262e6..c11dc3110 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -303,7 +303,7 @@ pub fn get_arc_spiral_end_point(layer: LayerNodeIdentifier, document: &DocumentM } pub fn get_log_spiral_end_point(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, viewport: DAffine2, theta: f64) -> Option { - let Some((_start_radius, outer_radius, turns)) = extract_log_spiral_parameters(layer, document) else { + let Some((_, outer_radius, turns)) = extract_log_spiral_parameters(layer, document) else { return None; }; diff --git a/editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs b/editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs index 60fcc395c..ef1548cbc 100644 --- a/editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs @@ -1,10 +1,12 @@ use super::*; -use crate::consts::{SPIRAL_OUTER_RADIUS_INDEX, SPIRAL_TURNS_INDEX, SPIRAL_TYPE_INDEX}; +use crate::consts::{SPIRAL_OUTER_RADIUS_INDEX, SPIRAL_TYPE_INDEX}; 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::arc_spiral_inner_radius_handle::{RadiusGizmo, RadiusGizmoState}; +use crate::messages::tool::common_functionality::gizmos::shape_gizmos::spiral_tightness_gizmo::{TightnessGizmo, TightnessGizmoState}; use crate::messages::tool::common_functionality::gizmos::shape_gizmos::spiral_turns_handle::{SpiralTurns, SpiralTurnsState}; use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; @@ -21,64 +23,116 @@ use std::collections::VecDeque; #[derive(Clone, Debug, Default)] pub struct SpiralGizmoHandler { + radius_handle: RadiusGizmo, turns_handle: SpiralTurns, + tightness_handle: TightnessGizmo, } impl ShapeGizmoHandler for SpiralGizmoHandler { fn is_any_gizmo_hovered(&self) -> bool { - self.turns_handle.hovered() + self.radius_handle.hovered() || self.turns_handle.hovered() || self.tightness_handle.hovered() } fn handle_state( &mut self, selected_spiral_layer: LayerNodeIdentifier, - _mouse_position: DVec2, + mouse_position: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, ) { - self.turns_handle.handle_actions(selected_spiral_layer, input.mouse.position, document, responses); + self.radius_handle.handle_actions(selected_spiral_layer, document, input.mouse.position, responses); + self.turns_handle.handle_actions(selected_spiral_layer, mouse_position, document, responses); + self.tightness_handle.handle_actions(selected_spiral_layer, input.mouse.position, document, responses); } fn handle_click(&mut self) { + if self.radius_handle.hovered() { + self.radius_handle.update_state(RadiusGizmoState::Dragging); + return; + } + + if self.turns_handle.hovered() && self.tightness_handle.hovered() { + self.turns_handle.update_state(SpiralTurnsState::Dragging); + self.tightness_handle.update_state(TightnessGizmoState::Inactive); + return; + } + + if self.radius_handle.hovered() && self.tightness_handle.hovered() { + self.radius_handle.update_state(RadiusGizmoState::Dragging); + self.tightness_handle.update_state(TightnessGizmoState::Inactive); + return; + } + if self.turns_handle.hovered() { self.turns_handle.update_state(SpiralTurnsState::Dragging); } + + if self.tightness_handle.hovered() { + self.tightness_handle.update_state(TightnessGizmoState::Dragging); + } } - fn handle_update(&mut self, _drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + if self.radius_handle.is_dragging() { + self.radius_handle.update_inner_radius(document, input, responses, drag_start); + } + if self.turns_handle.is_dragging() { self.turns_handle.update_number_of_turns(document, input, responses); } + + if self.tightness_handle.is_dragging() { + self.tightness_handle.update_number_of_turns(document, input, responses, drag_start); + } } fn overlays( &self, document: &DocumentMessageHandler, selected_spiral_layer: Option, - _input: &InputPreprocessorMessageHandler, + input: &InputPreprocessorMessageHandler, shape_editor: &mut &mut ShapeState, mouse_position: DVec2, overlay_context: &mut OverlayContext, ) { + if self.radius_handle.hovered() && self.tightness_handle.hovered() { + self.radius_handle.overlays(document, selected_spiral_layer, input, mouse_position, overlay_context); + return; + } + self.radius_handle.overlays(document, selected_spiral_layer, input, mouse_position, overlay_context); self.turns_handle.overlays(document, selected_spiral_layer, shape_editor, mouse_position, overlay_context); + self.tightness_handle.overlays(document, selected_spiral_layer, shape_editor, mouse_position, overlay_context); + + // polygon_outline(selected_polygon_layer, document, overlay_context); } fn dragging_overlays( &self, document: &DocumentMessageHandler, - _input: &InputPreprocessorMessageHandler, + input: &InputPreprocessorMessageHandler, shape_editor: &mut &mut ShapeState, mouse_position: DVec2, overlay_context: &mut OverlayContext, ) { - if self.turns_handle.is_dragging() { + if self.radius_handle.is_dragging() { + self.radius_handle.overlays(document, None, input, mouse_position, overlay_context); + } + + if self.radius_handle.is_dragging() { self.turns_handle.overlays(document, None, shape_editor, mouse_position, overlay_context); } + + if self.tightness_handle.is_dragging() { + self.tightness_handle.overlays(document, None, shape_editor, mouse_position, overlay_context); + } } fn cleanup(&mut self) { + // self.number_of_points_dial.cleanup(); + self.radius_handle.cleanup(); self.turns_handle.cleanup(); + self.tightness_handle.cleanup(); } } @@ -148,33 +202,19 @@ impl Spiral { /// Updates the number of turns of a spiral node and recalculates its radius based on drag distance. /// Also updates the Shape Tool's turns UI widget to reflect the change. pub fn update_turns(decrease: bool, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, responses: &mut VecDeque) { - let Some(node_id) = graph_modification_utils::get_spiral_id(layer, &document.network_interface) else { - return; - }; let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Spiral") else { return; }; - let Some(&TaggedValue::F64(n)) = node_inputs.get(SPIRAL_TURNS_INDEX).unwrap().as_value() else { - return; - }; - - let input: NodeInput; + let Some(&TaggedValue::F64(n)) = node_inputs.get(6).unwrap().as_value() else { return }; let turns: f64; if decrease { turns = (n - 1.).max(1.); - input = NodeInput::value(TaggedValue::F64(turns), false); responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Turns(turns))); } else { turns = n + 1.; - input = NodeInput::value(TaggedValue::F64(turns), false); responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Turns(turns))); } - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, SPIRAL_TURNS_INDEX), - input, - }); - responses.add(NodeGraphMessage::RunDocumentGraph); } } diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 5e36aed2f..858137ed5 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -438,6 +438,7 @@ impl Fsm for ShapeToolFsmState { 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); + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }); } if modifying_transform_cage && !matches!(self, ShapeToolFsmState::ModifyingGizmo) {