Skip to content

Commit ea68d62

Browse files
0SlowPoke0KeavonAnnonnymmousss
authored
Add gizmos for interacting with the Spiral node (#2851)
* made spiral node * number of turns in decimal and arc-angle implementation * logarithmic spiral * unified log and arc spiral into spiral node * add spiral shape in shape tool * fix min value and degree unit * make it compile * impl turns handle gizmo * chore : Refactoring PR #2851 for current code base with some fixes * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: Annonnymmousss <jatin02012006@gmail.com>
1 parent cd24109 commit ea68d62

File tree

6 files changed

+353
-3
lines changed

6 files changed

+353
-3
lines changed

editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::messages::tool::common_functionality::shapes::circle_shape::CircleGiz
1010
use crate::messages::tool::common_functionality::shapes::grid_shape::GridGizmoHandler;
1111
use crate::messages::tool::common_functionality::shapes::polygon_shape::PolygonGizmoHandler;
1212
use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler;
13+
use crate::messages::tool::common_functionality::shapes::spiral_shape::SpiralGizmoHandler;
1314
use crate::messages::tool::common_functionality::shapes::star_shape::StarGizmoHandler;
1415
use glam::DVec2;
1516
use std::collections::VecDeque;
@@ -30,6 +31,7 @@ pub enum ShapeGizmoHandlers {
3031
Arc(ArcGizmoHandler),
3132
Circle(CircleGizmoHandler),
3233
Grid(GridGizmoHandler),
34+
Spiral(SpiralGizmoHandler),
3335
}
3436

3537
impl ShapeGizmoHandlers {
@@ -42,6 +44,7 @@ impl ShapeGizmoHandlers {
4244
Self::Arc(_) => "arc",
4345
Self::Circle(_) => "circle",
4446
Self::Grid(_) => "grid",
47+
Self::Spiral(_) => "spiral",
4548
Self::None => "none",
4649
}
4750
}
@@ -54,6 +57,7 @@ impl ShapeGizmoHandlers {
5457
Self::Arc(h) => h.handle_state(layer, mouse_position, document, responses),
5558
Self::Circle(h) => h.handle_state(layer, mouse_position, document, responses),
5659
Self::Grid(h) => h.handle_state(layer, mouse_position, document, responses),
60+
Self::Spiral(h) => h.handle_state(layer, mouse_position, document, responses),
5761
Self::None => {}
5862
}
5963
}
@@ -66,6 +70,7 @@ impl ShapeGizmoHandlers {
6670
Self::Arc(h) => h.is_any_gizmo_hovered(),
6771
Self::Circle(h) => h.is_any_gizmo_hovered(),
6872
Self::Grid(h) => h.is_any_gizmo_hovered(),
73+
Self::Spiral(h) => h.is_any_gizmo_hovered(),
6974
Self::None => false,
7075
}
7176
}
@@ -78,6 +83,7 @@ impl ShapeGizmoHandlers {
7883
Self::Arc(h) => h.handle_click(),
7984
Self::Circle(h) => h.handle_click(),
8085
Self::Grid(h) => h.handle_click(),
86+
Self::Spiral(h) => h.handle_click(),
8187
Self::None => {}
8288
}
8389
}
@@ -90,6 +96,7 @@ impl ShapeGizmoHandlers {
9096
Self::Arc(h) => h.handle_update(drag_start, document, input, responses),
9197
Self::Circle(h) => h.handle_update(drag_start, document, input, responses),
9298
Self::Grid(h) => h.handle_update(drag_start, document, input, responses),
99+
Self::Spiral(h) => h.handle_update(drag_start, document, input, responses),
93100
Self::None => {}
94101
}
95102
}
@@ -102,6 +109,7 @@ impl ShapeGizmoHandlers {
102109
Self::Arc(h) => h.cleanup(),
103110
Self::Circle(h) => h.cleanup(),
104111
Self::Grid(h) => h.cleanup(),
112+
Self::Spiral(h) => h.cleanup(),
105113
Self::None => {}
106114
}
107115
}
@@ -122,6 +130,7 @@ impl ShapeGizmoHandlers {
122130
Self::Arc(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
123131
Self::Circle(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
124132
Self::Grid(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
133+
Self::Spiral(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
125134
Self::None => {}
126135
}
127136
}
@@ -141,6 +150,7 @@ impl ShapeGizmoHandlers {
141150
Self::Arc(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
142151
Self::Circle(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
143152
Self::Grid(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
153+
Self::Spiral(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
144154
Self::None => {}
145155
}
146156
}
@@ -152,6 +162,7 @@ impl ShapeGizmoHandlers {
152162
Self::Arc(h) => h.mouse_cursor_icon(),
153163
Self::Circle(h) => h.mouse_cursor_icon(),
154164
Self::Grid(h) => h.mouse_cursor_icon(),
165+
Self::Spiral(h) => h.mouse_cursor_icon(),
155166
Self::None => None,
156167
}
157168
}
@@ -199,6 +210,10 @@ impl GizmoManager {
199210
if graph_modification_utils::get_grid_id(layer, &document.network_interface).is_some() {
200211
return Some(ShapeGizmoHandlers::Grid(GridGizmoHandler::default()));
201212
}
213+
// Spiral
214+
if graph_modification_utils::get_spiral_id(layer, &document.network_interface).is_some() {
215+
return Some(ShapeGizmoHandlers::Spiral(SpiralGizmoHandler::default()));
216+
}
202217

203218
None
204219
}

editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ pub mod circle_arc_radius_handle;
22
pub mod grid_rows_columns_gizmo;
33
pub mod number_of_points_dial;
44
pub mod point_radius_handle;
5+
pub mod spiral_turns_handle;
56
pub mod sweep_angle_gizmo;
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
use crate::consts::{COLOR_OVERLAY_RED, POINT_RADIUS_HANDLE_SNAP_THRESHOLD};
2+
use crate::messages::message::Message;
3+
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
4+
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
5+
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
6+
use crate::messages::prelude::Responses;
7+
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage};
8+
use crate::messages::tool::common_functionality::graph_modification_utils;
9+
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
10+
use crate::messages::tool::common_functionality::shapes::shape_utility::extract_spiral_parameters;
11+
use crate::messages::tool::common_functionality::shapes::spiral_shape::calculate_spiral_endpoints;
12+
use glam::DVec2;
13+
use graph_craft::document::NodeInput;
14+
use graph_craft::document::value::TaggedValue;
15+
use graphene_std::NodeInputDecleration;
16+
use graphene_std::subpath::{calculate_growth_factor, spiral_point};
17+
use graphene_std::vector::misc::SpiralType;
18+
use std::collections::VecDeque;
19+
use std::f64::consts::TAU;
20+
21+
#[derive(Clone, Debug, Default, PartialEq)]
22+
pub enum GizmoType {
23+
#[default]
24+
None,
25+
Start,
26+
End,
27+
}
28+
29+
#[derive(Clone, Debug, Default, PartialEq)]
30+
pub enum SpiralTurnsState {
31+
#[default]
32+
Inactive,
33+
Hover,
34+
Dragging,
35+
}
36+
37+
#[derive(Clone, Debug, Default)]
38+
pub struct SpiralTurns {
39+
pub layer: Option<LayerNodeIdentifier>,
40+
pub handle_state: SpiralTurnsState,
41+
initial_turns: f64,
42+
initial_outer_radius: f64,
43+
initial_inner_radius: f64,
44+
initial_growth_factor: f64,
45+
initial_start_angle: f64,
46+
previous_mouse_position: DVec2,
47+
total_angle_delta: f64,
48+
gizmo_type: GizmoType,
49+
spiral_type: SpiralType,
50+
}
51+
52+
impl SpiralTurns {
53+
pub fn cleanup(&mut self) {
54+
self.handle_state = SpiralTurnsState::Inactive;
55+
self.total_angle_delta = 0.;
56+
self.gizmo_type = GizmoType::None;
57+
self.layer = None;
58+
}
59+
60+
pub fn update_state(&mut self, state: SpiralTurnsState) {
61+
self.handle_state = state;
62+
}
63+
64+
pub fn hovered(&self) -> bool {
65+
self.handle_state == SpiralTurnsState::Hover
66+
}
67+
68+
pub fn is_dragging(&self) -> bool {
69+
self.handle_state == SpiralTurnsState::Dragging
70+
}
71+
72+
pub fn store_initial_parameters(
73+
&mut self,
74+
layer: LayerNodeIdentifier,
75+
inner_radius: f64,
76+
outer_radius: f64,
77+
turns: f64,
78+
start_angle: f64,
79+
mouse_position: DVec2,
80+
gizmo_type: GizmoType,
81+
spiral_type: SpiralType,
82+
) {
83+
self.layer = Some(layer);
84+
self.initial_turns = turns;
85+
self.initial_growth_factor = calculate_growth_factor(inner_radius, turns, outer_radius, spiral_type);
86+
self.initial_inner_radius = inner_radius;
87+
self.initial_outer_radius = outer_radius;
88+
self.initial_start_angle = start_angle;
89+
self.previous_mouse_position = mouse_position;
90+
self.spiral_type = spiral_type;
91+
self.gizmo_type = gizmo_type;
92+
self.update_state(SpiralTurnsState::Hover);
93+
}
94+
95+
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, _responses: &mut VecDeque<Message>) {
96+
let viewport = document.metadata().transform_to_viewport(layer);
97+
98+
match &self.handle_state {
99+
SpiralTurnsState::Inactive => {
100+
if let Some((spiral_type, start_angle, inner_radius, outer_radius, turns, _)) = extract_spiral_parameters(layer, document) {
101+
let growth_factor = calculate_growth_factor(inner_radius, turns, outer_radius, spiral_type);
102+
let end_point = viewport.transform_point2(spiral_point(turns * TAU + start_angle.to_radians(), inner_radius, growth_factor, spiral_type));
103+
let start_point = viewport.transform_point2(spiral_point(0. + start_angle.to_radians(), inner_radius, growth_factor, spiral_type));
104+
105+
if mouse_position.distance(end_point) < POINT_RADIUS_HANDLE_SNAP_THRESHOLD {
106+
self.store_initial_parameters(layer, inner_radius, outer_radius, turns, start_angle, mouse_position, GizmoType::End, spiral_type);
107+
} else if mouse_position.distance(start_point) < POINT_RADIUS_HANDLE_SNAP_THRESHOLD {
108+
self.store_initial_parameters(layer, inner_radius, outer_radius, turns, start_angle, mouse_position, GizmoType::Start, spiral_type);
109+
}
110+
}
111+
}
112+
SpiralTurnsState::Hover | SpiralTurnsState::Dragging => {}
113+
}
114+
}
115+
116+
pub fn overlays(&self, document: &DocumentMessageHandler, layer: Option<LayerNodeIdentifier>, _shape_editor: &mut &mut ShapeState, _mouse_position: DVec2, overlay_context: &mut OverlayContext) {
117+
let Some(layer) = layer.or(self.layer) else { return };
118+
let viewport = document.metadata().transform_to_viewport(layer);
119+
120+
match &self.handle_state {
121+
SpiralTurnsState::Inactive => {
122+
if let Some((p1, p2)) = calculate_spiral_endpoints(layer, document, viewport, 0.).zip(calculate_spiral_endpoints(layer, document, viewport, TAU)) {
123+
overlay_context.manipulator_handle(p1, false, None);
124+
overlay_context.manipulator_handle(p2, false, None);
125+
}
126+
}
127+
SpiralTurnsState::Hover | SpiralTurnsState::Dragging => {
128+
// Is true only when hovered over the gizmo
129+
let selected = self.layer.is_some();
130+
let angle = match self.gizmo_type {
131+
GizmoType::End => TAU,
132+
GizmoType::Start => 0.,
133+
GizmoType::None => return,
134+
};
135+
136+
if let Some(endpoint) = calculate_spiral_endpoints(layer, document, viewport, angle) {
137+
overlay_context.manipulator_handle(endpoint, selected, Some(COLOR_OVERLAY_RED));
138+
}
139+
}
140+
}
141+
}
142+
143+
pub fn update_number_of_turns(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
144+
use graphene_std::vector::generator_nodes::spiral::*;
145+
146+
let Some(layer) = self.layer else {
147+
return;
148+
};
149+
150+
let viewport = document.metadata().transform_to_viewport(layer);
151+
let center = viewport.transform_point2(DVec2::ZERO);
152+
153+
let angle_delta = viewport
154+
.inverse()
155+
.transform_vector2(input.mouse.position - center)
156+
.angle_to(viewport.inverse().transform_vector2(self.previous_mouse_position - center))
157+
.to_degrees();
158+
159+
// Skip update if angle calculation produced NaN or infinity (can happen when mouse is at center)
160+
// Also skip very small angle changes to reduce jitter near center
161+
if !angle_delta.is_finite() || angle_delta.abs() < 0.5 {
162+
self.previous_mouse_position = input.mouse.position;
163+
return;
164+
}
165+
166+
// Increase the number of turns and outer radius in unison such that growth and tightness remain same
167+
let total_delta = self.total_angle_delta + angle_delta;
168+
// Convert the total angle (in degrees) to number of full turns
169+
let turns_delta = total_delta / 360.;
170+
171+
// Calculate the new outer radius based on spiral type and turn change
172+
let outer_radius_change = match self.spiral_type {
173+
SpiralType::Archimedean => turns_delta * (self.initial_growth_factor) * TAU,
174+
SpiralType::Logarithmic => self.initial_outer_radius * ((self.initial_growth_factor * TAU * turns_delta).exp() - 1.),
175+
};
176+
177+
// Skip if outer_radius calculation produced invalid values
178+
if !outer_radius_change.is_finite() {
179+
return;
180+
}
181+
182+
let Some(node_id) = graph_modification_utils::get_spiral_id(layer, &document.network_interface) else {
183+
return;
184+
};
185+
186+
match self.gizmo_type {
187+
GizmoType::Start => {
188+
let sign = -1.;
189+
let new_turns = (self.initial_turns + turns_delta * sign).max(0.5);
190+
let new_outer_radius = (self.initial_outer_radius + outer_radius_change * sign).max(0.1);
191+
192+
responses.add(NodeGraphMessage::SetInput {
193+
input_connector: InputConnector::node(node_id, StartAngleInput::INDEX),
194+
input: NodeInput::value(TaggedValue::F64(self.initial_start_angle + total_delta), false),
195+
});
196+
responses.add(NodeGraphMessage::SetInput {
197+
input_connector: InputConnector::node(node_id, TurnsInput::INDEX),
198+
input: NodeInput::value(TaggedValue::F64(new_turns), false),
199+
});
200+
responses.add(NodeGraphMessage::SetInput {
201+
input_connector: InputConnector::node(node_id, OuterRadiusInput::INDEX),
202+
input: NodeInput::value(TaggedValue::F64(new_outer_radius), false),
203+
});
204+
}
205+
GizmoType::End => {
206+
let new_turns = (self.initial_turns + turns_delta).max(0.5);
207+
let new_outer_radius = (self.initial_outer_radius + outer_radius_change).max(0.1);
208+
209+
responses.add(NodeGraphMessage::SetInput {
210+
input_connector: InputConnector::node(node_id, TurnsInput::INDEX),
211+
input: NodeInput::value(TaggedValue::F64(new_turns), false),
212+
});
213+
responses.add(NodeGraphMessage::SetInput {
214+
input_connector: InputConnector::node(node_id, OuterRadiusInput::INDEX),
215+
input: NodeInput::value(TaggedValue::F64(new_outer_radius), false),
216+
});
217+
}
218+
GizmoType::None => {
219+
return;
220+
}
221+
}
222+
223+
responses.add(NodeGraphMessage::RunDocumentGraph);
224+
self.total_angle_delta += angle_delta;
225+
self.previous_mouse_position = input.mouse.position;
226+
}
227+
}

editor/src/messages/tool/common_functionality/shapes/shape_utility.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use graph_craft::document::value::TaggedValue;
1818
use graphene_std::NodeInputDecleration;
1919
use graphene_std::subpath::{self, Subpath};
2020
use graphene_std::vector::click_target::ClickTargetType;
21-
use graphene_std::vector::misc::{ArcType, GridType, dvec2_to_point};
21+
use graphene_std::vector::misc::{ArcType, GridType, SpiralType, dvec2_to_point};
2222
use kurbo::{BezPath, PathEl, Shape};
2323
use std::collections::VecDeque;
2424
use std::f64::consts::{PI, TAU};
@@ -293,6 +293,35 @@ pub fn extract_arc_parameters(layer: Option<LayerNodeIdentifier>, document: &Doc
293293
Some((radius, start_angle, sweep_angle, arc_type))
294294
}
295295

296+
/// Extract the node input values of spiral.
297+
/// Returns an option of (spiral type, start angle, inner radius, outer radius, turns, angle resolution).
298+
pub fn extract_spiral_parameters(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<(SpiralType, f64, f64, f64, f64, f64)> {
299+
use graphene_std::vector::generator_nodes::spiral::*;
300+
301+
let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::spiral::IDENTIFIER))?;
302+
303+
let (
304+
Some(&TaggedValue::SpiralType(spiral_type)),
305+
Some(&TaggedValue::F64(start_angle)),
306+
Some(&TaggedValue::F64(inner_radius)),
307+
Some(&TaggedValue::F64(outer_radius)),
308+
Some(&TaggedValue::F64(turns)),
309+
Some(&TaggedValue::F64(angle_resolution)),
310+
) = (
311+
node_inputs.get(SpiralTypeInput::INDEX)?.as_value(),
312+
node_inputs.get(StartAngleInput::INDEX)?.as_value(),
313+
node_inputs.get(InnerRadiusInput::INDEX)?.as_value(),
314+
node_inputs.get(OuterRadiusInput::INDEX)?.as_value(),
315+
node_inputs.get(TurnsInput::INDEX)?.as_value(),
316+
node_inputs.get(AngularResolutionInput::INDEX)?.as_value(),
317+
)
318+
else {
319+
return None;
320+
};
321+
322+
Some((spiral_type, start_angle, inner_radius, outer_radius, turns, angle_resolution))
323+
}
324+
296325
/// Calculate the viewport positions of arc endpoints
297326
pub fn arc_end_points(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler) -> Option<(DVec2, DVec2)> {
298327
let (radius, start_angle, sweep_angle, _) = extract_arc_parameters(Some(layer?), document)?;

0 commit comments

Comments
 (0)