Skip to content
12 changes: 12 additions & 0 deletions editor/src/messages/input_mapper/input_mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions editor/src/messages/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
161 changes: 91 additions & 70 deletions editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs
Original file line number Diff line number Diff line change
@@ -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.
///
Expand All @@ -29,6 +28,7 @@ pub enum ShapeGizmoHandlers {
Polygon(PolygonGizmoHandler),
Arc(ArcGizmoHandler),
Circle(CircleGizmoHandler),
CircularRepeat(CircularRepeatGizmoHandler),
Grid(GridGizmoHandler),
}

Expand All @@ -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<Message>) {
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 => {}
}
}
Expand All @@ -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,
}
Expand All @@ -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<Message>) {
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 => {}
}
}
Expand All @@ -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<LayerNodeIdentifier>,
input: &InputPreprocessorMessageHandler,
shape_editor: &mut &mut ShapeState,
mouse_position: DVec2,
overlay_context: &mut OverlayContext,
) {
pub fn overlays(&self, layer: Option<LayerNodeIdentifier>, 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 => {}
}
}
Expand All @@ -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,
}
Expand Down Expand Up @@ -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<ShapeGizmoHandlers> {
// 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()
Expand All @@ -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<Message>) {
pub fn handle_actions(&mut self, mouse_position: DVec2, ctx: &mut GizmoContext) {
let mut handlers_layer: Vec<(ShapeGizmoHandlers, Vec<LayerNodeIdentifier>)> = 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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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<Message>) {
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);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod gizmo_manager;
pub mod operation_gizmos;
pub mod shape_gizmos;
Loading
Loading