Skip to content

Commit 485152b

Browse files
0SlowPoke0Keavon
andauthored
Add "Spiral" to the Shape tool and as a new node (#2803)
* 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 * updated the api * changed the function_name * [/] to update the turns widget in shape tool * Code review --------- Co-authored-by: Keavon Chambers <[email protected]>
1 parent ee586be commit 485152b

File tree

15 files changed

+483
-44
lines changed

15 files changed

+483
-44
lines changed

editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1776,6 +1776,7 @@ fn static_node_properties() -> NodeProperties {
17761776
map.insert("math_properties".to_string(), Box::new(node_properties::math_properties));
17771777
map.insert("rectangle_properties".to_string(), Box::new(node_properties::rectangle_properties));
17781778
map.insert("grid_properties".to_string(), Box::new(node_properties::grid_properties));
1779+
map.insert("spiral_properties".to_string(), Box::new(node_properties::spiral_properties));
17791780
map.insert("sample_polyline_properties".to_string(), Box::new(node_properties::sample_polyline_properties));
17801781
map.insert(
17811782
"monitor_properties".to_string(),
@@ -2394,6 +2395,7 @@ impl DocumentNodeDefinition {
23942395
/// `input_override` does not have to be the correct length.
23952396
pub fn node_template_input_override(&self, input_override: impl IntoIterator<Item = Option<NodeInput>>) -> NodeTemplate {
23962397
let mut template = self.node_template.clone();
2398+
// TODO: Replace the .enumerate() with changing the iterator to take a tuple of (index, input) so the user is forced to provide the correct index
23972399
input_override.into_iter().enumerate().for_each(|(index, input_override)| {
23982400
if let Some(input_override) = input_override {
23992401
// Only value inputs can be overridden, since node inputs change graph structure and must be handled by the network interface

editor/src/messages/portfolio/document/node_graph/node_properties.rs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use graphene_std::raster::{
2323
use graphene_std::table::{Table, TableRow};
2424
use graphene_std::text::{Font, TextAlign};
2525
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
26-
use graphene_std::vector::misc::{ArcType, CentroidType, GridType, MergeByDistanceAlgorithm, PointSpacingType};
26+
use graphene_std::vector::misc::{ArcType, CentroidType, GridType, MergeByDistanceAlgorithm, PointSpacingType, SpiralType};
2727
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
2828

2929
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
@@ -1286,6 +1286,66 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
12861286
widgets
12871287
}
12881288

1289+
pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
1290+
use graphene_std::vector::generator_nodes::spiral::*;
1291+
1292+
let spiral_type = enum_choice::<SpiralType>()
1293+
.for_socket(ParameterWidgetsInfo::new(node_id, SpiralTypeInput::INDEX, true, context))
1294+
.property_row();
1295+
let turns = number_widget(ParameterWidgetsInfo::new(node_id, TurnsInput::INDEX, true, context), NumberInput::default().min(0.1));
1296+
let start_angle = number_widget(ParameterWidgetsInfo::new(node_id, StartAngleInput::INDEX, true, context), NumberInput::default().unit("°"));
1297+
1298+
let mut widgets = vec![spiral_type, LayoutGroup::Row { widgets: turns }, LayoutGroup::Row { widgets: start_angle }];
1299+
1300+
let document_node = match get_document_node(node_id, context) {
1301+
Ok(document_node) => document_node,
1302+
Err(err) => {
1303+
log::error!("Could not get document node in exposure_properties: {err}");
1304+
return Vec::new();
1305+
}
1306+
};
1307+
1308+
let Some(spiral_type_input) = document_node.inputs.get(SpiralTypeInput::INDEX) else {
1309+
log::warn!("A widget failed to be built because its node's input index is invalid.");
1310+
return vec![];
1311+
};
1312+
if let Some(&TaggedValue::SpiralType(spiral_type)) = spiral_type_input.as_non_exposed_value() {
1313+
match spiral_type {
1314+
SpiralType::Archimedean => {
1315+
let inner_radius = LayoutGroup::Row {
1316+
widgets: number_widget(ParameterWidgetsInfo::new(node_id, InnerRadiusInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px")),
1317+
};
1318+
1319+
let outer_radius = LayoutGroup::Row {
1320+
widgets: number_widget(ParameterWidgetsInfo::new(node_id, OuterRadiusInput::INDEX, true, context), NumberInput::default().unit(" px")),
1321+
};
1322+
1323+
widgets.extend([inner_radius, outer_radius]);
1324+
}
1325+
SpiralType::Logarithmic => {
1326+
let inner_radius = LayoutGroup::Row {
1327+
widgets: number_widget(ParameterWidgetsInfo::new(node_id, InnerRadiusInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px")),
1328+
};
1329+
1330+
let outer_radius = LayoutGroup::Row {
1331+
widgets: number_widget(ParameterWidgetsInfo::new(node_id, OuterRadiusInput::INDEX, true, context), NumberInput::default().min(0.1).unit(" px")),
1332+
};
1333+
1334+
widgets.extend([inner_radius, outer_radius]);
1335+
}
1336+
}
1337+
}
1338+
1339+
let angular_resolution = number_widget(
1340+
ParameterWidgetsInfo::new(node_id, AngularResolutionInput::INDEX, true, context),
1341+
NumberInput::default().min(1.).max(180.).unit("°"),
1342+
);
1343+
1344+
widgets.push(LayoutGroup::Row { widgets: angular_resolution });
1345+
1346+
widgets
1347+
}
1348+
12891349
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_SPACING: &str = "Use a point sampling density controlled by a distance between, or specific number of, points.";
12901350
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_SEPARATION: &str = "Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).";
12911351
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_QUANTITY: &str = "Number of points to place along the path.";

editor/src/messages/tool/common_functionality/graph_modification_utils.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,10 @@ pub fn get_arc_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInt
363363
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Arc")
364364
}
365365

366+
pub fn get_spiral_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
367+
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Spiral")
368+
}
369+
366370
pub fn get_text_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
367371
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Text")
368372
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub mod line_shape;
66
pub mod polygon_shape;
77
pub mod rectangle_shape;
88
pub mod shape_utility;
9+
pub mod spiral_shape;
910
pub mod star_shape;
1011

1112
pub use super::shapes::ellipse_shape::Ellipse;

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

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -158,34 +158,34 @@ impl Polygon {
158158
}
159159
}
160160

161-
pub fn increase_decrease_sides(increase: bool, document: &DocumentMessageHandler, shape_tool_data: &mut ShapeToolData, responses: &mut VecDeque<Message>) {
162-
if let Some(layer) = shape_tool_data.data.layer {
163-
let Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface).or(graph_modification_utils::get_star_id(layer, &document.network_interface)) else {
164-
return;
165-
};
161+
/// Updates the number of sides of a polygon or star node and syncs the Shape tool UI widget accordingly.
162+
/// Increases or decreases the side count based on user input, clamped to a minimum of 3.
163+
pub fn decrease_or_increase_sides(decrease: bool, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
164+
let Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface).or(graph_modification_utils::get_star_id(layer, &document.network_interface)) else {
165+
return;
166+
};
166167

167-
let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface)
168-
.find_node_inputs("Regular Polygon")
169-
.or(NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star"))
170-
else {
171-
return;
172-
};
168+
let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface)
169+
.find_node_inputs("Regular Polygon")
170+
.or(NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star"))
171+
else {
172+
return;
173+
};
173174

174-
let Some(&TaggedValue::U32(n)) = node_inputs.get(1).unwrap().as_value() else {
175-
return;
176-
};
175+
let Some(&TaggedValue::U32(n)) = node_inputs.get(1).unwrap().as_value() else {
176+
return;
177+
};
177178

178-
let new_dimension = if increase { n + 1 } else { (n - 1).max(3) };
179+
let new_dimension = if decrease { (n - 1).max(3) } else { n + 1 };
179180

180-
responses.add(ShapeToolMessage::UpdateOptions {
181-
options: ShapeOptionsUpdate::Vertices(new_dimension),
182-
});
181+
responses.add(ShapeToolMessage::UpdateOptions {
182+
options: ShapeOptionsUpdate::Vertices(new_dimension),
183+
});
183184

184-
responses.add(NodeGraphMessage::SetInput {
185-
input_connector: InputConnector::node(node_id, 1),
186-
input: NodeInput::value(TaggedValue::U32(new_dimension), false),
187-
});
188-
responses.add(NodeGraphMessage::RunDocumentGraph);
189-
}
185+
responses.add(NodeGraphMessage::SetInput {
186+
input_connector: InputConnector::node(node_id, 1),
187+
input: NodeInput::value(TaggedValue::U32(new_dimension), false),
188+
});
189+
responses.add(NodeGraphMessage::RunDocumentGraph);
190190
}
191191
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub enum ShapeType {
2929
Star,
3030
Circle,
3131
Arc,
32+
Spiral,
3233
Grid,
3334
Rectangle,
3435
Ellipse,
@@ -43,6 +44,7 @@ impl ShapeType {
4344
Self::Circle => "Circle",
4445
Self::Arc => "Arc",
4546
Self::Grid => "Grid",
47+
Self::Spiral => "Spiral",
4648
Self::Rectangle => "Rectangle",
4749
Self::Ellipse => "Ellipse",
4850
Self::Line => "Line",
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
use super::*;
2+
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
3+
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
4+
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
5+
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};
6+
use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer};
7+
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapTypeConfiguration};
8+
use crate::messages::tool::tool_messages::shape_tool::ShapeOptionsUpdate;
9+
use crate::messages::tool::tool_messages::tool_prelude::*;
10+
use glam::DAffine2;
11+
use graph_craft::document::NodeInput;
12+
use graph_craft::document::value::TaggedValue;
13+
use graphene_std::NodeInputDecleration;
14+
use graphene_std::vector::misc::SpiralType;
15+
use std::collections::VecDeque;
16+
17+
#[derive(Default)]
18+
pub struct Spiral;
19+
20+
impl Spiral {
21+
pub fn create_node(spiral_type: SpiralType, turns: f64) -> NodeTemplate {
22+
let inner_radius = match spiral_type {
23+
SpiralType::Archimedean => 0.,
24+
SpiralType::Logarithmic => 0.1,
25+
};
26+
27+
let node_type = resolve_document_node_type("Spiral").expect("Spiral node can't be found");
28+
node_type.node_template_input_override([
29+
None,
30+
Some(NodeInput::value(TaggedValue::SpiralType(spiral_type), false)),
31+
Some(NodeInput::value(TaggedValue::F64(turns), false)),
32+
Some(NodeInput::value(TaggedValue::F64(0.), false)),
33+
Some(NodeInput::value(TaggedValue::F64(inner_radius), false)),
34+
Some(NodeInput::value(TaggedValue::F64(0.1), false)),
35+
Some(NodeInput::value(TaggedValue::F64(90.), false)),
36+
])
37+
}
38+
39+
pub fn update_shape(document: &DocumentMessageHandler, ipp: &InputPreprocessorMessageHandler, layer: LayerNodeIdentifier, shape_tool_data: &mut ShapeToolData, responses: &mut VecDeque<Message>) {
40+
use graphene_std::vector::generator_nodes::spiral::*;
41+
42+
let viewport_drag_start = shape_tool_data.data.viewport_drag_start(document);
43+
44+
let ignore = vec![layer];
45+
let snap_data = SnapData::ignore(document, ipp, &ignore);
46+
let config = SnapTypeConfiguration::default();
47+
let document_mouse = document.metadata().document_to_viewport.inverse().transform_point2(ipp.mouse.position);
48+
let snapped = shape_tool_data.data.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), config);
49+
let snapped_viewport_point = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);
50+
shape_tool_data.data.snap_manager.update_indicator(snapped);
51+
52+
let dragged_distance = (viewport_drag_start - snapped_viewport_point).length();
53+
54+
let Some(node_id) = graph_modification_utils::get_spiral_id(layer, &document.network_interface) else {
55+
return;
56+
};
57+
58+
let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Spiral") else {
59+
return;
60+
};
61+
62+
let Some(&TaggedValue::SpiralType(spiral_type)) = node_inputs.get(SpiralTypeInput::INDEX).unwrap().as_value() else {
63+
return;
64+
};
65+
66+
let new_radius = match spiral_type {
67+
SpiralType::Archimedean => dragged_distance,
68+
SpiralType::Logarithmic => (dragged_distance).max(0.1),
69+
};
70+
71+
responses.add(GraphOperationMessage::TransformSet {
72+
layer,
73+
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., viewport_drag_start),
74+
transform_in: TransformIn::Viewport,
75+
skip_rerender: false,
76+
});
77+
78+
responses.add(NodeGraphMessage::SetInput {
79+
input_connector: InputConnector::node(node_id, OuterRadiusInput::INDEX),
80+
input: NodeInput::value(TaggedValue::F64(new_radius), false),
81+
});
82+
}
83+
84+
/// Updates the number of turns of a Spiral node and recalculates its radius based on drag distance.
85+
/// Also updates the Shape tool's turns UI widget to reflect the change.
86+
pub fn update_turns(decrease: bool, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
87+
use graphene_std::vector::generator_nodes::spiral::*;
88+
89+
let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Spiral") else {
90+
return;
91+
};
92+
93+
let Some(node_id) = graph_modification_utils::get_spiral_id(layer, &document.network_interface) else {
94+
return;
95+
};
96+
97+
let Some(&TaggedValue::F64(mut turns)) = node_inputs.get(TurnsInput::INDEX).unwrap().as_value() else {
98+
return;
99+
};
100+
101+
if decrease {
102+
turns = (turns - 1.).max(1.);
103+
} else {
104+
turns += 1.;
105+
}
106+
107+
responses.add(ShapeToolMessage::UpdateOptions {
108+
options: ShapeOptionsUpdate::Turns(turns),
109+
});
110+
111+
responses.add(NodeGraphMessage::SetInput {
112+
input_connector: InputConnector::node(node_id, TurnsInput::INDEX),
113+
input: NodeInput::value(TaggedValue::F64(turns), false),
114+
});
115+
}
116+
}

0 commit comments

Comments
 (0)