Skip to content

Add new "Gradient" node#4177

Draft
YohYamasaki wants to merge 2 commits into
GraphiteEditor:masterfrom
YohYamasaki:gradient-node
Draft

Add new "Gradient" node#4177
YohYamasaki wants to merge 2 commits into
GraphiteEditor:masterfrom
YohYamasaki:gradient-node

Conversation

@YohYamasaki
Copy link
Copy Markdown
Contributor

WIP!

  • Add a new "Gradient" node that bundles stops + type + spread method + control geometry into a single node, outputting List.

  • Update From<List<GradientStops>> for Fill to honor the attributes (gradient_type / spread_method / transform) on the list.

  • Teach the Gradient tool to read from and write to the new Gradient node, supporting some connection patterns:

    • "Gradient" node → Layer
    • "Gradient" node → "Fill" node
    • "Gradient Value" node → Layer (existing)
    • "Gradient Value" node → Fill node (existing)
    • "Fill" node (uses Fill::Gradient enum, existing)

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for a new "Gradient" node alongside the legacy "Gradient Value" node, updating the gradient tool, graph operations, and style conversions to handle both. Key changes include adding helper functions to traverse and resolve upstream gradient nodes, implementing a new existing_proto_node_id_at method to walk and insert nodes at specific inputs, and updating the gradient stop, type, and spread setters to support the new node structure. Feedback focuses on preventing state corruption in existing_proto_node_id_at by resolving inputs before node insertion, expanding get_gradient_stops to support the new node type, and avoiding redundant lookups in gradient_stops_set.

Comment on lines +358 to +367
// splice new node between target_input and its current upstream
let node_definition = resolve_proto_node_type(reference)?;
let node_id = NodeId::new();
self.network_interface.insert_node(node_id, node_definition.default_node_template(), &[]);

let current_input = self.network_interface.input_from_connector(target_input, &[])?.clone();
self.network_interface.set_input(&InputConnector::node(node_id, 0), current_input, &[]);
self.network_interface.set_input(target_input, NodeInput::node(node_id, 0), &[]);

Some(node_id)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In existing_proto_node_id_at, if input_from_connector returns None (line 363), the function returns early but has already inserted the new node via insert_node (line 361). This leaves an orphaned node in the document network. Resolving current_input before inserting the node prevents this state corruption.

Suggested change
// splice new node between target_input and its current upstream
let node_definition = resolve_proto_node_type(reference)?;
let node_id = NodeId::new();
self.network_interface.insert_node(node_id, node_definition.default_node_template(), &[]);
let current_input = self.network_interface.input_from_connector(target_input, &[])?.clone();
self.network_interface.set_input(&InputConnector::node(node_id, 0), current_input, &[]);
self.network_interface.set_input(target_input, NodeInput::node(node_id, 0), &[]);
Some(node_id)
// splice new node between target_input and its current upstream
let current_input = self.network_interface.input_from_connector(target_input, &[])?.clone();
let node_definition = resolve_proto_node_type(reference)?;
let node_id = NodeId::new();
self.network_interface.insert_node(node_id, node_definition.default_node_template(), &[]);
self.network_interface.set_input(&InputConnector::node(node_id, 0), current_input, &[]);
self.network_interface.set_input(target_input, NodeInput::node(node_id, 0), &[]);
Some(node_id)

Comment on lines 324 to 330
pub fn get_gradient_stops(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<GradientStops> {
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::gradient_value::IDENTIFIER))?;
let TaggedValue::Gradient(stops) = inputs.get(graphene_std::math_nodes::gradient_value::GradientInput::INDEX)?.as_value()? else {
let gradient_value_node = network_interface.document_network().nodes.get(&get_upstream_gradient_value_node_id(layer, network_interface)?)?;
let TaggedValue::Gradient(stops) = gradient_value_node.inputs.get(graphene_std::math_nodes::gradient_value::GradientInput::INDEX)?.as_value()? else {
return None;
};
Some(stops.clone())
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

get_gradient_stops currently only supports retrieving stops from the legacy Gradient Value node. If a layer is configured with the new Gradient node, this function will return None, which could break other parts of the editor relying on it. We should update it to check for both node types.

Suggested change
pub fn get_gradient_stops(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<GradientStops> {
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::gradient_value::IDENTIFIER))?;
let TaggedValue::Gradient(stops) = inputs.get(graphene_std::math_nodes::gradient_value::GradientInput::INDEX)?.as_value()? else {
let gradient_value_node = network_interface.document_network().nodes.get(&get_upstream_gradient_value_node_id(layer, network_interface)?)?;
let TaggedValue::Gradient(stops) = gradient_value_node.inputs.get(graphene_std::math_nodes::gradient_value::GradientInput::INDEX)?.as_value()? else {
return None;
};
Some(stops.clone())
}
pub fn get_gradient_stops(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<GradientStops> {
if let Some(gradient_node_id) = get_upstream_gradient_node_id(layer, network_interface) {
let node = network_interface.document_network().nodes.get(&gradient_node_id)?;
let TaggedValue::Gradient(stops) = node.inputs.get(graphene_std::graphic_nodes::gradient::GradientInput::<List<GradientStops>>::INDEX)?.as_value()? else {
return None;
};
return Some(stops.clone());
}
let gradient_value_node = network_interface.document_network().nodes.get(&get_upstream_gradient_value_node_id(layer, network_interface)?)?;
let TaggedValue::Gradient(stops) = gradient_value_node.inputs.get(graphene_std::math_nodes::gradient_value::GradientInput::INDEX)?.as_value()? else {
return None;
};
Some(stops.clone())
}

Comment on lines 526 to 542
pub fn gradient_stops_set(&mut self, stops: GradientStops) {
let Some(gradient_node_id) = self.existing_proto_node_id(graphene_std::math_nodes::gradient_value::IDENTIFIER, true) else {
// Try to find a Gradient node first and update its gradient stops
if let Some(output_layer) = self.get_output_layer()
&& let Some(gradient_node_id) = get_upstream_gradient_node_id(output_layer, self.network_interface)
{
let input_connector = InputConnector::node(gradient_node_id, graphene_std::graphic_nodes::gradient::GradientInput::<List<GradientStops>>::INDEX);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Gradient(stops), false), false);
return;
};
let input_connector = InputConnector::node(gradient_node_id, graphene_std::math_nodes::gradient_value::GradientInput::INDEX);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Gradient(stops), false), false);
}
// Then try to find Gradient Value node that is connected to a Fill node
if let Some(output_layer) = self.get_output_layer()
&& let Some(gradient_value_id) = get_upstream_gradient_value_node_id(output_layer, self.network_interface)
{
let input_connector = InputConnector::node(gradient_value_id, graphene_std::math_nodes::gradient_value::GradientInput::INDEX);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Gradient(stops), false), false);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The gradient_stops_set function calls self.get_output_layer() twice. Binding it once at the beginning of the function simplifies the control flow and avoids redundant lookups.

	pub fn gradient_stops_set(&mut self, stops: GradientStops) {
		let Some(output_layer) = self.get_output_layer() else {
			return;
		};
		// Try to find a Gradient node first and update its gradient stops
		if let Some(gradient_node_id) = get_upstream_gradient_node_id(output_layer, self.network_interface) {
			let input_connector = InputConnector::node(gradient_node_id, graphene_std::graphic_nodes::gradient::GradientInput::<List<GradientStops>>::INDEX);
			self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Gradient(stops), false), false);
			return;
		}
		// Then try to find Gradient Value node that is connected to a Fill node
		if let Some(gradient_value_id) = get_upstream_gradient_value_node_id(output_layer, self.network_interface) {
			let input_connector = InputConnector::node(gradient_value_id, graphene_std::math_nodes::gradient_value::GradientInput::INDEX);
			self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Gradient(stops), false), false);
		}
	}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant