Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::utility_types::TransformIn;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};
use crate::messages::prelude::*;
use glam::{DAffine2, DVec2};
use graph_craft::document::NodeId;
Expand All @@ -27,6 +27,9 @@ pub enum GraphOperationMessage {
spread_method: GradientSpreadMethod,
transform: DAffine2,
},
InitializeFillGradientMetadata {
fill_node_id: NodeId,
},
BlendingFillSet {
layer: LayerNodeIdentifier,
fill: f64,
Expand Down Expand Up @@ -157,4 +160,8 @@ pub enum GraphOperationMessage {
/// When true, centers the SVG at the transform origin (clipboard paste / drag-drop). When false, keeps natural SVG coordinates (file-open flow).
center: bool,
},
NormalizeAfterInputConnection {
output_connector: OutputConnector,
input_connector: InputConnector,
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@ use super::transform_utils;
use super::utility_types::ModifyInputsContext;
use crate::consts::{LAYER_INDENT_OFFSET, STACK_VERTICAL_GAP};
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface, OutputConnector};
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeNetworkInterface, OutputConnector};
use crate::messages::portfolio::document::utility_types::nodes::CollapsedLayers;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode;
use glam::{DAffine2, DVec2, IVec2};
use graph_craft::descriptor;
use graph_craft::document::{NodeId, NodeInput};
use graphene_std::NodeInputDecleration;
use graphene_std::list::List;
use graphene_std::renderer::convert_usvg_path::convert_usvg_path;
use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::style::{GradientSpreadMethod, GradientStop, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
use graphene_std::{Artboard, Color};
use graphene_std::{Artboard, Color, Graphic};

#[derive(ExtractField)]
pub struct GraphOperationMessageContext<'a> {
Expand Down Expand Up @@ -50,6 +52,10 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
modify_inputs.fill_gradient_set(gradient, gradient_type, spread_method, transform);
}
}
GraphOperationMessage::InitializeFillGradientMetadata { fill_node_id } => {
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
modify_inputs.initialize_fill_gradient_metadata(fill_node_id);
}
GraphOperationMessage::BlendingFillSet { layer, fill } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.opacity_fill_set(fill);
Expand Down Expand Up @@ -479,6 +485,32 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
// (skipped automatically when identity, so file-open with content at origin creates no Transform node).
modify_inputs.transform_set(placement_transform, TransformIn::Local, false);
}
GraphOperationMessage::NormalizeAfterInputConnection { output_connector, input_connector } => {
let InputConnector::Node { node_id, input_index } = input_connector else { return };

// Fill node: When a Gradient Value chain is connected to a Fill node's paint input, reset the hidden gradient metadata.
// This prevents the rendered gradient and Gradient tool state from getting out of sync.
if let Some(def_id) = network_interface.reference(&node_id, &[]).as_ref()
&& def_id == &DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER)
&& input_index == graphene_std::vector::fill::FillInput::<List<Graphic>>::INDEX
{
let Some(walk_from) = output_connector.node_id() else { return };
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated

let is_gradient_value_connected = network_interface
.upstream_flow_back_from_nodes(vec![walk_from], &[], FlowType::PrimaryFlow)
Comment thread
YohYamasaki marked this conversation as resolved.
Outdated
.take_while(|node_id| !network_interface.is_layer(node_id, &[]))
.any(|node_id| {
let reference = network_interface.reference(&node_id, &[]);
reference.as_ref() == Some(&DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::gradient_value::IDENTIFIER))
});

if !is_gradient_value_connected {
return;
}

responses.add(GraphOperationMessage::InitializeFillGradientMetadata { fill_node_id: node_id });
};
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,33 @@ impl<'a> ModifyInputsContext<'a> {
);
}

// Reset all gradient-related metadata on the target Fill node.
pub fn initialize_fill_gradient_metadata(&mut self, fill_node_id: NodeId) {
self.set_input_with_refresh(
InputConnector::node(fill_node_id, graphene_std::vector::fill::BackupGradientInput::INDEX),
NodeInput::value(TaggedValue::Gradient(Default::default()), false),
true,
);

self.set_input_with_refresh(
InputConnector::node(fill_node_id, graphene_std::vector::fill::GradientTypeInput::INDEX),
NodeInput::value(TaggedValue::GradientType(Default::default()), false),
true,
);

self.set_input_with_refresh(
InputConnector::node(fill_node_id, graphene_std::vector::fill::SpreadMethodInput::INDEX),
NodeInput::value(TaggedValue::GradientSpreadMethod(Default::default()), false),
true,
);

self.set_input_with_refresh(
InputConnector::node(fill_node_id, graphene_std::vector::fill::TransformInput::INDEX),
NodeInput::value(TaggedValue::OptionalDAffine2(None), false),
false,
);
}

pub fn blend_mode_set(&mut self, blend_mode: BlendMode) {
let Some(blend_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::blend_mode::IDENTIFIER, true) else {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
return;
}
network_interface.create_wire(&output_connector, &input_connector, selection_network_path);

responses.add(GraphOperationMessage::NormalizeAfterInputConnection { output_connector, input_connector });
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
}
NodeGraphMessage::Copy => {
let all_selected_nodes = network_interface.upstream_chain_nodes(selection_network_path);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,33 @@ pub fn set_fill_for_selected_layers(fill_choice: FillChoice, document: &Document
}
}

/// Check if a gradient chain feeding a Fill node needs a Transform node.
pub fn gradient_fill_chain_needs_transform_node(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> bool {
let target_input = gradient_chain_target_input(layer, network_interface);
let InputConnector::Node { node_id: fill_id, .. } = target_input else {
return false;
};

// Only handle the case where a Gradient Value node is connected to a Fill node
if network_interface.reference(&fill_id, &[]).as_ref() != Some(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))
|| get_upstream_gradient_value_node_id(layer, network_interface).is_none()
{
return false;
}

// Skip chains that already have a Transform node
let Some(walk_from) = network_interface.upstream_output_connector(&target_input, &[]).and_then(|output| output.node_id()) else {
return false;
};

let has_transform_node = network_interface
.upstream_flow_back_from_nodes(vec![walk_from], &[], FlowType::HorizontalFlow)
.take_while(|node_id| !network_interface.is_layer(node_id, &[]))
.any(|node_id| network_interface.reference(&node_id, &[]).as_ref() == Some(&DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::transform::IDENTIFIER)));

!has_transform_node
}

/// Sets the stroke color on all selected non-artboard layers. Layers without an existing Stroke node get one created using
/// the provided `weight`, so picking any color (including `None`) from an unticked stroke control bar entry both attaches
/// the Stroke node and applies the chosen color.
Expand Down
28 changes: 25 additions & 3 deletions editor/src/messages/tool/tool_messages/gradient_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface};
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::graph_modification_utils::{
self, NodeGraphLayer, get_fill_node_id_with_direct_fill_input, get_gradient_stops, get_upstream_gradient_value_node_id, gradient_chain_target_input,
self, NodeGraphLayer, get_fill_node_id_with_direct_fill_input, get_gradient_stops, get_upstream_gradient_value_node_id, gradient_chain_target_input, gradient_fill_chain_needs_transform_node,
};
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
use glam::DMat2;
use graph_craft::document::value::TaggedValue;
use graphene_std::color::SRGBA8;
use graphene_std::raster::color::Color;
use graphene_std::vector::style::{FillChoice, FillChoiceUI, GradientSpreadMethod, GradientStop, GradientStops, GradientStopsUI, GradientType, build_transform_with_y_preservation};
use graphene_std::vector::style::{
FillChoice, FillChoiceUI, GradientSpreadMethod, GradientStop, GradientStops, GradientStopsUI, GradientType, build_transform_with_y_preservation, initial_gradient_transform_for_bounding_box,
};

#[derive(Default, ExtractField)]
pub struct GradientTool {
Expand Down Expand Up @@ -1119,6 +1121,16 @@ impl Fsm for GradientToolFsmState {
tool_data.color_picker_editing_color_stop = None;
}
tool_data.selected_gradient = None;

// Insert a Transform node if a chain-backed gradient does not have one yet. This prevents the rendered gradient and Gradient tool state from getting out of sync.
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
let network_interface = &document.network_interface;
for layer in network_interface.selected_nodes().selected_visible_layers(network_interface) {
if gradient_fill_chain_needs_transform_node(layer, network_interface) {
let transform = initial_gradient_transform_for_bounding_box(network_interface.document_metadata().nonzero_bounding_box(layer));
responses.add(GraphOperationMessage::GradientTransformSet { layer, transform });
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
}
}

GradientToolFsmState::Ready {
hovering: GradientHoverTarget::None,
selected: GradientSelectedTarget::None,
Expand Down Expand Up @@ -1490,7 +1502,7 @@ impl Fsm for GradientToolFsmState {
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
}

let (gradient, appearance, source) = match resolve_gradient(layer, &document.network_interface) {
let (gradient, mut appearance, source) = match resolve_gradient(layer, &document.network_interface) {
// Use the already existing gradient if it exists
Some(gradient) => gradient,
// Generate a new gradient running primary → secondary so the default working colors
Expand All @@ -1516,6 +1528,16 @@ impl Fsm for GradientToolFsmState {
GradientSource::Direct,
),
};

// Insert a Transform node if a chain-backed gradient does not have one yet. This prevents the rendered gradient and Gradient tool state from getting out of sync.
if matches!(source, GradientSource::Chain) && gradient_fill_chain_needs_transform_node(layer, &document.network_interface) {
let transform = initial_gradient_transform_for_bounding_box(document.network_interface.document_metadata().nonzero_bounding_box(layer));

responses.add(GraphOperationMessage::GradientTransformSet { layer, transform });

// GraphOperation is queued, so it won't affect this PointerDown immediately. Patch the local tool state too.
appearance.transform = transform;
}
let mut selected_gradient = SelectedGradient::new(gradient, appearance, source, layer, document);
selected_gradient.dragging = GradientDragTarget::New;

Expand Down
Loading