Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::messages::tool::common_functionality::shapes::circle_shape::CircleGiz
use crate::messages::tool::common_functionality::shapes::grid_shape::GridGizmoHandler;
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::spiral_shape::SpiralGizmoHandler;
use crate::messages::tool::common_functionality::shapes::star_shape::StarGizmoHandler;
use glam::DVec2;
use std::collections::VecDeque;
Expand All @@ -30,6 +31,7 @@ pub enum ShapeGizmoHandlers {
Arc(ArcGizmoHandler),
Circle(CircleGizmoHandler),
Grid(GridGizmoHandler),
Spiral(SpiralGizmoHandler),
}

impl ShapeGizmoHandlers {
Expand All @@ -42,6 +44,7 @@ impl ShapeGizmoHandlers {
Self::Arc(_) => "arc",
Self::Circle(_) => "circle",
Self::Grid(_) => "grid",
Self::Spiral(_) => "spiral",
Self::None => "none",
}
}
Expand All @@ -54,6 +57,7 @@ impl ShapeGizmoHandlers {
Self::Arc(h) => h.handle_state(layer, mouse_position, document, responses),
Self::Circle(h) => h.handle_state(layer, mouse_position, document, responses),
Self::Grid(h) => h.handle_state(layer, mouse_position, document, responses),
Self::Spiral(h) => h.handle_state(layer, mouse_position, document, responses),
Self::None => {}
}
}
Expand All @@ -66,6 +70,7 @@ impl ShapeGizmoHandlers {
Self::Arc(h) => h.is_any_gizmo_hovered(),
Self::Circle(h) => h.is_any_gizmo_hovered(),
Self::Grid(h) => h.is_any_gizmo_hovered(),
Self::Spiral(h) => h.is_any_gizmo_hovered(),
Self::None => false,
}
}
Expand All @@ -78,6 +83,7 @@ impl ShapeGizmoHandlers {
Self::Arc(h) => h.handle_click(),
Self::Circle(h) => h.handle_click(),
Self::Grid(h) => h.handle_click(),
Self::Spiral(h) => h.handle_click(),
Self::None => {}
}
}
Expand All @@ -90,6 +96,7 @@ impl ShapeGizmoHandlers {
Self::Arc(h) => h.handle_update(drag_start, document, input, responses),
Self::Circle(h) => h.handle_update(drag_start, document, input, responses),
Self::Grid(h) => h.handle_update(drag_start, document, input, responses),
Self::Spiral(h) => h.handle_update(drag_start, document, input, responses),
Self::None => {}
}
}
Expand All @@ -102,6 +109,7 @@ impl ShapeGizmoHandlers {
Self::Arc(h) => h.cleanup(),
Self::Circle(h) => h.cleanup(),
Self::Grid(h) => h.cleanup(),
Self::Spiral(h) => h.cleanup(),
Self::None => {}
}
}
Expand All @@ -122,6 +130,7 @@ impl ShapeGizmoHandlers {
Self::Arc(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
Self::Circle(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
Self::Grid(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
Self::Spiral(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
Self::None => {}
}
}
Expand All @@ -141,6 +150,7 @@ impl ShapeGizmoHandlers {
Self::Arc(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
Self::Circle(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
Self::Grid(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
Self::Spiral(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
Self::None => {}
}
}
Expand All @@ -152,6 +162,7 @@ impl ShapeGizmoHandlers {
Self::Arc(h) => h.mouse_cursor_icon(),
Self::Circle(h) => h.mouse_cursor_icon(),
Self::Grid(h) => h.mouse_cursor_icon(),
Self::Spiral(h) => h.mouse_cursor_icon(),
Self::None => None,
}
}
Expand Down Expand Up @@ -199,6 +210,10 @@ impl GizmoManager {
if graph_modification_utils::get_grid_id(layer, &document.network_interface).is_some() {
return Some(ShapeGizmoHandlers::Grid(GridGizmoHandler::default()));
}
// Spiral
if graph_modification_utils::get_spiral_id(layer, &document.network_interface).is_some() {
return Some(ShapeGizmoHandlers::Spiral(SpiralGizmoHandler::default()));
}

None
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ pub mod circle_arc_radius_handle;
pub mod grid_rows_columns_gizmo;
pub mod number_of_points_dial;
pub mod point_radius_handle;
pub mod spiral_turns_handle;
pub mod sweep_angle_gizmo;
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
use crate::consts::{COLOR_OVERLAY_RED, POINT_RADIUS_HANDLE_SNAP_THRESHOLD};
use crate::messages::message::Message;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
use crate::messages::prelude::Responses;
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage};
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
use crate::messages::tool::common_functionality::shapes::shape_utility::extract_spiral_parameters;
use crate::messages::tool::common_functionality::shapes::spiral_shape::calculate_spiral_endpoints;
use glam::DVec2;
use graph_craft::document::NodeInput;
use graph_craft::document::value::TaggedValue;
use graphene_std::NodeInputDecleration;
use graphene_std::subpath::{calculate_b, spiral_point};
use graphene_std::vector::misc::SpiralType;
use std::collections::VecDeque;
use std::f64::consts::TAU;

#[derive(Clone, Debug, Default, PartialEq)]
pub enum GizmoType {
#[default]
None,
Start,
End,
}

#[derive(Clone, Debug, Default, PartialEq)]
pub enum SpiralTurnsState {
#[default]
Inactive,
Hover,
Dragging,
}

#[derive(Clone, Debug, Default)]
pub struct SpiralTurns {
pub layer: Option<LayerNodeIdentifier>,
pub handle_state: SpiralTurnsState,
initial_turns: f64,
initial_outer_radius: f64,
initial_inner_radius: f64,
initial_b: f64,
initial_start_angle: f64,
previous_mouse_position: DVec2,
total_angle_delta: f64,
gizmo_type: GizmoType,
spiral_type: SpiralType,
}

impl SpiralTurns {
pub fn cleanup(&mut self) {
self.handle_state = SpiralTurnsState::Inactive;
self.total_angle_delta = 0.;
self.gizmo_type = GizmoType::None;
self.layer = None;
}

pub fn update_state(&mut self, state: SpiralTurnsState) {
self.handle_state = state;
}

pub fn hovered(&self) -> bool {
self.handle_state == SpiralTurnsState::Hover
}

pub fn is_dragging(&self) -> bool {
self.handle_state == SpiralTurnsState::Dragging
}

pub fn store_initial_parameters(
&mut self,
layer: LayerNodeIdentifier,
a: f64,
turns: f64,
outer_radius: f64,
mouse_position: DVec2,
start_angle: f64,
gizmo_type: GizmoType,
spiral_type: SpiralType,
) {
self.layer = Some(layer);
self.initial_turns = turns;
self.initial_b = calculate_b(a, turns, outer_radius, spiral_type);
self.initial_inner_radius = a;
self.initial_outer_radius = outer_radius;
self.initial_start_angle = start_angle;
self.previous_mouse_position = mouse_position;
self.spiral_type = spiral_type;
self.gizmo_type = gizmo_type;
self.update_state(SpiralTurnsState::Hover);
}

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 {
SpiralTurnsState::Inactive => {
if let Some((spiral_type, start_angle, inner_radius, outer_radius, turns, _)) = extract_spiral_parameters(layer, document) {
let b = calculate_b(inner_radius, turns, outer_radius, spiral_type);
let end_point = viewport.transform_point2(spiral_point(turns * TAU + start_angle.to_radians(), inner_radius, b, spiral_type));
let start_point = viewport.transform_point2(spiral_point(0. + start_angle.to_radians(), inner_radius, b, spiral_type));

if mouse_position.distance(end_point) < POINT_RADIUS_HANDLE_SNAP_THRESHOLD {
self.store_initial_parameters(layer, inner_radius, turns, outer_radius, mouse_position, start_angle, GizmoType::End, spiral_type);
return;
}

if mouse_position.distance(start_point) < POINT_RADIUS_HANDLE_SNAP_THRESHOLD {
self.store_initial_parameters(layer, inner_radius, turns, outer_radius, mouse_position, start_angle, GizmoType::Start, spiral_type);
return;
}
}
}
SpiralTurnsState::Hover | SpiralTurnsState::Dragging => {}
}
}

pub fn overlays(&self, document: &DocumentMessageHandler, layer: Option<LayerNodeIdentifier>, _shape_editor: &mut &mut ShapeState, _mouse_position: DVec2, overlay_context: &mut OverlayContext) {
let Some(layer) = layer.or(self.layer) else { return };
let viewport = document.metadata().transform_to_viewport(layer);

match &self.handle_state {
SpiralTurnsState::Inactive => {
if let Some((p1, p2)) = calculate_spiral_endpoints(layer, document, viewport, 0.).zip(calculate_spiral_endpoints(layer, document, viewport, TAU)) {
overlay_context.manipulator_handle(p1, false, None);
overlay_context.manipulator_handle(p2, false, None);
}
}
SpiralTurnsState::Hover | SpiralTurnsState::Dragging => {
// Is true only when hovered over the gizmo
let selected = self.layer.is_some();
let angle = match self.gizmo_type {
GizmoType::End => TAU,
GizmoType::Start => 0.,
GizmoType::None => return,
};

if let Some(endpoint) = calculate_spiral_endpoints(layer, document, viewport, angle) {
overlay_context.manipulator_handle(endpoint, selected, Some(COLOR_OVERLAY_RED));
}
}
}
}

pub fn update_number_of_turns(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
use graphene_std::vector::generator_nodes::spiral::*;

let Some(layer) = self.layer else {
return;
};

let viewport = document.metadata().transform_to_viewport(layer);
let center = viewport.transform_point2(DVec2::ZERO);

let angle_delta = viewport
.inverse()
.transform_vector2(input.mouse.position - center)
.angle_to(viewport.inverse().transform_vector2(self.previous_mouse_position - center))
.to_degrees();

// Skip update if angle calculation produced NaN or infinity (can happen when mouse is at center)
// Also skip very small angle changes to reduce jitter near center
if !angle_delta.is_finite() || angle_delta.abs() < 0.5 {
self.previous_mouse_position = input.mouse.position;
return;
}

// Increase the number of turns and outer radius in unison such that growth and tightness remain same
let total_delta = self.total_angle_delta + angle_delta;
// Convert the total angle (in degrees) to number of full turns
let turns_delta = total_delta / 360.;

// Calculate the new outer radius based on spiral type and turn change
let outer_radius_change = match self.spiral_type {
SpiralType::Archimedean => turns_delta * (self.initial_b) * TAU,
SpiralType::Logarithmic => self.initial_outer_radius * ((self.initial_b * TAU * turns_delta).exp() - 1.),
};

// Skip if outer_radius calculation produced invalid values
if !outer_radius_change.is_finite() {
return;
}

let Some(node_id) = graph_modification_utils::get_spiral_id(layer, &document.network_interface) else {
return;
};

match self.gizmo_type {
GizmoType::Start => {
let sign = -1.;
let new_turns = (self.initial_turns + turns_delta * sign).max(0.5);
let new_outer_radius = (self.initial_outer_radius + outer_radius_change * sign).max(0.1);

responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, StartAngleInput::INDEX),
input: NodeInput::value(TaggedValue::F64(self.initial_start_angle + total_delta), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, TurnsInput::INDEX),
input: NodeInput::value(TaggedValue::F64(new_turns), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, OuterRadiusInput::INDEX),
input: NodeInput::value(TaggedValue::F64(new_outer_radius), false),
});
}
GizmoType::End => {
let new_turns = (self.initial_turns + turns_delta).max(0.5);
let new_outer_radius = (self.initial_outer_radius + outer_radius_change).max(0.1);

responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, TurnsInput::INDEX),
input: NodeInput::value(TaggedValue::F64(new_turns), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, OuterRadiusInput::INDEX),
input: NodeInput::value(TaggedValue::F64(new_outer_radius), false),
});
}
GizmoType::None => {
return;
}
}

responses.add(NodeGraphMessage::RunDocumentGraph);
self.total_angle_delta += angle_delta;
self.previous_mouse_position = input.mouse.position;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use graph_craft::document::value::TaggedValue;
use graphene_std::NodeInputDecleration;
use graphene_std::subpath::{self, Subpath};
use graphene_std::vector::click_target::ClickTargetType;
use graphene_std::vector::misc::{ArcType, GridType, dvec2_to_point};
use graphene_std::vector::misc::{ArcType, GridType, SpiralType, dvec2_to_point};
use kurbo::{BezPath, PathEl, Shape};
use std::collections::VecDeque;
use std::f64::consts::{PI, TAU};
Expand Down Expand Up @@ -293,6 +293,35 @@ pub fn extract_arc_parameters(layer: Option<LayerNodeIdentifier>, document: &Doc
Some((radius, start_angle, sweep_angle, arc_type))
}

/// Extract the node input values of spiral.
/// Returns an option of (spiral type, start angle, inner radius, outer radius, turns, angle resolution).
pub fn extract_spiral_parameters(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<(SpiralType, f64, f64, f64, f64, f64)> {
use graphene_std::vector::generator_nodes::spiral::*;

let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::spiral::IDENTIFIER))?;

let (
Some(&TaggedValue::SpiralType(spiral_type)),
Some(&TaggedValue::F64(start_angle)),
Some(&TaggedValue::F64(inner_radius)),
Some(&TaggedValue::F64(outer_radius)),
Some(&TaggedValue::F64(turns)),
Some(&TaggedValue::F64(angle_resolution)),
) = (
node_inputs.get(SpiralTypeInput::INDEX)?.as_value(),
node_inputs.get(StartAngleInput::INDEX)?.as_value(),
node_inputs.get(InnerRadiusInput::INDEX)?.as_value(),
node_inputs.get(OuterRadiusInput::INDEX)?.as_value(),
node_inputs.get(TurnsInput::INDEX)?.as_value(),
node_inputs.get(AngularResolutionInput::INDEX)?.as_value(),
)
else {
return None;
};

Some((spiral_type, start_angle, inner_radius, outer_radius, turns, angle_resolution))
}

/// Calculate the viewport positions of arc endpoints
pub fn arc_end_points(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler) -> Option<(DVec2, DVec2)> {
let (radius, start_angle, sweep_angle, _) = extract_arc_parameters(Some(layer?), document)?;
Expand Down
Loading
Loading