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 <keavon@keavon.com>
This commit is contained in:
0SlowPoke0
2025-09-09 03:07:43 +05:30
committed by GitHub
parent ee586be381
commit 485152bf8d
15 changed files with 483 additions and 44 deletions

View File

@@ -1776,6 +1776,7 @@ fn static_node_properties() -> NodeProperties {
map.insert("math_properties".to_string(), Box::new(node_properties::math_properties));
map.insert("rectangle_properties".to_string(), Box::new(node_properties::rectangle_properties));
map.insert("grid_properties".to_string(), Box::new(node_properties::grid_properties));
map.insert("spiral_properties".to_string(), Box::new(node_properties::spiral_properties));
map.insert("sample_polyline_properties".to_string(), Box::new(node_properties::sample_polyline_properties));
map.insert(
"monitor_properties".to_string(),
@@ -2394,6 +2395,7 @@ impl DocumentNodeDefinition {
/// `input_override` does not have to be the correct length.
pub fn node_template_input_override(&self, input_override: impl IntoIterator<Item = Option<NodeInput>>) -> NodeTemplate {
let mut template = self.node_template.clone();
// 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
input_override.into_iter().enumerate().for_each(|(index, input_override)| {
if let Some(input_override) = input_override {
// Only value inputs can be overridden, since node inputs change graph structure and must be handled by the network interface

View File

@@ -23,7 +23,7 @@ use graphene_std::raster::{
use graphene_std::table::{Table, TableRow};
use graphene_std::text::{Font, TextAlign};
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
use graphene_std::vector::misc::{ArcType, CentroidType, GridType, MergeByDistanceAlgorithm, PointSpacingType};
use graphene_std::vector::misc::{ArcType, CentroidType, GridType, MergeByDistanceAlgorithm, PointSpacingType, SpiralType};
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
@@ -1286,6 +1286,66 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
widgets
}
pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
use graphene_std::vector::generator_nodes::spiral::*;
let spiral_type = enum_choice::<SpiralType>()
.for_socket(ParameterWidgetsInfo::new(node_id, SpiralTypeInput::INDEX, true, context))
.property_row();
let turns = number_widget(ParameterWidgetsInfo::new(node_id, TurnsInput::INDEX, true, context), NumberInput::default().min(0.1));
let start_angle = number_widget(ParameterWidgetsInfo::new(node_id, StartAngleInput::INDEX, true, context), NumberInput::default().unit("°"));
let mut widgets = vec![spiral_type, LayoutGroup::Row { widgets: turns }, LayoutGroup::Row { widgets: start_angle }];
let document_node = match get_document_node(node_id, context) {
Ok(document_node) => document_node,
Err(err) => {
log::error!("Could not get document node in exposure_properties: {err}");
return Vec::new();
}
};
let Some(spiral_type_input) = document_node.inputs.get(SpiralTypeInput::INDEX) else {
log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![];
};
if let Some(&TaggedValue::SpiralType(spiral_type)) = spiral_type_input.as_non_exposed_value() {
match spiral_type {
SpiralType::Archimedean => {
let inner_radius = LayoutGroup::Row {
widgets: number_widget(ParameterWidgetsInfo::new(node_id, InnerRadiusInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px")),
};
let outer_radius = LayoutGroup::Row {
widgets: number_widget(ParameterWidgetsInfo::new(node_id, OuterRadiusInput::INDEX, true, context), NumberInput::default().unit(" px")),
};
widgets.extend([inner_radius, outer_radius]);
}
SpiralType::Logarithmic => {
let inner_radius = LayoutGroup::Row {
widgets: number_widget(ParameterWidgetsInfo::new(node_id, InnerRadiusInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px")),
};
let outer_radius = LayoutGroup::Row {
widgets: number_widget(ParameterWidgetsInfo::new(node_id, OuterRadiusInput::INDEX, true, context), NumberInput::default().min(0.1).unit(" px")),
};
widgets.extend([inner_radius, outer_radius]);
}
}
}
let angular_resolution = number_widget(
ParameterWidgetsInfo::new(node_id, AngularResolutionInput::INDEX, true, context),
NumberInput::default().min(1.).max(180.).unit("°"),
);
widgets.push(LayoutGroup::Row { widgets: angular_resolution });
widgets
}
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_SPACING: &str = "Use a point sampling density controlled by a distance between, or specific number of, points.";
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_SEPARATION: &str = "Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).";
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_QUANTITY: &str = "Number of points to place along the path.";

View File

@@ -363,6 +363,10 @@ pub fn get_arc_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInt
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Arc")
}
pub fn get_spiral_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Spiral")
}
pub fn get_text_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Text")
}

View File

@@ -6,6 +6,7 @@ pub mod line_shape;
pub mod polygon_shape;
pub mod rectangle_shape;
pub mod shape_utility;
pub mod spiral_shape;
pub mod star_shape;
pub use super::shapes::ellipse_shape::Ellipse;

View File

@@ -158,34 +158,34 @@ impl Polygon {
}
}
pub fn increase_decrease_sides(increase: bool, document: &DocumentMessageHandler, shape_tool_data: &mut ShapeToolData, responses: &mut VecDeque<Message>) {
if let Some(layer) = shape_tool_data.data.layer {
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 {
return;
};
/// Updates the number of sides of a polygon or star node and syncs the Shape tool UI widget accordingly.
/// Increases or decreases the side count based on user input, clamped to a minimum of 3.
pub fn decrease_or_increase_sides(decrease: bool, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
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 {
return;
};
let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface)
.find_node_inputs("Regular Polygon")
.or(NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star"))
else {
return;
};
let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface)
.find_node_inputs("Regular Polygon")
.or(NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star"))
else {
return;
};
let Some(&TaggedValue::U32(n)) = node_inputs.get(1).unwrap().as_value() else {
return;
};
let Some(&TaggedValue::U32(n)) = node_inputs.get(1).unwrap().as_value() else {
return;
};
let new_dimension = if increase { n + 1 } else { (n - 1).max(3) };
let new_dimension = if decrease { (n - 1).max(3) } else { n + 1 };
responses.add(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::Vertices(new_dimension),
});
responses.add(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::Vertices(new_dimension),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 1),
input: NodeInput::value(TaggedValue::U32(new_dimension), false),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
}
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 1),
input: NodeInput::value(TaggedValue::U32(new_dimension), false),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
}
}

View File

@@ -29,6 +29,7 @@ pub enum ShapeType {
Star,
Circle,
Arc,
Spiral,
Grid,
Rectangle,
Ellipse,
@@ -43,6 +44,7 @@ impl ShapeType {
Self::Circle => "Circle",
Self::Arc => "Arc",
Self::Grid => "Grid",
Self::Spiral => "Spiral",
Self::Rectangle => "Rectangle",
Self::Ellipse => "Ellipse",
Self::Line => "Line",

View File

@@ -0,0 +1,116 @@
use super::*;
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::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};
use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer};
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapTypeConfiguration};
use crate::messages::tool::tool_messages::shape_tool::ShapeOptionsUpdate;
use crate::messages::tool::tool_messages::tool_prelude::*;
use glam::DAffine2;
use graph_craft::document::NodeInput;
use graph_craft::document::value::TaggedValue;
use graphene_std::NodeInputDecleration;
use graphene_std::vector::misc::SpiralType;
use std::collections::VecDeque;
#[derive(Default)]
pub struct Spiral;
impl Spiral {
pub fn create_node(spiral_type: SpiralType, turns: f64) -> NodeTemplate {
let inner_radius = match spiral_type {
SpiralType::Archimedean => 0.,
SpiralType::Logarithmic => 0.1,
};
let node_type = resolve_document_node_type("Spiral").expect("Spiral node can't be found");
node_type.node_template_input_override([
None,
Some(NodeInput::value(TaggedValue::SpiralType(spiral_type), false)),
Some(NodeInput::value(TaggedValue::F64(turns), false)),
Some(NodeInput::value(TaggedValue::F64(0.), false)),
Some(NodeInput::value(TaggedValue::F64(inner_radius), false)),
Some(NodeInput::value(TaggedValue::F64(0.1), false)),
Some(NodeInput::value(TaggedValue::F64(90.), false)),
])
}
pub fn update_shape(document: &DocumentMessageHandler, ipp: &InputPreprocessorMessageHandler, layer: LayerNodeIdentifier, shape_tool_data: &mut ShapeToolData, responses: &mut VecDeque<Message>) {
use graphene_std::vector::generator_nodes::spiral::*;
let viewport_drag_start = shape_tool_data.data.viewport_drag_start(document);
let ignore = vec![layer];
let snap_data = SnapData::ignore(document, ipp, &ignore);
let config = SnapTypeConfiguration::default();
let document_mouse = document.metadata().document_to_viewport.inverse().transform_point2(ipp.mouse.position);
let snapped = shape_tool_data.data.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), config);
let snapped_viewport_point = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);
shape_tool_data.data.snap_manager.update_indicator(snapped);
let dragged_distance = (viewport_drag_start - snapped_viewport_point).length();
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::SpiralType(spiral_type)) = node_inputs.get(SpiralTypeInput::INDEX).unwrap().as_value() else {
return;
};
let new_radius = match spiral_type {
SpiralType::Archimedean => dragged_distance,
SpiralType::Logarithmic => (dragged_distance).max(0.1),
};
responses.add(GraphOperationMessage::TransformSet {
layer,
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., viewport_drag_start),
transform_in: TransformIn::Viewport,
skip_rerender: false,
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, OuterRadiusInput::INDEX),
input: NodeInput::value(TaggedValue::F64(new_radius), false),
});
}
/// 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>) {
use graphene_std::vector::generator_nodes::spiral::*;
let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Spiral") else {
return;
};
let Some(node_id) = graph_modification_utils::get_spiral_id(layer, &document.network_interface) else {
return;
};
let Some(&TaggedValue::F64(mut turns)) = node_inputs.get(TurnsInput::INDEX).unwrap().as_value() else {
return;
};
if decrease {
turns = (turns - 1.).max(1.);
} else {
turns += 1.;
}
responses.add(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::Turns(turns),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, TurnsInput::INDEX),
input: NodeInput::value(TaggedValue::F64(turns), false),
});
}
}

View File

@@ -14,6 +14,7 @@ use crate::messages::tool::common_functionality::shapes::grid_shape::Grid;
use crate::messages::tool::common_functionality::shapes::line_shape::{LineToolData, clicked_on_line_endpoints};
use crate::messages::tool::common_functionality::shapes::polygon_shape::Polygon;
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays};
use crate::messages::tool::common_functionality::shapes::spiral_shape::Spiral;
use crate::messages::tool::common_functionality::shapes::star_shape::Star;
use crate::messages::tool::common_functionality::shapes::{Ellipse, Line, Rectangle};
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapTypeConfiguration};
@@ -22,7 +23,7 @@ use crate::messages::tool::common_functionality::utility_functions::{closest_poi
use graph_craft::document::NodeId;
use graphene_std::Color;
use graphene_std::renderer::Quad;
use graphene_std::vector::misc::{ArcType, GridType};
use graphene_std::vector::misc::{ArcType, GridType, SpiralType};
use std::vec;
#[derive(Default, ExtractField)]
@@ -40,6 +41,8 @@ pub struct ShapeToolOptions {
shape_type: ShapeType,
arc_type: ArcType,
grid_type: GridType,
spiral_type: SpiralType,
turns: f64,
}
impl Default for ShapeToolOptions {
@@ -51,6 +54,8 @@ impl Default for ShapeToolOptions {
vertices: 5,
shape_type: ShapeType::Polygon,
arc_type: ArcType::Open,
spiral_type: SpiralType::Archimedean,
turns: 5.,
grid_type: GridType::Rectangular,
}
}
@@ -67,6 +72,8 @@ pub enum ShapeOptionsUpdate {
Vertices(u32),
ShapeType(ShapeType),
ArcType(ArcType),
SpiralType(SpiralType),
Turns(f64),
GridType(GridType),
}
@@ -109,6 +116,20 @@ fn create_sides_widget(vertices: u32) -> WidgetHolder {
.widget_holder()
}
fn create_turns_widget(turns: f64) -> WidgetHolder {
NumberInput::new(Some(turns))
.label("Turns")
.min(0.5)
.mode(NumberInputMode::Increment)
.on_update(|number_input: &NumberInput| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::Turns(number_input.value.unwrap()),
}
.into()
})
.widget_holder()
}
fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder {
let entries = vec![vec![
MenuListEntry::new("Polygon").label("Polygon").on_commit(move |_| {
@@ -135,6 +156,12 @@ fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder {
}
.into()
}),
MenuListEntry::new("Spiral").label("Spiral").on_commit(move |_| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ShapeType(ShapeType::Spiral),
}
.into()
}),
MenuListEntry::new("Grid").label("Grid").on_commit(move |_| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ShapeType(ShapeType::Grid),
@@ -184,6 +211,24 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder {
.widget_holder()
}
fn create_spiral_type_widget(spiral_type: SpiralType) -> WidgetHolder {
let entries = vec![vec![
MenuListEntry::new("Archimedean").label("Archimedean").on_commit(move |_| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::SpiralType(SpiralType::Archimedean),
}
.into()
}),
MenuListEntry::new("Logarithmic").label("Logarithmic").on_commit(move |_| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::SpiralType(SpiralType::Logarithmic),
}
.into()
}),
]];
DropdownInput::new(entries).selected_index(Some(spiral_type as u32)).widget_holder()
}
fn create_grid_type_widget(grid_type: GridType) -> WidgetHolder {
let entries = vec![
RadioEntryData::new("Rectangular").label("Rectangular").on_update(move |_| {
@@ -221,6 +266,14 @@ impl LayoutHolder for ShapeTool {
}
}
if self.options.shape_type == ShapeType::Spiral {
widgets.push(create_spiral_type_widget(self.options.spiral_type));
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
widgets.push(create_turns_widget(self.options.turns));
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
}
if self.options.shape_type == ShapeType::Grid {
widgets.push(create_grid_type_widget(self.options.grid_type));
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
@@ -327,6 +380,12 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Shap
ShapeOptionsUpdate::ArcType(arc_type) => {
self.options.arc_type = arc_type;
}
ShapeOptionsUpdate::SpiralType(spiral_type) => {
self.options.spiral_type = spiral_type;
}
ShapeOptionsUpdate::Turns(turns) => {
self.options.turns = turns;
}
ShapeOptionsUpdate::GridType(grid_type) => {
self.options.grid_type = grid_type;
}
@@ -471,6 +530,18 @@ impl ShapeToolData {
fn shape_tool_modifier_keys() -> [Key; 3] {
[Key::Alt, Key::Shift, Key::Control]
}
fn decrease_or_increase_sides(&self, document: &DocumentMessageHandler, shape_type: ShapeType, responses: &mut VecDeque<Message>, decrease: bool) {
if let Some(layer) = self.data.layer {
match shape_type {
ShapeType::Star | ShapeType::Polygon => Polygon::decrease_or_increase_sides(decrease, layer, document, responses),
ShapeType::Spiral => Spiral::update_turns(decrease, layer, document, responses),
_ => {}
}
}
responses.add(NodeGraphMessage::RunDocumentGraph);
}
}
impl Fsm for ShapeToolFsmState {
@@ -584,15 +655,32 @@ impl Fsm for ShapeToolFsmState {
self
}
(ShapeToolFsmState::Ready(_), ShapeToolMessage::IncreaseSides) => {
responses.add(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::Vertices(tool_options.vertices + 1),
});
if matches!(tool_options.shape_type, ShapeType::Star | ShapeType::Polygon) {
responses.add(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::Vertices(tool_options.vertices + 1),
});
}
if matches!(tool_options.shape_type, ShapeType::Spiral) {
responses.add(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::Turns(tool_options.turns + 1.),
});
}
self
}
(ShapeToolFsmState::Ready(_), ShapeToolMessage::DecreaseSides) => {
responses.add(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::Vertices((tool_options.vertices - 1).max(3)),
});
if matches!(tool_options.shape_type, ShapeType::Star | ShapeType::Polygon) {
responses.add(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::Vertices((tool_options.vertices - 1).max(3)),
});
}
if matches!(tool_options.shape_type, ShapeType::Spiral) {
responses.add(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::Turns((tool_options.turns - 1.).max(1.)),
});
}
self
}
(
@@ -629,13 +717,11 @@ impl Fsm for ShapeToolFsmState {
self
}
(ShapeToolFsmState::Drawing(_), ShapeToolMessage::IncreaseSides) => {
Polygon::increase_decrease_sides(true, document, tool_data, responses);
tool_data.decrease_or_increase_sides(document, tool_options.shape_type, responses, false);
self
}
(ShapeToolFsmState::Drawing(_), ShapeToolMessage::DecreaseSides) => {
Polygon::increase_decrease_sides(false, document, tool_data, responses);
tool_data.decrease_or_increase_sides(document, tool_options.shape_type, responses, true);
self
}
(ShapeToolFsmState::Ready(_), ShapeToolMessage::DragStart) => {
@@ -719,7 +805,9 @@ impl Fsm for ShapeToolFsmState {
};
match tool_data.current_shape {
ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Grid | ShapeType::Rectangle | ShapeType::Ellipse => tool_data.data.start(document, input),
ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Spiral | ShapeType::Grid | ShapeType::Rectangle | ShapeType::Ellipse => {
tool_data.data.start(document, input)
}
ShapeType::Line => {
let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
let snapped = tool_data.data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default());
@@ -734,6 +822,7 @@ impl Fsm for ShapeToolFsmState {
ShapeType::Star => Star::create_node(tool_options.vertices),
ShapeType::Circle => Circle::create_node(),
ShapeType::Arc => Arc::create_node(tool_options.arc_type),
ShapeType::Spiral => Spiral::create_node(tool_options.spiral_type, tool_options.turns),
ShapeType::Grid => Grid::create_node(tool_options.grid_type),
ShapeType::Rectangle => Rectangle::create_node(),
ShapeType::Ellipse => Ellipse::create_node(),
@@ -746,7 +835,7 @@ impl Fsm for ShapeToolFsmState {
let defered_responses = &mut VecDeque::new();
match tool_data.current_shape {
ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Grid | ShapeType::Rectangle | ShapeType::Ellipse => {
ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Spiral | ShapeType::Grid | ShapeType::Rectangle | ShapeType::Ellipse => {
defered_responses.add(GraphOperationMessage::TransformSet {
layer,
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position),
@@ -783,6 +872,7 @@ impl Fsm for ShapeToolFsmState {
ShapeType::Star => Star::update_shape(document, input, layer, tool_data, modifier, responses),
ShapeType::Circle => Circle::update_shape(document, input, layer, tool_data, modifier, responses),
ShapeType::Arc => Arc::update_shape(document, input, layer, tool_data, modifier, responses),
ShapeType::Spiral => Spiral::update_shape(document, input, layer, tool_data, responses),
ShapeType::Grid => Grid::update_shape(document, input, layer, tool_options.grid_type, tool_data, modifier, responses),
ShapeType::Rectangle => Rectangle::update_shape(document, input, layer, tool_data, modifier, responses),
ShapeType::Ellipse => Ellipse::update_shape(document, input, layer, tool_data, modifier, responses),
@@ -964,6 +1054,9 @@ impl Fsm for ShapeToolFsmState {
responses.add(DocumentMessage::AbortTransaction);
tool_data.data.cleanup(responses);
tool_data.current_shape = shape;
responses.add(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ShapeType(shape),
});
responses.add(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ShapeType(shape),
@@ -1000,6 +1093,10 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque<Mess
]),
HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Sides")]),
],
ShapeType::Spiral => vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Spiral")]),
HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Turns")]),
],
ShapeType::Ellipse => vec![HintGroup(vec![
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Ellipse"),
HintInfo::keys([Key::Shift], "Constrain Circular").prepend_plus(),
@@ -1046,6 +1143,7 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque<Mess
HintInfo::keys([Key::Control], "Lock Angle"),
]),
ShapeType::Circle => HintGroup(vec![HintInfo::keys([Key::Alt], "From Center")]),
ShapeType::Spiral => HintGroup(vec![]),
};
if !tool_hint_group.0.is_empty() {
@@ -1056,6 +1154,10 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque<Mess
common_hint_group.push(HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Sides")]));
}
if matches!(shape, ShapeType::Spiral) {
common_hint_group.push(HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Turns")]));
}
HintData(common_hint_group)
}
ShapeToolFsmState::DraggingLineEndpoints => HintData(vec![

View File

@@ -119,7 +119,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
return;
}
if !using_path_tool {
if !using_path_tool || !using_shape_tool {
self.pivot_gizmo.recalculate_transform(document);
*selected.pivot = self.pivot_gizmo.position(document);
self.local_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot);