multi-layer drag and repeat

This commit is contained in:
0SlowPoke0
2025-08-24 22:02:20 +05:30
parent 57ecdac1ec
commit dabdef7bed
5 changed files with 96 additions and 14 deletions

View File

@@ -22,8 +22,11 @@ pub enum GraphOperationMessage {
layer: LayerNodeIdentifier,
fill: Fill,
},
RepeatSet {
CircularRepeatSet {
layer: LayerNodeIdentifier,
angle: f64,
radius: f64,
count: u32,
},
BlendingFillSet {
layer: LayerNodeIdentifier,

View File

@@ -37,9 +37,9 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
modify_inputs.fill_set(fill);
}
}
GraphOperationMessage::RepeatSet { layer } => {
GraphOperationMessage::CircularRepeatSet { layer, angle, radius, count } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.create_node("Repeat");
modify_inputs.circular_repeat_set(angle, radius, count);
}
}
GraphOperationMessage::BlendingFillSet { layer, fill } => {

View File

@@ -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) {

View File

@@ -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)> {

View File

@@ -8,6 +8,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils::{self, find_spline, merge_layers, merge_points};
use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_circular_repeat_parameters, extract_star_parameters};
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapManager, SnapTypeConfiguration, SnappedPoint};
use crate::messages::tool::common_functionality::utility_functions::{closest_point, should_extend};
use graph_craft::document::{NodeId, NodeInput};
@@ -49,7 +50,6 @@ pub enum OperationToolMessage {
Confirm,
DragStart,
DragStop,
MergeEndpoints,
PointerMove,
PointerOutsideViewport,
Undo,
@@ -222,10 +222,19 @@ impl ToolTransition for OperationTool {
}
#[derive(Clone, Debug, Default)]
struct OperationToolData {}
struct OperationToolData {
drag_start: DVec2,
clicked_layer: LayerNodeIdentifier,
layers_dragging: Vec<(LayerNodeIdentifier, f64)>,
initial_center: DVec2,
}
impl OperationToolData {
fn cleanup(&mut self) {}
fn cleanup(&mut self) {
self.layers_dragging.clear();
}
fn modify_circular_repeat(&mut self) {}
}
impl Fsm for OperationToolFsmState {
@@ -251,19 +260,66 @@ impl Fsm for OperationToolFsmState {
let ToolMessage::Operation(event) = event else { return self };
match (self, event) {
(_, OperationToolMessage::Overlays { context: mut overlay_context }) => self,
(_, OperationToolMessage::Overlays { context: mut overlay_context }) => {}
(OperationToolFsmState::Ready, OperationToolMessage::DragStart) => {
let selected_layers = document
.network_interface
.selected_nodes()
.selected_layers(document.metadata())
.collect::<HashSet<LayerNodeIdentifier>>();
let Some(layer) = document.click(&input) else { return self };
responses.add(GraphOperationMessage::RepeatSet { layer });
responses.add(NodeGraphMessage::RunDocumentGraph);
let viewport = document.metadata().transform_to_viewport(layer);
if selected_layers.contains(&layer) {
// store all
tool_data.layers_dragging = selected_layers
.iter()
.map(|layer| {
let (_angle_offset, radius, _count) = extract_circular_repeat_parameters(Some(*layer), document).unwrap_or((0.0, 0.0, 6));
(*layer, radius)
})
.collect::<Vec<(LayerNodeIdentifier, f64)>>();
} else {
// deselect all the layer and store the clicked layer for repeat and dragging
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
let (_angle_offset, radius, _count) = extract_circular_repeat_parameters(Some(layer), document).unwrap_or((0.0, 0.0, 6));
tool_data.layers_dragging = vec![(layer, radius)];
}
tool_data.drag_start = input.mouse.position;
tool_data.clicked_layer = layer;
tool_data.initial_center = viewport.transform_point2(DVec2::ZERO);
OperationToolFsmState::Drawing
}
(OperationToolFsmState::Drawing, OperationToolMessage::DragStop) => OperationToolFsmState::Drawing,
(OperationToolFsmState::Drawing, OperationToolMessage::PointerMove) => OperationToolFsmState::Drawing,
(_, OperationToolMessage::PointerMove) => {
log::info!("hello");
self
(OperationToolFsmState::Drawing, OperationToolMessage::DragStop) => {
tool_data.cleanup();
OperationToolFsmState::Ready
}
(OperationToolFsmState::Drawing, OperationToolMessage::PointerMove) => {
// Don't add the repeat node unless dragging more that 5 px
if tool_data.drag_start.distance(input.mouse.position) < 5. {
return self;
};
let viewport = document.metadata().transform_to_viewport(tool_data.clicked_layer);
let center = viewport.transform_point2(DVec2::ZERO);
let sign = (input.mouse.position - tool_data.initial_center).dot(tool_data.drag_start - tool_data.initial_center).signum();
let delta = viewport.inverse().transform_vector2(input.mouse.position - tool_data.drag_start).length() * sign;
log::info!("{:?}", delta);
for (layer, initial_radius) in &tool_data.layers_dragging {
responses.add(GraphOperationMessage::CircularRepeatSet {
layer: *layer,
angle: 0.,
radius: initial_radius + delta,
count: 6,
});
}
responses.add(NodeGraphMessage::RunDocumentGraph);
OperationToolFsmState::Drawing
}
(_, OperationToolMessage::PointerMove) => self,
(OperationToolFsmState::Drawing, OperationToolMessage::PointerOutsideViewport) => OperationToolFsmState::Drawing,
(state, OperationToolMessage::PointerOutsideViewport) => state,