diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index 44ce4eecf3..60e3a953a5 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -184,6 +184,18 @@ pub fn input_mappings(zoom_with_scroll: bool) -> 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::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), 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..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,6 +22,12 @@ pub enum GraphOperationMessage { layer: LayerNodeIdentifier, fill: Fill, }, + CircularRepeatSet { + layer: LayerNodeIdentifier, + angle: f64, + radius: f64, + count: u32, + }, 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 7c3a4b1020..5195f0ddee 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 @@ -39,6 +39,11 @@ impl MessageHandler> for modify_inputs.fill_set(fill); } } + GraphOperationMessage::CircularRepeatSet { layer, angle, radius, count } => { + if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) { + modify_inputs.circular_repeat_set(angle, radius, count); + } + } 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/portfolio/document/graph_operation/utility_types.rs b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs index d364a78774..778b3d7099 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -423,6 +423,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/prelude.rs b/editor/src/messages/prelude.rs index d331697cc0..e55327fe5d 100644 --- a/editor/src/messages/prelude.rs +++ b/editor/src/messages/prelude.rs @@ -42,6 +42,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/common_functionality/gizmos/gizmo_manager.rs b/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs index 9dc1c52586..4974c08573 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs @@ -1,18 +1,17 @@ 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::grid_shape::GridGizmoHandler; use crate::messages::tool::common_functionality::shapes::polygon_shape::PolygonGizmoHandler; -use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler; +use crate::messages::tool::common_functionality::shapes::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. /// @@ -29,6 +28,7 @@ pub enum ShapeGizmoHandlers { Polygon(PolygonGizmoHandler), Arc(ArcGizmoHandler), Circle(CircleGizmoHandler), + CircularRepeat(CircularRepeatGizmoHandler), Grid(GridGizmoHandler), } @@ -41,19 +41,21 @@ impl ShapeGizmoHandlers { Self::Polygon(_) => "polygon", Self::Arc(_) => "arc", Self::Circle(_) => "circle", + Self::CircularRepeat(_) => "circular_repeat", Self::Grid(_) => "grid", 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::Grid(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::Grid(h) => h.handle_state(layer, mouse_position, ctx), Self::None => {} } } @@ -65,6 +67,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::Grid(h) => h.is_any_gizmo_hovered(), Self::None => false, } @@ -77,19 +80,21 @@ 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::Grid(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::Grid(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::Grid(h) => h.handle_update(drag_start, ctx), Self::None => {} } } @@ -102,45 +107,33 @@ impl ShapeGizmoHandlers { Self::Arc(h) => h.cleanup(), Self::Circle(h) => h.cleanup(), Self::Grid(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::Grid(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::Grid(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::Grid(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::Grid(h) => h.dragging_overlays(mouse_position, ctx, overlay_context), Self::None => {} } } @@ -151,6 +144,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::Grid(h) => h.mouse_cursor_icon(), Self::None => None, } @@ -203,6 +197,15 @@ 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() @@ -211,12 +214,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 { @@ -225,7 +228,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 { @@ -238,6 +241,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 { @@ -254,45 +289,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 6152a6a5dd..c220f6a6c7 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/grid_rows_columns_gizmo.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_rows_columns_gizmo.rs index 308dbbdb48..56bfcc5d7f 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_rows_columns_gizmo.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_rows_columns_gizmo.rs @@ -9,7 +9,7 @@ use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageH use crate::messages::prelude::{GraphOperationMessage, Responses}; 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_grid_parameters; +use crate::messages::tool::common_functionality::shapes::shape_utility::{GizmoContext, extract_grid_parameters}; use glam::{DAffine2, DVec2}; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; @@ -64,7 +64,9 @@ impl RowColumnGizmo { } } - pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler) { + pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, ctx: &mut GizmoContext, mouse_position: DVec2) { + let GizmoContext { document, .. } = ctx; + let Some((grid_type, spacing, columns, rows, angles)) = extract_grid_parameters(layer, document) else { return; }; @@ -81,7 +83,9 @@ impl RowColumnGizmo { } } - 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, ctx: &mut GizmoContext, _mouse_position: DVec2, overlay_context: &mut OverlayContext) { + let GizmoContext { document, .. } = ctx; + let Some(layer) = layer.or(self.layer) else { return }; let Some((grid_type, spacing, columns, rows, angles)) = extract_grid_parameters(layer, document) else { return; @@ -95,7 +99,9 @@ impl RowColumnGizmo { } } - pub fn update(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { + pub fn update(&mut self, ctx: &mut GizmoContext, drag_start: DVec2) { + let GizmoContext { document, input, responses, .. } = ctx; + let Some(layer) = self.layer else { return }; let viewport = document.metadata().transform_to_viewport(layer); 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 c6fef1fbda..6ec3d01851 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 d72414f48a..4b2b714ed3 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 @@ -7,14 +7,13 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye 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)] @@ -55,7 +54,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 @@ -143,7 +143,9 @@ impl PointRadiusHandle { } } - pub fn overlays(&self, selected_star_layer: Option, document: &DocumentMessageHandler, 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 }; @@ -413,7 +415,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 efa4b18351..db6842b88c 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 @@ -4,9 +4,8 @@ use crate::messages::portfolio::document::overlays::utility_functions::text_widt 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; @@ -57,7 +56,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 { @@ -93,14 +94,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 { @@ -188,7 +184,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 a0d4cf1dbd..eae0447030 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -384,6 +384,10 @@ pub fn get_grid_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkIn NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::grid::IDENTIFIER)) } +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(&DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER))?; diff --git a/editor/src/messages/tool/common_functionality/mod.rs b/editor/src/messages/tool/common_functionality/mod.rs index acd0df472a..08788a0bbf 100644 --- a/editor/src/messages/tool/common_functionality/mod.rs +++ b/editor/src/messages/tool/common_functionality/mod.rs @@ -5,6 +5,7 @@ pub mod gizmos; pub mod graph_modification_utils; pub mod layer_origin_cross; 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..a210f84690 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/operations/circular_repeat.rs @@ -0,0 +1,201 @@ +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::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; + +#[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 (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 + } else { + *initial_radius + delta.signum() * -1. * delta.abs() + }; + + responses.add(GraphOperationMessage::CircularRepeatSet { + layer: *layer, + angle, + radius: new_radius, + count, + }); + } + + 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 => { + // 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); + } + } + _ => {} + } + } + + 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); + } + } + } + + 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/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/common_functionality/shapes/arc_shape.rs b/editor/src/messages/tool/common_functionality/shapes/arc_shape.rs index 7eabb3c619..67a0e30ba7 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_proto_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 836f50f9e6..76f7e321b5 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/grid_shape.rs b/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs index 7a7f1b0b19..e242b12c23 100644 --- a/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs @@ -7,8 +7,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::grid_rows_columns_gizmo::{RowColumnGizmo, RowColumnGizmoState}; 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; +use crate::messages::tool::common_functionality::shapes::shape_utility::{GizmoContext, ShapeGizmoHandler}; use crate::messages::tool::tool_messages::tool_prelude::*; use glam::DAffine2; use graph_craft::document::NodeInput; @@ -27,8 +26,8 @@ impl ShapeGizmoHandler for GridGizmoHandler { self.row_column_gizmo.is_hovered() } - fn handle_state(&mut self, selected_grid_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, _responses: &mut VecDeque) { - self.row_column_gizmo.handle_actions(selected_grid_layer, mouse_position, document); + fn handle_state(&mut self, selected_grid_layer: LayerNodeIdentifier, mouse_position: DVec2, ctx: &mut GizmoContext) { + self.row_column_gizmo.handle_actions(selected_grid_layer, ctx, mouse_position); } fn handle_click(&mut self) { @@ -37,34 +36,19 @@ impl ShapeGizmoHandler for GridGizmoHandler { } } - 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.row_column_gizmo.is_dragging() { - self.row_column_gizmo.update(document, input, responses, drag_start); + self.row_column_gizmo.update(ctx, drag_start); } } - fn overlays( - &self, - document: &DocumentMessageHandler, - selected_grid_layer: Option, - _input: &InputPreprocessorMessageHandler, - shape_editor: &mut &mut ShapeState, - mouse_position: DVec2, - overlay_context: &mut OverlayContext, - ) { - self.row_column_gizmo.overlays(document, selected_grid_layer, shape_editor, mouse_position, overlay_context); + fn overlays(&self, selected_grid_layer: Option, mouse_position: DVec2, ctx: &mut GizmoContext, overlay_context: &mut OverlayContext) { + self.row_column_gizmo.overlays(selected_grid_layer, ctx, mouse_position, 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.row_column_gizmo.is_dragging() { - self.row_column_gizmo.overlays(document, None, shape_editor, mouse_position, overlay_context); + self.row_column_gizmo.overlays(None, ctx, mouse_position, 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 7b22144a71..715f9eed37 100644 --- a/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs @@ -8,7 +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::{self, NodeGraphLayer}; -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, polygon_outline}; use crate::messages::tool::tool_messages::shape_tool::ShapeOptionsUpdate; use crate::messages::tool::tool_messages::tool_prelude::*; @@ -28,9 +28,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) { @@ -44,45 +44,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, 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, 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 aa9c215449..31284bc8c3 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -98,13 +98,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. /// @@ -115,32 +122,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; @@ -263,6 +255,20 @@ 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")?; + + 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/common_functionality/shapes/star_shape.rs b/editor/src/messages/tool/common_functionality/shapes/star_shape.rs index acdde2c286..81e05b0cf8 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, 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, overlay_context); + self.point_radius_handle.overlays(None, ctx, overlay_context); } } diff --git a/editor/src/messages/tool/tool_message.rs b/editor/src/messages/tool/tool_message.rs index 02f28e0191..9a000b427c 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..5b44670053 --- /dev/null +++ b/editor/src/messages/tool/tool_messages/operation_tool.rs @@ -0,0 +1,314 @@ +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 { + #[default] + CircularRepeat = 0, + Repeat, +} + +#[derive(Default, ExtractField)] +pub struct OperationTool { + fsm_state: OperationToolFsmState, + tool_data: OperationToolData, + options: OperationOptions, +} + +pub struct OperationOptions { + operation_type: OperationType, +} + +impl Default for OperationOptions { + fn default() -> Self { + Self { + operation_type: OperationType::CircularRepeat, + } + } +} + +#[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 + IncreaseCount, + DecreaseCount, + Confirm, + DragStart, + DragStop, + PointerMove, + PointerOutsideViewport, + Undo, + UpdateOptions { options: OperationOptionsUpdate }, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum OperationToolFsmState { + #[default] + Ready, + ModifyingGizmo, + Drawing, +} + +#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +pub enum OperationOptionsUpdate { + OperationType(OperationType), +} + +impl ToolMetadata for OperationTool { + fn icon_name(&self) -> String { + "GeneralOperationTool".into() + } + fn tooltip_label(&self) -> String { + "Operation Tool".into() + } + fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { + ToolType::Operation + } +} + +fn create_operation_type_option_widget(operation_type: OperationType) -> WidgetIter { + let entries = vec![vec![ + MenuListEntry::new("Circular Repeat").label("Circular Repeat").on_commit(move |_| { + OperationToolMessage::UpdateOptions { + options: OperationOptionsUpdate::OperationType(OperationType::CircularRepeat), + } + .into() + }), + MenuListEntry::new("Repeat").label("Repeat").on_commit(move |_| { + OperationToolMessage::UpdateOptions { + options: OperationOptionsUpdate::OperationType(OperationType::Repeat), + } + .into() + }), + ]]; + DropdownInput::new(entries).selected_index(Some(operation_type as u32)).widget_holder() +} + +impl LayoutHolder for OperationTool { + fn layout(&self) -> Layout { + let mut widgets = vec![]; + + widgets.push(create_operation_type_option_widget(self.options.operation_type)); + + 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::OperationType(operation_type) => self.options.operation_type = operation_type, + } + + 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 | OperationToolFsmState::ModifyingGizmo => actions!(OperationToolMessageDiscriminant; + DragStop, + PointerMove, + Confirm, + Abort, + IncreaseCount, + DecreaseCount, + ), + } + } +} + +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)] +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 { + 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, 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 }) => { + 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); + } + OperationType::Repeat => {} + } + + OperationToolFsmState::Drawing + } + (OperationToolFsmState::Drawing | OperationToolFsmState::ModifyingGizmo, 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) => { + // Don't add the repeat node unless dragging more that 5 px + if tool_data.drag_start.distance(input.mouse.position) < 5. { + return self; + }; + + match tool_options.operation_type { + OperationType::CircularRepeat => { + CircularRepeatOperation::update_shape(tool_data, document, responses, input); + } + OperationType::Repeat => {} + } + + 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), + _ => {} + } + 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 + } + + (OperationToolFsmState::Drawing, OperationToolMessage::PointerOutsideViewport) => OperationToolFsmState::Drawing, + (state, OperationToolMessage::PointerOutsideViewport) => state, + (OperationToolFsmState::Drawing | OperationToolFsmState::ModifyingGizmo, OperationToolMessage::Abort) => { + responses.add(DocumentMessage::AbortTransaction); + OperationToolFsmState::Ready + } + (_, OperationToolMessage::WorkingColorChanged) => self, + _ => self, + } + } + + fn update_hints(&self, responses: &mut VecDeque) { + let hint_data = match self { + OperationToolFsmState::Ready | OperationToolFsmState::ModifyingGizmo => HintData(vec![HintGroup(vec![ + HintInfo::mouse(MouseMotion::Lmb, "Draw Spline"), + HintInfo::keys([Key::Shift], "Append to Selected Layer").prepend_plus(), + ])]), + _ => 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 }); + } +} + +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 e459c9cb11..d21ab5c374 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::circle_shape::Circle; use crate::messages::tool::common_functionality::shapes::grid_shape::Grid; use crate::messages::tool::common_functionality::shapes::line_shape::{LineToolData, clicked_on_line_endpoints}; use crate::messages::tool::common_functionality::shapes::polygon_shape::Polygon; -use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays}; +use crate::messages::tool::common_functionality::shapes::shape_utility::{GizmoContext, ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays}; use crate::messages::tool::common_functionality::shapes::spiral_shape::Spiral; use crate::messages::tool::common_functionality::shapes::star_shape::Star; use crate::messages::tool::common_functionality::shapes::{Ellipse, Line, Rectangle}; @@ -606,12 +606,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 }); @@ -671,9 +683,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); } } @@ -932,7 +950,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); diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index ead8c52214..01dcebaadd 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -370,6 +370,7 @@ pub enum ToolType { Eyedropper, Fill, Gradient, + Operation, // Vector tool group Path, @@ -420,6 +421,7 @@ fn list_tools_in_groups() -> Vec> { ToolRole::Normal(Box::::default()), ToolRole::Normal(Box::::default()), ToolRole::Normal(Box::::default()), + ToolRole::Normal(Box::::default()), ], vec![ // Vector tool group @@ -472,6 +474,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, @@ -501,6 +504,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,