Move source_node_id to "editor:layer" and Vector<Upstream> to "editor:merged_layers", backed by a new 'Write Attribute' node (#4061)

* Fix click target propagation with the Rasterize node

* Add the 'Write Attribute' node

* Remove tag_layer in favor of the new Write Attribute node, prune redundant attribute writes

* Replace the Vector<Upstream> type argument with the "editor:merged_layers" attribute
This commit is contained in:
Keavon Chambers
2026-04-28 03:28:31 -07:00
parent 76938eb69a
commit afc2c9178e
27 changed files with 312 additions and 376 deletions
@@ -275,6 +275,8 @@ impl<T: TableRowLayout> TableRowLayout for Table<T> {
Some(format_alpha_blending(value))
} else if let Some(&value) = ty.downcast_ref::<Option<NodeId>>() {
Some(value.map_or_else(|| "-".to_string(), |id| id.to_string()))
} else if let Some(value) = ty.downcast_ref::<Table<Graphic>>() {
Some(format!("{} Objects", value.len()))
} else {
None
}
@@ -458,17 +460,6 @@ impl TableRowLayout for Vector {
TextLabel::new("Colinear Handle IDs").narrow(true).widget_instance(),
TextLabel::new(colinear).narrow(true).widget_instance(),
]);
table_rows.push(vec![
TextLabel::new("Upstream Nested Layers").narrow(true).widget_instance(),
TextLabel::new(if self.upstream_data.is_some() {
"Yes (this preserves references to its upstream nested layers for editing by tools)"
} else {
"No (this doesn't preserve references to its upstream nested layers for editing by tools)"
})
.narrow(true)
.widget_instance(),
]);
}
VectorTableTab::Points => {
table_rows.push(column_headings(&["", "position"]));
@@ -219,7 +219,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(4), 0)],
exports: vec![NodeInput::node(NodeId(5), 0)],
nodes: [
// Primary (bottom) input type coercion
DocumentNode {
@@ -233,22 +233,33 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
implementation: DocumentNodeImplementation::ProtoNode(graphic::wrap_graphic::IDENTIFIER),
..Default::default()
},
// Store the ID of the parent node (which encapsulates this sub-network) in each row we are extending the table with.
// Derive the parent layer's NodeId from the document path
DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
implementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER),
inputs: vec![NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
implementation: DocumentNodeImplementation::ProtoNode(graphic::parent_layer::IDENTIFIER),
..Default::default()
},
// Stamp each row of the content with the parent layer's NodeId via the `editor:layer` attribute,
// so editor tools (e.g. selection, click target routing) can trace data back to its owning layer.
DocumentNode {
inputs: vec![
NodeInput::node(NodeId(1), 0),
NodeInput::value(TaggedValue::String(String::from("editor:layer")), false),
NodeInput::node(NodeId(2), 0),
],
implementation: DocumentNodeImplementation::ProtoNode(graphic::write_attribute::IDENTIFIER),
..Default::default()
},
// The monitor node is used to display a thumbnail in the UI
DocumentNode {
inputs: vec![NodeInput::node(NodeId(2), 0)],
inputs: vec![NodeInput::node(NodeId(3), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
skip_deduplication: true,
..Default::default()
},
DocumentNode {
call_argument: generic!(T),
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(3), 0)],
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(4), 0)],
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
..Default::default()
},
@@ -272,6 +283,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
// 0: to_graphic
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -3)),
@@ -279,6 +291,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
},
..Default::default()
},
// 1: wrap_graphic
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -1)),
@@ -286,6 +299,15 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
},
..Default::default()
},
// 2: parent_layer
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, 1)),
..Default::default()
},
..Default::default()
},
// 3: write_attribute
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -1)),
@@ -293,6 +315,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
},
..Default::default()
},
// 4: monitor
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-7, -1)),
@@ -300,6 +323,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
},
..Default::default()
},
// 5: extend
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, -3)),
@@ -328,7 +352,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(3), 0)],
exports: vec![NodeInput::node(NodeId(4), 0)],
nodes: [
// Ensure this ID is kept in sync with the ID in set_alias so that the name input is kept in sync with the alias
DocumentNode {
@@ -344,16 +368,27 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
],
..Default::default()
},
// Store the ID of the parent node (which encapsulates this sub-network) in each row we are extending the table with.
// Derive the parent layer's NodeId from the document path
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
implementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER),
inputs: vec![NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
implementation: DocumentNodeImplementation::ProtoNode(graphic::parent_layer::IDENTIFIER),
..Default::default()
},
// Stamp each row of the content with the parent layer's NodeId via the `editor:layer` attribute,
// so editor tools (e.g. selection, click target routing) can trace data back to its owning layer.
DocumentNode {
inputs: vec![
NodeInput::node(NodeId(0), 0),
NodeInput::value(TaggedValue::String(String::from("editor:layer")), false),
NodeInput::node(NodeId(1), 0),
],
implementation: DocumentNodeImplementation::ProtoNode(graphic::write_attribute::IDENTIFIER),
..Default::default()
},
// The monitor node is used to display a thumbnail in the UI.
// TODO: Check if thumbnail is reversed
DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)],
inputs: vec![NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
call_argument: generic!(T),
skip_deduplication: true,
@@ -362,7 +397,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
DocumentNode {
inputs: vec![
NodeInput::import(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(Table<Artboard>))), 0),
NodeInput::node(NodeId(2), 0),
NodeInput::node(NodeId(3), 0),
],
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
..Default::default()
@@ -418,6 +453,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
// 0: create_artboard
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -3)),
@@ -425,6 +461,15 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
},
..Default::default()
},
// 1: parent_layer
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, 3)),
..Default::default()
},
..Default::default()
},
// 2: write_attribute
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -3)),
@@ -432,6 +477,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
},
..Default::default()
},
// 3: monitor
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-7, -3)),
@@ -439,6 +485,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
},
..Default::default()
},
// 4: extend
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, -4)),
@@ -169,10 +169,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
"graphene_core::graphic::LegacyLayerExtendNode",
],
},
NodeReplacement {
node: graphene_std::graphic_nodes::source_node_id::IDENTIFIER,
aliases: &["graphene_core::graphic::graphic::SourceNodeIdNode", "graphene_core::graphic::SourceNodeIdNode"],
},
NodeReplacement {
node: graphene_std::graphic::to_graphic::IDENTIFIER,
aliases: &[
@@ -1024,6 +1020,16 @@ pub fn document_migration_reset_node_definition(document_serialized_content: &st
return true;
}
// The `source_node_id` proto node was removed in favor of `parent_layer` + `write_attribute`.
// Documents that still reference it inside their Merge or Artboard layer networks need those layer definitions
// reset to the current default so the new internal plumbing replaces the obsolete node.
if document_serialized_content.contains("graphic_nodes::graphic::SourceNodeIdNode")
|| document_serialized_content.contains("graphene_core::graphic::graphic::SourceNodeIdNode")
|| document_serialized_content.contains("graphene_core::graphic::SourceNodeIdNode")
{
return true;
}
false
}