impl inner outer gizmos

This commit is contained in:
0SlowPoke0 2025-07-10 14:11:30 +05:30
parent f3b6ab3085
commit 0ec46dbd12
8 changed files with 585 additions and 28 deletions

View file

@ -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

View file

@ -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<f64>,
dash_gap_width: Option<f64>,
dash_offset: Option<f64>,
transform: Option<DAffine2>,
) {
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;

View file

@ -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<LayerNodeIdentifier>,
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<Message>) {
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<LayerNodeIdentifier>,
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<Message>, 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);
}
}

View file

@ -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;

View file

@ -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<LayerNodeIdentifier>,
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<Message>) {
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<LayerNodeIdentifier>,
_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<Message>, 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);
}
}

View file

@ -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<DVec2> {
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;
};

View file

@ -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<Message>,
) {
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<Message>) {
fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
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<LayerNodeIdentifier>,
_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<Message>) {
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);
}
}

View file

@ -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) {