From 57ecdac1ec0659433a038ee8dfbf14c707d44f6c Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Sat, 23 Aug 2025 02:12:32 +0530 Subject: [PATCH 1/8] added operation tool --- .../messages/input_mapper/input_mappings.rs | 8 + .../graph_operation_message.rs | 3 + .../graph_operation_message_handler.rs | 5 + editor/src/messages/prelude.rs | 1 + editor/src/messages/tool/tool_message.rs | 3 + editor/src/messages/tool/tool_messages/mod.rs | 1 + .../tool/tool_messages/operation_tool.rs | 295 ++++++++++++++++++ editor/src/messages/tool/utility_types.rs | 4 + 8 files changed, 320 insertions(+) create mode 100644 editor/src/messages/tool/tool_messages/operation_tool.rs diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index 211104bc21..87c146d4f8 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -171,6 +171,14 @@ pub fn input_mappings() -> Mapping { entry!(KeyDown(MouseRight); action_dispatch=GradientToolMessage::Abort), entry!(KeyDown(Escape); action_dispatch=GradientToolMessage::Abort), // + // OperationToolMessage + entry!(PointerMove; action_dispatch=OperationToolMessage::PointerMove), + entry!(KeyDown(MouseLeft); action_dispatch=OperationToolMessage::DragStart), + entry!(KeyUp(MouseLeft); action_dispatch=OperationToolMessage::DragStop), + entry!(KeyDown(MouseRight); action_dispatch=OperationToolMessage::Confirm), + entry!(KeyDown(Escape); action_dispatch=OperationToolMessage::Confirm), + entry!(KeyDown(Enter); action_dispatch=OperationToolMessage::Confirm), + // // ShapeToolMessage entry!(KeyDown(MouseLeft); action_dispatch=ShapeToolMessage::DragStart), entry!(KeyUp(MouseLeft); action_dispatch=ShapeToolMessage::DragStop), diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs index 8ebde695b5..6251e74124 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs @@ -22,6 +22,9 @@ pub enum GraphOperationMessage { layer: LayerNodeIdentifier, fill: Fill, }, + RepeatSet { + layer: LayerNodeIdentifier, + }, BlendingFillSet { layer: LayerNodeIdentifier, fill: f64, diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs index 5bf6cce032..fd7bcddb23 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs @@ -37,6 +37,11 @@ impl MessageHandler> for modify_inputs.fill_set(fill); } } + GraphOperationMessage::RepeatSet { layer } => { + if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) { + modify_inputs.create_node("Repeat"); + } + } GraphOperationMessage::BlendingFillSet { layer, fill } => { if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) { modify_inputs.blending_fill_set(fill); diff --git a/editor/src/messages/prelude.rs b/editor/src/messages/prelude.rs index 7a696f9ede..16c9256e23 100644 --- a/editor/src/messages/prelude.rs +++ b/editor/src/messages/prelude.rs @@ -41,6 +41,7 @@ pub use crate::messages::tool::tool_messages::fill_tool::{FillToolMessage, FillT pub use crate::messages::tool::tool_messages::freehand_tool::{FreehandToolMessage, FreehandToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::gradient_tool::{GradientToolMessage, GradientToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::navigate_tool::{NavigateToolMessage, NavigateToolMessageDiscriminant}; +pub use crate::messages::tool::tool_messages::operation_tool::{OperationToolMessage, OperationToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::path_tool::{PathToolMessage, PathToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::pen_tool::{PenToolMessage, PenToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::select_tool::{SelectToolMessage, SelectToolMessageDiscriminant}; diff --git a/editor/src/messages/tool/tool_message.rs b/editor/src/messages/tool/tool_message.rs index 70668a26df..62c2559fcb 100644 --- a/editor/src/messages/tool/tool_message.rs +++ b/editor/src/messages/tool/tool_message.rs @@ -22,6 +22,8 @@ pub enum ToolMessage { Fill(FillToolMessage), #[child] Gradient(GradientToolMessage), + #[child] + Operation(OperationToolMessage), #[child] Path(PathToolMessage), @@ -58,6 +60,7 @@ pub enum ToolMessage { ActivateToolEyedropper, ActivateToolFill, ActivateToolGradient, + ActivateToolOperation, // Vector tools ActivateToolPath, ActivateToolPen, diff --git a/editor/src/messages/tool/tool_messages/mod.rs b/editor/src/messages/tool/tool_messages/mod.rs index 6d29ad81a9..9ddc4a26ff 100644 --- a/editor/src/messages/tool/tool_messages/mod.rs +++ b/editor/src/messages/tool/tool_messages/mod.rs @@ -5,6 +5,7 @@ pub mod fill_tool; pub mod freehand_tool; pub mod gradient_tool; pub mod navigate_tool; +pub mod operation_tool; pub mod path_tool; pub mod pen_tool; pub mod select_tool; diff --git a/editor/src/messages/tool/tool_messages/operation_tool.rs b/editor/src/messages/tool/tool_messages/operation_tool.rs new file mode 100644 index 0000000000..e4c06616cb --- /dev/null +++ b/editor/src/messages/tool/tool_messages/operation_tool.rs @@ -0,0 +1,295 @@ +use super::tool_prelude::*; +use crate::consts::{DEFAULT_STROKE_WIDTH, DRAG_THRESHOLD, PATH_JOIN_THRESHOLD, SNAP_POINT_TOLERANCE}; +use crate::messages::input_mapper::utility_types::input_mouse::MouseKeys; +use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; +use crate::messages::portfolio::document::overlays::utility_functions::path_endpoint_overlays; +use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; +use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; +use crate::messages::tool::common_functionality::auto_panning::AutoPanning; +use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; +use crate::messages::tool::common_functionality::graph_modification_utils::{self, find_spline, merge_layers, merge_points}; +use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapManager, SnapTypeConfiguration, SnappedPoint}; +use crate::messages::tool::common_functionality::utility_functions::{closest_point, should_extend}; +use graph_craft::document::{NodeId, NodeInput}; +use graphene_std::Color; +use graphene_std::vector::{PointId, SegmentId, VectorModificationType}; + +#[derive(Default, ExtractField)] +pub struct OperationTool { + fsm_state: OperationToolFsmState, + tool_data: OperationToolData, + options: OperationOptions, +} + +pub struct OperationOptions { + line_weight: f64, + fill: ToolColorOptions, + stroke: ToolColorOptions, +} + +impl Default for OperationOptions { + fn default() -> Self { + Self { + line_weight: DEFAULT_STROKE_WIDTH, + fill: ToolColorOptions::new_none(), + stroke: ToolColorOptions::new_primary(), + } + } +} + +#[impl_message(Message, ToolMessage, Operation)] +#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +pub enum OperationToolMessage { + // Standard messages + Overlays { context: OverlayContext }, + Abort, + WorkingColorChanged, + + // Tool-specific messages + Confirm, + DragStart, + DragStop, + MergeEndpoints, + PointerMove, + PointerOutsideViewport, + Undo, + UpdateOptions { options: OperationOptionsUpdate }, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +enum OperationToolFsmState { + #[default] + Ready, + Drawing, +} + +#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +pub enum OperationOptionsUpdate { + FillColor(Option), + FillColorType(ToolColorType), + LineWeight(f64), + StrokeColor(Option), + StrokeColorType(ToolColorType), + WorkingColors(Option, Option), +} + +impl ToolMetadata for OperationTool { + fn icon_name(&self) -> String { + "GeneralOperationTool".into() + } + fn tooltip(&self) -> String { + "Operation Tool".into() + } + fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { + ToolType::Operation + } +} + +fn create_weight_widget(line_weight: f64) -> WidgetHolder { + NumberInput::new(Some(line_weight)) + .unit(" px") + .label("Weight") + .min(0.) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) + .on_update(|number_input: &NumberInput| { + OperationToolMessage::UpdateOptions { + options: OperationOptionsUpdate::LineWeight(number_input.value.unwrap()), + } + .into() + }) + .widget_holder() +} + +impl LayoutHolder for OperationTool { + fn layout(&self) -> Layout { + let mut widgets = self.options.fill.create_widgets( + "Fill", + true, + |_| { + OperationToolMessage::UpdateOptions { + options: OperationOptionsUpdate::FillColor(None), + } + .into() + }, + |color_type: ToolColorType| { + WidgetCallback::new(move |_| { + OperationToolMessage::UpdateOptions { + options: OperationOptionsUpdate::FillColorType(color_type.clone()), + } + .into() + }) + }, + |color: &ColorInput| { + OperationToolMessage::UpdateOptions { + options: OperationOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb())), + } + .into() + }, + ); + + widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); + + widgets.append(&mut self.options.stroke.create_widgets( + "Stroke", + true, + |_| { + OperationToolMessage::UpdateOptions { + options: OperationOptionsUpdate::StrokeColor(None), + } + .into() + }, + |color_type: ToolColorType| { + WidgetCallback::new(move |_| { + OperationToolMessage::UpdateOptions { + options: OperationOptionsUpdate::StrokeColorType(color_type.clone()), + } + .into() + }) + }, + |color: &ColorInput| { + OperationToolMessage::UpdateOptions { + options: OperationOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb())), + } + .into() + }, + )); + widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); + widgets.push(create_weight_widget(self.options.line_weight)); + + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) + } +} + +#[message_handler_data] +impl<'a> MessageHandler> for OperationTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, context: &mut ToolActionMessageContext<'a>) { + let ToolMessage::Operation(OperationToolMessage::UpdateOptions { options }) = message else { + self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true); + return; + }; + match options { + OperationOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, + OperationOptionsUpdate::FillColor(color) => { + self.options.fill.custom_color = color; + self.options.fill.color_type = ToolColorType::Custom; + } + OperationOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, + OperationOptionsUpdate::StrokeColor(color) => { + self.options.stroke.custom_color = color; + self.options.stroke.color_type = ToolColorType::Custom; + } + OperationOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, + OperationOptionsUpdate::WorkingColors(primary, secondary) => { + self.options.stroke.primary_working_color = primary; + self.options.stroke.secondary_working_color = secondary; + self.options.fill.primary_working_color = primary; + self.options.fill.secondary_working_color = secondary; + } + } + + self.send_layout(responses, LayoutTarget::ToolOptions); + } + + fn actions(&self) -> ActionList { + match self.fsm_state { + OperationToolFsmState::Ready => actions!(OperationToolMessageDiscriminant; + Undo, + DragStart, + DragStop, + PointerMove, + Confirm, + Abort, + ), + OperationToolFsmState::Drawing => actions!(OperationToolMessageDiscriminant; + DragStop, + PointerMove, + Confirm, + Abort, + ), + } + } +} + +impl ToolTransition for OperationTool { + fn event_to_message_map(&self) -> EventToMessageMap { + EventToMessageMap { + overlay_provider: Some(|context: OverlayContext| OperationToolMessage::Overlays { context }.into()), + tool_abort: Some(OperationToolMessage::Abort.into()), + working_color_changed: Some(OperationToolMessage::WorkingColorChanged.into()), + ..Default::default() + } + } +} + +#[derive(Clone, Debug, Default)] +struct OperationToolData {} + +impl OperationToolData { + fn cleanup(&mut self) {} +} + +impl Fsm for OperationToolFsmState { + type ToolData = OperationToolData; + type ToolOptions = OperationOptions; + + fn transition( + self, + event: ToolMessage, + tool_data: &mut Self::ToolData, + tool_action_data: &mut ToolActionMessageContext, + tool_options: &Self::ToolOptions, + responses: &mut VecDeque, + ) -> Self { + let ToolActionMessageContext { + document, + global_tool_data, + input, + shape_editor, + preferences, + .. + } = tool_action_data; + + let ToolMessage::Operation(event) = event else { return self }; + match (self, event) { + (_, OperationToolMessage::Overlays { context: mut overlay_context }) => self, + (OperationToolFsmState::Ready, OperationToolMessage::DragStart) => { + let Some(layer) = document.click(&input) else { return self }; + responses.add(GraphOperationMessage::RepeatSet { layer }); + responses.add(NodeGraphMessage::RunDocumentGraph); + OperationToolFsmState::Drawing + } + (OperationToolFsmState::Drawing, OperationToolMessage::DragStop) => OperationToolFsmState::Drawing, + (OperationToolFsmState::Drawing, OperationToolMessage::PointerMove) => OperationToolFsmState::Drawing, + (_, OperationToolMessage::PointerMove) => { + log::info!("hello"); + self + } + + (OperationToolFsmState::Drawing, OperationToolMessage::PointerOutsideViewport) => OperationToolFsmState::Drawing, + (state, OperationToolMessage::PointerOutsideViewport) => state, + (OperationToolFsmState::Drawing, OperationToolMessage::Abort) => OperationToolFsmState::Ready, + (_, OperationToolMessage::WorkingColorChanged) => self, + _ => self, + } + } + + fn update_hints(&self, responses: &mut VecDeque) { + let hint_data = match self { + OperationToolFsmState::Ready => HintData(vec![HintGroup(vec![ + HintInfo::mouse(MouseMotion::Lmb, "Draw Spline"), + HintInfo::keys([Key::Shift], "Append to Selected Layer").prepend_plus(), + ])]), + OperationToolFsmState::Drawing => HintData(vec![ + HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), + HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Extend Spline")]), + HintGroup(vec![HintInfo::keys([Key::Enter], "End Spline")]), + ]), + }; + + responses.add(FrontendMessage::UpdateInputHints { hint_data }); + } + + fn update_cursor(&self, responses: &mut VecDeque) { + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); + } +} diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index b26e14d620..07893a348a 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -345,6 +345,7 @@ pub enum ToolType { Eyedropper, Fill, Gradient, + Operation, // Vector tool group Path, @@ -397,6 +398,7 @@ fn list_tools_in_groups() -> Vec> { ToolAvailability::Available(Box::::default()), ToolAvailability::Available(Box::::default()), ToolAvailability::Available(Box::::default()), + ToolAvailability::Available(Box::::default()), ], vec![ // Vector tool group @@ -431,6 +433,7 @@ pub fn tool_message_to_tool_type(tool_message: &ToolMessage) -> ToolType { ToolMessage::Eyedropper(_) => ToolType::Eyedropper, ToolMessage::Fill(_) => ToolType::Fill, ToolMessage::Gradient(_) => ToolType::Gradient, + ToolMessage::Operation(_) => ToolType::Operation, // Vector tool group ToolMessage::Path(_) => ToolType::Path, @@ -460,6 +463,7 @@ pub fn tool_type_to_activate_tool_message(tool_type: ToolType) -> ToolMessageDis ToolType::Eyedropper => ToolMessageDiscriminant::ActivateToolEyedropper, ToolType::Fill => ToolMessageDiscriminant::ActivateToolFill, ToolType::Gradient => ToolMessageDiscriminant::ActivateToolGradient, + ToolType::Operation => ToolMessageDiscriminant::ActivateToolOperation, // Vector tool group ToolType::Path => ToolMessageDiscriminant::ActivateToolPath, From dabdef7bed21c3b9d74ba8221ffe8092541bd1e3 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Sun, 24 Aug 2025 22:02:20 +0530 Subject: [PATCH 2/8] multi-layer drag and repeat --- .../graph_operation_message.rs | 5 +- .../graph_operation_message_handler.rs | 4 +- .../document/graph_operation/utility_types.rs | 11 +++ .../shapes/shape_utility.rs | 12 +++ .../tool/tool_messages/operation_tool.rs | 78 ++++++++++++++++--- 5 files changed, 96 insertions(+), 14 deletions(-) diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs index 6251e74124..a304d0f4bc 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs @@ -22,8 +22,11 @@ pub enum GraphOperationMessage { layer: LayerNodeIdentifier, fill: Fill, }, - RepeatSet { + CircularRepeatSet { layer: LayerNodeIdentifier, + angle: f64, + radius: f64, + count: u32, }, BlendingFillSet { layer: LayerNodeIdentifier, diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs index fd7bcddb23..bb25241272 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs @@ -37,9 +37,9 @@ impl MessageHandler> for modify_inputs.fill_set(fill); } } - GraphOperationMessage::RepeatSet { layer } => { + GraphOperationMessage::CircularRepeatSet { layer, angle, radius, count } => { if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) { - modify_inputs.create_node("Repeat"); + modify_inputs.circular_repeat_set(angle, radius, count); } } GraphOperationMessage::BlendingFillSet { layer, fill } => { diff --git a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs index 4079fefd32..49a0674723 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -394,6 +394,17 @@ impl<'a> ModifyInputsContext<'a> { self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.dash_offset), false), true); } + pub fn circular_repeat_set(&mut self, angle: f64, radius: f64, count: u32) { + let Some(circular_repeat_node_id) = self.existing_node_id("Circular Repeat", true) else { return }; + + let input_connector = InputConnector::node(circular_repeat_node_id, graphene_std::vector::circular_repeat::AngleOffsetInput::INDEX); + self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(angle), false), true); + let input_connector = InputConnector::node(circular_repeat_node_id, graphene_std::vector::circular_repeat::RadiusInput::INDEX); + self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(radius), false), true); + let input_connector = InputConnector::node(circular_repeat_node_id, graphene_std::vector::circular_repeat::CountInput::INDEX); + self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::U32(count), false), false); + } + /// Update the transform value of the upstream Transform node based a change to its existing value and the given parent transform. /// A new Transform node is created if one does not exist, unless it would be given the identity transform. pub fn transform_change_with_parent(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, skip_rerender: bool) { diff --git a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs index 17c7f8e574..5dfad7d169 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -230,6 +230,18 @@ pub fn extract_star_parameters(layer: Option, document: &Do Some((sides, radius_1, radius_2)) } +pub fn extract_circular_repeat_parameters(layer: Option, document: &DocumentMessageHandler) -> Option<(f64, f64, u32)> { + let node_inputs = NodeGraphLayer::new(layer?, &document.network_interface).find_node_inputs("Circular Repeat")?; + + let (Some(&TaggedValue::F64(angle)), Some(&TaggedValue::F64(radius)), Some(&TaggedValue::U32(count))) = + (node_inputs.get(1)?.as_value(), node_inputs.get(2)?.as_value(), node_inputs.get(3)?.as_value()) + else { + return None; + }; + + Some((angle, radius, count)) +} + /// Extract the node input values of Polygon. /// Returns an option of (sides, radius). pub fn extract_polygon_parameters(layer: Option, document: &DocumentMessageHandler) -> Option<(u32, f64)> { diff --git a/editor/src/messages/tool/tool_messages/operation_tool.rs b/editor/src/messages/tool/tool_messages/operation_tool.rs index e4c06616cb..5228edb6aa 100644 --- a/editor/src/messages/tool/tool_messages/operation_tool.rs +++ b/editor/src/messages/tool/tool_messages/operation_tool.rs @@ -8,6 +8,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::graph_modification_utils::{self, find_spline, merge_layers, merge_points}; +use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_circular_repeat_parameters, extract_star_parameters}; use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapManager, SnapTypeConfiguration, SnappedPoint}; use crate::messages::tool::common_functionality::utility_functions::{closest_point, should_extend}; use graph_craft::document::{NodeId, NodeInput}; @@ -49,7 +50,6 @@ pub enum OperationToolMessage { Confirm, DragStart, DragStop, - MergeEndpoints, PointerMove, PointerOutsideViewport, Undo, @@ -222,10 +222,19 @@ impl ToolTransition for OperationTool { } #[derive(Clone, Debug, Default)] -struct OperationToolData {} +struct OperationToolData { + drag_start: DVec2, + clicked_layer: LayerNodeIdentifier, + layers_dragging: Vec<(LayerNodeIdentifier, f64)>, + initial_center: DVec2, +} impl OperationToolData { - fn cleanup(&mut self) {} + fn cleanup(&mut self) { + self.layers_dragging.clear(); + } + + fn modify_circular_repeat(&mut self) {} } impl Fsm for OperationToolFsmState { @@ -251,19 +260,66 @@ impl Fsm for OperationToolFsmState { let ToolMessage::Operation(event) = event else { return self }; match (self, event) { - (_, OperationToolMessage::Overlays { context: mut overlay_context }) => self, + (_, OperationToolMessage::Overlays { context: mut overlay_context }) => {} (OperationToolFsmState::Ready, OperationToolMessage::DragStart) => { + let selected_layers = document + .network_interface + .selected_nodes() + .selected_layers(document.metadata()) + .collect::>(); let Some(layer) = document.click(&input) else { return self }; - responses.add(GraphOperationMessage::RepeatSet { layer }); - responses.add(NodeGraphMessage::RunDocumentGraph); + let viewport = document.metadata().transform_to_viewport(layer); + + if selected_layers.contains(&layer) { + // store all + tool_data.layers_dragging = selected_layers + .iter() + .map(|layer| { + let (_angle_offset, radius, _count) = extract_circular_repeat_parameters(Some(*layer), document).unwrap_or((0.0, 0.0, 6)); + (*layer, radius) + }) + .collect::>(); + } else { + // deselect all the layer and store the clicked layer for repeat and dragging + + responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }); + let (_angle_offset, radius, _count) = extract_circular_repeat_parameters(Some(layer), document).unwrap_or((0.0, 0.0, 6)); + tool_data.layers_dragging = vec![(layer, radius)]; + } + tool_data.drag_start = input.mouse.position; + tool_data.clicked_layer = layer; + tool_data.initial_center = viewport.transform_point2(DVec2::ZERO); + OperationToolFsmState::Drawing } - (OperationToolFsmState::Drawing, OperationToolMessage::DragStop) => OperationToolFsmState::Drawing, - (OperationToolFsmState::Drawing, OperationToolMessage::PointerMove) => OperationToolFsmState::Drawing, - (_, OperationToolMessage::PointerMove) => { - log::info!("hello"); - self + (OperationToolFsmState::Drawing, OperationToolMessage::DragStop) => { + tool_data.cleanup(); + OperationToolFsmState::Ready + } + (OperationToolFsmState::Drawing, OperationToolMessage::PointerMove) => { + // Don't add the repeat node unless dragging more that 5 px + if tool_data.drag_start.distance(input.mouse.position) < 5. { + return self; + }; + + let viewport = document.metadata().transform_to_viewport(tool_data.clicked_layer); + let center = viewport.transform_point2(DVec2::ZERO); + let sign = (input.mouse.position - tool_data.initial_center).dot(tool_data.drag_start - tool_data.initial_center).signum(); + let delta = viewport.inverse().transform_vector2(input.mouse.position - tool_data.drag_start).length() * sign; + log::info!("{:?}", delta); + for (layer, initial_radius) in &tool_data.layers_dragging { + responses.add(GraphOperationMessage::CircularRepeatSet { + layer: *layer, + angle: 0., + radius: initial_radius + delta, + count: 6, + }); + } + responses.add(NodeGraphMessage::RunDocumentGraph); + + OperationToolFsmState::Drawing } + (_, OperationToolMessage::PointerMove) => self, (OperationToolFsmState::Drawing, OperationToolMessage::PointerOutsideViewport) => OperationToolFsmState::Drawing, (state, OperationToolMessage::PointerOutsideViewport) => state, From 40db3238f3c1ed427de2e0c0465d6496f8924e74 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Tue, 26 Aug 2025 02:39:33 +0530 Subject: [PATCH 3/8] multi layer click and repeat --- .../tool/tool_messages/operation_tool.rs | 125 ++++++++++++------ 1 file changed, 86 insertions(+), 39 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/operation_tool.rs b/editor/src/messages/tool/tool_messages/operation_tool.rs index 5228edb6aa..63fb65d9ad 100644 --- a/editor/src/messages/tool/tool_messages/operation_tool.rs +++ b/editor/src/messages/tool/tool_messages/operation_tool.rs @@ -1,19 +1,10 @@ use super::tool_prelude::*; -use crate::consts::{DEFAULT_STROKE_WIDTH, DRAG_THRESHOLD, PATH_JOIN_THRESHOLD, SNAP_POINT_TOLERANCE}; -use crate::messages::input_mapper::utility_types::input_mouse::MouseKeys; -use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; -use crate::messages::portfolio::document::overlays::utility_functions::path_endpoint_overlays; +use crate::consts::DEFAULT_STROKE_WIDTH; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; -use crate::messages::tool::common_functionality::graph_modification_utils::{self, find_spline, merge_layers, merge_points}; -use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_circular_repeat_parameters, extract_star_parameters}; -use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapManager, SnapTypeConfiguration, SnappedPoint}; -use crate::messages::tool::common_functionality::utility_functions::{closest_point, should_extend}; -use graph_craft::document::{NodeId, NodeInput}; +use crate::messages::tool::common_functionality::shapes::shape_utility::extract_circular_repeat_parameters; use graphene_std::Color; -use graphene_std::vector::{PointId, SegmentId, VectorModificationType}; #[derive(Default, ExtractField)] pub struct OperationTool { @@ -224,7 +215,7 @@ impl ToolTransition for OperationTool { #[derive(Clone, Debug, Default)] struct OperationToolData { drag_start: DVec2, - clicked_layer: LayerNodeIdentifier, + clicked_layer_radius: (LayerNodeIdentifier, f64), layers_dragging: Vec<(LayerNodeIdentifier, f64)>, initial_center: DVec2, } @@ -233,8 +224,6 @@ impl OperationToolData { fn cleanup(&mut self) { self.layers_dragging.clear(); } - - fn modify_circular_repeat(&mut self) {} } impl Fsm for OperationToolFsmState { @@ -246,54 +235,95 @@ impl Fsm for OperationToolFsmState { event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionMessageContext, - tool_options: &Self::ToolOptions, + _tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { - let ToolActionMessageContext { - document, - global_tool_data, - input, - shape_editor, - preferences, - .. - } = tool_action_data; + let ToolActionMessageContext { document, input, .. } = tool_action_data; let ToolMessage::Operation(event) = event else { return self }; match (self, event) { - (_, OperationToolMessage::Overlays { context: mut overlay_context }) => {} + (_, OperationToolMessage::Overlays { context: mut overlay_context }) => { + match self { + OperationToolFsmState::Ready => { + for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) { + let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue }; + let viewport = document.metadata().transform_to_viewport(layer); + let center = viewport.transform_point2(DVec2::ZERO); + if center.distance(input.mouse.position) < 5. { + overlay_context.circle(center, 3., None, None); + } + + overlay_context.outline_vector(&vector, viewport); + } + if let Some(layer) = document.click(&input) { + let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return self }; + let viewport = document.metadata().transform_to_viewport(layer); + let center = viewport.transform_point2(DVec2::ZERO); + if center.distance(input.mouse.position) < 5. { + overlay_context.circle(center, 3., None, None); + } + + overlay_context.outline_vector(&vector, viewport); + } + } + _ => { + for layer in tool_data.layers_dragging.iter().map(|(l, _)| l) { + let Some(vector) = document.network_interface.compute_modified_vector(*layer) else { continue }; + let viewport = document.metadata().transform_to_viewport(*layer); + + overlay_context.outline_vector(&vector, viewport); + } + } + } + + self + } (OperationToolFsmState::Ready, OperationToolMessage::DragStart) => { let selected_layers = document .network_interface .selected_nodes() .selected_layers(document.metadata()) .collect::>(); - let Some(layer) = document.click(&input) else { return self }; - let viewport = document.metadata().transform_to_viewport(layer); + let Some(clicked_layer) = document.click(&input) else { return self }; + responses.add(DocumentMessage::StartTransaction); + let viewport = document.metadata().transform_to_viewport(clicked_layer); + let center = viewport.transform_point2(DVec2::ZERO); - if selected_layers.contains(&layer) { + if center.distance(input.mouse.position) > 5. { + return self; + }; + + if selected_layers.contains(&clicked_layer) { // store all tool_data.layers_dragging = selected_layers .iter() .map(|layer| { let (_angle_offset, radius, _count) = extract_circular_repeat_parameters(Some(*layer), document).unwrap_or((0.0, 0.0, 6)); + if *layer == clicked_layer { + tool_data.clicked_layer_radius = (*layer, radius) + } (*layer, radius) }) .collect::>(); } else { // deselect all the layer and store the clicked layer for repeat and dragging - responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }); - let (_angle_offset, radius, _count) = extract_circular_repeat_parameters(Some(layer), document).unwrap_or((0.0, 0.0, 6)); - tool_data.layers_dragging = vec![(layer, radius)]; + responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![clicked_layer.to_node()] }); + let (_angle_offset, radius, _count) = extract_circular_repeat_parameters(Some(clicked_layer), document).unwrap_or((0.0, 0.0, 6)); + tool_data.clicked_layer_radius = (clicked_layer, radius); + tool_data.layers_dragging = vec![(clicked_layer, radius)]; } tool_data.drag_start = input.mouse.position; - tool_data.clicked_layer = layer; tool_data.initial_center = viewport.transform_point2(DVec2::ZERO); OperationToolFsmState::Drawing } (OperationToolFsmState::Drawing, OperationToolMessage::DragStop) => { + if tool_data.drag_start.distance(input.mouse.position) < 5. { + responses.add(DocumentMessage::AbortTransaction); + }; tool_data.cleanup(); + responses.add(DocumentMessage::EndTransaction); OperationToolFsmState::Ready } (OperationToolFsmState::Drawing, OperationToolMessage::PointerMove) => { @@ -302,16 +332,27 @@ impl Fsm for OperationToolFsmState { return self; }; - let viewport = document.metadata().transform_to_viewport(tool_data.clicked_layer); - let center = viewport.transform_point2(DVec2::ZERO); - let sign = (input.mouse.position - tool_data.initial_center).dot(tool_data.drag_start - tool_data.initial_center).signum(); - let delta = viewport.inverse().transform_vector2(input.mouse.position - tool_data.drag_start).length() * sign; - log::info!("{:?}", delta); + let (_clicked_layer, clicked_radius) = tool_data.clicked_layer_radius; + let viewport = document.metadata().transform_to_viewport(tool_data.clicked_layer_radius.0); + let sign = (input.mouse.position - tool_data.initial_center).dot(viewport.transform_vector2(DVec2::Y)).signum(); + let delta = document + .metadata() + .downstream_transform_to_viewport(tool_data.clicked_layer_radius.0) + .inverse() + .transform_vector2(input.mouse.position - tool_data.initial_center) + .length() * sign; + for (layer, initial_radius) in &tool_data.layers_dragging { + let new_radius = if initial_radius.signum() == clicked_radius.signum() { + *initial_radius + delta + } else { + *initial_radius + delta.signum() * -1. * delta.abs() + }; + responses.add(GraphOperationMessage::CircularRepeatSet { layer: *layer, angle: 0., - radius: initial_radius + delta, + radius: new_radius, count: 6, }); } @@ -319,11 +360,17 @@ impl Fsm for OperationToolFsmState { OperationToolFsmState::Drawing } - (_, OperationToolMessage::PointerMove) => self, + (_, OperationToolMessage::PointerMove) => { + responses.add(OverlaysMessage::Draw); + self + } (OperationToolFsmState::Drawing, OperationToolMessage::PointerOutsideViewport) => OperationToolFsmState::Drawing, (state, OperationToolMessage::PointerOutsideViewport) => state, - (OperationToolFsmState::Drawing, OperationToolMessage::Abort) => OperationToolFsmState::Ready, + (OperationToolFsmState::Drawing, OperationToolMessage::Abort) => { + responses.add(DocumentMessage::AbortTransaction); + OperationToolFsmState::Ready + } (_, OperationToolMessage::WorkingColorChanged) => self, _ => self, } From 2aa95d6ee87b7870f47739a480a5a08adf139ca5 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Tue, 26 Aug 2025 12:20:39 +0530 Subject: [PATCH 4/8] added operation type widget --- .../tool/tool_messages/operation_tool.rs | 122 ++++-------------- 1 file changed, 27 insertions(+), 95 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/operation_tool.rs b/editor/src/messages/tool/tool_messages/operation_tool.rs index 63fb65d9ad..e96f2fc442 100644 --- a/editor/src/messages/tool/tool_messages/operation_tool.rs +++ b/editor/src/messages/tool/tool_messages/operation_tool.rs @@ -1,10 +1,7 @@ use super::tool_prelude::*; -use crate::consts::DEFAULT_STROKE_WIDTH; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::shapes::shape_utility::extract_circular_repeat_parameters; -use graphene_std::Color; #[derive(Default, ExtractField)] pub struct OperationTool { @@ -14,17 +11,13 @@ pub struct OperationTool { } pub struct OperationOptions { - line_weight: f64, - fill: ToolColorOptions, - stroke: ToolColorOptions, + operation_type: OperationType, } impl Default for OperationOptions { fn default() -> Self { Self { - line_weight: DEFAULT_STROKE_WIDTH, - fill: ToolColorOptions::new_none(), - stroke: ToolColorOptions::new_primary(), + operation_type: OperationType::CircularRepeat, } } } @@ -56,12 +49,7 @@ enum OperationToolFsmState { #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] pub enum OperationOptionsUpdate { - FillColor(Option), - FillColorType(ToolColorType), - LineWeight(f64), - StrokeColor(Option), - StrokeColorType(ToolColorType), - WorkingColors(Option, Option), + OperationType(OperationType), } impl ToolMetadata for OperationTool { @@ -76,76 +64,29 @@ impl ToolMetadata for OperationTool { } } -fn create_weight_widget(line_weight: f64) -> WidgetHolder { - NumberInput::new(Some(line_weight)) - .unit(" px") - .label("Weight") - .min(0.) - .max((1_u64 << f64::MANTISSA_DIGITS) as f64) - .on_update(|number_input: &NumberInput| { +fn create_operation_type_option_widget(operation_type: OperationType) -> WidgetHolder { + let entries = vec![vec![ + MenuListEntry::new("Repeat").label("Repeat").on_commit(move |_| { OperationToolMessage::UpdateOptions { - options: OperationOptionsUpdate::LineWeight(number_input.value.unwrap()), + options: OperationOptionsUpdate::OperationType(OperationType::Repeat), } .into() - }) - .widget_holder() + }), + MenuListEntry::new("Repeat").label("Circular Repeat").on_commit(move |_| { + OperationToolMessage::UpdateOptions { + options: OperationOptionsUpdate::OperationType(OperationType::CircularRepeat), + } + .into() + }), + ]]; + DropdownInput::new(entries).selected_index(Some(operation_type as u32)).widget_holder() } impl LayoutHolder for OperationTool { fn layout(&self) -> Layout { - let mut widgets = self.options.fill.create_widgets( - "Fill", - true, - |_| { - OperationToolMessage::UpdateOptions { - options: OperationOptionsUpdate::FillColor(None), - } - .into() - }, - |color_type: ToolColorType| { - WidgetCallback::new(move |_| { - OperationToolMessage::UpdateOptions { - options: OperationOptionsUpdate::FillColorType(color_type.clone()), - } - .into() - }) - }, - |color: &ColorInput| { - OperationToolMessage::UpdateOptions { - options: OperationOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb())), - } - .into() - }, - ); - - widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); - - widgets.append(&mut self.options.stroke.create_widgets( - "Stroke", - true, - |_| { - OperationToolMessage::UpdateOptions { - options: OperationOptionsUpdate::StrokeColor(None), - } - .into() - }, - |color_type: ToolColorType| { - WidgetCallback::new(move |_| { - OperationToolMessage::UpdateOptions { - options: OperationOptionsUpdate::StrokeColorType(color_type.clone()), - } - .into() - }) - }, - |color: &ColorInput| { - OperationToolMessage::UpdateOptions { - options: OperationOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb())), - } - .into() - }, - )); - widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); - widgets.push(create_weight_widget(self.options.line_weight)); + let mut widgets = vec![]; + + widgets.push(create_operation_type_option_widget(self.options.operation_type)); Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) } @@ -159,23 +100,7 @@ impl<'a> MessageHandler> for Oper return; }; match options { - OperationOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, - OperationOptionsUpdate::FillColor(color) => { - self.options.fill.custom_color = color; - self.options.fill.color_type = ToolColorType::Custom; - } - OperationOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, - OperationOptionsUpdate::StrokeColor(color) => { - self.options.stroke.custom_color = color; - self.options.stroke.color_type = ToolColorType::Custom; - } - OperationOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, - OperationOptionsUpdate::WorkingColors(primary, secondary) => { - self.options.stroke.primary_working_color = primary; - self.options.stroke.secondary_working_color = secondary; - self.options.fill.primary_working_color = primary; - self.options.fill.secondary_working_color = secondary; - } + OperationOptionsUpdate::OperationType(operation_type) => self.options.operation_type = operation_type, } self.send_layout(responses, LayoutTarget::ToolOptions); @@ -396,3 +321,10 @@ impl Fsm for OperationToolFsmState { responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); } } + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)] +pub enum OperationType { + #[default] + CircularRepeat = 0, + Repeat = 1, +} From 26984093150114d936d4079194fb30295b46aaa9 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Wed, 27 Aug 2025 19:45:06 +0530 Subject: [PATCH 5/8] separate operation into different files --- .../messages/input_mapper/input_mappings.rs | 2 +- .../messages/tool/common_functionality/mod.rs | 1 + .../operations/circular_repeat.rs | 143 ++++++++++++++++++ .../common_functionality/operations/mod.rs | 1 + .../tool/tool_messages/operation_tool.rs | 142 ++++------------- 5 files changed, 177 insertions(+), 112 deletions(-) create mode 100644 editor/src/messages/tool/common_functionality/operations/circular_repeat.rs create mode 100644 editor/src/messages/tool/common_functionality/operations/mod.rs diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index 87c146d4f8..9cc018ba23 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -176,7 +176,7 @@ pub fn input_mappings() -> Mapping { entry!(KeyDown(MouseLeft); action_dispatch=OperationToolMessage::DragStart), entry!(KeyUp(MouseLeft); action_dispatch=OperationToolMessage::DragStop), entry!(KeyDown(MouseRight); action_dispatch=OperationToolMessage::Confirm), - entry!(KeyDown(Escape); action_dispatch=OperationToolMessage::Confirm), + entry!(KeyDown(Escape); action_dispatch=OperationToolMessage::Abort), entry!(KeyDown(Enter); action_dispatch=OperationToolMessage::Confirm), // // ShapeToolMessage diff --git a/editor/src/messages/tool/common_functionality/mod.rs b/editor/src/messages/tool/common_functionality/mod.rs index ca3e629e19..5154848e9f 100644 --- a/editor/src/messages/tool/common_functionality/mod.rs +++ b/editor/src/messages/tool/common_functionality/mod.rs @@ -4,6 +4,7 @@ pub mod compass_rose; pub mod gizmos; pub mod graph_modification_utils; pub mod measure; +pub mod operations; pub mod pivot; pub mod resize; pub mod shape_editor; diff --git a/editor/src/messages/tool/common_functionality/operations/circular_repeat.rs b/editor/src/messages/tool/common_functionality/operations/circular_repeat.rs new file mode 100644 index 0000000000..2ba25d1af8 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/operations/circular_repeat.rs @@ -0,0 +1,143 @@ +use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; +use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; +use crate::messages::tool::common_functionality::shapes::shape_utility::extract_circular_repeat_parameters; +use crate::messages::tool::tool_messages::operation_tool::{OperationToolData, OperationToolFsmState}; +use crate::messages::tool::tool_messages::tool_prelude::*; +use std::collections::VecDeque; + +#[derive(Default)] +pub struct CircularRepeatOperation; + +#[derive(Clone, Debug, Default)] +pub struct CircularRepeatOperationData { + clicked_layer_radius: (LayerNodeIdentifier, f64), + layers_dragging: Vec<(LayerNodeIdentifier, f64)>, + initial_center: DVec2, +} + +impl CircularRepeatOperation { + pub fn create_node(tool_data: &mut OperationToolData, document: &DocumentMessageHandler, responses: &mut VecDeque, input: &InputPreprocessorMessageHandler) { + let selected_layers = document + .network_interface + .selected_nodes() + .selected_layers(document.metadata()) + .collect::>(); + + let Some(clicked_layer) = document.click(&input) else { return }; + responses.add(DocumentMessage::StartTransaction); + let viewport = document.metadata().transform_to_viewport(clicked_layer); + let center = viewport.transform_point2(DVec2::ZERO); + + // Only activate the operation if the click is close enough to the repeat center + if center.distance(input.mouse.position) > 5. { + return; + }; + + // If the clicked layer is part of the current selection, apply the operation to all selected layers + if selected_layers.contains(&clicked_layer) { + tool_data.circular_operation_data.layers_dragging = selected_layers + .iter() + .map(|layer| { + let (_angle_offset, radius, _count) = extract_circular_repeat_parameters(Some(*layer), document).unwrap_or((0.0, 0.0, 6)); + if *layer == clicked_layer { + tool_data.circular_operation_data.clicked_layer_radius = (*layer, radius) + } + (*layer, radius) + }) + .collect::>(); + } else { + // If the clicked layer is not in the selection, deselect all and only apply the operation to the clicked layer + responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![clicked_layer.to_node()] }); + + let (_angle_offset, radius, _count) = extract_circular_repeat_parameters(Some(clicked_layer), document).unwrap_or((0.0, 0.0, 6)); + + tool_data.circular_operation_data.clicked_layer_radius = (clicked_layer, radius); + tool_data.circular_operation_data.layers_dragging = vec![(clicked_layer, radius)]; + } + tool_data.drag_start = input.mouse.position; + tool_data.circular_operation_data.initial_center = viewport.transform_point2(DVec2::ZERO); + } + + pub fn update_shape(tool_data: &mut OperationToolData, document: &DocumentMessageHandler, responses: &mut VecDeque, input: &InputPreprocessorMessageHandler) { + let (_clicked_layer, clicked_radius) = tool_data.circular_operation_data.clicked_layer_radius; + + let viewport = document.metadata().transform_to_viewport(tool_data.circular_operation_data.clicked_layer_radius.0); + let sign = (input.mouse.position - tool_data.circular_operation_data.initial_center) + .dot(viewport.transform_vector2(DVec2::Y)) + .signum(); + + // Compute mouse movement in local space, ignoring the layer’s own transform + let delta = document + .metadata() + .downstream_transform_to_viewport(tool_data.circular_operation_data.clicked_layer_radius.0) + .inverse() + .transform_vector2(input.mouse.position - tool_data.circular_operation_data.initial_center) + .length() * sign; + + for (layer, initial_radius) in &tool_data.circular_operation_data.layers_dragging { + // If the layer’s sign differs from the clicked layer, invert delta to preserve consistent in/out dragging behavior + let new_radius = if initial_radius.signum() == clicked_radius.signum() { + *initial_radius + delta + } else { + *initial_radius + delta.signum() * -1. * delta.abs() + }; + + responses.add(GraphOperationMessage::CircularRepeatSet { + layer: *layer, + angle: 0., + radius: new_radius, + count: 6, + }); + } + + responses.add(NodeGraphMessage::RunDocumentGraph); + } + + pub fn overlays( + tool_state: &OperationToolFsmState, + tool_data: &mut OperationToolData, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + overlay_context: &mut OverlayContext, + ) { + match tool_state { + OperationToolFsmState::Ready => { + // Draw overlays for all selected layers + for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) { + Self::draw_layer_overlay(layer, document, input, overlay_context) + } + + // Also highlight the hovered layer if it’s not selected + if let Some(layer) = document.click(&input) { + Self::draw_layer_overlay(layer, document, input, overlay_context); + } + } + _ => { + // While dragging, only draw overlays for the layers being modified + for layer in tool_data.circular_operation_data.layers_dragging.iter().map(|(l, _)| l) { + let Some(vector) = document.network_interface.compute_modified_vector(*layer) else { continue }; + let viewport = document.metadata().transform_to_viewport(*layer); + + overlay_context.outline_vector(&vector, viewport); + } + } + } + } + + fn draw_layer_overlay(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, overlay_context: &mut OverlayContext) { + if let Some(vector) = document.network_interface.compute_modified_vector(layer) { + let viewport = document.metadata().transform_to_viewport(layer); + let center = viewport.transform_point2(DVec2::ZERO); + // Show a small circle if the mouse is near the repeat center + if center.distance(input.mouse.position) < 5. { + overlay_context.circle(center, 3., None, None); + } + overlay_context.outline_vector(&vector, viewport); + } + } + + pub fn cleanup(tool_data: &mut OperationToolData) { + // Clear stored drag state at the end of the operation + tool_data.circular_operation_data.layers_dragging.clear(); + } +} diff --git a/editor/src/messages/tool/common_functionality/operations/mod.rs b/editor/src/messages/tool/common_functionality/operations/mod.rs new file mode 100644 index 0000000000..392f6fa3ae --- /dev/null +++ b/editor/src/messages/tool/common_functionality/operations/mod.rs @@ -0,0 +1 @@ +pub mod circular_repeat; diff --git a/editor/src/messages/tool/tool_messages/operation_tool.rs b/editor/src/messages/tool/tool_messages/operation_tool.rs index e96f2fc442..9db24457d0 100644 --- a/editor/src/messages/tool/tool_messages/operation_tool.rs +++ b/editor/src/messages/tool/tool_messages/operation_tool.rs @@ -1,7 +1,13 @@ use super::tool_prelude::*; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; -use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::tool::common_functionality::shapes::shape_utility::extract_circular_repeat_parameters; +use crate::messages::tool::common_functionality::operations::circular_repeat::{CircularRepeatOperation, CircularRepeatOperationData}; + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)] +pub enum OperationType { + #[default] + CircularRepeat = 0, + Repeat, +} #[derive(Default, ExtractField)] pub struct OperationTool { @@ -41,7 +47,7 @@ pub enum OperationToolMessage { } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -enum OperationToolFsmState { +pub enum OperationToolFsmState { #[default] Ready, Drawing, @@ -66,15 +72,15 @@ impl ToolMetadata for OperationTool { fn create_operation_type_option_widget(operation_type: OperationType) -> WidgetHolder { let entries = vec![vec![ - MenuListEntry::new("Repeat").label("Repeat").on_commit(move |_| { + MenuListEntry::new("Circular Repeat").label("Circular Repeat").on_commit(move |_| { OperationToolMessage::UpdateOptions { - options: OperationOptionsUpdate::OperationType(OperationType::Repeat), + options: OperationOptionsUpdate::OperationType(OperationType::CircularRepeat), } .into() }), - MenuListEntry::new("Repeat").label("Circular Repeat").on_commit(move |_| { + MenuListEntry::new("Repeat").label("Repeat").on_commit(move |_| { OperationToolMessage::UpdateOptions { - options: OperationOptionsUpdate::OperationType(OperationType::CircularRepeat), + options: OperationOptionsUpdate::OperationType(OperationType::Repeat), } .into() }), @@ -138,16 +144,14 @@ impl ToolTransition for OperationTool { } #[derive(Clone, Debug, Default)] -struct OperationToolData { - drag_start: DVec2, - clicked_layer_radius: (LayerNodeIdentifier, f64), - layers_dragging: Vec<(LayerNodeIdentifier, f64)>, - initial_center: DVec2, +pub struct OperationToolData { + pub drag_start: DVec2, + pub circular_operation_data: CircularRepeatOperationData, } impl OperationToolData { fn cleanup(&mut self) { - self.layers_dragging.clear(); + CircularRepeatOperation::cleanup(self); } } @@ -160,7 +164,7 @@ impl Fsm for OperationToolFsmState { event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionMessageContext, - _tool_options: &Self::ToolOptions, + tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { let ToolActionMessageContext { document, input, .. } = tool_action_data; @@ -168,78 +172,20 @@ impl Fsm for OperationToolFsmState { let ToolMessage::Operation(event) = event else { return self }; match (self, event) { (_, OperationToolMessage::Overlays { context: mut overlay_context }) => { - match self { - OperationToolFsmState::Ready => { - for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) { - let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue }; - let viewport = document.metadata().transform_to_viewport(layer); - let center = viewport.transform_point2(DVec2::ZERO); - if center.distance(input.mouse.position) < 5. { - overlay_context.circle(center, 3., None, None); - } - - overlay_context.outline_vector(&vector, viewport); - } - if let Some(layer) = document.click(&input) { - let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return self }; - let viewport = document.metadata().transform_to_viewport(layer); - let center = viewport.transform_point2(DVec2::ZERO); - if center.distance(input.mouse.position) < 5. { - overlay_context.circle(center, 3., None, None); - } - - overlay_context.outline_vector(&vector, viewport); - } - } - _ => { - for layer in tool_data.layers_dragging.iter().map(|(l, _)| l) { - let Some(vector) = document.network_interface.compute_modified_vector(*layer) else { continue }; - let viewport = document.metadata().transform_to_viewport(*layer); - - overlay_context.outline_vector(&vector, viewport); - } - } + match tool_options.operation_type { + OperationType::CircularRepeat => CircularRepeatOperation::overlays(&self, tool_data, document, input, &mut overlay_context), + _ => {} } self } (OperationToolFsmState::Ready, OperationToolMessage::DragStart) => { - let selected_layers = document - .network_interface - .selected_nodes() - .selected_layers(document.metadata()) - .collect::>(); - let Some(clicked_layer) = document.click(&input) else { return self }; - responses.add(DocumentMessage::StartTransaction); - let viewport = document.metadata().transform_to_viewport(clicked_layer); - let center = viewport.transform_point2(DVec2::ZERO); - - if center.distance(input.mouse.position) > 5. { - return self; - }; - - if selected_layers.contains(&clicked_layer) { - // store all - tool_data.layers_dragging = selected_layers - .iter() - .map(|layer| { - let (_angle_offset, radius, _count) = extract_circular_repeat_parameters(Some(*layer), document).unwrap_or((0.0, 0.0, 6)); - if *layer == clicked_layer { - tool_data.clicked_layer_radius = (*layer, radius) - } - (*layer, radius) - }) - .collect::>(); - } else { - // deselect all the layer and store the clicked layer for repeat and dragging - - responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![clicked_layer.to_node()] }); - let (_angle_offset, radius, _count) = extract_circular_repeat_parameters(Some(clicked_layer), document).unwrap_or((0.0, 0.0, 6)); - tool_data.clicked_layer_radius = (clicked_layer, radius); - tool_data.layers_dragging = vec![(clicked_layer, radius)]; + match tool_options.operation_type { + OperationType::CircularRepeat => { + CircularRepeatOperation::create_node(tool_data, document, responses, input); + } + OperationType::Repeat => {} } - tool_data.drag_start = input.mouse.position; - tool_data.initial_center = viewport.transform_point2(DVec2::ZERO); OperationToolFsmState::Drawing } @@ -257,31 +203,12 @@ impl Fsm for OperationToolFsmState { return self; }; - let (_clicked_layer, clicked_radius) = tool_data.clicked_layer_radius; - let viewport = document.metadata().transform_to_viewport(tool_data.clicked_layer_radius.0); - let sign = (input.mouse.position - tool_data.initial_center).dot(viewport.transform_vector2(DVec2::Y)).signum(); - let delta = document - .metadata() - .downstream_transform_to_viewport(tool_data.clicked_layer_radius.0) - .inverse() - .transform_vector2(input.mouse.position - tool_data.initial_center) - .length() * sign; - - for (layer, initial_radius) in &tool_data.layers_dragging { - let new_radius = if initial_radius.signum() == clicked_radius.signum() { - *initial_radius + delta - } else { - *initial_radius + delta.signum() * -1. * delta.abs() - }; - - responses.add(GraphOperationMessage::CircularRepeatSet { - layer: *layer, - angle: 0., - radius: new_radius, - count: 6, - }); + match tool_options.operation_type { + OperationType::CircularRepeat => { + CircularRepeatOperation::update_shape(tool_data, document, responses, input); + } + OperationType::Repeat => {} } - responses.add(NodeGraphMessage::RunDocumentGraph); OperationToolFsmState::Drawing } @@ -321,10 +248,3 @@ impl Fsm for OperationToolFsmState { responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); } } - -#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)] -pub enum OperationType { - #[default] - CircularRepeat = 0, - Repeat = 1, -} From 9ac766b82419177d7543a54beb647819181057be Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Wed, 27 Aug 2025 21:52:12 +0530 Subject: [PATCH 6/8] add increase count when dragging --- .../messages/input_mapper/input_mappings.rs | 4 +++ .../operations/circular_repeat.rs | 27 +++++++++++++++++-- .../shapes/shape_utility.rs | 2 ++ .../tool/tool_messages/operation_tool.rs | 18 +++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index 9cc018ba23..a8276f93a4 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -178,6 +178,10 @@ pub fn input_mappings() -> Mapping { entry!(KeyDown(MouseRight); action_dispatch=OperationToolMessage::Confirm), entry!(KeyDown(Escape); action_dispatch=OperationToolMessage::Abort), entry!(KeyDown(Enter); action_dispatch=OperationToolMessage::Confirm), + entry!(KeyDown(ArrowUp);action_dispatch=OperationToolMessage::IncreaseCount), + entry!(KeyDown(ArrowDown);action_dispatch=OperationToolMessage::DecreaseCount), + entry!(KeyDown(BracketRight);action_dispatch=OperationToolMessage::IncreaseCount), + entry!(KeyDown(BracketLeft);action_dispatch=OperationToolMessage::DecreaseCount), // // ShapeToolMessage entry!(KeyDown(MouseLeft); action_dispatch=ShapeToolMessage::DragStart), diff --git a/editor/src/messages/tool/common_functionality/operations/circular_repeat.rs b/editor/src/messages/tool/common_functionality/operations/circular_repeat.rs index 2ba25d1af8..19ed343c3c 100644 --- a/editor/src/messages/tool/common_functionality/operations/circular_repeat.rs +++ b/editor/src/messages/tool/common_functionality/operations/circular_repeat.rs @@ -76,6 +76,11 @@ impl CircularRepeatOperation { for (layer, initial_radius) in &tool_data.circular_operation_data.layers_dragging { // If the layer’s sign differs from the clicked layer, invert delta to preserve consistent in/out dragging behavior + + let Some((angle, _, count)) = extract_circular_repeat_parameters(Some(*layer), document) else { + return; + }; + let new_radius = if initial_radius.signum() == clicked_radius.signum() { *initial_radius + delta } else { @@ -84,9 +89,9 @@ impl CircularRepeatOperation { responses.add(GraphOperationMessage::CircularRepeatSet { layer: *layer, - angle: 0., + angle, radius: new_radius, - count: 6, + count, }); } @@ -136,6 +141,24 @@ impl CircularRepeatOperation { } } + pub fn increase_decrease_count(tool_data: &mut OperationToolData, increase: bool, document: &DocumentMessageHandler, responses: &mut VecDeque) { + for (layer, _) in &tool_data.circular_operation_data.layers_dragging { + let Some((angle, radius, mut count)) = extract_circular_repeat_parameters(Some(*layer), document) else { + return; + }; + + if increase { + count += 1 + } else { + count = (count - 1).max(1) + } + + responses.add(GraphOperationMessage::CircularRepeatSet { layer: *layer, angle, radius, count }); + } + + responses.add(NodeGraphMessage::RunDocumentGraph); + } + pub fn cleanup(tool_data: &mut OperationToolData) { // Clear stored drag state at the end of the operation tool_data.circular_operation_data.layers_dragging.clear(); diff --git a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs index 5dfad7d169..b049612948 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -230,6 +230,8 @@ pub fn extract_star_parameters(layer: Option, document: &Do Some((sides, radius_1, radius_2)) } +/// Extract the node input values of Circular Repeat Node. +/// Returns an option of (angle_offset, radius, count). pub fn extract_circular_repeat_parameters(layer: Option, document: &DocumentMessageHandler) -> Option<(f64, f64, u32)> { let node_inputs = NodeGraphLayer::new(layer?, &document.network_interface).find_node_inputs("Circular Repeat")?; diff --git a/editor/src/messages/tool/tool_messages/operation_tool.rs b/editor/src/messages/tool/tool_messages/operation_tool.rs index 9db24457d0..c8e5bf3f6d 100644 --- a/editor/src/messages/tool/tool_messages/operation_tool.rs +++ b/editor/src/messages/tool/tool_messages/operation_tool.rs @@ -37,6 +37,8 @@ pub enum OperationToolMessage { WorkingColorChanged, // Tool-specific messages + IncreaseCount, + DecreaseCount, Confirm, DragStart, DragStop, @@ -127,6 +129,8 @@ impl<'a> MessageHandler> for Oper PointerMove, Confirm, Abort, + IncreaseCount, + DecreaseCount, ), } } @@ -212,6 +216,20 @@ impl Fsm for OperationToolFsmState { OperationToolFsmState::Drawing } + (OperationToolFsmState::Drawing, OperationToolMessage::IncreaseCount) => { + match tool_options.operation_type { + OperationType::CircularRepeat => CircularRepeatOperation::increase_decrease_count(tool_data, true, document, responses), + _ => {} + } + self + } + (OperationToolFsmState::Drawing, OperationToolMessage::DecreaseCount) => { + match tool_options.operation_type { + OperationType::CircularRepeat => CircularRepeatOperation::increase_decrease_count(tool_data, false, document, responses), + _ => {} + } + self + } (_, OperationToolMessage::PointerMove) => { responses.add(OverlaysMessage::Draw); self From 5ab6f2e5ae2e63077ff0e13bda8a2fb907b87558 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Thu, 4 Sep 2025 13:36:25 +0530 Subject: [PATCH 7/8] added multi select changed added gizmo context --- .../gizmos/gizmo_manager.rs | 154 +++++++++-------- .../tool/common_functionality/gizmos/mod.rs | 1 + .../gizmos/operation_gizmos/count_gizmos.rs | 156 ++++++++++++++++++ .../gizmos/operation_gizmos/mod.rs | 1 + .../shape_gizmos/circle_arc_radius_handle.rs | 18 +- .../shape_gizmos/number_of_points_dial.rs | 20 ++- .../shape_gizmos/point_radius_handle.rs | 17 +- .../gizmos/shape_gizmos/sweep_angle_gizmo.rs | 22 ++- .../graph_modification_utils.rs | 4 + .../operations/circular_repeat.rs | 73 +++++--- .../common_functionality/shapes/arc_shape.rs | 54 +++--- .../shapes/circle_shape.rs | 34 +--- .../shapes/polygon_shape.rs | 42 ++--- .../shapes/shape_utility.rs | 30 ++-- .../common_functionality/shapes/star_shape.rs | 44 ++--- .../tool/tool_messages/operation_tool.rs | 64 ++++++- .../messages/tool/tool_messages/shape_tool.rs | 36 +++- 17 files changed, 500 insertions(+), 270 deletions(-) create mode 100644 editor/src/messages/tool/common_functionality/gizmos/operation_gizmos/count_gizmos.rs create mode 100644 editor/src/messages/tool/common_functionality/gizmos/operation_gizmos/mod.rs diff --git a/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs b/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs index 57cbb4ed33..f9d2f4ec57 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs @@ -1,17 +1,16 @@ 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::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler}; +use crate::messages::prelude::DocumentMessageHandler; use crate::messages::tool::common_functionality::graph_modification_utils; -use crate::messages::tool::common_functionality::shape_editor::ShapeState; +use crate::messages::tool::common_functionality::operations::circular_repeat::CircularRepeatGizmoHandler; use crate::messages::tool::common_functionality::shapes::arc_shape::ArcGizmoHandler; use crate::messages::tool::common_functionality::shapes::circle_shape::CircleGizmoHandler; 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::shape_utility::{GizmoContext, ShapeGizmoHandler}; use crate::messages::tool::common_functionality::shapes::star_shape::StarGizmoHandler; use glam::DVec2; -use std::collections::VecDeque; +use std::collections::HashMap; /// A unified enum wrapper around all available shape-specific gizmo handlers. /// @@ -28,6 +27,7 @@ pub enum ShapeGizmoHandlers { Polygon(PolygonGizmoHandler), Arc(ArcGizmoHandler), Circle(CircleGizmoHandler), + CircularRepeat(CircularRepeatGizmoHandler), } impl ShapeGizmoHandlers { @@ -39,17 +39,19 @@ impl ShapeGizmoHandlers { Self::Polygon(_) => "polygon", Self::Arc(_) => "arc", Self::Circle(_) => "circle", + Self::CircularRepeat(_) => "circular_repeat", Self::None => "none", } } /// Dispatches interaction state updates to the corresponding shape-specific handler. - pub fn handle_state(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { + pub fn handle_state(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, ctx: &mut GizmoContext) { match self { - Self::Star(h) => h.handle_state(layer, mouse_position, document, responses), - Self::Polygon(h) => h.handle_state(layer, mouse_position, document, responses), - Self::Arc(h) => h.handle_state(layer, mouse_position, document, responses), - Self::Circle(h) => h.handle_state(layer, mouse_position, document, responses), + Self::Star(h) => h.handle_state(layer, mouse_position, ctx), + Self::Polygon(h) => h.handle_state(layer, mouse_position, ctx), + Self::Arc(h) => h.handle_state(layer, mouse_position, ctx), + Self::Circle(h) => h.handle_state(layer, mouse_position, ctx), + Self::CircularRepeat(h) => h.handle_state(layer, mouse_position, ctx), Self::None => {} } } @@ -61,6 +63,7 @@ impl ShapeGizmoHandlers { Self::Polygon(h) => h.is_any_gizmo_hovered(), Self::Arc(h) => h.is_any_gizmo_hovered(), Self::Circle(h) => h.is_any_gizmo_hovered(), + Self::CircularRepeat(h) => h.is_any_gizmo_hovered(), Self::None => false, } } @@ -72,17 +75,19 @@ impl ShapeGizmoHandlers { Self::Polygon(h) => h.handle_click(), Self::Arc(h) => h.handle_click(), Self::Circle(h) => h.handle_click(), + Self::CircularRepeat(h) => h.handle_click(), Self::None => {} } } /// Updates the gizmo state while the user is dragging a handle (e.g., adjusting radius). - pub fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + pub fn handle_update(&mut self, drag_start: DVec2, ctx: &mut GizmoContext) { match self { - Self::Star(h) => h.handle_update(drag_start, document, input, responses), - Self::Polygon(h) => h.handle_update(drag_start, document, input, responses), - Self::Arc(h) => h.handle_update(drag_start, document, input, responses), - Self::Circle(h) => h.handle_update(drag_start, document, input, responses), + Self::Star(h) => h.handle_update(drag_start, ctx), + Self::Polygon(h) => h.handle_update(drag_start, ctx), + Self::Arc(h) => h.handle_update(drag_start, ctx), + Self::Circle(h) => h.handle_update(drag_start, ctx), + Self::CircularRepeat(h) => h.handle_update(drag_start, ctx), Self::None => {} } } @@ -94,43 +99,31 @@ impl ShapeGizmoHandlers { Self::Polygon(h) => h.cleanup(), Self::Arc(h) => h.cleanup(), Self::Circle(h) => h.cleanup(), + Self::CircularRepeat(h) => h.cleanup(), Self::None => {} } } /// Draws overlays like control points or outlines for the shape handled by this gizmo. - pub fn overlays( - &self, - document: &DocumentMessageHandler, - layer: Option, - input: &InputPreprocessorMessageHandler, - shape_editor: &mut &mut ShapeState, - mouse_position: DVec2, - overlay_context: &mut OverlayContext, - ) { + pub fn overlays(&self, layer: Option, mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { match self { - Self::Star(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context), - Self::Polygon(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context), - 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::Star(h) => h.overlays(layer, mouse_position, ctx, overlay_context), + Self::Polygon(h) => h.overlays(layer, mouse_position, ctx, overlay_context), + Self::Arc(h) => h.overlays(layer, mouse_position, ctx, overlay_context), + Self::Circle(h) => h.overlays(layer, mouse_position, ctx, overlay_context), + Self::CircularRepeat(h) => h.overlays(layer, mouse_position, ctx, overlay_context), Self::None => {} } } /// Draws live-updating overlays during drag interactions for the shape handled by this gizmo. - pub fn dragging_overlays( - &self, - document: &DocumentMessageHandler, - input: &InputPreprocessorMessageHandler, - shape_editor: &mut &mut ShapeState, - mouse_position: DVec2, - overlay_context: &mut OverlayContext, - ) { + pub fn dragging_overlays(&self, mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { match self { - Self::Star(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context), - Self::Polygon(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context), - 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::Star(h) => h.dragging_overlays(mouse_position, ctx, overlay_context), + Self::Polygon(h) => h.dragging_overlays(mouse_position, ctx, overlay_context), + Self::Arc(h) => h.dragging_overlays(mouse_position, ctx, overlay_context), + Self::Circle(h) => h.dragging_overlays(mouse_position, ctx, overlay_context), + Self::CircularRepeat(h) => h.dragging_overlays(mouse_position, ctx, overlay_context), Self::None => {} } } @@ -141,6 +134,7 @@ impl ShapeGizmoHandlers { Self::Polygon(h) => h.mouse_cursor_icon(), Self::Arc(h) => h.mouse_cursor_icon(), Self::Circle(h) => h.mouse_cursor_icon(), + Self::CircularRepeat(h) => h.mouse_cursor_icon(), Self::None => None, } } @@ -188,6 +182,16 @@ impl GizmoManager { None } + /// Detects and returns a operation gizmo handler based on the layer type (e.g., circular_repeat, repeat). + pub fn detect_operation_gizmo_handler(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option { + // Circular Repeat + if graph_modification_utils::get_circular_repeat(layer, &document.network_interface).is_some() { + return Some(ShapeGizmoHandlers::CircularRepeat(CircularRepeatGizmoHandler::default())); + } + + None + } + /// Returns `true` if a gizmo is currently active (hovered or being interacted with). pub fn hovering_over_gizmo(&self) -> bool { self.active_shape_handler.is_some() @@ -196,12 +200,12 @@ impl GizmoManager { /// Called every frame to check selected layers and update the active shape gizmo, if hovered. /// /// Also groups all shape layers with the same kind of gizmo to support overlays for multi-shape editing. - pub fn handle_actions(&mut self, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { + pub fn handle_actions(&mut self, mouse_position: DVec2, ctx: &mut GizmoContext) { let mut handlers_layer: Vec<(ShapeGizmoHandlers, Vec)> = Vec::new(); - for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) { - if let Some(mut handler) = Self::detect_shape_handler(layer, document) { - handler.handle_state(layer, mouse_position, document, responses); + for layer in ctx.document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&ctx.document.network_interface) { + if let Some(mut handler) = Self::detect_shape_handler(layer, ctx.document) { + handler.handle_state(layer, mouse_position, ctx); let is_hovered = handler.is_any_gizmo_hovered(); if is_hovered { @@ -210,7 +214,7 @@ impl GizmoManager { return; } - // Try to group this handler with others of the same type + // Group same-kind handlers together if let Some((_, layers)) = handlers_layer.iter_mut().find(|(existing_handler, _)| existing_handler.kind() == handler.kind()) { layers.push(layer); } else { @@ -223,6 +227,38 @@ impl GizmoManager { self.active_shape_handler = None; } + pub fn handle_operation_actions(&mut self, mouse_position: DVec2, ctx: &mut GizmoContext) { + self.active_shape_handler = None; + + let mut handlers_map: HashMap<&'static str, ShapeGizmoHandlers> = HashMap::new(); + let mut maybe_active_kind: Option<&'static str> = None; + + for layer in ctx.document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&ctx.document.network_interface) { + if let Some(mut handler) = Self::detect_operation_gizmo_handler(layer, ctx.document) { + let kind = handler.kind(); + + // Reuse existing handler to accumulate layers + if let Some(existing_handler) = handlers_map.remove(kind) { + handler = existing_handler; + } + + handler.handle_state(layer, mouse_position, ctx); + + if handler.is_any_gizmo_hovered() { + maybe_active_kind = Some(kind); + } + + handlers_map.insert(kind, handler); + } + } + + if let Some(kind) = maybe_active_kind { + if let Some(handler) = handlers_map.remove(kind) { + self.active_shape_handler = Some(handler); + } + } + } + /// Handles click interactions if a gizmo is active. Returns `true` if a gizmo handled the click. pub fn handle_click(&mut self) -> bool { if let Some(handle) = &mut self.active_shape_handler { @@ -239,45 +275,31 @@ impl GizmoManager { } /// Passes drag update data to the active gizmo to update shape parameters live. - pub fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + pub fn handle_update(&mut self, drag_start: DVec2, ctx: &mut GizmoContext) { if let Some(handle) = &mut self.active_shape_handler { - handle.handle_update(drag_start, document, input, responses); + handle.handle_update(drag_start, ctx); } } /// Draws overlays for the currently active shape gizmo during a drag interaction. - pub fn dragging_overlays( - &self, - document: &DocumentMessageHandler, - input: &InputPreprocessorMessageHandler, - shape_editor: &mut &mut ShapeState, - mouse_position: DVec2, - overlay_context: &mut OverlayContext, - ) { + pub fn dragging_overlays(&self, mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { if let Some(handle) = &self.active_shape_handler { - handle.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context); + handle.dragging_overlays(mouse_position, ctx, overlay_context); } } /// Draws overlays for either the active gizmo (if hovered) or all grouped selected gizmos. /// /// If no single gizmo is active, it renders overlays for all grouped layers with associated handlers. - pub fn overlays( - &self, - document: &DocumentMessageHandler, - input: &InputPreprocessorMessageHandler, - shape_editor: &mut &mut ShapeState, - mouse_position: DVec2, - overlay_context: &mut OverlayContext, - ) { + pub fn overlays(&self, mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { if let Some(handler) = &self.active_shape_handler { - handler.overlays(document, None, input, shape_editor, mouse_position, overlay_context); + handler.overlays(None, mouse_position, ctx, overlay_context); return; } for (handler, selected_layers) in &self.layers_handlers { for layer in selected_layers { - handler.overlays(document, Some(*layer), input, shape_editor, mouse_position, overlay_context); + handler.overlays(Some(*layer), mouse_position, ctx, overlay_context); } } } diff --git a/editor/src/messages/tool/common_functionality/gizmos/mod.rs b/editor/src/messages/tool/common_functionality/gizmos/mod.rs index 108c45d6a3..9ad6142b3f 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/mod.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/mod.rs @@ -1,2 +1,3 @@ pub mod gizmo_manager; +pub mod operation_gizmos; pub mod shape_gizmos; diff --git a/editor/src/messages/tool/common_functionality/gizmos/operation_gizmos/count_gizmos.rs b/editor/src/messages/tool/common_functionality/gizmos/operation_gizmos/count_gizmos.rs new file mode 100644 index 0000000000..ca79bb9254 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/gizmos/operation_gizmos/count_gizmos.rs @@ -0,0 +1,156 @@ +use crate::consts::{NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION, NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH}; +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::NodeGraphMessage; +use crate::messages::prelude::Responses; +use crate::messages::tool::common_functionality::graph_modification_utils; +use crate::messages::tool::common_functionality::shapes::shape_utility::{GizmoContext, extract_circular_repeat_parameters}; +use glam::{DAffine2, DVec2}; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; +use std::collections::HashMap; +use std::f64::consts::{FRAC_PI_2, TAU}; + +#[derive(Clone, Debug, Default, PartialEq)] +pub enum RepeatCountDialState { + #[default] + Inactive, + Hover, + Dragging, +} + +#[derive(Clone, Debug, Default)] +pub struct RepeatCountDial { + pub layer: Option, + pub initial_points: u32, + pub handle_state: RepeatCountDialState, + + // store the other layers whose gizmo is not hovered but still has repeat node in them + selected_layers: HashMap, +} + +impl RepeatCountDial { + pub fn cleanup(&mut self) { + self.handle_state = RepeatCountDialState::Inactive; + self.layer = None; + self.selected_layers.clear(); + } + + pub fn update_state(&mut self, state: RepeatCountDialState) { + self.handle_state = state; + } + + pub fn is_hovering(&self) -> bool { + self.handle_state == RepeatCountDialState::Hover + } + + pub fn is_dragging(&self) -> bool { + self.handle_state == RepeatCountDialState::Dragging + } + + pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, ctx: &mut GizmoContext) { + let GizmoContext { document, .. } = ctx; + + match &self.handle_state { + RepeatCountDialState::Inactive => { + let Some((_, radius, count)) = extract_circular_repeat_parameters(Some(layer), document) else { + return; + }; + let viewport = document.metadata().transform_to_viewport(layer); + + let center = viewport.transform_point2(DVec2::ZERO); + let offset_vector = document.metadata().downstream_transform_to_viewport(layer).transform_vector2(DVec2::NEG_Y * radius); + + let repeat_center = center + offset_vector; + + if repeat_center.distance(mouse_position) < NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH { + self.layer = Some(layer); + self.update_state(RepeatCountDialState::Hover); + self.initial_points = count; + } + + self.selected_layers.insert(layer, count); + } + RepeatCountDialState::Hover | RepeatCountDialState::Dragging => { + // Even though we the gizmo is in hovered state store the other layers + let Some((_, _, count)) = extract_circular_repeat_parameters(Some(layer), document) else { + return; + }; + self.selected_layers.insert(layer, count); + } + } + } + + pub fn overlays(&self, layer: Option, mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { + let GizmoContext { document, .. } = ctx; + + match &self.handle_state { + RepeatCountDialState::Inactive => { + let Some(layer) = layer else { return }; + let Some((angle, radius, count)) = extract_circular_repeat_parameters(Some(layer), document) else { + return; + }; + let viewport = document.metadata().transform_to_viewport(layer); + + let center = viewport.transform_point2(DVec2::ZERO); + let offset_vector = document.metadata().downstream_transform_to_viewport(layer).transform_vector2(DVec2::NEG_Y * radius); + let repeat_center = center + offset_vector; + + if repeat_center.distance(mouse_position) < radius.abs() { + self.draw_spokes(repeat_center, document.metadata().downstream_transform_to_viewport(layer), count, angle.to_radians(), overlay_context); + } + } + RepeatCountDialState::Hover | RepeatCountDialState::Dragging => { + let Some(layer) = self.layer else { return }; + let Some((angle, radius, count)) = extract_circular_repeat_parameters(Some(layer), document) else { + return; + }; + let viewport = document.metadata().transform_to_viewport(layer); + + let center = viewport.transform_point2(DVec2::ZERO); + let offset_vector = document.metadata().downstream_transform_to_viewport(layer).transform_vector2(DVec2::NEG_Y * radius); + + let repeat_center = center + offset_vector; + + self.draw_spokes(repeat_center, document.metadata().downstream_transform_to_viewport(layer), count, angle.to_radians(), overlay_context); + } + } + } + + fn draw_spokes(&self, center: DVec2, viewport: DAffine2, count: u32, angle: f64, overlay_context: &mut OverlayContext) { + for i in 0..count { + let angle = ((i as f64) * TAU) / (count as f64) + angle + FRAC_PI_2; + + let direction_vector = viewport.transform_vector2(DVec2 { x: angle.cos(), y: -angle.sin() }); + + let end_point = direction_vector * 20.; + if matches!(self.handle_state, RepeatCountDialState::Hover | RepeatCountDialState::Dragging) { + overlay_context.line(center, end_point * NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION + center, None, None); + } else { + overlay_context.line(center, end_point + center, None, None); + } + } + } + + pub fn update_number_of_sides(&self, drag_start: DVec2, ctx: &mut GizmoContext) { + let GizmoContext { document, input, responses, .. } = ctx; + + let delta = input.mouse.position - drag_start; + let sign = (input.mouse.position.x - drag_start.x).signum(); + let net_delta = (delta.length() / 25.).round() * sign; + + for (layer, count) in &self.selected_layers { + let Some(node_id) = graph_modification_utils::get_circular_repeat(*layer, &document.network_interface) else { + return; + }; + let new_point_count = ((*count as i32) + (net_delta as i32)).max(1); + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 3), + input: NodeInput::value(TaggedValue::U32(new_point_count as u32), false), + }); + } + responses.add(NodeGraphMessage::RunDocumentGraph); + } +} diff --git a/editor/src/messages/tool/common_functionality/gizmos/operation_gizmos/mod.rs b/editor/src/messages/tool/common_functionality/gizmos/operation_gizmos/mod.rs new file mode 100644 index 0000000000..a9d34b6f30 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/gizmos/operation_gizmos/mod.rs @@ -0,0 +1 @@ +pub mod count_gizmos; diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/circle_arc_radius_handle.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/circle_arc_radius_handle.rs index 2cbf1c4509..0584308635 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/circle_arc_radius_handle.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/circle_arc_radius_handle.rs @@ -1,17 +1,15 @@ use crate::consts::GIZMO_HIDE_THRESHOLD; 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::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage}; +use crate::messages::prelude::{DocumentMessageHandler, NodeGraphMessage}; use crate::messages::prelude::{FrontendMessage, Responses}; use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_arc_id, get_stroke_width}; -use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_arc_parameters, extract_circle_radius}; +use crate::messages::tool::common_functionality::shapes::shape_utility::{GizmoContext, extract_arc_parameters, extract_circle_radius}; use glam::{DAffine2, DVec2}; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; -use std::collections::VecDeque; use std::f64::consts::FRAC_PI_2; #[derive(Clone, Debug, Default, PartialEq)] @@ -77,7 +75,9 @@ impl RadiusHandle { stroke_width + extra_spacing } - pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque) { + pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, ctx: &mut GizmoContext) { + let GizmoContext { document, responses, .. } = ctx; + match &self.handle_state { RadiusHandleState::Inactive => { let Some(radius) = extract_circle_radius(layer, document).or(extract_arc_parameters(Some(layer), document).map(|(r, _, _, _)| r)) else { @@ -107,7 +107,9 @@ impl RadiusHandle { } } - pub fn overlays(&self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { + pub fn overlays(&self, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { + let GizmoContext { document, .. } = ctx; + match &self.handle_state { RadiusHandleState::Inactive => {} RadiusHandleState::Dragging | RadiusHandleState::Hover => { @@ -145,7 +147,9 @@ impl RadiusHandle { } } - pub fn update_inner_radius(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { + pub fn update_inner_radius(&mut self, drag_start: DVec2, ctx: &mut GizmoContext) { + let GizmoContext { document, responses, input, .. } = ctx; + let Some(layer) = self.layer else { return }; let Some(node_id) = graph_modification_utils::get_circle_id(layer, &document.network_interface).or(get_arc_id(layer, &document.network_interface)) else { return; diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs index fa4dbe2ed8..66ffe42906 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs @@ -1,19 +1,18 @@ use crate::consts::{GIZMO_HIDE_THRESHOLD, NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION, NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD}; 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::prelude::{FrontendMessage, 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_polygon_parameters, inside_polygon, inside_star, polygon_outline, polygon_vertex_position, star_outline}; +use crate::messages::tool::common_functionality::shapes::shape_utility::{ + GizmoContext, extract_polygon_parameters, inside_polygon, inside_star, polygon_outline, polygon_vertex_position, star_outline, +}; use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_star_parameters, star_vertex_position}; use glam::{DAffine2, DVec2}; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; -use std::collections::VecDeque; use std::f64::consts::TAU; #[derive(Clone, Debug, Default, PartialEq)] @@ -49,7 +48,8 @@ impl NumberOfPointsDial { self.handle_state == NumberOfPointsDialState::Dragging } - pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { + pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, ctx: &mut GizmoContext) { + let GizmoContext { document, responses, .. } = ctx; match &self.handle_state { NumberOfPointsDialState::Inactive => { // Star @@ -97,7 +97,9 @@ impl NumberOfPointsDial { } } - pub fn overlays(&self, document: &DocumentMessageHandler, layer: Option, shape_editor: &mut &mut ShapeState, mouse_position: DVec2, overlay_context: &mut OverlayContext) { + pub fn overlays(&self, layer: Option, mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { + let GizmoContext { document, shape_editor, .. } = ctx; + match &self.handle_state { NumberOfPointsDialState::Inactive => { let Some(layer) = layer else { return }; @@ -188,7 +190,9 @@ impl NumberOfPointsDial { } } - pub fn update_number_of_sides(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { + pub fn update_number_of_sides(&self, drag_start: DVec2, ctx: &mut GizmoContext) { + let GizmoContext { document, input, responses, .. } = ctx; + let delta = input.mouse.position - drag_start; let sign = (input.mouse.position.x - drag_start.x).signum(); let net_delta = (delta.length() / 25.).round() * sign; diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs index 25516284bd..a4e87bf1e6 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs @@ -1,19 +1,17 @@ use crate::consts::GIZMO_HIDE_THRESHOLD; use crate::consts::{COLOR_OVERLAY_RED, POINT_RADIUS_HANDLE_SNAP_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::prelude::{DocumentMessageHandler, NodeGraphMessage}; use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer}; -use crate::messages::tool::common_functionality::shapes::shape_utility::{draw_snapping_ticks, extract_polygon_parameters, polygon_outline, polygon_vertex_position, star_outline}; +use crate::messages::tool::common_functionality::shapes::shape_utility::{GizmoContext, draw_snapping_ticks, extract_polygon_parameters, polygon_outline, polygon_vertex_position, star_outline}; use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_star_parameters, star_vertex_position}; use glam::DVec2; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; -use std::collections::VecDeque; use std::f64::consts::{FRAC_1_SQRT_2, FRAC_PI_4, PI, SQRT_2}; #[derive(Clone, Debug, Default, PartialEq)] @@ -54,7 +52,8 @@ impl PointRadiusHandle { self.handle_state = state; } - pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque) { + pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, ctx: &mut GizmoContext) { + let GizmoContext { document, responses, .. } = ctx; match &self.handle_state { PointRadiusHandleState::Inactive => { // Draw the point handle gizmo for the star shape @@ -142,7 +141,9 @@ impl PointRadiusHandle { } } - pub fn overlays(&self, selected_star_layer: Option, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, overlay_context: &mut OverlayContext) { + pub fn overlays(&self, selected_star_layer: Option, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { + let GizmoContext { document, input, .. } = ctx; + match &self.handle_state { PointRadiusHandleState::Inactive => { let Some(layer) = selected_star_layer else { return }; @@ -411,7 +412,9 @@ impl PointRadiusHandle { .map(|(i, rad)| (i, *rad - original_radius)) } - pub fn update_inner_radius(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { + pub fn update_inner_radius(&mut self, drag_start: DVec2, ctx: &mut GizmoContext) { + let GizmoContext { document, responses, input, .. } = ctx; + let Some(layer) = self.layer else { return }; let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else { diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/sweep_angle_gizmo.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/sweep_angle_gizmo.rs index 68d69d9eba..638cf80529 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/sweep_angle_gizmo.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/sweep_angle_gizmo.rs @@ -3,9 +3,8 @@ 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::DocumentMessageHandler; use crate::messages::tool::common_functionality::graph_modification_utils; -use crate::messages::tool::common_functionality::shapes::shape_utility::{arc_end_points, calculate_arc_text_transform, extract_arc_parameters, format_rounded}; +use crate::messages::tool::common_functionality::shapes::shape_utility::{GizmoContext, arc_end_points, calculate_arc_text_transform, extract_arc_parameters, format_rounded}; use crate::messages::tool::tool_messages::tool_prelude::*; use glam::DVec2; use graph_craft::document::value::TaggedValue; @@ -56,7 +55,9 @@ impl SweepAngleGizmo { self.handle_state == SweepAngleGizmoState::Dragging || self.handle_state == SweepAngleGizmoState::Snapped } - pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2) { + pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, ctx: &mut GizmoContext) { + let GizmoContext { document, .. } = ctx; + if self.handle_state == SweepAngleGizmoState::Inactive { let Some((start, end)) = arc_end_points(Some(layer), document) else { return }; let Some((_, start_angle, sweep_angle, _)) = extract_arc_parameters(Some(layer), document) else { @@ -92,14 +93,9 @@ impl SweepAngleGizmo { } } - pub fn overlays( - &self, - selected_arc_layer: Option, - document: &DocumentMessageHandler, - _input: &InputPreprocessorMessageHandler, - _mouse_position: DVec2, - overlay_context: &mut OverlayContext, - ) { + pub fn overlays(&self, selected_arc_layer: Option, _mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { + let GizmoContext { document, .. } = ctx; + let tilt_offset = document.document_ptz.unmodified_tilt(); match self.handle_state { @@ -183,7 +179,9 @@ impl SweepAngleGizmo { overlay_context.arc_sweep_angle(offset_angle, angle, final_point, bold_radius, center, &text, transform); } - pub fn update_arc(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + pub fn update_arc(&mut self, ctx: &mut GizmoContext) { + let GizmoContext { document, input, responses, .. } = ctx; + let Some(layer) = self.layer else { return }; let Some((_, current_start_angle, current_sweep_angle, _)) = extract_arc_parameters(Some(layer), document) else { return; diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs index 4cde2d08d6..c35473ec08 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -367,6 +367,10 @@ pub fn get_text_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkIn NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Text") } +pub fn get_circular_repeat(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { + NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Circular Repeat") +} + /// Gets properties from the Text node pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<(&String, &Font, TypesettingConfig, bool)> { let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Text")?; diff --git a/editor/src/messages/tool/common_functionality/operations/circular_repeat.rs b/editor/src/messages/tool/common_functionality/operations/circular_repeat.rs index 19ed343c3c..a210f84690 100644 --- a/editor/src/messages/tool/common_functionality/operations/circular_repeat.rs +++ b/editor/src/messages/tool/common_functionality/operations/circular_repeat.rs @@ -1,10 +1,60 @@ use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::tool::common_functionality::shapes::shape_utility::extract_circular_repeat_parameters; +use crate::messages::tool::common_functionality::gizmos::operation_gizmos::count_gizmos::{RepeatCountDial, RepeatCountDialState}; +use crate::messages::tool::common_functionality::shapes::shape_utility::{GizmoContext, ShapeGizmoHandler, extract_circular_repeat_parameters}; use crate::messages::tool::tool_messages::operation_tool::{OperationToolData, OperationToolFsmState}; use crate::messages::tool::tool_messages::tool_prelude::*; use std::collections::VecDeque; +#[derive(Clone, Debug, Default)] +pub struct CircularRepeatGizmoHandler { + count_dial: RepeatCountDial, +} + +impl CircularRepeatGizmoHandler { + pub fn new() -> Self { + Self { ..Default::default() } + } +} + +impl ShapeGizmoHandler for CircularRepeatGizmoHandler { + fn handle_state(&mut self, selected_shape_layer: LayerNodeIdentifier, mouse_position: DVec2, ctx: &mut GizmoContext) { + self.count_dial.handle_actions(selected_shape_layer, mouse_position, ctx); + } + + fn is_any_gizmo_hovered(&self) -> bool { + self.count_dial.is_hovering() + } + + fn handle_click(&mut self) { + if self.count_dial.is_hovering() { + self.count_dial.update_state(RepeatCountDialState::Dragging); + } + } + + fn handle_update(&mut self, drag_start: DVec2, ctx: &mut GizmoContext) { + if self.count_dial.is_dragging() { + self.count_dial.update_number_of_sides(drag_start, ctx); + } + } + + fn dragging_overlays(&self, mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { + if self.count_dial.is_dragging() { + self.count_dial.overlays(None, mouse_position, ctx, overlay_context); + } + } + + fn overlays(&self, selected_shape_layer: Option, mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { + self.count_dial.overlays(selected_shape_layer, mouse_position, ctx, overlay_context); + } + + fn mouse_cursor_icon(&self) -> Option { + None + } + + fn cleanup(&mut self) {} +} + #[derive(Default)] pub struct CircularRepeatOperation; @@ -77,9 +127,7 @@ impl CircularRepeatOperation { for (layer, initial_radius) in &tool_data.circular_operation_data.layers_dragging { // If the layer’s sign differs from the clicked layer, invert delta to preserve consistent in/out dragging behavior - let Some((angle, _, count)) = extract_circular_repeat_parameters(Some(*layer), document) else { - return; - }; + let (angle, _, count) = extract_circular_repeat_parameters(Some(*layer), document).unwrap_or((0.0, 0.0, 6)); let new_radius = if initial_radius.signum() == clicked_radius.signum() { *initial_radius + delta @@ -107,25 +155,12 @@ impl CircularRepeatOperation { ) { match tool_state { OperationToolFsmState::Ready => { - // Draw overlays for all selected layers - for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) { - Self::draw_layer_overlay(layer, document, input, overlay_context) - } - // Also highlight the hovered layer if it’s not selected if let Some(layer) = document.click(&input) { Self::draw_layer_overlay(layer, document, input, overlay_context); } } - _ => { - // While dragging, only draw overlays for the layers being modified - for layer in tool_data.circular_operation_data.layers_dragging.iter().map(|(l, _)| l) { - let Some(vector) = document.network_interface.compute_modified_vector(*layer) else { continue }; - let viewport = document.metadata().transform_to_viewport(*layer); - - overlay_context.outline_vector(&vector, viewport); - } - } + _ => {} } } @@ -133,11 +168,11 @@ impl CircularRepeatOperation { if let Some(vector) = document.network_interface.compute_modified_vector(layer) { let viewport = document.metadata().transform_to_viewport(layer); let center = viewport.transform_point2(DVec2::ZERO); + // Show a small circle if the mouse is near the repeat center if center.distance(input.mouse.position) < 5. { overlay_context.circle(center, 3., None, None); } - overlay_context.outline_vector(&vector, viewport); } } diff --git a/editor/src/messages/tool/common_functionality/shapes/arc_shape.rs b/editor/src/messages/tool/common_functionality/shapes/arc_shape.rs index 68803598de..c9f60a02cd 100644 --- a/editor/src/messages/tool/common_functionality/shapes/arc_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/arc_shape.rs @@ -2,12 +2,13 @@ use super::shape_utility::ShapeToolModifierKey; 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::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::circle_arc_radius_handle::{RadiusHandle, RadiusHandleState}; use crate::messages::tool::common_functionality::gizmos::shape_gizmos::sweep_angle_gizmo::{SweepAngleGizmo, SweepAngleGizmoState}; use crate::messages::tool::common_functionality::graph_modification_utils; -use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, arc_outline}; +use crate::messages::tool::common_functionality::shapes::shape_utility::{GizmoContext, ShapeGizmoHandler, arc_outline}; use crate::messages::tool::tool_messages::tool_prelude::*; use glam::DAffine2; use graph_craft::document::NodeInput; @@ -28,9 +29,9 @@ impl ArcGizmoHandler { } impl ShapeGizmoHandler for ArcGizmoHandler { - fn handle_state(&mut self, selected_shape_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { - self.sweep_angle_gizmo.handle_actions(selected_shape_layer, document, mouse_position); - self.arc_radius_handle.handle_actions(selected_shape_layer, document, mouse_position, responses); + fn handle_state(&mut self, selected_shape_layer: LayerNodeIdentifier, mouse_position: DVec2, ctx: &mut GizmoContext) { + self.sweep_angle_gizmo.handle_actions(selected_shape_layer, mouse_position, ctx); + self.arc_radius_handle.handle_actions(selected_shape_layer, mouse_position, ctx); } fn is_any_gizmo_hovered(&self) -> bool { @@ -54,61 +55,46 @@ impl ShapeGizmoHandler for ArcGizmoHandler { } } - fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + fn handle_update(&mut self, drag_start: DVec2, ctx: &mut GizmoContext) { if self.sweep_angle_gizmo.is_dragging_or_snapped() { - self.sweep_angle_gizmo.update_arc(document, input, responses); + self.sweep_angle_gizmo.update_arc(ctx); } if self.arc_radius_handle.is_dragging() { - self.arc_radius_handle.update_inner_radius(document, input, responses, drag_start); + self.arc_radius_handle.update_inner_radius(drag_start, ctx); } } - fn dragging_overlays( - &self, - document: &DocumentMessageHandler, - input: &InputPreprocessorMessageHandler, - _shape_editor: &mut &mut crate::messages::tool::common_functionality::shape_editor::ShapeState, - mouse_position: DVec2, - overlay_context: &mut crate::messages::portfolio::document::overlays::utility_types::OverlayContext, - ) { + fn dragging_overlays(&self, mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { if self.sweep_angle_gizmo.is_dragging_or_snapped() { - self.sweep_angle_gizmo.overlays(None, document, input, mouse_position, overlay_context); - arc_outline(self.sweep_angle_gizmo.layer, document, overlay_context); + self.sweep_angle_gizmo.overlays(None, mouse_position, ctx, overlay_context); + arc_outline(self.sweep_angle_gizmo.layer, ctx.document, overlay_context); } if self.arc_radius_handle.is_dragging() { - self.sweep_angle_gizmo.overlays(self.arc_radius_handle.layer, document, input, mouse_position, overlay_context); - self.arc_radius_handle.overlays(document, overlay_context); + self.sweep_angle_gizmo.overlays(self.arc_radius_handle.layer, mouse_position, ctx, overlay_context); + self.arc_radius_handle.overlays(ctx, overlay_context); } } - fn overlays( - &self, - document: &DocumentMessageHandler, - selected_shape_layer: Option, - input: &InputPreprocessorMessageHandler, - _shape_editor: &mut &mut crate::messages::tool::common_functionality::shape_editor::ShapeState, - mouse_position: DVec2, - overlay_context: &mut crate::messages::portfolio::document::overlays::utility_types::OverlayContext, - ) { + fn overlays(&self, selected_shape_layer: Option, mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { // If hovering over both the gizmos give priority to sweep angle gizmo if self.sweep_angle_gizmo.hovered() && self.arc_radius_handle.hovered() { - self.sweep_angle_gizmo.overlays(selected_shape_layer, document, input, mouse_position, overlay_context); + self.sweep_angle_gizmo.overlays(selected_shape_layer, mouse_position, ctx, overlay_context); return; } if self.arc_radius_handle.hovered() { let layer = self.arc_radius_handle.layer; - self.arc_radius_handle.overlays(document, overlay_context); - self.sweep_angle_gizmo.overlays(layer, document, input, mouse_position, overlay_context); + self.arc_radius_handle.overlays(ctx, overlay_context); + self.sweep_angle_gizmo.overlays(layer, mouse_position, ctx, overlay_context); } - self.sweep_angle_gizmo.overlays(selected_shape_layer, document, input, mouse_position, overlay_context); - self.arc_radius_handle.overlays(document, overlay_context); + self.sweep_angle_gizmo.overlays(selected_shape_layer, mouse_position, ctx, overlay_context); + self.arc_radius_handle.overlays(ctx, overlay_context); - arc_outline(selected_shape_layer.or(self.sweep_angle_gizmo.layer), document, overlay_context); + arc_outline(selected_shape_layer.or(self.sweep_angle_gizmo.layer), ctx.document, overlay_context); } fn mouse_cursor_icon(&self) -> Option { diff --git a/editor/src/messages/tool/common_functionality/shapes/circle_shape.rs b/editor/src/messages/tool/common_functionality/shapes/circle_shape.rs index d622950260..a3f86ccf44 100644 --- a/editor/src/messages/tool/common_functionality/shapes/circle_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/circle_shape.rs @@ -5,8 +5,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate}; use crate::messages::tool::common_functionality::gizmos::shape_gizmos::circle_arc_radius_handle::{RadiusHandle, RadiusHandleState}; 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::{ShapeGizmoHandler, ShapeToolModifierKey}; +use crate::messages::tool::common_functionality::shapes::shape_utility::{GizmoContext, ShapeGizmoHandler, ShapeToolModifierKey}; use crate::messages::tool::tool_messages::shape_tool::ShapeToolData; use crate::messages::tool::tool_messages::tool_prelude::*; use glam::DAffine2; @@ -23,8 +22,8 @@ impl ShapeGizmoHandler for CircleGizmoHandler { self.circle_radius_handle.hovered() } - fn handle_state(&mut self, selected_circle_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { - self.circle_radius_handle.handle_actions(selected_circle_layer, document, mouse_position, responses); + fn handle_state(&mut self, selected_circle_layer: LayerNodeIdentifier, mouse_position: DVec2, ctx: &mut GizmoContext) { + self.circle_radius_handle.handle_actions(selected_circle_layer, mouse_position, ctx); } fn handle_click(&mut self) { @@ -33,34 +32,19 @@ impl ShapeGizmoHandler for CircleGizmoHandler { } } - fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + fn handle_update(&mut self, drag_start: DVec2, ctx: &mut GizmoContext) { if self.circle_radius_handle.is_dragging() { - self.circle_radius_handle.update_inner_radius(document, input, responses, drag_start); + self.circle_radius_handle.update_inner_radius(drag_start, ctx); } } - fn overlays( - &self, - document: &DocumentMessageHandler, - _selected_circle_layer: Option, - _input: &InputPreprocessorMessageHandler, - _shape_editor: &mut &mut ShapeState, - _mouse_position: DVec2, - overlay_context: &mut OverlayContext, - ) { - self.circle_radius_handle.overlays(document, overlay_context); + fn overlays(&self, _selected_circle_layer: Option, _mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { + self.circle_radius_handle.overlays(ctx, overlay_context); } - fn dragging_overlays( - &self, - document: &DocumentMessageHandler, - _input: &InputPreprocessorMessageHandler, - _shape_editor: &mut &mut ShapeState, - _mouse_position: DVec2, - overlay_context: &mut OverlayContext, - ) { + fn dragging_overlays(&self, _mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { if self.circle_radius_handle.is_dragging() { - self.circle_radius_handle.overlays(document, overlay_context); + self.circle_radius_handle.overlays(ctx, overlay_context); } } diff --git a/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs b/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs index ef90e7a4fa..10ad42b8ae 100644 --- a/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs @@ -11,7 +11,7 @@ use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::PointRadiusHandle; use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::PointRadiusHandleState; 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::GizmoContext; use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler; use crate::messages::tool::common_functionality::shapes::shape_utility::polygon_outline; use crate::messages::tool::tool_messages::tool_prelude::*; @@ -31,9 +31,9 @@ impl ShapeGizmoHandler for PolygonGizmoHandler { self.number_of_points_dial.is_hovering() || self.point_radius_handle.hovered() } - fn handle_state(&mut self, selected_star_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { - self.number_of_points_dial.handle_actions(selected_star_layer, mouse_position, document, responses); - self.point_radius_handle.handle_actions(selected_star_layer, document, mouse_position, responses); + fn handle_state(&mut self, selected_polygon_layer: LayerNodeIdentifier, mouse_position: DVec2, ctx: &mut GizmoContext) { + self.number_of_points_dial.handle_actions(selected_polygon_layer, mouse_position, ctx); + self.point_radius_handle.handle_actions(selected_polygon_layer, mouse_position, ctx); } fn handle_click(&mut self) { @@ -47,45 +47,31 @@ impl ShapeGizmoHandler for PolygonGizmoHandler { } } - fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + fn handle_update(&mut self, drag_start: DVec2, ctx: &mut GizmoContext) { if self.number_of_points_dial.is_dragging() { - self.number_of_points_dial.update_number_of_sides(document, input, responses, drag_start); + self.number_of_points_dial.update_number_of_sides(drag_start, ctx); } if self.point_radius_handle.is_dragging_or_snapped() { - self.point_radius_handle.update_inner_radius(document, input, responses, drag_start); + self.point_radius_handle.update_inner_radius(drag_start, ctx); } } - fn overlays( - &self, - document: &DocumentMessageHandler, - selected_polygon_layer: Option, - input: &InputPreprocessorMessageHandler, - shape_editor: &mut &mut ShapeState, - mouse_position: DVec2, - overlay_context: &mut OverlayContext, - ) { - self.number_of_points_dial.overlays(document, selected_polygon_layer, shape_editor, mouse_position, overlay_context); - self.point_radius_handle.overlays(selected_polygon_layer, document, input, overlay_context); + fn overlays(&self, selected_polygon_layer: Option, mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { + self.number_of_points_dial.overlays(selected_polygon_layer, mouse_position, ctx, overlay_context); + self.point_radius_handle.overlays(selected_polygon_layer, ctx, overlay_context); + let GizmoContext { document, .. } = ctx; polygon_outline(selected_polygon_layer, document, overlay_context); } - fn dragging_overlays( - &self, - document: &DocumentMessageHandler, - input: &InputPreprocessorMessageHandler, - shape_editor: &mut &mut ShapeState, - mouse_position: DVec2, - overlay_context: &mut OverlayContext, - ) { + fn dragging_overlays(&self, mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { if self.number_of_points_dial.is_dragging() { - self.number_of_points_dial.overlays(document, None, shape_editor, mouse_position, overlay_context); + self.number_of_points_dial.overlays(None, mouse_position, ctx, overlay_context); } if self.point_radius_handle.is_dragging_or_snapped() { - self.point_radius_handle.overlays(None, document, input, overlay_context); + self.point_radius_handle.overlays(None, ctx, overlay_context); } } diff --git a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs index b049612948..f1e250104e 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -79,13 +79,20 @@ impl ShapeType { pub type ShapeToolModifierKey = [Key; 3]; +pub struct GizmoContext<'a> { + pub document: &'a DocumentMessageHandler, + pub input: &'a InputPreprocessorMessageHandler, + pub responses: &'a mut VecDeque, + pub shape_editor: &'a mut ShapeState, +} + /// The `ShapeGizmoHandler` trait defines the interactive behavior and overlay logic for shape-specific tools in the editor. /// A gizmo is a visual handle or control point used to manipulate a shape's properties (e.g., number of sides, radius, angle). pub trait ShapeGizmoHandler { /// Called every frame to update the gizmo's interaction state based on the mouse position and selection. /// /// This includes detecting hover states and preparing interaction flags or visual feedback (e.g., highlighting a hovered handle). - fn handle_state(&mut self, selected_shape_layers: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque); + fn handle_state(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, ctx: &mut GizmoContext); /// Called when a mouse click occurs over the canvas and a gizmo handle is hovered. /// @@ -96,32 +103,17 @@ pub trait ShapeGizmoHandler { /// Called during a drag interaction to update the shape's parameters in real time. /// /// For example, a handle might calculate the distance from the drag start to determine a new radius or update the number of points. - fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque); + fn handle_update(&mut self, drag_start: DVec2, ctx: &mut GizmoContext); /// Draws the static or hover-dependent overlays associated with the gizmo. /// /// These overlays include visual indicators like shape outlines, control points, and hover highlights. - fn overlays( - &self, - document: &DocumentMessageHandler, - selected_shape_layers: Option, - input: &InputPreprocessorMessageHandler, - shape_editor: &mut &mut ShapeState, - mouse_position: DVec2, - overlay_context: &mut OverlayContext, - ); + fn overlays(&self, selected_shape_layers: Option, mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext); /// Draws overlays specifically during a drag operation. /// /// Used to give real-time visual feedback based on drag progress, such as showing the updated shape preview or snapping guides. - fn dragging_overlays( - &self, - document: &DocumentMessageHandler, - input: &InputPreprocessorMessageHandler, - shape_editor: &mut &mut ShapeState, - mouse_position: DVec2, - overlay_context: &mut OverlayContext, - ); + fn dragging_overlays(&self, mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext); /// Returns `true` if any handle or control point in the gizmo is currently being hovered. fn is_any_gizmo_hovered(&self) -> bool; diff --git a/editor/src/messages/tool/common_functionality/shapes/star_shape.rs b/editor/src/messages/tool/common_functionality/shapes/star_shape.rs index 4effa98957..0f44784c1e 100644 --- a/editor/src/messages/tool/common_functionality/shapes/star_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/star_shape.rs @@ -8,8 +8,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Inp use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::{NumberOfPointsDial, NumberOfPointsDialState}; use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::{PointRadiusHandle, PointRadiusHandleState}; 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::{ShapeGizmoHandler, star_outline}; +use crate::messages::tool::common_functionality::shapes::shape_utility::{GizmoContext, ShapeGizmoHandler, star_outline}; use crate::messages::tool::tool_messages::tool_prelude::*; use core::f64; use glam::DAffine2; @@ -28,9 +27,9 @@ impl ShapeGizmoHandler for StarGizmoHandler { self.number_of_points_dial.is_hovering() || self.point_radius_handle.hovered() } - fn handle_state(&mut self, selected_star_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { - self.number_of_points_dial.handle_actions(selected_star_layer, mouse_position, document, responses); - self.point_radius_handle.handle_actions(selected_star_layer, document, mouse_position, responses); + fn handle_state(&mut self, selected_star_layer: LayerNodeIdentifier, mouse_position: DVec2, ctx: &mut GizmoContext) { + self.number_of_points_dial.handle_actions(selected_star_layer, mouse_position, ctx); + self.point_radius_handle.handle_actions(selected_star_layer, mouse_position, ctx); } fn handle_click(&mut self) { @@ -44,45 +43,30 @@ impl ShapeGizmoHandler for StarGizmoHandler { } } - fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + fn handle_update(&mut self, drag_start: DVec2, ctx: &mut GizmoContext) { if self.number_of_points_dial.is_dragging() { - self.number_of_points_dial.update_number_of_sides(document, input, responses, drag_start); + self.number_of_points_dial.update_number_of_sides(drag_start, ctx); } if self.point_radius_handle.is_dragging_or_snapped() { - self.point_radius_handle.update_inner_radius(document, input, responses, drag_start); + self.point_radius_handle.update_inner_radius(drag_start, ctx); } } - fn overlays( - &self, - document: &DocumentMessageHandler, - selected_star_layer: Option, - input: &InputPreprocessorMessageHandler, - shape_editor: &mut &mut ShapeState, - mouse_position: DVec2, - overlay_context: &mut OverlayContext, - ) { - self.number_of_points_dial.overlays(document, selected_star_layer, shape_editor, mouse_position, overlay_context); - self.point_radius_handle.overlays(selected_star_layer, document, input, overlay_context); + fn overlays(&self, selected_star_layer: Option, mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { + self.number_of_points_dial.overlays(selected_star_layer, mouse_position, ctx, overlay_context); + self.point_radius_handle.overlays(selected_star_layer, ctx, overlay_context); - star_outline(selected_star_layer, document, overlay_context); + star_outline(selected_star_layer, ctx.document, overlay_context); } - fn dragging_overlays( - &self, - document: &DocumentMessageHandler, - input: &InputPreprocessorMessageHandler, - shape_editor: &mut &mut ShapeState, - mouse_position: DVec2, - overlay_context: &mut OverlayContext, - ) { + fn dragging_overlays(&self, mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { if self.number_of_points_dial.is_dragging() { - self.number_of_points_dial.overlays(document, None, shape_editor, mouse_position, overlay_context); + self.number_of_points_dial.overlays(None, mouse_position, ctx, overlay_context); } if self.point_radius_handle.is_dragging_or_snapped() { - self.point_radius_handle.overlays(None, document, input, overlay_context); + self.point_radius_handle.overlays(None, ctx, overlay_context); } } diff --git a/editor/src/messages/tool/tool_messages/operation_tool.rs b/editor/src/messages/tool/tool_messages/operation_tool.rs index c8e5bf3f6d..40313d257a 100644 --- a/editor/src/messages/tool/tool_messages/operation_tool.rs +++ b/editor/src/messages/tool/tool_messages/operation_tool.rs @@ -1,6 +1,9 @@ use super::tool_prelude::*; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; +use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; +use crate::messages::tool::common_functionality::gizmos::gizmo_manager::GizmoManager; use crate::messages::tool::common_functionality::operations::circular_repeat::{CircularRepeatOperation, CircularRepeatOperationData}; +use crate::messages::tool::common_functionality::shapes::shape_utility::GizmoContext; #[derive(Debug, Clone, Copy, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)] pub enum OperationType { @@ -52,6 +55,7 @@ pub enum OperationToolMessage { pub enum OperationToolFsmState { #[default] Ready, + ModifyingGizmo, Drawing, } @@ -124,7 +128,7 @@ impl<'a> MessageHandler> for Oper Confirm, Abort, ), - OperationToolFsmState::Drawing => actions!(OperationToolMessageDiscriminant; + OperationToolFsmState::Drawing | OperationToolFsmState::ModifyingGizmo => actions!(OperationToolMessageDiscriminant; DragStop, PointerMove, Confirm, @@ -151,12 +155,24 @@ impl ToolTransition for OperationTool { pub struct OperationToolData { pub drag_start: DVec2, pub circular_operation_data: CircularRepeatOperationData, + gizmo_manager: GizmoManager, } impl OperationToolData { fn cleanup(&mut self) { CircularRepeatOperation::cleanup(self); } + + fn common_overlays(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, overlay_context: &mut OverlayContext) { + for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) { + outline_layer(layer, document, overlay_context); + + let Some(hovered_layer) = document.click(input) else { continue }; + if hovered_layer != layer { + outline_layer(hovered_layer, document, overlay_context); + } + } + } } impl Fsm for OperationToolFsmState { @@ -171,19 +187,37 @@ impl Fsm for OperationToolFsmState { tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { - let ToolActionMessageContext { document, input, .. } = tool_action_data; + let ToolActionMessageContext { document, input, shape_editor, .. } = tool_action_data; + + let mut ctx = GizmoContext { + document, + input, + responses, + shape_editor, + }; let ToolMessage::Operation(event) = event else { return self }; match (self, event) { (_, OperationToolMessage::Overlays { context: mut overlay_context }) => { - match tool_options.operation_type { - OperationType::CircularRepeat => CircularRepeatOperation::overlays(&self, tool_data, document, input, &mut overlay_context), - _ => {} + if matches!(self, OperationToolFsmState::Ready) { + tool_data.gizmo_manager.handle_operation_actions(input.mouse.position, &mut ctx); + tool_data.gizmo_manager.overlays(input.mouse.position, &mut ctx, &mut overlay_context); + CircularRepeatOperation::overlays(&self, tool_data, document, input, &mut overlay_context); } + if matches!(self, OperationToolFsmState::ModifyingGizmo) { + tool_data.gizmo_manager.overlays(input.mouse.position, &mut ctx, &mut overlay_context); + } + + tool_data.common_overlays(document, input, &mut overlay_context); + self } (OperationToolFsmState::Ready, OperationToolMessage::DragStart) => { + if tool_data.gizmo_manager.handle_click() { + tool_data.drag_start = input.mouse.position; + return OperationToolFsmState::ModifyingGizmo; + } match tool_options.operation_type { OperationType::CircularRepeat => { CircularRepeatOperation::create_node(tool_data, document, responses, input); @@ -193,7 +227,7 @@ impl Fsm for OperationToolFsmState { OperationToolFsmState::Drawing } - (OperationToolFsmState::Drawing, OperationToolMessage::DragStop) => { + (OperationToolFsmState::Drawing | OperationToolFsmState::ModifyingGizmo, OperationToolMessage::DragStop) => { if tool_data.drag_start.distance(input.mouse.position) < 5. { responses.add(DocumentMessage::AbortTransaction); }; @@ -216,6 +250,12 @@ impl Fsm for OperationToolFsmState { OperationToolFsmState::Drawing } + (OperationToolFsmState::ModifyingGizmo, OperationToolMessage::PointerMove) => { + // Don't add the repeat node unless dragging more that 5 px + tool_data.gizmo_manager.handle_update(tool_data.drag_start, &mut ctx); + + OperationToolFsmState::ModifyingGizmo + } (OperationToolFsmState::Drawing, OperationToolMessage::IncreaseCount) => { match tool_options.operation_type { OperationType::CircularRepeat => CircularRepeatOperation::increase_decrease_count(tool_data, true, document, responses), @@ -237,7 +277,7 @@ impl Fsm for OperationToolFsmState { (OperationToolFsmState::Drawing, OperationToolMessage::PointerOutsideViewport) => OperationToolFsmState::Drawing, (state, OperationToolMessage::PointerOutsideViewport) => state, - (OperationToolFsmState::Drawing, OperationToolMessage::Abort) => { + (OperationToolFsmState::Drawing | OperationToolFsmState::ModifyingGizmo, OperationToolMessage::Abort) => { responses.add(DocumentMessage::AbortTransaction); OperationToolFsmState::Ready } @@ -248,11 +288,11 @@ impl Fsm for OperationToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { - OperationToolFsmState::Ready => HintData(vec![HintGroup(vec![ + OperationToolFsmState::Ready | OperationToolFsmState::ModifyingGizmo => HintData(vec![HintGroup(vec![ HintInfo::mouse(MouseMotion::Lmb, "Draw Spline"), HintInfo::keys([Key::Shift], "Append to Selected Layer").prepend_plus(), ])]), - OperationToolFsmState::Drawing => HintData(vec![ + _ => HintData(vec![ HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Extend Spline")]), HintGroup(vec![HintInfo::keys([Key::Enter], "End Spline")]), @@ -266,3 +306,9 @@ impl Fsm for OperationToolFsmState { responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); } } + +fn outline_layer(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { + let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return }; + let viewport = document.metadata().transform_to_viewport(layer); + overlay_context.outline_vector(&vector, viewport); +} diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 2c6eea957b..8612824399 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -14,7 +14,7 @@ use crate::messages::tool::common_functionality::shapes::arc_shape::Arc; use crate::messages::tool::common_functionality::shapes::circle_shape::Circle; 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::shape_utility::{GizmoContext, ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays}; 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}; @@ -477,12 +477,24 @@ impl Fsm for ShapeToolFsmState { .unwrap_or(input.mouse.position); if matches!(self, Self::Ready(_)) && !input.keyboard.key(Key::Control) { - tool_data.gizmo_manager.handle_actions(mouse_position, document, responses); - tool_data.gizmo_manager.overlays(document, input, shape_editor, mouse_position, &mut overlay_context); + let mut ctx = GizmoContext { + document, + input, + responses, + shape_editor, + }; + tool_data.gizmo_manager.handle_actions(mouse_position, &mut ctx); + tool_data.gizmo_manager.overlays(mouse_position, &mut ctx, &mut overlay_context); } if matches!(self, ShapeToolFsmState::ModifyingGizmo) && !input.keyboard.key(Key::Control) { - tool_data.gizmo_manager.dragging_overlays(document, input, shape_editor, mouse_position, &mut overlay_context); + let mut ctx = GizmoContext { + document, + input, + responses, + shape_editor, + }; + tool_data.gizmo_manager.dragging_overlays(mouse_position, &mut ctx, &mut overlay_context); let cursor = tool_data.gizmo_manager.mouse_cursor_icon().unwrap_or(MouseCursorIcon::Crosshair); tool_data.cursor = cursor; responses.add(FrontendMessage::UpdateMouseCursor { cursor }); @@ -541,9 +553,15 @@ impl Fsm for ShapeToolFsmState { } if matches!(self, ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::DraggingLineEndpoints) { + let mut ctx = GizmoContext { + document, + input, + responses, + shape_editor, + }; Line::overlays(document, tool_data, &mut overlay_context); if tool_options.shape_type == ShapeType::Circle { - tool_data.gizmo_manager.overlays(document, input, shape_editor, mouse_position, &mut overlay_context); + tool_data.gizmo_manager.overlays(mouse_position, &mut ctx, &mut overlay_context); } } @@ -822,7 +840,13 @@ impl Fsm for ShapeToolFsmState { self } (ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::PointerMove { .. }) => { - tool_data.gizmo_manager.handle_update(tool_data.data.viewport_drag_start(document), document, input, responses); + let mut ctx = GizmoContext { + document, + input, + responses, + shape_editor, + }; + tool_data.gizmo_manager.handle_update(tool_data.data.viewport_drag_start(document), &mut ctx); responses.add(OverlaysMessage::Draw); From 0e501b663165aee44ed14c675eaf65cba6e40107 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Sun, 8 Feb 2026 04:03:16 +0530 Subject: [PATCH 8/8] remove tree file --- hierarchical_message_system_tree.txt | 1740 -------------------------- 1 file changed, 1740 deletions(-) delete mode 100644 hierarchical_message_system_tree.txt diff --git a/hierarchical_message_system_tree.txt b/hierarchical_message_system_tree.txt deleted file mode 100644 index e1ef1af94a..0000000000 --- a/hierarchical_message_system_tree.txt +++ /dev/null @@ -1,1740 +0,0 @@ -Message `editor\src\messages\message.rs` -├── Animation -│ ├── AnimationMessage `editor\src\messages\animation\animation_message.rs` -│ │ ├── ToggleLivePreview -│ │ ├── EnableLivePreview -│ │ ├── DisableLivePreview -│ │ ├── RestartAnimation -│ │ ├── SetFrameIndex -│ │ │ └── frame: f64 -│ │ ├── SetTime -│ │ │ └── time: f64 -│ │ ├── UpdateTime -│ │ ├── IncrementFrameCounter -│ │ └── SetAnimationTimeMode -│ │ └── animation_time_mode: AnimationTimeMode -│ └── AnimationMessageHandler `editor\src\messages\animation\animation_message_handler.rs` -│ ├── live_preview_recently_zero: bool -│ ├── timestamp: f64 -│ ├── frame_index: f64 -│ ├── animation_state: AnimationState -│ ├── fps: f64 -│ └── animation_time_mode: AnimationTimeMode -├── AppWindow -│ ├── AppWindowMessage `editor\src\messages\app_window\app_window_message.rs` -│ │ ├── AppWindowMinimize -│ │ ├── AppWindowMaximize -│ │ └── AppWindowClose -│ └── AppWindowMessageHandler `editor\src\messages\app_window\app_window_message_handler.rs` -│ ├── platform: AppWindowPlatform -│ ├── maximized: bool -│ └── viewport_hole_punch_active: bool -├── Broadcast -│ ├── BroadcastMessage `editor\src\messages\broadcast\broadcast_message.rs` -│ │ ├── TriggerEvent -│ │ │ ├── EventMessage `editor\src\messages\broadcast\event\event_message.rs` -│ │ │ │ ├── AnimationFrame -│ │ │ │ ├── CanvasTransformed -│ │ │ │ ├── ToolAbort -│ │ │ │ ├── SelectionChanged -│ │ │ │ └── WorkingColorChanged -│ │ │ ├── EventMessageHandler `editor\src\messages\broadcast\event\event_message_handler.rs` -│ │ │ └── EventMessageContext `editor\src\messages\broadcast\event\event_message_handler.rs` -│ │ │ └── listeners: &'a mut HashMap> -│ │ ├── SubscribeEvent -│ │ │ ├── on: EventMessage -│ │ │ └── send: Box -│ │ └── UnsubscribeEvent -│ │ ├── on: EventMessage -│ │ └── send: Box -│ └── BroadcastMessageHandler `editor\src\messages\broadcast\broadcast_message_handler.rs` -│ ├── event: EventMessageHandler -│ └── listeners: HashMap> -├── Debug -│ ├── DebugMessage `editor\src\messages\debug\debug_message.rs` -│ │ ├── ToggleTraceLogs -│ │ ├── MessageOff -│ │ ├── MessageNames -│ │ └── MessageContents -│ └── DebugMessageHandler `editor\src\messages\debug\debug_message_handler.rs` -│ └── message_logging_verbosity: MessageLoggingVerbosity -├── Defer -│ ├── DeferMessage `editor\src\messages\defer\defer_message.rs` -│ │ ├── SetGraphSubmissionIndex -│ │ │ └── execution_id: u64 -│ │ ├── TriggerGraphRun -│ │ │ ├── execution_id: u64 -│ │ │ └── document_id: DocumentId -│ │ ├── AfterGraphRun -│ │ │ └── messages: Vec -│ │ ├── TriggerNavigationReady -│ │ └── AfterNavigationReady -│ │ └── messages: Vec -│ ├── DeferMessageHandler `editor\src\messages\defer\defer_message_handler.rs` -│ │ ├── after_graph_run: HashMap> -│ │ ├── after_viewport_resize: Vec -│ │ └── current_graph_submission_id: u64 -│ └── DeferMessageContext `editor\src\messages\defer\defer_message_handler.rs` -│ └── portfolio: &'a PortfolioMessageHandler -├── Dialog -│ ├── DialogMessage `editor\src\messages\dialog\dialog_message.rs` -│ │ ├── ExportDialog -│ │ │ ├── ExportDialogMessage `editor\src\messages\dialog\export_dialog\export_dialog_message.rs` -│ │ │ │ ├── FileType -│ │ │ │ │ └── file_type: FileType -│ │ │ │ ├── ScaleFactor -│ │ │ │ │ └── factor: f64 -│ │ │ │ ├── TransparentBackground -│ │ │ │ │ └── transparent: bool -│ │ │ │ ├── ExportBounds -│ │ │ │ │ └── bounds: ExportBounds -│ │ │ │ └── Submit -│ │ │ ├── ExportDialogMessageHandler `editor\src\messages\dialog\export_dialog\export_dialog_message_handler.rs` -│ │ │ │ ├── file_type: FileType -│ │ │ │ ├── scale_factor: f64 -│ │ │ │ ├── bounds: ExportBounds -│ │ │ │ ├── transparent_background: bool -│ │ │ │ ├── artboards: HashMap -│ │ │ │ └── has_selection: bool -│ │ │ └── ExportDialogMessageContext `editor\src\messages\dialog\export_dialog\export_dialog_message_handler.rs` -│ │ │ └── portfolio: &'a PortfolioMessageHandler -│ │ ├── NewDocumentDialog -│ │ │ ├── NewDocumentDialogMessage `editor\src\messages\dialog\new_document_dialog\new_document_dialog_message.rs` -│ │ │ │ ├── Name -│ │ │ │ │ └── name: String -│ │ │ │ ├── Infinite -│ │ │ │ │ └── infinite: bool -│ │ │ │ ├── DimensionsX -│ │ │ │ │ └── width: f64 -│ │ │ │ ├── DimensionsY -│ │ │ │ │ └── height: f64 -│ │ │ │ └── Submit -│ │ │ ├── NewDocumentDialogMessageHandler `editor\src\messages\dialog\new_document_dialog\new_document_dialog_message_handler.rs` -│ │ │ │ ├── name: String -│ │ │ │ ├── infinite: bool -│ │ │ │ └── dimensions: UVec2 -│ │ │ └── NewDocumentDialogMessageContext `editor\src\messages\dialog\new_document_dialog\new_document_dialog_message_handler.rs` -│ │ │ └── viewport_bounds: &'a ViewportBounds -│ │ ├── PreferencesDialog -│ │ │ ├── PreferencesDialogMessage `editor\src\messages\dialog\preferences_dialog\preferences_dialog_message.rs` -│ │ │ │ └── Confirm -│ │ │ ├── PreferencesDialogMessageHandler `editor\src\messages\dialog\preferences_dialog\preferences_dialog_message_handler.rs` -│ │ │ └── PreferencesDialogMessageContext `editor\src\messages\dialog\preferences_dialog\preferences_dialog_message_handler.rs` -│ │ │ └── preferences: &'a PreferencesMessageHandler -│ │ ├── CloseAllDocumentsWithConfirmation -│ │ ├── CloseDialogAndThen -│ │ │ └── followups: Vec -│ │ ├── DisplayDialogError -│ │ │ ├── title: String -│ │ │ └── description: String -│ │ ├── RequestAboutGraphiteDialog -│ │ ├── RequestAboutGraphiteDialogWithLocalizedCommitDate -│ │ │ ├── localized_commit_date: String -│ │ │ └── localized_commit_year: String -│ │ ├── RequestComingSoonDialog -│ │ │ └── issue: Option -│ │ ├── RequestDemoArtworkDialog -│ │ ├── RequestExportDialog -│ │ ├── RequestLicensesDialogWithLocalizedCommitDate -│ │ │ └── localized_commit_year: String -│ │ ├── RequestNewDocumentDialog -│ │ └── RequestPreferencesDialog -│ ├── DialogMessageHandler `editor\src\messages\dialog\dialog_message_handler.rs` -│ │ ├── export_dialog: ExportDialogMessageHandler -│ │ ├── new_document_dialog: NewDocumentDialogMessageHandler -│ │ └── preferences_dialog: PreferencesDialogMessageHandler -│ └── DialogMessageContext `editor\src\messages\dialog\dialog_message_handler.rs` -│ ├── portfolio: &'a PortfolioMessageHandler -│ ├── viewport_bounds: &'a ViewportBounds -│ └── preferences: &'a PreferencesMessageHandler -├── Frontend -│ ├── FrontendMessage `editor\src\messages\frontend\frontend_message.rs` -│ │ ├── DisplayDialog -│ │ │ ├── title: String -│ │ │ └── icon: String -│ │ ├── DisplayDialogDismiss -│ │ ├── DisplayDialogPanic -│ │ │ └── panic_info: String -│ │ ├── DisplayEditableTextbox -│ │ │ ├── text: String -│ │ │ ├── line_height_ratio: f64 -│ │ │ ├── font_size: f64 -│ │ │ ├── color: Color -│ │ │ ├── url: String -│ │ │ ├── transform: [f64; 6] -│ │ │ ├── max_width: Option -│ │ │ ├── max_height: Option -│ │ │ └── align: TextAlign -│ │ ├── DisplayEditableTextboxTransform -│ │ │ └── transform: [f64; 6] -│ │ ├── DisplayRemoveEditableTextbox -│ │ ├── SendUIMetadata -│ │ │ ├── node_descriptions: Vec<(String, String)> -│ │ │ └── node_types: Vec -│ │ ├── TriggerAboutGraphiteLocalizedCommitDate -│ │ │ └── commit_date: String -│ │ ├── TriggerSaveDocument -│ │ │ ├── document_id: DocumentId -│ │ │ ├── name: String -│ │ │ ├── path: Option -│ │ │ └── content: Vec -│ │ ├── TriggerSaveFile -│ │ │ ├── name: String -│ │ │ └── content: Vec -│ │ ├── TriggerExportImage -│ │ │ ├── svg: String -│ │ │ ├── name: String -│ │ │ ├── mime: String -│ │ │ └── size: (f64, f64) -│ │ ├── TriggerFetchAndOpenDocument -│ │ │ ├── name: String -│ │ │ └── filename: String -│ │ ├── TriggerFontLoad -│ │ │ └── font: Font -│ │ ├── TriggerImport -│ │ ├── TriggerIndexedDbRemoveDocument -│ │ │ └── document_id: DocumentId -│ │ ├── TriggerIndexedDbWriteDocument -│ │ │ ├── document: String -│ │ │ └── details: FrontendDocumentDetails -│ │ ├── TriggerLoadFirstAutoSaveDocument -│ │ ├── TriggerLoadRestAutoSaveDocuments -│ │ ├── TriggerLoadPreferences -│ │ ├── TriggerOpenDocument -│ │ ├── TriggerPaste -│ │ ├── TriggerSavePreferences -│ │ │ └── preferences: PreferencesMessageHandler -│ │ ├── TriggerSaveActiveDocument -│ │ │ └── document_id: DocumentId -│ │ ├── TriggerTextCommit -│ │ ├── TriggerTextCopy -│ │ │ └── copy_text: String -│ │ ├── TriggerVisitLink -│ │ │ └── url: String -│ │ ├── UpdateActiveDocument -│ │ │ └── document_id: DocumentId -│ │ ├── UpdateImportsExports -│ │ │ ├── imports: Vec<(FrontendGraphOutput, i32, i32)> -│ │ │ ├── exports: Vec<(FrontendGraphInput, i32, i32)> -│ │ │ ├── add_import: Option<(i32, i32)> -│ │ │ └── add_export: Option<(i32, i32)> -│ │ ├── UpdateInSelectedNetwork -│ │ │ └── in_selected_network: bool -│ │ ├── UpdateBox -│ │ │ └── box_selection: Option -│ │ ├── UpdateContextMenuInformation -│ │ │ └── context_menu_information: Option -│ │ ├── UpdateClickTargets -│ │ │ └── click_targets: Option -│ │ ├── UpdateGraphViewOverlay -│ │ │ └── open: bool -│ │ ├── UpdateDataPanelState -│ │ │ └── open: bool -│ │ ├── UpdatePropertiesPanelState -│ │ │ └── open: bool -│ │ ├── UpdateLayersPanelState -│ │ │ └── open: bool -│ │ ├── UpdateDataPanelLayout -│ │ │ ├── layout_target: LayoutTarget -│ │ │ └── diff: Vec -│ │ ├── UpdateImportReorderIndex -│ │ │ └── index: Option -│ │ ├── UpdateExportReorderIndex -│ │ │ └── index: Option -│ │ ├── UpdateLayerWidths -│ │ │ ├── layer_widths: HashMap -│ │ │ ├── chain_widths: HashMap -│ │ │ └── has_left_input_wire: HashMap -│ │ ├── UpdateDialogButtons -│ │ │ ├── layout_target: LayoutTarget -│ │ │ └── diff: Vec -│ │ ├── UpdateDialogColumn1 -│ │ │ ├── layout_target: LayoutTarget -│ │ │ └── diff: Vec -│ │ ├── UpdateDialogColumn2 -│ │ │ ├── layout_target: LayoutTarget -│ │ │ └── diff: Vec -│ │ ├── UpdateDocumentArtwork -│ │ │ └── svg: String -│ │ ├── UpdateImageData -│ │ │ └── image_data: Vec<(u64, Image)> -│ │ ├── UpdateDocumentBarLayout -│ │ │ ├── layout_target: LayoutTarget -│ │ │ └── diff: Vec -│ │ ├── UpdateDocumentLayerDetails -│ │ │ └── data: LayerPanelEntry -│ │ ├── UpdateDocumentLayerStructure -│ │ │ └── data_buffer: RawBuffer -│ │ ├── UpdateDocumentLayerStructureJs -│ │ │ └── data_buffer: JsRawBuffer -│ │ ├── UpdateDocumentModeLayout -│ │ │ ├── layout_target: LayoutTarget -│ │ │ └── diff: Vec -│ │ ├── UpdateDocumentRulers -│ │ │ ├── origin: (f64, f64) -│ │ │ ├── spacing: f64 -│ │ │ ├── interval: f64 -│ │ │ └── visible: bool -│ │ ├── UpdateDocumentScrollbars -│ │ │ ├── position: (f64, f64) -│ │ │ ├── size: (f64, f64) -│ │ │ └── multiplier: (f64, f64) -│ │ ├── UpdateEyedropperSamplingState -│ │ │ ├── mouse_position: Option<(f64, f64)> -│ │ │ ├── primary_color: String -│ │ │ ├── secondary_color: String -│ │ │ └── set_color_choice: Option -│ │ ├── UpdateGraphFadeArtwork -│ │ │ └── percentage: f64 -│ │ ├── UpdateInputHints -│ │ │ └── hint_data: HintData -│ │ ├── UpdateLayersPanelControlBarLeftLayout -│ │ │ ├── layout_target: LayoutTarget -│ │ │ └── diff: Vec -│ │ ├── UpdateLayersPanelControlBarRightLayout -│ │ │ ├── layout_target: LayoutTarget -│ │ │ └── diff: Vec -│ │ ├── UpdateLayersPanelBottomBarLayout -│ │ │ ├── layout_target: LayoutTarget -│ │ │ └── diff: Vec -│ │ ├── UpdateMenuBarLayout -│ │ │ ├── layout_target: LayoutTarget -│ │ │ └── layout: Vec -│ │ ├── UpdateMouseCursor -│ │ │ └── cursor: MouseCursorIcon -│ │ ├── UpdateNodeGraphNodes -│ │ │ └── nodes: Vec -│ │ ├── UpdateVisibleNodes -│ │ │ └── nodes: Vec -│ │ ├── UpdateNodeGraphWires -│ │ │ └── wires: Vec -│ │ ├── ClearAllNodeGraphWires -│ │ ├── UpdateNodeGraphControlBarLayout -│ │ │ ├── layout_target: LayoutTarget -│ │ │ └── diff: Vec -│ │ ├── UpdateNodeGraphSelection -│ │ │ └── selected: Vec -│ │ ├── UpdateNodeGraphTransform -│ │ │ └── transform: Transform -│ │ ├── UpdateNodeThumbnail -│ │ │ ├── id: NodeId -│ │ │ └── value: String -│ │ ├── UpdateOpenDocumentsList -│ │ │ └── open_documents: Vec -│ │ ├── UpdatePropertiesPanelLayout -│ │ │ ├── layout_target: LayoutTarget -│ │ │ └── diff: Vec -│ │ ├── UpdateToolOptionsLayout -│ │ │ ├── layout_target: LayoutTarget -│ │ │ └── diff: Vec -│ │ ├── UpdateToolShelfLayout -│ │ │ ├── layout_target: LayoutTarget -│ │ │ └── diff: Vec -│ │ ├── UpdateWirePathInProgress -│ │ │ └── wire_path: Option -│ │ ├── UpdateWorkingColorsLayout -│ │ │ ├── layout_target: LayoutTarget -│ │ │ └── diff: Vec -│ │ ├── UpdatePlatform -│ │ │ └── platform: AppWindowPlatform -│ │ ├── UpdateMaximized -│ │ │ └── maximized: bool -│ │ ├── UpdateViewportHolePunch -│ │ │ └── active: bool -│ │ └── RenderOverlays -│ │ └── context: OverlayContext -├── Globals -│ ├── GlobalsMessage `editor\src\messages\globals\globals_message.rs` -│ │ └── SetPlatform -│ │ └── platform: Platform -│ └── GlobalsMessageHandler `editor\src\messages\globals\globals_message_handler.rs` -├── InputPreprocessor -│ ├── InputPreprocessorMessage `editor\src\messages\input_preprocessor\input_preprocessor_message.rs` -│ │ ├── BoundsOfViewports -│ │ │ └── bounds_of_viewports: Vec -│ │ ├── DoubleClick -│ │ │ ├── editor_mouse_state: EditorMouseState -│ │ │ └── modifier_keys: ModifierKeys -│ │ ├── KeyDown -│ │ │ ├── key: Key -│ │ │ ├── key_repeat: bool -│ │ │ └── modifier_keys: ModifierKeys -│ │ ├── KeyUp -│ │ │ ├── key: Key -│ │ │ ├── key_repeat: bool -│ │ │ └── modifier_keys: ModifierKeys -│ │ ├── PointerDown -│ │ │ ├── editor_mouse_state: EditorMouseState -│ │ │ └── modifier_keys: ModifierKeys -│ │ ├── PointerMove -│ │ │ ├── editor_mouse_state: EditorMouseState -│ │ │ └── modifier_keys: ModifierKeys -│ │ ├── PointerUp -│ │ │ ├── editor_mouse_state: EditorMouseState -│ │ │ └── modifier_keys: ModifierKeys -│ │ ├── PointerShake -│ │ │ ├── editor_mouse_state: EditorMouseState -│ │ │ └── modifier_keys: ModifierKeys -│ │ ├── CurrentTime -│ │ │ └── timestamp: u64 -│ │ └── WheelScroll -│ │ ├── editor_mouse_state: EditorMouseState -│ │ └── modifier_keys: ModifierKeys -│ ├── InputPreprocessorMessageHandler `editor\src\messages\input_preprocessor\input_preprocessor_message_handler.rs` -│ │ ├── frame_time: FrameTimeInfo -│ │ ├── time: u64 -│ │ ├── keyboard: KeyStates -│ │ ├── mouse: MouseState -│ │ └── viewport_bounds: ViewportBounds -│ └── InputPreprocessorMessageContext `editor\src\messages\input_preprocessor\input_preprocessor_message_handler.rs` -│ └── keyboard_platform: KeyboardPlatformLayout -├── KeyMapping -│ ├── KeyMappingMessage `editor\src\messages\input_mapper\key_mapping\key_mapping_message.rs` -│ │ ├── Lookup -│ │ │ ├── InputMapperMessage `editor\src\messages\input_mapper\input_mapper_message.rs` -│ │ │ │ ├── KeyDown -│ │ │ │ │ └── Key -│ │ │ │ ├── KeyUp -│ │ │ │ │ └── Key -│ │ │ │ ├── KeyDownNoRepeat -│ │ │ │ │ └── Key -│ │ │ │ ├── KeyUpNoRepeat -│ │ │ │ │ └── Key -│ │ │ │ ├── DoubleClick -│ │ │ │ │ └── MouseButton -│ │ │ │ ├── PointerMove -│ │ │ │ ├── PointerShake -│ │ │ │ └── WheelScroll -│ │ │ ├── InputMapperMessageHandler `editor\src\messages\input_mapper\input_mapper_message_handler.rs` -│ │ │ │ └── mapping: Mapping -│ │ │ └── InputMapperMessageContext `editor\src\messages\input_mapper\input_mapper_message_handler.rs` -│ │ │ ├── input: &'a InputPreprocessorMessageHandler -│ │ │ └── actions: ActionList -│ │ └── ModifyMapping -│ │ └── mapping: MappingVariant -│ ├── KeyMappingMessageHandler `editor\src\messages\input_mapper\key_mapping\key_mapping_message_handler.rs` -│ │ └── mapping_handler: InputMapperMessageHandler -│ └── KeyMappingMessageContext `editor\src\messages\input_mapper\key_mapping\key_mapping_message_handler.rs` -│ ├── input: &'a InputPreprocessorMessageHandler -│ └── actions: ActionList -├── Layout -│ ├── LayoutMessage `editor\src\messages\layout\layout_message.rs` -│ │ ├── ResendActiveWidget -│ │ │ ├── layout_target: LayoutTarget -│ │ │ └── widget_id: WidgetId -│ │ ├── SendLayout -│ │ │ ├── layout: Layout -│ │ │ └── layout_target: LayoutTarget -│ │ ├── WidgetValueCommit -│ │ │ ├── layout_target: LayoutTarget -│ │ │ ├── widget_id: WidgetId -│ │ │ └── value: serde_json::Value -│ │ └── WidgetValueUpdate -│ │ ├── layout_target: LayoutTarget -│ │ ├── widget_id: WidgetId -│ │ └── value: serde_json::Value -│ ├── LayoutMessageHandler `editor\src\messages\layout\layout_message_handler.rs` -│ │ └── layouts: [Layout; LayoutTarget::LayoutTargetLength as usize] -│ └── LayoutMessageContext `editor\src\messages\layout\layout_message_handler.rs` -│ └── action_input_mapping: &'a dyn Fn(&MessageDiscriminant) -> Option -├── Portfolio -│ ├── PortfolioMessage `editor\src\messages\portfolio\portfolio_message.rs` -│ │ ├── MenuBar -│ │ │ ├── MenuBarMessage `editor\src\messages\portfolio\menu_bar\menu_bar_message.rs` -│ │ │ │ └── SendLayout -│ │ │ └── MenuBarMessageHandler `editor\src\messages\portfolio\menu_bar\menu_bar_message_handler.rs` -│ │ │ ├── has_active_document: bool -│ │ │ ├── canvas_tilted: bool -│ │ │ ├── canvas_flipped: bool -│ │ │ ├── rulers_visible: bool -│ │ │ ├── node_graph_open: bool -│ │ │ ├── has_selected_nodes: bool -│ │ │ ├── has_selected_layers: bool -│ │ │ ├── has_selection_history: (bool, bool) -│ │ │ ├── message_logging_verbosity: MessageLoggingVerbosity -│ │ │ ├── reset_node_definitions_on_open: bool -│ │ │ ├── make_path_editable_is_allowed: bool -│ │ │ ├── data_panel_open: bool -│ │ │ ├── layers_panel_open: bool -│ │ │ └── properties_panel_open: bool -│ │ ├── Document -│ │ │ ├── DocumentMessage `editor\src\messages\portfolio\document\document_message.rs` -│ │ │ │ ├── Noop -│ │ │ │ ├── GraphOperation -│ │ │ │ │ ├── GraphOperationMessage `editor\src\messages\portfolio\document\graph_operation\graph_operation_message.rs` -│ │ │ │ │ │ ├── FillSet -│ │ │ │ │ │ │ ├── layer: LayerNodeIdentifier -│ │ │ │ │ │ │ └── fill: Fill -│ │ │ │ │ │ ├── BlendingFillSet -│ │ │ │ │ │ │ ├── layer: LayerNodeIdentifier -│ │ │ │ │ │ │ └── fill: f64 -│ │ │ │ │ │ ├── OpacitySet -│ │ │ │ │ │ │ ├── layer: LayerNodeIdentifier -│ │ │ │ │ │ │ └── opacity: f64 -│ │ │ │ │ │ ├── BlendModeSet -│ │ │ │ │ │ │ ├── layer: LayerNodeIdentifier -│ │ │ │ │ │ │ └── blend_mode: BlendMode -│ │ │ │ │ │ ├── ClipModeToggle -│ │ │ │ │ │ │ └── layer: LayerNodeIdentifier -│ │ │ │ │ │ ├── StrokeSet -│ │ │ │ │ │ │ ├── layer: LayerNodeIdentifier -│ │ │ │ │ │ │ └── stroke: Stroke -│ │ │ │ │ │ ├── TransformChange -│ │ │ │ │ │ │ ├── layer: LayerNodeIdentifier -│ │ │ │ │ │ │ ├── transform: DAffine2 -│ │ │ │ │ │ │ ├── transform_in: TransformIn -│ │ │ │ │ │ │ └── skip_rerender: bool -│ │ │ │ │ │ ├── TransformSet -│ │ │ │ │ │ │ ├── layer: LayerNodeIdentifier -│ │ │ │ │ │ │ ├── transform: DAffine2 -│ │ │ │ │ │ │ ├── transform_in: TransformIn -│ │ │ │ │ │ │ └── skip_rerender: bool -│ │ │ │ │ │ ├── Vector -│ │ │ │ │ │ │ ├── layer: LayerNodeIdentifier -│ │ │ │ │ │ │ └── modification_type: VectorModificationType -│ │ │ │ │ │ ├── Brush -│ │ │ │ │ │ │ ├── layer: LayerNodeIdentifier -│ │ │ │ │ │ │ └── strokes: Vec -│ │ │ │ │ │ ├── SetUpstreamToChain -│ │ │ │ │ │ │ └── layer: LayerNodeIdentifier -│ │ │ │ │ │ ├── NewArtboard -│ │ │ │ │ │ │ ├── id: NodeId -│ │ │ │ │ │ │ └── artboard: Artboard -│ │ │ │ │ │ ├── NewBitmapLayer -│ │ │ │ │ │ │ ├── id: NodeId -│ │ │ │ │ │ │ ├── image_frame: Table> -│ │ │ │ │ │ │ ├── parent: LayerNodeIdentifier -│ │ │ │ │ │ │ └── insert_index: usize -│ │ │ │ │ │ ├── NewBooleanOperationLayer -│ │ │ │ │ │ │ ├── id: NodeId -│ │ │ │ │ │ │ ├── operation: graphene_std::path_bool::BooleanOperation -│ │ │ │ │ │ │ ├── parent: LayerNodeIdentifier -│ │ │ │ │ │ │ └── insert_index: usize -│ │ │ │ │ │ ├── NewCustomLayer -│ │ │ │ │ │ │ ├── id: NodeId -│ │ │ │ │ │ │ ├── nodes: Vec<(NodeId, NodeTemplate)> -│ │ │ │ │ │ │ ├── parent: LayerNodeIdentifier -│ │ │ │ │ │ │ └── insert_index: usize -│ │ │ │ │ │ ├── NewVectorLayer -│ │ │ │ │ │ │ ├── id: NodeId -│ │ │ │ │ │ │ ├── subpaths: Vec> -│ │ │ │ │ │ │ ├── parent: LayerNodeIdentifier -│ │ │ │ │ │ │ └── insert_index: usize -│ │ │ │ │ │ ├── NewTextLayer -│ │ │ │ │ │ │ ├── id: NodeId -│ │ │ │ │ │ │ ├── text: String -│ │ │ │ │ │ │ ├── font: Font -│ │ │ │ │ │ │ ├── typesetting: TypesettingConfig -│ │ │ │ │ │ │ ├── parent: LayerNodeIdentifier -│ │ │ │ │ │ │ └── insert_index: usize -│ │ │ │ │ │ ├── ResizeArtboard -│ │ │ │ │ │ │ ├── layer: LayerNodeIdentifier -│ │ │ │ │ │ │ ├── location: IVec2 -│ │ │ │ │ │ │ └── dimensions: IVec2 -│ │ │ │ │ │ ├── RemoveArtboards -│ │ │ │ │ │ └── NewSvg -│ │ │ │ │ │ ├── id: NodeId -│ │ │ │ │ │ ├── svg: String -│ │ │ │ │ │ ├── transform: DAffine2 -│ │ │ │ │ │ ├── parent: LayerNodeIdentifier -│ │ │ │ │ │ └── insert_index: usize -│ │ │ │ │ ├── GraphOperationMessageHandler `editor\src\messages\portfolio\document\graph_operation\graph_operation_message_handler.rs` -│ │ │ │ │ └── GraphOperationMessageContext `editor\src\messages\portfolio\document\graph_operation\graph_operation_message_handler.rs` -│ │ │ │ │ ├── network_interface: &'a mut NodeNetworkInterface -│ │ │ │ │ ├── collapsed: &'a mut CollapsedLayers -│ │ │ │ │ └── node_graph: &'a mut NodeGraphMessageHandler -│ │ │ │ ├── Navigation -│ │ │ │ │ ├── NavigationMessage `editor\src\messages\portfolio\document\navigation\navigation_message.rs` -│ │ │ │ │ │ ├── BeginCanvasPan -│ │ │ │ │ │ ├── BeginCanvasTilt -│ │ │ │ │ │ │ └── was_dispatched_from_menu: bool -│ │ │ │ │ │ ├── BeginCanvasZoom -│ │ │ │ │ │ ├── CanvasPan -│ │ │ │ │ │ │ └── delta: DVec2 -│ │ │ │ │ │ ├── CanvasPanAbortPrepare -│ │ │ │ │ │ │ └── x_not_y_axis: bool -│ │ │ │ │ │ ├── CanvasPanAbort -│ │ │ │ │ │ │ └── x_not_y_axis: bool -│ │ │ │ │ │ ├── CanvasPanByViewportFraction -│ │ │ │ │ │ │ └── delta: DVec2 -│ │ │ │ │ │ ├── CanvasPanMouseWheel -│ │ │ │ │ │ │ └── use_y_as_x: bool -│ │ │ │ │ │ ├── CanvasTiltResetAndZoomTo100Percent -│ │ │ │ │ │ ├── CanvasTiltSet -│ │ │ │ │ │ │ └── angle_radians: f64 -│ │ │ │ │ │ ├── CanvasZoomDecrease -│ │ │ │ │ │ │ └── center_on_mouse: bool -│ │ │ │ │ │ ├── CanvasZoomIncrease -│ │ │ │ │ │ │ └── center_on_mouse: bool -│ │ │ │ │ │ ├── CanvasZoomMouseWheel -│ │ │ │ │ │ ├── CanvasZoomSet -│ │ │ │ │ │ │ └── zoom_factor: f64 -│ │ │ │ │ │ ├── CanvasFlip -│ │ │ │ │ │ ├── EndCanvasPTZ -│ │ │ │ │ │ │ └── abort_transform: bool -│ │ │ │ │ │ ├── EndCanvasPTZWithClick -│ │ │ │ │ │ │ └── commit_key: Key -│ │ │ │ │ │ ├── FitViewportToBounds -│ │ │ │ │ │ │ ├── bounds: [DVec2; 2] -│ │ │ │ │ │ │ └── prevent_zoom_past_100: bool -│ │ │ │ │ │ ├── FitViewportToSelection -│ │ │ │ │ │ └── PointerMove -│ │ │ │ │ │ └── snap: Key -│ │ │ │ │ ├── NavigationMessageHandler `editor\src\messages\portfolio\document\navigation\navigation_message_handler.rs` -│ │ │ │ │ │ ├── navigation_operation: NavigationOperation -│ │ │ │ │ │ ├── mouse_position: ViewportPosition -│ │ │ │ │ │ ├── finish_operation_with_click: bool -│ │ │ │ │ │ └── abortable_pan_start: Option -│ │ │ │ │ └── NavigationMessageContext `editor\src\messages\portfolio\document\navigation\navigation_message_handler.rs` -│ │ │ │ │ ├── network_interface: &'a mut NodeNetworkInterface -│ │ │ │ │ ├── breadcrumb_network_path: &'a [NodeId] -│ │ │ │ │ ├── ipp: &'a InputPreprocessorMessageHandler -│ │ │ │ │ ├── document_ptz: &'a mut PTZ -│ │ │ │ │ ├── graph_view_overlay_open: bool -│ │ │ │ │ └── preferences: &'a PreferencesMessageHandler -│ │ │ │ ├── NodeGraph -│ │ │ │ │ ├── NodeGraphMessage `editor\src\messages\portfolio\document\node_graph\node_graph_message.rs` -│ │ │ │ │ │ ├── AddNodes -│ │ │ │ │ │ │ ├── nodes: Vec<(NodeId, NodeTemplate)> -│ │ │ │ │ │ │ └── new_ids: HashMap -│ │ │ │ │ │ ├── AddPathNode -│ │ │ │ │ │ ├── AddImport -│ │ │ │ │ │ ├── AddExport -│ │ │ │ │ │ ├── Init -│ │ │ │ │ │ ├── SelectedNodesUpdated -│ │ │ │ │ │ ├── Copy -│ │ │ │ │ │ ├── CreateNodeInLayerNoTransaction -│ │ │ │ │ │ │ ├── node_type: String -│ │ │ │ │ │ │ └── layer: LayerNodeIdentifier -│ │ │ │ │ │ ├── CreateNodeInLayerWithTransaction -│ │ │ │ │ │ │ ├── node_type: String -│ │ │ │ │ │ │ └── layer: LayerNodeIdentifier -│ │ │ │ │ │ ├── CreateNodeFromContextMenu -│ │ │ │ │ │ │ ├── node_id: Option -│ │ │ │ │ │ │ ├── node_type: String -│ │ │ │ │ │ │ ├── xy: Option<(i32, i32)> -│ │ │ │ │ │ │ └── add_transaction: bool -│ │ │ │ │ │ ├── CreateWire -│ │ │ │ │ │ │ ├── output_connector: OutputConnector -│ │ │ │ │ │ │ └── input_connector: InputConnector -│ │ │ │ │ │ ├── ConnectUpstreamOutputToInput -│ │ │ │ │ │ │ ├── downstream_input: InputConnector -│ │ │ │ │ │ │ └── input_connector: InputConnector -│ │ │ │ │ │ ├── Cut -│ │ │ │ │ │ ├── DeleteNodes -│ │ │ │ │ │ │ ├── node_ids: Vec -│ │ │ │ │ │ │ └── delete_children: bool -│ │ │ │ │ │ ├── DeleteSelectedNodes -│ │ │ │ │ │ │ └── delete_children: bool -│ │ │ │ │ │ ├── DisconnectInput -│ │ │ │ │ │ │ └── input_connector: InputConnector -│ │ │ │ │ │ ├── DisconnectRootNode -│ │ │ │ │ │ ├── EnterNestedNetwork -│ │ │ │ │ │ ├── DuplicateSelectedNodes -│ │ │ │ │ │ ├── ExposeInput -│ │ │ │ │ │ │ ├── input_connector: InputConnector -│ │ │ │ │ │ │ ├── set_to_exposed: bool -│ │ │ │ │ │ │ └── start_transaction: bool -│ │ │ │ │ │ ├── InsertNode -│ │ │ │ │ │ │ ├── node_id: NodeId -│ │ │ │ │ │ │ └── node_template: NodeTemplate -│ │ │ │ │ │ ├── InsertNodeBetween -│ │ │ │ │ │ │ ├── node_id: NodeId -│ │ │ │ │ │ │ ├── input_connector: InputConnector -│ │ │ │ │ │ │ └── insert_node_input_index: usize -│ │ │ │ │ │ ├── MergeSelectedNodes -│ │ │ │ │ │ ├── MoveLayerToStack -│ │ │ │ │ │ │ ├── layer: LayerNodeIdentifier -│ │ │ │ │ │ │ ├── parent: LayerNodeIdentifier -│ │ │ │ │ │ │ └── insert_index: usize -│ │ │ │ │ │ ├── MoveNodeToChainStart -│ │ │ │ │ │ │ ├── node_id: NodeId -│ │ │ │ │ │ │ └── parent: LayerNodeIdentifier -│ │ │ │ │ │ ├── SetChainPosition -│ │ │ │ │ │ │ └── node_id: NodeId -│ │ │ │ │ │ ├── PasteNodes -│ │ │ │ │ │ │ └── serialized_nodes: String -│ │ │ │ │ │ ├── PointerDown -│ │ │ │ │ │ │ ├── shift_click: bool -│ │ │ │ │ │ │ ├── control_click: bool -│ │ │ │ │ │ │ ├── alt_click: bool -│ │ │ │ │ │ │ └── right_click: bool -│ │ │ │ │ │ ├── PointerMove -│ │ │ │ │ │ │ └── shift: Key -│ │ │ │ │ │ ├── PointerUp -│ │ │ │ │ │ ├── PointerOutsideViewport -│ │ │ │ │ │ │ └── shift: Key -│ │ │ │ │ │ ├── ShakeNode -│ │ │ │ │ │ ├── RemoveImport -│ │ │ │ │ │ │ └── import_index: usize -│ │ │ │ │ │ ├── RemoveExport -│ │ │ │ │ │ │ └── export_index: usize -│ │ │ │ │ │ ├── ReorderImport -│ │ │ │ │ │ │ ├── start_index: usize -│ │ │ │ │ │ │ └── end_index: usize -│ │ │ │ │ │ ├── ReorderExport -│ │ │ │ │ │ │ ├── start_index: usize -│ │ │ │ │ │ │ └── end_index: usize -│ │ │ │ │ │ ├── RunDocumentGraph -│ │ │ │ │ │ ├── ForceRunDocumentGraph -│ │ │ │ │ │ ├── SelectedNodesAdd -│ │ │ │ │ │ │ └── nodes: Vec -│ │ │ │ │ │ ├── SelectedNodesRemove -│ │ │ │ │ │ │ └── nodes: Vec -│ │ │ │ │ │ ├── SelectedNodesSet -│ │ │ │ │ │ │ └── nodes: Vec -│ │ │ │ │ │ ├── SendClickTargets -│ │ │ │ │ │ ├── EndSendClickTargets -│ │ │ │ │ │ ├── UnloadWires -│ │ │ │ │ │ ├── SendWires -│ │ │ │ │ │ ├── UpdateVisibleNodes -│ │ │ │ │ │ ├── SendGraph -│ │ │ │ │ │ ├── SetGridAlignedEdges -│ │ │ │ │ │ ├── SetInputValue -│ │ │ │ │ │ │ ├── node_id: NodeId -│ │ │ │ │ │ │ ├── input_index: usize -│ │ │ │ │ │ │ └── value: TaggedValue -│ │ │ │ │ │ ├── SetInput -│ │ │ │ │ │ │ ├── input_connector: InputConnector -│ │ │ │ │ │ │ └── input: NodeInput -│ │ │ │ │ │ ├── SetDisplayName -│ │ │ │ │ │ │ ├── node_id: NodeId -│ │ │ │ │ │ │ ├── alias: String -│ │ │ │ │ │ │ └── skip_adding_history_step: bool -│ │ │ │ │ │ ├── SetDisplayNameImpl -│ │ │ │ │ │ │ ├── node_id: NodeId -│ │ │ │ │ │ │ └── alias: String -│ │ │ │ │ │ ├── SetToNodeOrLayer -│ │ │ │ │ │ │ ├── node_id: NodeId -│ │ │ │ │ │ │ └── is_layer: bool -│ │ │ │ │ │ ├── ShiftNodePosition -│ │ │ │ │ │ │ ├── node_id: NodeId -│ │ │ │ │ │ │ ├── x: i32 -│ │ │ │ │ │ │ └── y: i32 -│ │ │ │ │ │ ├── ShiftSelectedNodes -│ │ │ │ │ │ │ ├── direction: Direction -│ │ │ │ │ │ │ └── rubber_band: bool -│ │ │ │ │ │ ├── ShiftSelectedNodesByAmount -│ │ │ │ │ │ │ ├── graph_delta: IVec2 -│ │ │ │ │ │ │ └── rubber_band: bool -│ │ │ │ │ │ ├── TogglePreview -│ │ │ │ │ │ │ └── node_id: NodeId -│ │ │ │ │ │ ├── TogglePreviewImpl -│ │ │ │ │ │ │ └── node_id: NodeId -│ │ │ │ │ │ ├── SetImportExportName -│ │ │ │ │ │ │ ├── name: String -│ │ │ │ │ │ │ └── index: ImportOrExport -│ │ │ │ │ │ ├── SetImportExportNameImpl -│ │ │ │ │ │ │ ├── name: String -│ │ │ │ │ │ │ └── index: ImportOrExport -│ │ │ │ │ │ ├── ToggleSelectedAsLayersOrNodes -│ │ │ │ │ │ ├── ToggleSelectedLocked -│ │ │ │ │ │ ├── ToggleLocked -│ │ │ │ │ │ │ └── node_id: NodeId -│ │ │ │ │ │ ├── SetLocked -│ │ │ │ │ │ │ ├── node_id: NodeId -│ │ │ │ │ │ │ └── locked: bool -│ │ │ │ │ │ ├── ToggleSelectedIsPinned -│ │ │ │ │ │ ├── ToggleSelectedVisibility -│ │ │ │ │ │ ├── ToggleVisibility -│ │ │ │ │ │ │ └── node_id: NodeId -│ │ │ │ │ │ ├── SetPinned -│ │ │ │ │ │ │ ├── node_id: NodeId -│ │ │ │ │ │ │ └── pinned: bool -│ │ │ │ │ │ ├── SetVisibility -│ │ │ │ │ │ │ ├── node_id: NodeId -│ │ │ │ │ │ │ └── visible: bool -│ │ │ │ │ │ ├── SetLockedOrVisibilitySideEffects -│ │ │ │ │ │ │ └── node_ids: Vec -│ │ │ │ │ │ ├── UpdateEdges -│ │ │ │ │ │ ├── UpdateBoxSelection -│ │ │ │ │ │ ├── UpdateImportsExports -│ │ │ │ │ │ ├── UpdateLayerPanel -│ │ │ │ │ │ ├── UpdateNewNodeGraph -│ │ │ │ │ │ ├── UpdateTypes -│ │ │ │ │ │ │ ├── resolved_types: ResolvedDocumentNodeTypesDelta -│ │ │ │ │ │ │ └── node_graph_errors: GraphErrors -│ │ │ │ │ │ ├── UpdateActionButtons -│ │ │ │ │ │ ├── UpdateGraphBarRight -│ │ │ │ │ │ ├── UpdateInSelectedNetwork -│ │ │ │ │ │ ├── UpdateHints -│ │ │ │ │ │ └── SendSelectedNodes -│ │ │ │ │ ├── NodeGraphMessageHandler `editor\src\messages\portfolio\document\node_graph\node_graph_message_handler.rs` -│ │ │ │ │ │ ├── network: Vec -│ │ │ │ │ │ ├── node_graph_errors: GraphErrors -│ │ │ │ │ │ ├── has_selection: bool -│ │ │ │ │ │ ├── widgets: [LayoutGroup; 2] -│ │ │ │ │ │ ├── begin_dragging: bool -│ │ │ │ │ │ ├── node_has_moved_in_drag: bool -│ │ │ │ │ │ ├── drag_start: Option<(DragStart, bool)> -│ │ │ │ │ │ ├── drag_start_chain_nodes: Vec -│ │ │ │ │ │ ├── box_selection_start: Option<(DVec2, bool)> -│ │ │ │ │ │ ├── selection_before_pointer_down: Vec -│ │ │ │ │ │ ├── shift_without_push: bool -│ │ │ │ │ │ ├── disconnecting: Option -│ │ │ │ │ │ ├── initial_disconnecting: bool -│ │ │ │ │ │ ├── select_if_not_dragged: Option -│ │ │ │ │ │ ├── wire_in_progress_from_connector: Option -│ │ │ │ │ │ ├── wire_in_progress_type: FrontendGraphDataType -│ │ │ │ │ │ ├── wire_in_progress_to_connector: Option -│ │ │ │ │ │ ├── context_menu: Option -│ │ │ │ │ │ ├── deselect_on_pointer_up: Option -│ │ │ │ │ │ ├── auto_panning: AutoPanning -│ │ │ │ │ │ ├── preview_on_mouse_up: Option -│ │ │ │ │ │ ├── reordering_import: Option -│ │ │ │ │ │ ├── reordering_export: Option -│ │ │ │ │ │ ├── end_index: Option -│ │ │ │ │ │ ├── frontend_nodes: Vec -│ │ │ │ │ │ └── frontend_wires: HashSet<(NodeId, usize)> -│ │ │ │ │ └── NodeGraphMessageContext `editor\src\messages\portfolio\document\node_graph\node_graph_message_handler.rs` -│ │ │ │ │ ├── network_interface: &'a mut NodeNetworkInterface -│ │ │ │ │ ├── selection_network_path: &'a [NodeId] -│ │ │ │ │ ├── breadcrumb_network_path: &'a [NodeId] -│ │ │ │ │ ├── document_id: DocumentId -│ │ │ │ │ ├── collapsed: &'a mut CollapsedLayers -│ │ │ │ │ ├── ipp: &'a InputPreprocessorMessageHandler -│ │ │ │ │ ├── graph_view_overlay_open: bool -│ │ │ │ │ ├── graph_fade_artwork_percentage: f64 -│ │ │ │ │ ├── navigation_handler: &'a NavigationMessageHandler -│ │ │ │ │ ├── preferences: &'a PreferencesMessageHandler -│ │ │ │ │ └── layers_panel_open: bool -│ │ │ │ ├── Overlays -│ │ │ │ │ ├── OverlaysMessage `editor\src\messages\portfolio\document\overlays\overlays_message.rs` -│ │ │ │ │ │ ├── Draw -│ │ │ │ │ │ ├── AddProvider -│ │ │ │ │ │ │ └── provider: OverlayProvider -│ │ │ │ │ │ └── RemoveProvider -│ │ │ │ │ │ └── provider: OverlayProvider -│ │ │ │ │ ├── OverlaysMessageHandler `editor\src\messages\portfolio\document\overlays\overlays_message_handler.rs` -│ │ │ │ │ │ └── overlay_providers: HashSet -│ │ │ │ │ └── OverlaysMessageContext `editor\src\messages\portfolio\document\overlays\overlays_message_handler.rs` -│ │ │ │ │ ├── visibility_settings: OverlaysVisibilitySettings -│ │ │ │ │ ├── ipp: &'a InputPreprocessorMessageHandler -│ │ │ │ │ └── device_pixel_ratio: f64 -│ │ │ │ ├── PropertiesPanel -│ │ │ │ │ ├── PropertiesPanelMessage `editor\src\messages\portfolio\document\properties_panel\properties_panel_message.rs` -│ │ │ │ │ │ ├── Clear -│ │ │ │ │ │ └── Refresh -│ │ │ │ │ ├── PropertiesPanelMessageHandler `editor\src\messages\portfolio\document\properties_panel\properties_panel_message_handler.rs` -│ │ │ │ │ └── PropertiesPanelMessageContext `editor\src\messages\portfolio\document\properties_panel\properties_panel_message_handler.rs` -│ │ │ │ │ ├── network_interface: &'a mut NodeNetworkInterface -│ │ │ │ │ ├── selection_network_path: &'a [NodeId] -│ │ │ │ │ ├── document_name: &'a str -│ │ │ │ │ ├── executor: &'a mut NodeGraphExecutor -│ │ │ │ │ ├── persistent_data: &'a PersistentData -│ │ │ │ │ └── properties_panel_open: bool -│ │ │ │ ├── DataPanel -│ │ │ │ │ ├── DataPanelMessage `editor\src\messages\portfolio\document\data_panel\data_panel_message.rs` -│ │ │ │ │ │ ├── UpdateLayout -│ │ │ │ │ │ │ └── inspect_result: InspectResult -│ │ │ │ │ │ ├── ClearLayout -│ │ │ │ │ │ ├── PushToElementPath -│ │ │ │ │ │ │ └── index: usize -│ │ │ │ │ │ ├── TruncateElementPath -│ │ │ │ │ │ │ └── len: usize -│ │ │ │ │ │ └── ViewVectorTableTab -│ │ │ │ │ │ └── tab: VectorTableTab -│ │ │ │ │ ├── DataPanelMessageHandler `editor\src\messages\portfolio\document\data_panel\data_panel_message_handler.rs` -│ │ │ │ │ │ ├── introspected_node: Option -│ │ │ │ │ │ ├── introspected_data: Option> -│ │ │ │ │ │ ├── element_path: Vec -│ │ │ │ │ │ └── active_vector_table_tab: VectorTableTab -│ │ │ │ │ └── DataPanelMessageContext `editor\src\messages\portfolio\document\data_panel\data_panel_message_handler.rs` -│ │ │ │ │ ├── network_interface: &'a mut NodeNetworkInterface -│ │ │ │ │ └── data_panel_open: bool -│ │ │ │ ├── AlignSelectedLayers -│ │ │ │ │ ├── axis: AlignAxis -│ │ │ │ │ └── aggregate: AlignAggregate -│ │ │ │ ├── RemoveArtboards -│ │ │ │ ├── ClearLayersPanel -│ │ │ │ ├── CreateEmptyFolder -│ │ │ │ ├── DeleteNode -│ │ │ │ │ └── node_id: NodeId -│ │ │ │ ├── DeleteSelectedLayers -│ │ │ │ ├── DeselectAllLayers -│ │ │ │ ├── DocumentHistoryBackward -│ │ │ │ ├── DocumentHistoryForward -│ │ │ │ ├── DocumentStructureChanged -│ │ │ │ ├── DrawArtboardOverlays -│ │ │ │ │ └── context: OverlayContext -│ │ │ │ ├── DuplicateSelectedLayers -│ │ │ │ ├── EnterNestedNetwork -│ │ │ │ │ └── node_id: NodeId -│ │ │ │ ├── Escape -│ │ │ │ ├── ExitNestedNetwork -│ │ │ │ │ └── steps_back: usize -│ │ │ │ ├── FlipSelectedLayers -│ │ │ │ │ └── flip_axis: FlipAxis -│ │ │ │ ├── RotateSelectedLayers -│ │ │ │ │ └── degrees: f64 -│ │ │ │ ├── GraphViewOverlay -│ │ │ │ │ └── open: bool -│ │ │ │ ├── GraphViewOverlayToggle -│ │ │ │ ├── GridOptions -│ │ │ │ │ └── options: GridSnapping -│ │ │ │ ├── GridOverlays -│ │ │ │ │ └── context: OverlayContext -│ │ │ │ ├── GridVisibility -│ │ │ │ │ └── visible: bool -│ │ │ │ ├── GroupSelectedLayers -│ │ │ │ │ └── group_folder_type: GroupFolderType -│ │ │ │ ├── MoveSelectedLayersTo -│ │ │ │ │ ├── parent: LayerNodeIdentifier -│ │ │ │ │ └── insert_index: usize -│ │ │ │ ├── MoveSelectedLayersToGroup -│ │ │ │ │ └── parent: LayerNodeIdentifier -│ │ │ │ ├── NudgeSelectedLayers -│ │ │ │ │ ├── delta_x: f64 -│ │ │ │ │ ├── delta_y: f64 -│ │ │ │ │ ├── resize: Key -│ │ │ │ │ └── resize_opposite_corner: Key -│ │ │ │ ├── PasteImage -│ │ │ │ │ ├── name: Option -│ │ │ │ │ ├── image: Image -│ │ │ │ │ ├── mouse: Option<(f64, f64)> -│ │ │ │ │ └── parent_and_insert_index: Option<(LayerNodeIdentifier, usize)> -│ │ │ │ ├── PasteSvg -│ │ │ │ │ ├── name: Option -│ │ │ │ │ ├── svg: String -│ │ │ │ │ ├── mouse: Option<(f64, f64)> -│ │ │ │ │ └── parent_and_insert_index: Option<(LayerNodeIdentifier, usize)> -│ │ │ │ ├── Redo -│ │ │ │ ├── RenameDocument -│ │ │ │ │ └── new_name: String -│ │ │ │ ├── RenderRulers -│ │ │ │ ├── RenderScrollbars -│ │ │ │ ├── SaveDocument -│ │ │ │ ├── SaveDocumentAs -│ │ │ │ ├── SavedDocument -│ │ │ │ │ └── path: Option -│ │ │ │ ├── SelectParentLayer -│ │ │ │ ├── SelectAllLayers -│ │ │ │ ├── SelectedLayersLower -│ │ │ │ ├── SelectedLayersLowerToBack -│ │ │ │ ├── SelectedLayersRaise -│ │ │ │ ├── SelectedLayersRaiseToFront -│ │ │ │ ├── SelectedLayersReverse -│ │ │ │ ├── SelectedLayersReorder -│ │ │ │ │ └── relative_index_offset: isize -│ │ │ │ ├── ClipLayer -│ │ │ │ │ └── id: NodeId -│ │ │ │ ├── SelectLayer -│ │ │ │ │ ├── id: NodeId -│ │ │ │ │ ├── ctrl: bool -│ │ │ │ │ └── shift: bool -│ │ │ │ ├── SetActivePanel -│ │ │ │ │ └── active_panel: PanelType -│ │ │ │ ├── SetBlendModeForSelectedLayers -│ │ │ │ │ └── blend_mode: BlendMode -│ │ │ │ ├── SetGraphFadeArtwork -│ │ │ │ │ └── percentage: f64 -│ │ │ │ ├── SetNodePinned -│ │ │ │ │ ├── node_id: NodeId -│ │ │ │ │ └── pinned: bool -│ │ │ │ ├── SetOpacityForSelectedLayers -│ │ │ │ │ └── opacity: f64 -│ │ │ │ ├── SetFillForSelectedLayers -│ │ │ │ │ └── fill: f64 -│ │ │ │ ├── SetOverlaysVisibility -│ │ │ │ │ ├── visible: bool -│ │ │ │ │ └── overlays_type: Option -│ │ │ │ ├── SetRangeSelectionLayer -│ │ │ │ │ └── new_layer: Option -│ │ │ │ ├── SetSnapping -│ │ │ │ │ ├── closure: Optionfn(&'a mut SnappingState) -> &'a mut bool> -│ │ │ │ │ └── snapping_state: bool -│ │ │ │ ├── SetToNodeOrLayer -│ │ │ │ │ ├── node_id: NodeId -│ │ │ │ │ └── is_layer: bool -│ │ │ │ ├── SetViewMode -│ │ │ │ │ └── view_mode: ViewMode -│ │ │ │ ├── AddTransaction -│ │ │ │ ├── StartTransaction -│ │ │ │ ├── EndTransaction -│ │ │ │ ├── CommitTransaction -│ │ │ │ ├── AbortTransaction -│ │ │ │ ├── RepeatedAbortTransaction -│ │ │ │ │ └── undo_count: usize -│ │ │ │ ├── ToggleLayerExpansion -│ │ │ │ │ ├── id: NodeId -│ │ │ │ │ └── recursive: bool -│ │ │ │ ├── ToggleSelectedVisibility -│ │ │ │ ├── ToggleSelectedLocked -│ │ │ │ ├── ToggleGridVisibility -│ │ │ │ ├── ToggleOverlaysVisibility -│ │ │ │ ├── ToggleSnapping -│ │ │ │ ├── UpdateUpstreamTransforms -│ │ │ │ │ ├── upstream_footprints: HashMap -│ │ │ │ │ ├── local_transforms: HashMap -│ │ │ │ │ └── first_element_source_id: HashMap> -│ │ │ │ ├── UpdateClickTargets -│ │ │ │ │ └── click_targets: HashMap> -│ │ │ │ ├── UpdateClipTargets -│ │ │ │ │ └── clip_targets: HashSet -│ │ │ │ ├── Undo -│ │ │ │ ├── UngroupSelectedLayers -│ │ │ │ ├── UngroupLayer -│ │ │ │ │ └── layer: LayerNodeIdentifier -│ │ │ │ ├── PTZUpdate -│ │ │ │ ├── SelectionStepBack -│ │ │ │ ├── SelectionStepForward -│ │ │ │ ├── WrapContentInArtboard -│ │ │ │ │ └── place_artboard_at_origin: bool -│ │ │ │ ├── ZoomCanvasTo100Percent -│ │ │ │ ├── ZoomCanvasTo200Percent -│ │ │ │ └── ZoomCanvasToFitAll -│ │ │ ├── DocumentMessageHandler `editor\src\messages\portfolio\document\document_message_handler.rs` -│ │ │ │ ├── navigation_handler: NavigationMessageHandler -│ │ │ │ ├── node_graph_handler: NodeGraphMessageHandler -│ │ │ │ ├── overlays_message_handler: OverlaysMessageHandler -│ │ │ │ ├── properties_panel_message_handler: PropertiesPanelMessageHandler -│ │ │ │ ├── data_panel_message_handler: DataPanelMessageHandler -│ │ │ │ ├── network_interface: NodeNetworkInterface -│ │ │ │ ├── collapsed: CollapsedLayers -│ │ │ │ ├── commit_hash: String -│ │ │ │ ├── document_ptz: PTZ -│ │ │ │ ├── document_mode: DocumentMode -│ │ │ │ ├── view_mode: ViewMode -│ │ │ │ ├── overlays_visibility_settings: OverlaysVisibilitySettings -│ │ │ │ ├── rulers_visible: bool -│ │ │ │ ├── snapping_state: SnappingState -│ │ │ │ ├── graph_view_overlay_open: bool -│ │ │ │ ├── graph_fade_artwork_percentage: f64 -│ │ │ │ ├── name: String -│ │ │ │ ├── path: Option -│ │ │ │ ├── breadcrumb_network_path: Vec -│ │ │ │ ├── selection_network_path: Vec -│ │ │ │ ├── document_undo_history: VecDeque -│ │ │ │ ├── document_redo_history: VecDeque -│ │ │ │ ├── saved_hash: Option -│ │ │ │ ├── auto_saved_hash: Option -│ │ │ │ ├── layer_range_selection_reference: Option -│ │ │ │ └── is_loaded: bool -│ │ │ └── DocumentMessageContext `editor\src\messages\portfolio\document\document_message_handler.rs` -│ │ │ ├── document_id: DocumentId -│ │ │ ├── ipp: &'a InputPreprocessorMessageHandler -│ │ │ ├── persistent_data: &'a PersistentData -│ │ │ ├── executor: &'a mut NodeGraphExecutor -│ │ │ ├── current_tool: &'a ToolType -│ │ │ ├── preferences: &'a PreferencesMessageHandler -│ │ │ ├── device_pixel_ratio: f64 -│ │ │ ├── data_panel_open: bool -│ │ │ ├── layers_panel_open: bool -│ │ │ └── properties_panel_open: bool -│ │ ├── Init -│ │ ├── DocumentPassMessage -│ │ │ ├── document_id: DocumentId -│ │ │ └── message: DocumentMessage -│ │ ├── AutoSaveActiveDocument -│ │ ├── AutoSaveAllDocuments -│ │ ├── AutoSaveDocument -│ │ │ └── document_id: DocumentId -│ │ ├── CloseActiveDocumentWithConfirmation -│ │ ├── CloseAllDocuments -│ │ ├── CloseAllDocumentsWithConfirmation -│ │ ├── CloseDocument -│ │ │ └── document_id: DocumentId -│ │ ├── CloseDocumentWithConfirmation -│ │ │ └── document_id: DocumentId -│ │ ├── Copy -│ │ │ └── clipboard: Clipboard -│ │ ├── Cut -│ │ │ └── clipboard: Clipboard -│ │ ├── DeleteDocument -│ │ │ └── document_id: DocumentId -│ │ ├── DestroyAllDocuments -│ │ ├── EditorPreferences -│ │ ├── FontLoaded -│ │ │ ├── font_family: String -│ │ │ ├── font_style: String -│ │ │ ├── preview_url: String -│ │ │ └── data: Vec -│ │ ├── Import -│ │ ├── LoadDocumentResources -│ │ │ └── document_id: DocumentId -│ │ ├── LoadFont -│ │ │ └── font: Font -│ │ ├── NewDocumentWithName -│ │ │ └── name: String -│ │ ├── NextDocument -│ │ ├── OpenDocument -│ │ ├── OpenDocumentFile -│ │ │ ├── document_name: Option -│ │ │ ├── document_path: Option -│ │ │ └── document_serialized_content: String -│ │ ├── OpenDocumentFileWithId -│ │ │ ├── document_id: DocumentId -│ │ │ ├── document_name: Option -│ │ │ ├── document_path: Option -│ │ │ ├── document_is_auto_saved: bool -│ │ │ ├── document_is_saved: bool -│ │ │ ├── document_serialized_content: String -│ │ │ └── to_front: bool -│ │ ├── ToggleResetNodesToDefinitionsOnOpen -│ │ ├── PasteIntoFolder -│ │ │ ├── clipboard: Clipboard -│ │ │ ├── parent: LayerNodeIdentifier -│ │ │ └── insert_index: usize -│ │ ├── PasteSerializedData -│ │ │ └── data: String -│ │ ├── PasteSerializedVector -│ │ │ └── data: String -│ │ ├── CenterPastedLayers -│ │ │ └── layers: Vec -│ │ ├── PasteImage -│ │ │ ├── name: Option -│ │ │ ├── image: Image -│ │ │ ├── mouse: Option<(f64, f64)> -│ │ │ └── parent_and_insert_index: Option<(LayerNodeIdentifier, usize)> -│ │ ├── PasteSvg -│ │ │ ├── name: Option -│ │ │ ├── svg: String -│ │ │ ├── mouse: Option<(f64, f64)> -│ │ │ └── parent_and_insert_index: Option<(LayerNodeIdentifier, usize)> -│ │ ├── PrevDocument -│ │ ├── SetActivePanel -│ │ │ └── panel: PanelType -│ │ ├── SetDevicePixelRatio -│ │ │ └── ratio: f64 -│ │ ├── SelectDocument -│ │ │ └── document_id: DocumentId -│ │ ├── SubmitDocumentExport -│ │ │ ├── name: String -│ │ │ ├── file_type: FileType -│ │ │ ├── scale_factor: f64 -│ │ │ ├── bounds: ExportBounds -│ │ │ └── transparent_background: bool -│ │ ├── SubmitActiveGraphRender -│ │ ├── SubmitGraphRender -│ │ │ ├── document_id: DocumentId -│ │ │ └── ignore_hash: bool -│ │ ├── ToggleDataPanelOpen -│ │ ├── TogglePropertiesPanelOpen -│ │ ├── ToggleLayersPanelOpen -│ │ ├── ToggleRulers -│ │ ├── UpdateDocumentWidgets -│ │ ├── UpdateOpenDocumentsList -│ │ └── UpdateVelloPreference -│ ├── PortfolioMessageHandler `editor\src\messages\portfolio\portfolio_message_handler.rs` -│ │ ├── menu_bar_message_handler: MenuBarMessageHandler -│ │ ├── documents: HashMap -│ │ ├── document_ids: VecDeque -│ │ ├── active_panel: PanelType -│ │ ├── active_document_id: Option -│ │ ├── copy_buffer: [Vec; INTERNAL_CLIPBOARD_COUNT as usize] -│ │ ├── persistent_data: PersistentData -│ │ ├── executor: NodeGraphExecutor -│ │ ├── selection_mode: SelectionMode -│ │ ├── device_pixel_ratio: Option -│ │ ├── reset_node_definitions_on_open: bool -│ │ ├── data_panel_open: bool -│ │ ├── layers_panel_open: bool -│ │ └── properties_panel_open: bool -│ └── PortfolioMessageContext `editor\src\messages\portfolio\portfolio_message_handler.rs` -│ ├── ipp: &'a InputPreprocessorMessageHandler -│ ├── preferences: &'a PreferencesMessageHandler -│ ├── animation: &'a AnimationMessageHandler -│ ├── current_tool: &'a ToolType -│ ├── message_logging_verbosity: MessageLoggingVerbosity -│ ├── reset_node_definitions_on_open: bool -│ └── timing_information: TimingInformation -├── Preferences -│ ├── PreferencesMessage `editor\src\messages\preferences\preferences_message.rs` -│ │ ├── Load -│ │ │ └── preferences: String -│ │ ├── ResetToDefaults -│ │ ├── UseVello -│ │ │ └── use_vello: bool -│ │ ├── SelectionMode -│ │ │ └── selection_mode: SelectionMode -│ │ ├── VectorMeshes -│ │ │ └── enabled: bool -│ │ ├── ModifyLayout -│ │ │ └── zoom_with_scroll: bool -│ │ ├── GraphWireStyle -│ │ │ └── style: GraphWireStyle -│ │ └── ViewportZoomWheelRate -│ │ └── rate: f64 -│ └── PreferencesMessageHandler `editor\src\messages\preferences\preferences_message_handler.rs` -│ ├── selection_mode: SelectionMode -│ ├── zoom_with_scroll: bool -│ ├── use_vello: bool -│ ├── vector_meshes: bool -│ ├── graph_wire_style: GraphWireStyle -│ └── viewport_zoom_wheel_rate: f64 -├── Tool -│ ├── ToolMessage `editor\src\messages\tool\tool_message.rs` -│ │ ├── TransformLayer -│ │ │ ├── TransformLayerMessage `editor\src\messages\tool\transform_layer\transform_layer_message.rs` -│ │ │ │ ├── Overlays -│ │ │ │ │ └── context: OverlayContext -│ │ │ │ ├── ApplyTransformOperation -│ │ │ │ │ └── final_transform: bool -│ │ │ │ ├── BeginTransformOperation -│ │ │ │ │ └── operation: TransformType -│ │ │ │ ├── BeginGrab -│ │ │ │ ├── BeginRotate -│ │ │ │ ├── BeginScale -│ │ │ │ ├── BeginGRS -│ │ │ │ │ └── operation: TransformType -│ │ │ │ ├── BeginGrabPen -│ │ │ │ │ ├── last_point: DVec2 -│ │ │ │ │ └── handle: DVec2 -│ │ │ │ ├── BeginRotatePen -│ │ │ │ │ ├── last_point: DVec2 -│ │ │ │ │ └── handle: DVec2 -│ │ │ │ ├── BeginScalePen -│ │ │ │ │ ├── last_point: DVec2 -│ │ │ │ │ └── handle: DVec2 -│ │ │ │ ├── CancelTransformOperation -│ │ │ │ ├── ConstrainX -│ │ │ │ ├── ConstrainY -│ │ │ │ ├── PointerMove -│ │ │ │ │ ├── slow_key: Key -│ │ │ │ │ └── increments_key: Key -│ │ │ │ ├── SelectionChanged -│ │ │ │ ├── TypeBackspace -│ │ │ │ ├── TypeDecimalPoint -│ │ │ │ ├── TypeDigit -│ │ │ │ │ └── digit: u8 -│ │ │ │ ├── TypeNegate -│ │ │ │ └── SetPivotGizmo -│ │ │ │ └── pivot_gizmo: PivotGizmo -│ │ │ ├── TransformLayerMessageHandler `editor\src\messages\tool\transform_layer\transform_layer_message_handler.rs` -│ │ │ │ ├── transform_operation: TransformOperation -│ │ │ │ ├── slow: bool -│ │ │ │ ├── increments: bool -│ │ │ │ ├── local: bool -│ │ │ │ ├── layer_bounding_box: Quad -│ │ │ │ ├── typing: Typing -│ │ │ │ ├── mouse_position: ViewportPosition -│ │ │ │ ├── start_mouse: ViewportPosition -│ │ │ │ ├── original_transforms: OriginalTransforms -│ │ │ │ ├── pivot_gizmo: PivotGizmo -│ │ │ │ ├── pivot: ViewportPosition -│ │ │ │ ├── path_bounds: Option<[DVec2; 2]> -│ │ │ │ ├── local_pivot: DocumentPosition -│ │ │ │ ├── local_mouse_start: DocumentPosition -│ │ │ │ ├── grab_target: DocumentPosition -│ │ │ │ ├── ptz: PTZ -│ │ │ │ ├── initial_transform: DAffine2 -│ │ │ │ ├── operation_count: usize -│ │ │ │ ├── handle: DVec2 -│ │ │ │ ├── last_point: DVec2 -│ │ │ │ ├── grs_pen_handle: bool -│ │ │ │ ├── ghost_outline: Vec<(Vec, DAffine2)> -│ │ │ │ └── was_grabbing: bool -│ │ │ └── TransformLayerMessageContext `editor\src\messages\tool\transform_layer\transform_layer_message_handler.rs` -│ │ │ ├── document: &'a DocumentMessageHandler -│ │ │ ├── input: &'a InputPreprocessorMessageHandler -│ │ │ ├── tool_data: &'a ToolData -│ │ │ └── shape_editor: &'a mut ShapeState -│ │ ├── Select -│ │ │ ├── SelectToolMessage `editor\src\messages\tool\tool_messages\select_tool.rs` -│ │ │ │ ├── Abort -│ │ │ │ ├── Overlays -│ │ │ │ │ └── context: OverlayContext -│ │ │ │ ├── DragStart -│ │ │ │ │ ├── extend_selection: Key -│ │ │ │ │ ├── remove_from_selection: Key -│ │ │ │ │ ├── select_deepest: Key -│ │ │ │ │ ├── lasso_select: Key -│ │ │ │ │ └── skew: Key -│ │ │ │ ├── DragStop -│ │ │ │ │ └── remove_from_selection: Key -│ │ │ │ ├── EditLayer -│ │ │ │ ├── EditLayerExec -│ │ │ │ ├── Enter -│ │ │ │ ├── PointerMove -│ │ │ │ │ └── modifier_keys: SelectToolPointerKeys -│ │ │ │ ├── PointerOutsideViewport -│ │ │ │ │ └── modifier_keys: SelectToolPointerKeys -│ │ │ │ ├── SelectOptions -│ │ │ │ │ └── options: SelectOptionsUpdate -│ │ │ │ ├── SetPivot -│ │ │ │ │ └── position: ReferencePoint -│ │ │ │ ├── SyncHistory -│ │ │ │ ├── ShiftSelectedNodes -│ │ │ │ │ └── offset: DVec2 -│ │ │ │ └── PivotShift -│ │ │ │ ├── offset: Option -│ │ │ │ └── flush: bool -│ │ │ ├── SelectTool `editor\src\messages\tool\tool_messages\select_tool.rs` -│ │ │ │ ├── fsm_state: SelectToolFsmState -│ │ │ │ └── tool_data: SelectToolData -│ │ │ └── &mut ToolActionMessageContext<'a> `editor\src\messages\tool\utility_types.rs` -│ │ │ ├── document: &'a mut DocumentMessageHandler -│ │ │ ├── document_id: DocumentId -│ │ │ ├── global_tool_data: &'a DocumentToolData -│ │ │ ├── input: &'a InputPreprocessorMessageHandler -│ │ │ ├── font_cache: &'a FontCache -│ │ │ ├── shape_editor: &'a mut ShapeState -│ │ │ ├── node_graph: &'a NodeGraphExecutor -│ │ │ └── preferences: &'a PreferencesMessageHandler -│ │ ├── Artboard -│ │ │ ├── ArtboardToolMessage `editor\src\messages\tool\tool_messages\artboard_tool.rs` -│ │ │ │ ├── Abort -│ │ │ │ ├── Overlays -│ │ │ │ │ └── context: OverlayContext -│ │ │ │ ├── UpdateSelectedArtboard -│ │ │ │ ├── DeleteSelected -│ │ │ │ ├── NudgeSelected -│ │ │ │ │ ├── delta_x: f64 -│ │ │ │ │ ├── delta_y: f64 -│ │ │ │ │ ├── resize: Key -│ │ │ │ │ └── resize_opposite_corner: Key -│ │ │ │ ├── PointerDown -│ │ │ │ ├── PointerMove -│ │ │ │ │ ├── constrain_axis_or_aspect: Key -│ │ │ │ │ └── center: Key -│ │ │ │ ├── PointerOutsideViewport -│ │ │ │ │ ├── constrain_axis_or_aspect: Key -│ │ │ │ │ └── center: Key -│ │ │ │ └── PointerUp -│ │ │ ├── ArtboardTool `editor\src\messages\tool\tool_messages\artboard_tool.rs` -│ │ │ │ ├── fsm_state: ArtboardToolFsmState -│ │ │ │ └── data: ArtboardToolData -│ │ │ └── &mut ToolActionMessageContext<'a> `editor\src\messages\tool\utility_types.rs` -│ │ │ ├── document: &'a mut DocumentMessageHandler -│ │ │ ├── document_id: DocumentId -│ │ │ ├── global_tool_data: &'a DocumentToolData -│ │ │ ├── input: &'a InputPreprocessorMessageHandler -│ │ │ ├── font_cache: &'a FontCache -│ │ │ ├── shape_editor: &'a mut ShapeState -│ │ │ ├── node_graph: &'a NodeGraphExecutor -│ │ │ └── preferences: &'a PreferencesMessageHandler -│ │ ├── Navigate -│ │ │ ├── NavigateToolMessage `editor\src\messages\tool\tool_messages\navigate_tool.rs` -│ │ │ │ ├── Abort -│ │ │ │ ├── PointerUp -│ │ │ │ │ └── zoom_in: bool -│ │ │ │ ├── PointerMove -│ │ │ │ │ └── snap: Key -│ │ │ │ ├── TiltCanvasBegin -│ │ │ │ ├── ZoomCanvasBegin -│ │ │ │ └── End -│ │ │ ├── NavigateTool `editor\src\messages\tool\tool_messages\navigate_tool.rs` -│ │ │ │ ├── fsm_state: NavigateToolFsmState -│ │ │ │ └── tool_data: NavigateToolData -│ │ │ └── &mut ToolActionMessageContext<'a> `editor\src\messages\tool\utility_types.rs` -│ │ │ ├── document: &'a mut DocumentMessageHandler -│ │ │ ├── document_id: DocumentId -│ │ │ ├── global_tool_data: &'a DocumentToolData -│ │ │ ├── input: &'a InputPreprocessorMessageHandler -│ │ │ ├── font_cache: &'a FontCache -│ │ │ ├── shape_editor: &'a mut ShapeState -│ │ │ ├── node_graph: &'a NodeGraphExecutor -│ │ │ └── preferences: &'a PreferencesMessageHandler -│ │ ├── Eyedropper -│ │ │ ├── EyedropperToolMessage `editor\src\messages\tool\tool_messages\eyedropper_tool.rs` -│ │ │ │ ├── Abort -│ │ │ │ ├── SamplePrimaryColorBegin -│ │ │ │ ├── SamplePrimaryColorEnd -│ │ │ │ ├── PointerMove -│ │ │ │ ├── SampleSecondaryColorBegin -│ │ │ │ └── SampleSecondaryColorEnd -│ │ │ ├── EyedropperTool `editor\src\messages\tool\tool_messages\eyedropper_tool.rs` -│ │ │ │ ├── fsm_state: EyedropperToolFsmState -│ │ │ │ └── data: EyedropperToolData -│ │ │ └── &mut ToolActionMessageContext<'a> `editor\src\messages\tool\utility_types.rs` -│ │ │ ├── document: &'a mut DocumentMessageHandler -│ │ │ ├── document_id: DocumentId -│ │ │ ├── global_tool_data: &'a DocumentToolData -│ │ │ ├── input: &'a InputPreprocessorMessageHandler -│ │ │ ├── font_cache: &'a FontCache -│ │ │ ├── shape_editor: &'a mut ShapeState -│ │ │ ├── node_graph: &'a NodeGraphExecutor -│ │ │ └── preferences: &'a PreferencesMessageHandler -│ │ ├── Fill -│ │ │ ├── FillToolMessage `editor\src\messages\tool\tool_messages\fill_tool.rs` -│ │ │ │ ├── Abort -│ │ │ │ ├── WorkingColorChanged -│ │ │ │ ├── Overlays -│ │ │ │ │ └── context: OverlayContext -│ │ │ │ ├── PointerMove -│ │ │ │ ├── PointerUp -│ │ │ │ ├── FillPrimaryColor -│ │ │ │ └── FillSecondaryColor -│ │ │ ├── FillTool `editor\src\messages\tool\tool_messages\fill_tool.rs` -│ │ │ │ └── fsm_state: FillToolFsmState -│ │ │ └── &mut ToolActionMessageContext<'a> `editor\src\messages\tool\utility_types.rs` -│ │ │ ├── document: &'a mut DocumentMessageHandler -│ │ │ ├── document_id: DocumentId -│ │ │ ├── global_tool_data: &'a DocumentToolData -│ │ │ ├── input: &'a InputPreprocessorMessageHandler -│ │ │ ├── font_cache: &'a FontCache -│ │ │ ├── shape_editor: &'a mut ShapeState -│ │ │ ├── node_graph: &'a NodeGraphExecutor -│ │ │ └── preferences: &'a PreferencesMessageHandler -│ │ ├── Gradient -│ │ │ ├── GradientToolMessage `editor\src\messages\tool\tool_messages\gradient_tool.rs` -│ │ │ │ ├── Abort -│ │ │ │ ├── Overlays -│ │ │ │ │ └── context: OverlayContext -│ │ │ │ ├── DeleteStop -│ │ │ │ ├── InsertStop -│ │ │ │ ├── PointerDown -│ │ │ │ ├── PointerMove -│ │ │ │ │ └── constrain_axis: Key -│ │ │ │ ├── PointerOutsideViewport -│ │ │ │ │ └── constrain_axis: Key -│ │ │ │ ├── PointerUp -│ │ │ │ └── UpdateOptions -│ │ │ │ └── options: GradientOptionsUpdate -│ │ │ ├── GradientTool `editor\src\messages\tool\tool_messages\gradient_tool.rs` -│ │ │ │ ├── fsm_state: GradientToolFsmState -│ │ │ │ ├── data: GradientToolData -│ │ │ │ └── options: GradientOptions -│ │ │ └── &mut ToolActionMessageContext<'a> `editor\src\messages\tool\utility_types.rs` -│ │ │ ├── document: &'a mut DocumentMessageHandler -│ │ │ ├── document_id: DocumentId -│ │ │ ├── global_tool_data: &'a DocumentToolData -│ │ │ ├── input: &'a InputPreprocessorMessageHandler -│ │ │ ├── font_cache: &'a FontCache -│ │ │ ├── shape_editor: &'a mut ShapeState -│ │ │ ├── node_graph: &'a NodeGraphExecutor -│ │ │ └── preferences: &'a PreferencesMessageHandler -│ │ ├── Path -│ │ │ ├── PathToolMessage `editor\src\messages\tool\tool_messages\path_tool.rs` -│ │ │ │ ├── Abort -│ │ │ │ ├── SelectionChanged -│ │ │ │ ├── Overlays -│ │ │ │ │ └── context: OverlayContext -│ │ │ │ ├── BreakPath -│ │ │ │ ├── DeselectAllPoints -│ │ │ │ ├── Delete -│ │ │ │ ├── DeleteAndBreakPath -│ │ │ │ ├── DragStop -│ │ │ │ │ ├── extend_selection: Key -│ │ │ │ │ └── shrink_selection: Key -│ │ │ │ ├── Enter -│ │ │ │ │ ├── extend_selection: Key -│ │ │ │ │ └── shrink_selection: Key -│ │ │ │ ├── Escape -│ │ │ │ ├── ClosePath -│ │ │ │ ├── DoubleClick -│ │ │ │ │ ├── extend_selection: Key -│ │ │ │ │ └── shrink_selection: Key -│ │ │ │ ├── GRS -│ │ │ │ │ └── key: Key -│ │ │ │ ├── ManipulatorMakeHandlesFree -│ │ │ │ ├── ManipulatorMakeHandlesColinear -│ │ │ │ ├── MouseDown -│ │ │ │ │ ├── extend_selection: Key -│ │ │ │ │ ├── lasso_select: Key -│ │ │ │ │ ├── handle_drag_from_anchor: Key -│ │ │ │ │ ├── drag_restore_handle: Key -│ │ │ │ │ └── segment_editing_modifier: Key -│ │ │ │ ├── NudgeSelectedPoints -│ │ │ │ │ ├── delta_x: f64 -│ │ │ │ │ └── delta_y: f64 -│ │ │ │ ├── PointerMove -│ │ │ │ │ ├── equidistant: Key -│ │ │ │ │ ├── toggle_colinear: Key -│ │ │ │ │ ├── move_anchor_with_handles: Key -│ │ │ │ │ ├── snap_angle: Key -│ │ │ │ │ ├── lock_angle: Key -│ │ │ │ │ ├── delete_segment: Key -│ │ │ │ │ ├── break_colinear_molding: Key -│ │ │ │ │ └── segment_editing_modifier: Key -│ │ │ │ ├── PointerOutsideViewport -│ │ │ │ │ ├── equidistant: Key -│ │ │ │ │ ├── toggle_colinear: Key -│ │ │ │ │ ├── move_anchor_with_handles: Key -│ │ │ │ │ ├── snap_angle: Key -│ │ │ │ │ ├── lock_angle: Key -│ │ │ │ │ ├── delete_segment: Key -│ │ │ │ │ ├── break_colinear_molding: Key -│ │ │ │ │ └── segment_editing_modifier: Key -│ │ │ │ ├── RightClick -│ │ │ │ ├── SelectAllAnchors -│ │ │ │ ├── SelectedPointUpdated -│ │ │ │ ├── SelectedPointXChanged -│ │ │ │ │ └── new_x: f64 -│ │ │ │ ├── SelectedPointYChanged -│ │ │ │ │ └── new_y: f64 -│ │ │ │ ├── SetPivot -│ │ │ │ │ └── position: ReferencePoint -│ │ │ │ ├── SwapSelectedHandles -│ │ │ │ ├── UpdateOptions -│ │ │ │ │ └── options: PathOptionsUpdate -│ │ │ │ ├── UpdateSelectedPointsStatus -│ │ │ │ │ └── overlay_context: OverlayContext -│ │ │ │ ├── StartSlidingPoint -│ │ │ │ ├── Copy -│ │ │ │ │ └── clipboard: Clipboard -│ │ │ │ ├── Cut -│ │ │ │ │ └── clipboard: Clipboard -│ │ │ │ ├── Paste -│ │ │ │ │ └── data: String -│ │ │ │ ├── DeleteSelected -│ │ │ │ ├── Duplicate -│ │ │ │ ├── TogglePointEditing -│ │ │ │ └── ToggleSegmentEditing -│ │ │ ├── PathTool `editor\src\messages\tool\tool_messages\path_tool.rs` -│ │ │ │ ├── fsm_state: PathToolFsmState -│ │ │ │ ├── tool_data: PathToolData -│ │ │ │ └── options: PathToolOptions -│ │ │ └── &mut ToolActionMessageContext<'a> `editor\src\messages\tool\utility_types.rs` -│ │ │ ├── document: &'a mut DocumentMessageHandler -│ │ │ ├── document_id: DocumentId -│ │ │ ├── global_tool_data: &'a DocumentToolData -│ │ │ ├── input: &'a InputPreprocessorMessageHandler -│ │ │ ├── font_cache: &'a FontCache -│ │ │ ├── shape_editor: &'a mut ShapeState -│ │ │ ├── node_graph: &'a NodeGraphExecutor -│ │ │ └── preferences: &'a PreferencesMessageHandler -│ │ ├── Pen -│ │ │ ├── PenToolMessage `editor\src\messages\tool\tool_messages\pen_tool.rs` -│ │ │ │ ├── Abort -│ │ │ │ ├── SelectionChanged -│ │ │ │ ├── WorkingColorChanged -│ │ │ │ ├── Overlays -│ │ │ │ │ └── context: OverlayContext -│ │ │ │ ├── AddPointLayerPosition -│ │ │ │ │ ├── layer: LayerNodeIdentifier -│ │ │ │ │ └── viewport: DVec2 -│ │ │ │ ├── Confirm -│ │ │ │ ├── DragStart -│ │ │ │ │ └── append_to_selected: Key -│ │ │ │ ├── DragStop -│ │ │ │ ├── PointerMove -│ │ │ │ │ ├── snap_angle: Key -│ │ │ │ │ ├── break_handle: Key -│ │ │ │ │ ├── lock_angle: Key -│ │ │ │ │ ├── colinear: Key -│ │ │ │ │ └── move_anchor_with_handles: Key -│ │ │ │ ├── PointerOutsideViewport -│ │ │ │ │ ├── snap_angle: Key -│ │ │ │ │ ├── break_handle: Key -│ │ │ │ │ ├── lock_angle: Key -│ │ │ │ │ ├── colinear: Key -│ │ │ │ │ └── move_anchor_with_handles: Key -│ │ │ │ ├── Redo -│ │ │ │ ├── Undo -│ │ │ │ ├── UpdateOptions -│ │ │ │ │ └── options: PenOptionsUpdate -│ │ │ │ ├── RecalculateLatestPointsPosition -│ │ │ │ ├── RemovePreviousHandle -│ │ │ │ ├── GRS -│ │ │ │ │ ├── grab: Key -│ │ │ │ │ ├── rotate: Key -│ │ │ │ │ └── scale: Key -│ │ │ │ ├── FinalPosition -│ │ │ │ │ └── final_position: DVec2 -│ │ │ │ └── SwapHandles -│ │ │ ├── PenTool `editor\src\messages\tool\tool_messages\pen_tool.rs` -│ │ │ │ ├── fsm_state: PenToolFsmState -│ │ │ │ ├── tool_data: PenToolData -│ │ │ │ └── options: PenOptions -│ │ │ └── &mut ToolActionMessageContext<'a> `editor\src\messages\tool\utility_types.rs` -│ │ │ ├── document: &'a mut DocumentMessageHandler -│ │ │ ├── document_id: DocumentId -│ │ │ ├── global_tool_data: &'a DocumentToolData -│ │ │ ├── input: &'a InputPreprocessorMessageHandler -│ │ │ ├── font_cache: &'a FontCache -│ │ │ ├── shape_editor: &'a mut ShapeState -│ │ │ ├── node_graph: &'a NodeGraphExecutor -│ │ │ └── preferences: &'a PreferencesMessageHandler -│ │ ├── Freehand -│ │ │ ├── FreehandToolMessage `editor\src\messages\tool\tool_messages\freehand_tool.rs` -│ │ │ │ ├── Overlays -│ │ │ │ │ └── context: OverlayContext -│ │ │ │ ├── Abort -│ │ │ │ ├── WorkingColorChanged -│ │ │ │ ├── DragStart -│ │ │ │ │ └── append_to_selected: Key -│ │ │ │ ├── DragStop -│ │ │ │ ├── PointerMove -│ │ │ │ └── UpdateOptions -│ │ │ │ └── options: FreehandOptionsUpdate -│ │ │ ├── FreehandTool `editor\src\messages\tool\tool_messages\freehand_tool.rs` -│ │ │ │ ├── fsm_state: FreehandToolFsmState -│ │ │ │ ├── data: FreehandToolData -│ │ │ │ └── options: FreehandOptions -│ │ │ └── &mut ToolActionMessageContext<'a> `editor\src\messages\tool\utility_types.rs` -│ │ │ ├── document: &'a mut DocumentMessageHandler -│ │ │ ├── document_id: DocumentId -│ │ │ ├── global_tool_data: &'a DocumentToolData -│ │ │ ├── input: &'a InputPreprocessorMessageHandler -│ │ │ ├── font_cache: &'a FontCache -│ │ │ ├── shape_editor: &'a mut ShapeState -│ │ │ ├── node_graph: &'a NodeGraphExecutor -│ │ │ └── preferences: &'a PreferencesMessageHandler -│ │ ├── Spline -│ │ │ ├── SplineToolMessage `editor\src\messages\tool\tool_messages\spline_tool.rs` -│ │ │ │ ├── Overlays -│ │ │ │ │ └── context: OverlayContext -│ │ │ │ ├── CanvasTransformed -│ │ │ │ ├── Abort -│ │ │ │ ├── WorkingColorChanged -│ │ │ │ ├── Confirm -│ │ │ │ ├── DragStart -│ │ │ │ │ └── append_to_selected: Key -│ │ │ │ ├── DragStop -│ │ │ │ ├── MergeEndpoints -│ │ │ │ ├── PointerMove -│ │ │ │ ├── PointerOutsideViewport -│ │ │ │ ├── Undo -│ │ │ │ └── UpdateOptions -│ │ │ │ └── options: SplineOptionsUpdate -│ │ │ ├── SplineTool `editor\src\messages\tool\tool_messages\spline_tool.rs` -│ │ │ │ ├── fsm_state: SplineToolFsmState -│ │ │ │ ├── tool_data: SplineToolData -│ │ │ │ └── options: SplineOptions -│ │ │ └── &mut ToolActionMessageContext<'a> `editor\src\messages\tool\utility_types.rs` -│ │ │ ├── document: &'a mut DocumentMessageHandler -│ │ │ ├── document_id: DocumentId -│ │ │ ├── global_tool_data: &'a DocumentToolData -│ │ │ ├── input: &'a InputPreprocessorMessageHandler -│ │ │ ├── font_cache: &'a FontCache -│ │ │ ├── shape_editor: &'a mut ShapeState -│ │ │ ├── node_graph: &'a NodeGraphExecutor -│ │ │ └── preferences: &'a PreferencesMessageHandler -│ │ ├── Shape -│ │ │ ├── ShapeToolMessage `editor\src\messages\tool\tool_messages\shape_tool.rs` -│ │ │ │ ├── Overlays -│ │ │ │ │ └── context: OverlayContext -│ │ │ │ ├── Abort -│ │ │ │ ├── WorkingColorChanged -│ │ │ │ ├── DragStart -│ │ │ │ ├── DragStop -│ │ │ │ ├── HideShapeTypeWidget -│ │ │ │ │ └── hide: bool -│ │ │ │ ├── PointerMove -│ │ │ │ │ └── modifier: ShapeToolModifierKey -│ │ │ │ ├── PointerOutsideViewport -│ │ │ │ │ └── modifier: ShapeToolModifierKey -│ │ │ │ ├── UpdateOptions -│ │ │ │ │ └── options: ShapeOptionsUpdate -│ │ │ │ ├── SetShape -│ │ │ │ │ └── shape: ShapeType -│ │ │ │ ├── IncreaseSides -│ │ │ │ ├── DecreaseSides -│ │ │ │ └── NudgeSelectedLayers -│ │ │ │ ├── delta_x: f64 -│ │ │ │ ├── delta_y: f64 -│ │ │ │ ├── resize: Key -│ │ │ │ └── resize_opposite_corner: Key -│ │ │ ├── ShapeTool `editor\src\messages\tool\tool_messages\shape_tool.rs` -│ │ │ │ ├── fsm_state: ShapeToolFsmState -│ │ │ │ ├── tool_data: ShapeToolData -│ │ │ │ └── options: ShapeToolOptions -│ │ │ └── &mut ToolActionMessageContext<'a> `editor\src\messages\tool\utility_types.rs` -│ │ │ ├── document: &'a mut DocumentMessageHandler -│ │ │ ├── document_id: DocumentId -│ │ │ ├── global_tool_data: &'a DocumentToolData -│ │ │ ├── input: &'a InputPreprocessorMessageHandler -│ │ │ ├── font_cache: &'a FontCache -│ │ │ ├── shape_editor: &'a mut ShapeState -│ │ │ ├── node_graph: &'a NodeGraphExecutor -│ │ │ └── preferences: &'a PreferencesMessageHandler -│ │ ├── Text -│ │ │ ├── TextToolMessage `editor\src\messages\tool\tool_messages\text_tool.rs` -│ │ │ │ ├── Abort -│ │ │ │ ├── WorkingColorChanged -│ │ │ │ ├── Overlays -│ │ │ │ │ └── context: OverlayContext -│ │ │ │ ├── DragStart -│ │ │ │ ├── DragStop -│ │ │ │ ├── EditSelected -│ │ │ │ ├── Interact -│ │ │ │ ├── PointerMove -│ │ │ │ │ ├── center: Key -│ │ │ │ │ └── lock_ratio: Key -│ │ │ │ ├── PointerOutsideViewport -│ │ │ │ │ ├── center: Key -│ │ │ │ │ └── lock_ratio: Key -│ │ │ │ ├── TextChange -│ │ │ │ │ ├── new_text: String -│ │ │ │ │ └── is_left_or_right_click: bool -│ │ │ │ ├── UpdateBounds -│ │ │ │ │ └── new_text: String -│ │ │ │ └── UpdateOptions -│ │ │ │ └── options: TextOptionsUpdate -│ │ │ ├── TextTool `editor\src\messages\tool\tool_messages\text_tool.rs` -│ │ │ │ ├── fsm_state: TextToolFsmState -│ │ │ │ ├── tool_data: TextToolData -│ │ │ │ └── options: TextOptions -│ │ │ └── &mut ToolActionMessageContext<'a> `editor\src\messages\tool\utility_types.rs` -│ │ │ ├── document: &'a mut DocumentMessageHandler -│ │ │ ├── document_id: DocumentId -│ │ │ ├── global_tool_data: &'a DocumentToolData -│ │ │ ├── input: &'a InputPreprocessorMessageHandler -│ │ │ ├── font_cache: &'a FontCache -│ │ │ ├── shape_editor: &'a mut ShapeState -│ │ │ ├── node_graph: &'a NodeGraphExecutor -│ │ │ └── preferences: &'a PreferencesMessageHandler -│ │ ├── Brush -│ │ │ ├── BrushToolMessage `editor\src\messages\tool\tool_messages\brush_tool.rs` -│ │ │ │ ├── Abort -│ │ │ │ ├── WorkingColorChanged -│ │ │ │ ├── DragStart -│ │ │ │ ├── DragStop -│ │ │ │ ├── PointerMove -│ │ │ │ └── UpdateOptions -│ │ │ │ └── options: BrushToolMessageOptionsUpdate -│ │ │ ├── BrushTool `editor\src\messages\tool\tool_messages\brush_tool.rs` -│ │ │ │ ├── fsm_state: BrushToolFsmState -│ │ │ │ ├── data: BrushToolData -│ │ │ │ └── options: BrushOptions -│ │ │ └── &mut ToolActionMessageContext<'a> `editor\src\messages\tool\utility_types.rs` -│ │ │ ├── document: &'a mut DocumentMessageHandler -│ │ │ ├── document_id: DocumentId -│ │ │ ├── global_tool_data: &'a DocumentToolData -│ │ │ ├── input: &'a InputPreprocessorMessageHandler -│ │ │ ├── font_cache: &'a FontCache -│ │ │ ├── shape_editor: &'a mut ShapeState -│ │ │ ├── node_graph: &'a NodeGraphExecutor -│ │ │ └── preferences: &'a PreferencesMessageHandler -│ │ ├── ActivateToolSelect -│ │ ├── ActivateToolArtboard -│ │ ├── ActivateToolNavigate -│ │ ├── ActivateToolEyedropper -│ │ ├── ActivateToolFill -│ │ ├── ActivateToolGradient -│ │ ├── ActivateToolPath -│ │ ├── ActivateToolPen -│ │ ├── ActivateToolFreehand -│ │ ├── ActivateToolSpline -│ │ ├── ActivateToolShapeLine -│ │ ├── ActivateToolShapeRectangle -│ │ ├── ActivateToolShapeEllipse -│ │ ├── ActivateToolShape -│ │ ├── ActivateToolText -│ │ ├── ActivateToolBrush -│ │ ├── ActivateTool -│ │ │ └── tool_type: ToolType -│ │ ├── DeactivateTools -│ │ ├── InitTools -│ │ ├── PreUndo -│ │ ├── Redo -│ │ ├── RefreshToolOptions -│ │ ├── ResetColors -│ │ ├── SelectWorkingColor -│ │ │ ├── color: Color -│ │ │ └── primary: bool -│ │ ├── SelectRandomWorkingColor -│ │ │ └── primary: bool -│ │ ├── ToggleSelectVsPath -│ │ ├── SwapColors -│ │ ├── Undo -│ │ ├── UpdateCursor -│ │ ├── UpdateHints -│ │ └── UpdateSelectionMode -│ │ └── selection_mode: SelectionMode -│ ├── ToolMessageHandler `editor\src\messages\tool\tool_message_handler.rs` -│ │ ├── tool_state: ToolFsmState -│ │ ├── transform_layer_handler: TransformLayerMessageHandler -│ │ ├── shape_editor: ShapeState -│ │ └── tool_is_active: bool -│ └── ToolMessageContext `editor\src\messages\tool\tool_message_handler.rs` -│ ├── document_id: DocumentId -│ ├── document: &'a mut DocumentMessageHandler -│ ├── input: &'a InputPreprocessorMessageHandler -│ ├── persistent_data: &'a PersistentData -│ ├── node_graph: &'a NodeGraphExecutor -│ └── preferences: &'a PreferencesMessageHandler -├── Batched -│ └── messages: Box<[Message]> -└── NoOp