Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions editor/src/messages/input_mapper/input_mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,14 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(MouseRight); action_dispatch=GradientToolMessage::Abort),
entry!(KeyDown(Escape); action_dispatch=GradientToolMessage::Abort),
//
// OperationToolMessage
entry!(PointerMove; action_dispatch=OperationToolMessage::PointerMove),
entry!(KeyDown(MouseLeft); action_dispatch=OperationToolMessage::DragStart),
entry!(KeyUp(MouseLeft); action_dispatch=OperationToolMessage::DragStop),
entry!(KeyDown(MouseRight); action_dispatch=OperationToolMessage::Confirm),
entry!(KeyDown(Escape); action_dispatch=OperationToolMessage::Abort),
entry!(KeyDown(Enter); action_dispatch=OperationToolMessage::Confirm),
//
// 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 @@ -37,6 +37,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 @@ -394,6 +394,17 @@ impl<'a> ModifyInputsContext<'a> {
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.dash_offset), false), true);
}

pub fn circular_repeat_set(&mut self, angle: f64, radius: f64, count: u32) {
let Some(circular_repeat_node_id) = self.existing_node_id("Circular Repeat", true) else { return };

let input_connector = InputConnector::node(circular_repeat_node_id, graphene_std::vector::circular_repeat::AngleOffsetInput::INDEX);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(angle), false), true);
let input_connector = InputConnector::node(circular_repeat_node_id, graphene_std::vector::circular_repeat::RadiusInput::INDEX);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(radius), false), true);
let input_connector = InputConnector::node(circular_repeat_node_id, graphene_std::vector::circular_repeat::CountInput::INDEX);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::U32(count), false), false);
}

/// Update the transform value of the upstream Transform node based a change to its existing value and the given parent transform.
/// A new Transform node is created if one does not exist, unless it would be given the identity transform.
pub fn transform_change_with_parent(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, skip_rerender: bool) {
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 @@ -41,6 +41,7 @@ pub use crate::messages::tool::tool_messages::fill_tool::{FillToolMessage, FillT
pub use crate::messages::tool::tool_messages::freehand_tool::{FreehandToolMessage, FreehandToolMessageDiscriminant};
pub use crate::messages::tool::tool_messages::gradient_tool::{GradientToolMessage, GradientToolMessageDiscriminant};
pub use crate::messages::tool::tool_messages::navigate_tool::{NavigateToolMessage, NavigateToolMessageDiscriminant};
pub use crate::messages::tool::tool_messages::operation_tool::{OperationToolMessage, OperationToolMessageDiscriminant};
pub use crate::messages::tool::tool_messages::path_tool::{PathToolMessage, PathToolMessageDiscriminant};
pub use crate::messages::tool::tool_messages::pen_tool::{PenToolMessage, PenToolMessageDiscriminant};
pub use crate::messages::tool::tool_messages::select_tool::{SelectToolMessage, SelectToolMessageDiscriminant};
Expand Down
1 change: 1 addition & 0 deletions editor/src/messages/tool/common_functionality/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod compass_rose;
pub mod gizmos;
pub mod graph_modification_utils;
pub mod measure;
pub mod operations;
pub mod pivot;
pub mod resize;
pub mod shape_editor;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::tool::common_functionality::shapes::shape_utility::extract_circular_repeat_parameters;
use crate::messages::tool::tool_messages::operation_tool::{OperationToolData, OperationToolFsmState};
use crate::messages::tool::tool_messages::tool_prelude::*;
use std::collections::VecDeque;

#[derive(Default)]
pub struct CircularRepeatOperation;

#[derive(Clone, Debug, Default)]
pub struct CircularRepeatOperationData {
clicked_layer_radius: (LayerNodeIdentifier, f64),
layers_dragging: Vec<(LayerNodeIdentifier, f64)>,
initial_center: DVec2,
}

impl CircularRepeatOperation {
pub fn create_node(tool_data: &mut OperationToolData, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, input: &InputPreprocessorMessageHandler) {
let selected_layers = document
.network_interface
.selected_nodes()
.selected_layers(document.metadata())
.collect::<HashSet<LayerNodeIdentifier>>();

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::<Vec<(LayerNodeIdentifier, f64)>>();
} 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<Message>, input: &InputPreprocessorMessageHandler) {
let (_clicked_layer, clicked_radius) = tool_data.circular_operation_data.clicked_layer_radius;

let viewport = document.metadata().transform_to_viewport(tool_data.circular_operation_data.clicked_layer_radius.0);
let sign = (input.mouse.position - tool_data.circular_operation_data.initial_center)
.dot(viewport.transform_vector2(DVec2::Y))
.signum();

// Compute mouse movement in local space, ignoring the layer’s own transform
let delta = document
.metadata()
.downstream_transform_to_viewport(tool_data.circular_operation_data.clicked_layer_radius.0)
.inverse()
.transform_vector2(input.mouse.position - tool_data.circular_operation_data.initial_center)
.length() * sign;

for (layer, initial_radius) in &tool_data.circular_operation_data.layers_dragging {
// If the layer’s sign differs from the clicked layer, invert delta to preserve consistent in/out dragging behavior
let new_radius = if initial_radius.signum() == clicked_radius.signum() {
*initial_radius + delta
} else {
*initial_radius + delta.signum() * -1. * delta.abs()
};

responses.add(GraphOperationMessage::CircularRepeatSet {
layer: *layer,
angle: 0.,
radius: new_radius,
count: 6,
});
}

responses.add(NodeGraphMessage::RunDocumentGraph);
}

pub fn overlays(
tool_state: &OperationToolFsmState,
tool_data: &mut OperationToolData,
document: &DocumentMessageHandler,
input: &InputPreprocessorMessageHandler,
overlay_context: &mut OverlayContext,
) {
match tool_state {
OperationToolFsmState::Ready => {
// Draw overlays for all selected layers
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
Self::draw_layer_overlay(layer, document, input, overlay_context)
}

// Also highlight the hovered layer if it’s not selected
if let Some(layer) = document.click(&input) {
Self::draw_layer_overlay(layer, document, input, overlay_context);
}
}
_ => {
// While dragging, only draw overlays for the layers being modified
for layer in tool_data.circular_operation_data.layers_dragging.iter().map(|(l, _)| l) {
let Some(vector) = document.network_interface.compute_modified_vector(*layer) else { continue };
let viewport = document.metadata().transform_to_viewport(*layer);

overlay_context.outline_vector(&vector, viewport);
}
}
}
}

fn draw_layer_overlay(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, overlay_context: &mut OverlayContext) {
if let Some(vector) = document.network_interface.compute_modified_vector(layer) {
let viewport = document.metadata().transform_to_viewport(layer);
let center = viewport.transform_point2(DVec2::ZERO);
// Show a small circle if the mouse is near the repeat center
if center.distance(input.mouse.position) < 5. {
overlay_context.circle(center, 3., None, None);
}
overlay_context.outline_vector(&vector, viewport);
}
}

pub fn cleanup(tool_data: &mut OperationToolData) {
// Clear stored drag state at the end of the operation
tool_data.circular_operation_data.layers_dragging.clear();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod circular_repeat;
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,18 @@ pub fn extract_star_parameters(layer: Option<LayerNodeIdentifier>, document: &Do
Some((sides, radius_1, radius_2))
}

pub fn extract_circular_repeat_parameters(layer: Option<LayerNodeIdentifier>, 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<LayerNodeIdentifier>, document: &DocumentMessageHandler) -> Option<(u32, f64)> {
Expand Down
3 changes: 3 additions & 0 deletions editor/src/messages/tool/tool_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub enum ToolMessage {
Fill(FillToolMessage),
#[child]
Gradient(GradientToolMessage),
#[child]
Operation(OperationToolMessage),

#[child]
Path(PathToolMessage),
Expand Down Expand Up @@ -58,6 +60,7 @@ pub enum ToolMessage {
ActivateToolEyedropper,
ActivateToolFill,
ActivateToolGradient,
ActivateToolOperation,
// Vector tools
ActivateToolPath,
ActivateToolPen,
Expand Down
1 change: 1 addition & 0 deletions editor/src/messages/tool/tool_messages/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading
Loading