mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-27 06:48:12 +08:00
Convert the node catalog to rank-polymorphic kernels, materialize stored values as ranked wires, and display wire rank in the graph
Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
co-authored by
Dennis Kobert
parent
83cfd0225a
commit
04d6c0d5cf
@@ -70,10 +70,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
// ================================
|
||||
// brush
|
||||
// ================================
|
||||
NodeReplacement {
|
||||
node: graphene_std::brush::brush::blit::IDENTIFIER,
|
||||
aliases: &["graphene_brush::BlitNode", "graphene_std::brush::BlitNode", "graphene_brush::brush::BlitNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::brush::brush::brush::IDENTIFIER,
|
||||
aliases: &["graphene_brush::BrushNode", "graphene_std::brush::BrushNode", "graphene_brush::brush::BrushNode"],
|
||||
@@ -172,8 +168,10 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
node: graphene_std::graphic::remove_at_index::IDENTIFIER,
|
||||
aliases: &["graphic_nodes::graphic::OmitElementNode"],
|
||||
},
|
||||
// The legacy layer extend no longer exists as a node; the aliases still land on its identifier so the
|
||||
// subgraph rebuild below recognizes and replaces the networks that carried it.
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic::legacy_layer_extend::IDENTIFIER,
|
||||
node: ProtoNodeIdentifier::new("graphic_nodes::graphic::LegacyLayerExtendNode"),
|
||||
aliases: &[
|
||||
"graphene_core::graphic_element::LayerNode",
|
||||
"graphene_core::graphic_types::LayerNode",
|
||||
@@ -739,8 +737,12 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
// vector
|
||||
// ================================
|
||||
NodeReplacement {
|
||||
node: graphene_std::vector::apply_transform::IDENTIFIER,
|
||||
aliases: &["graphene_core::vector::ApplyTransformNode", "graphene_core::vector::vector_modification::ApplyTransformNode"],
|
||||
node: graphene_std::vector::bake_transform::IDENTIFIER,
|
||||
aliases: &[
|
||||
"graphene_core::vector::ApplyTransformNode",
|
||||
"graphene_core::vector::vector_modification::ApplyTransformNode",
|
||||
"vector_nodes::vector_modification_nodes::ApplyTransformNode",
|
||||
],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::vector::area::IDENTIFIER,
|
||||
@@ -846,9 +848,10 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
node: graphene_std::context::read_index::IDENTIFIER,
|
||||
aliases: &["graphene_core::vector::InstanceIndexNode", "core_types::vector::InstanceIndexNode"],
|
||||
},
|
||||
// The string map folded into the general Map, and its reader into the vararg readers.
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic::map::IDENTIFIER,
|
||||
aliases: &["graphene_core::vector::InstanceMapNode"],
|
||||
aliases: &["graphene_core::vector::InstanceMapNode", "text_nodes::MapStringNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::context::read_position::IDENTIFIER,
|
||||
@@ -858,6 +861,10 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
node: graphene_std::context::read_vector::IDENTIFIER,
|
||||
aliases: &["graphene_core::vector::InstanceVectorNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::context::read_string::IDENTIFIER,
|
||||
aliases: &["text_nodes::ReadStringNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::repeat::repeat::IDENTIFIER,
|
||||
aliases: &["graphene_core::vector::InstanceRepeatNode", "core_types::vector::InstanceRepeatNode"],
|
||||
@@ -1289,9 +1296,9 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
||||
migrate_node(node_id, node, network_path, document, reset_node_definitions_on_open);
|
||||
}
|
||||
|
||||
// The old geometry-producing "Text" node was split into the current "Text" (`String[]`) -> "Text to Vector" pair, which reuses the same
|
||||
// proto identifier. Runs after `migrate_node` normalizes old text nodes to the legacy 13-input layout, distinguished from the current
|
||||
// 12-input node by the trailing `separate_glyphs` input (index 12): forward inputs 0..=11 onto the new node and move it onto `text_to_vector`.
|
||||
// The old geometry-producing "Text" node was split into the current "Text" (`String[]`) -> converter pair, which reuses the same proto
|
||||
// identifier. Runs after `migrate_node` normalizes old text nodes to the legacy 13-input layout, distinguished from the current 12-input
|
||||
// node by the trailing `separate_glyphs` input (index 12): forward inputs 0..=11 onto the new node and splice the matching converter after it.
|
||||
let old_text_nodes: Vec<(NodeId, Vec<NodeId>)> = document
|
||||
.network_interface
|
||||
.document_network()
|
||||
@@ -1325,7 +1332,8 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, new_index), input.clone(), network_path);
|
||||
}
|
||||
}
|
||||
let separate_glyphs = old_inputs.get(12).cloned();
|
||||
// A `true` toggle at index 12 chose per-glyph geometry, which is now the dedicated "Text to Vector Glyphs" node
|
||||
let separate_glyphs = matches!(old_inputs.get(12).and_then(|input| input.as_value()), Some(TaggedValue::Bool(true)));
|
||||
|
||||
// Collect the inputs reading the old text node's output before any rewiring so the new node can be spliced onto those wires.
|
||||
let downstream_consumers: Vec<InputConnector> = document
|
||||
@@ -1337,40 +1345,35 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
||||
|
||||
let text_was_in_chain = text_nodes_in_chain.contains(node_id);
|
||||
|
||||
// Insert the `text_to_vector` node that converts the `text` `String[]` output back into vector geometry.
|
||||
let Some(text_to_vector_definition) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::text::text_to_vector::IDENTIFIER)) else {
|
||||
// Insert the converter that turns the `text` `String[]` output back into vector geometry: "Text to Vector Glyphs" for the per-glyph case, otherwise "Text to Vector".
|
||||
let converter_identifier = if separate_glyphs {
|
||||
graphene_std::text::text_to_vector_glyphs::IDENTIFIER
|
||||
} else {
|
||||
graphene_std::text::text_to_vector::IDENTIFIER
|
||||
};
|
||||
let Some(converter_definition) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(converter_identifier)) else {
|
||||
continue;
|
||||
};
|
||||
let text_to_vector_id = NodeId::new();
|
||||
document
|
||||
.network_interface
|
||||
.insert_node(text_to_vector_id, text_to_vector_definition.default_node_template(), network_path);
|
||||
let converter_id = NodeId::new();
|
||||
document.network_interface.insert_node(converter_id, converter_definition.default_node_template(), network_path);
|
||||
|
||||
// Splice `text_to_vector` onto the wire(s) leaving `text` (`insert_node_between` is the pure wire-splice the editor uses for
|
||||
// dropping a node on a wire), then carry the old `separate_glyphs` value onto its second input.
|
||||
// Splice the converter onto the wire(s) leaving `text` (`insert_node_between` is the pure wire-splice the editor uses for dropping a node on a wire).
|
||||
if let Some((first_consumer, remaining_consumers)) = downstream_consumers.split_first() {
|
||||
document.network_interface.insert_node_between(&text_to_vector_id, first_consumer, 0, network_path);
|
||||
document.network_interface.insert_node_between(&converter_id, first_consumer, 0, network_path);
|
||||
for consumer in remaining_consumers {
|
||||
document.network_interface.set_input(consumer, NodeInput::node(text_to_vector_id, 0), network_path);
|
||||
document.network_interface.set_input(consumer, NodeInput::node(converter_id, 0), network_path);
|
||||
}
|
||||
} else {
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(text_to_vector_id, 0), NodeInput::node(*node_id, 0), network_path);
|
||||
}
|
||||
if let Some(separate_glyphs) = separate_glyphs {
|
||||
document.network_interface.set_input(&InputConnector::node(text_to_vector_id, 1), separate_glyphs, network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(converter_id, 0), NodeInput::node(*node_id, 0), network_path);
|
||||
}
|
||||
|
||||
// If `text` was in a layer chain, re-chain `text_to_vector` and its upstream so both lay out by distance from the layer (the splice
|
||||
// broke the chain, like `move_node_to_chain_start`). Otherwise `text` is absolute, so place `text_to_vector` beside it instead of
|
||||
// If `text` was in a layer chain, re-chain the converter and its upstream so both lay out by distance from the layer (the splice
|
||||
// broke the chain, like `move_node_to_chain_start`). Otherwise `text` is absolute, so place the converter beside it instead of
|
||||
// leaving it at the origin.
|
||||
if text_was_in_chain {
|
||||
document.network_interface.force_set_upstream_to_chain(&text_to_vector_id, network_path);
|
||||
document.network_interface.force_set_upstream_to_chain(&converter_id, network_path);
|
||||
} else if let Some(text_position) = document.network_interface.position(node_id, network_path) {
|
||||
document
|
||||
.network_interface
|
||||
.shift_absolute_node_position(&text_to_vector_id, text_position + IVec2::new(7, 0), network_path);
|
||||
document.network_interface.shift_absolute_node_position(&converter_id, text_position + IVec2::new(7, 0), network_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1639,8 +1642,8 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
inputs_count = 5;
|
||||
}
|
||||
|
||||
// Upgrade the legacy 4-input Fill node (content, fill: Fill, _backup_color, _backup_gradient: Gradient) to the
|
||||
// value-model 7-input shape (content, fill: generic paint list, _backup_color, _backup_gradient, _gradient_type, _spread_method, _transform).
|
||||
// Upgrade the legacy 4-input Fill node (content, fill: Fill, _backup_color, _backup_gradient: Gradient) to the value-model
|
||||
// 8-input shape (content, fill: generic paint list, _backup_color, _backup_gradient, _gradient_type, _spread_method, _has_transform, _transform).
|
||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::fill::IDENTIFIER) && inputs_count == 4 {
|
||||
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
@@ -2192,6 +2195,13 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
}
|
||||
|
||||
// A brush node saved before `Item<Raster<CPU>>` had a default stored its unconnected background as the invalid `()`,
|
||||
// which fails type resolution against the raster primary; adopt the definition's empty-raster default instead.
|
||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::brush::brush::brush::IDENTIFIER) && matches!(node.inputs.first().and_then(|input| input.as_value()), Some(TaggedValue::None)) {
|
||||
let default_background = resolve_document_node_type(&reference)?.node_template.document_node.inputs.first()?.clone();
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), default_background, network_path);
|
||||
}
|
||||
|
||||
if reference == DefinitionIdentifier::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::RemoveHandlesNode")) {
|
||||
let mut node_template = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::vector::auto_tangents::IDENTIFIER))?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
@@ -2408,7 +2418,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
// Migrate from the v2 "Morph" node (2 inputs: content, progression) to the v3 "Morph" node (5 inputs: content, progression, reverse, distribution, path).
|
||||
// The old progression used integer part for pair selection (range 0..N-1 where N is the number of content objects).
|
||||
// The new progression uses fractional 0..1 for euclidean traversal through all objects.
|
||||
// We insert Count Elements → Subtract 1 → Divide to remap: new_progression = old_progression / (N - 1).
|
||||
// We insert List Length → Subtract 1 → Divide to remap: new_progression = old_progression / (N - 1).
|
||||
// For the common 2-object case (N=2), this divides by 1 which is a no-op, preserving identical behavior.
|
||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::morph::IDENTIFIER) && inputs_count == 2 {
|
||||
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
|
||||
@@ -2463,10 +2473,10 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
document.network_interface.insert_node(divide_id, divide_template, network_path);
|
||||
document.network_interface.shift_absolute_node_position(÷_id, morph_position + IVec2::new(-7, 1), network_path);
|
||||
|
||||
// Wire: content source → Count Elements input 0
|
||||
// Wire: content source → List Length input 0
|
||||
document.network_interface.set_input(&InputConnector::node(list_length_id, 0), old_inputs[0].clone(), network_path);
|
||||
|
||||
// Wire: Count Elements output → Subtract input 0 (minuend)
|
||||
// Wire: List Length output → Subtract input 0 (minuend)
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(subtract_id, 0), NodeInput::node(list_length_id, 0), network_path);
|
||||
@@ -2677,6 +2687,34 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
}
|
||||
}
|
||||
|
||||
// A value input stored as a List-form TypeDefault adopts the definition's current default when the connector's declared default has since changed (e.g. the connector was ranked down to Item).
|
||||
// The red-slash no-paint choice shares that stored form but is a deliberate value, not a stale disconnect default, so it is exempt.
|
||||
if let Some(definition) = resolve_document_node_type(&reference) {
|
||||
let definition_inputs = definition.node_template.document_node.inputs.clone();
|
||||
for (index, definition_input) in definition_inputs.iter().enumerate() {
|
||||
if !matches!(definition_input, NodeInput::Value { .. }) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let stale_list_default = document
|
||||
.network_interface
|
||||
.input_from_connector(&InputConnector::node(*node_id, index), network_path)
|
||||
.is_some_and(|stored_input| match stored_input {
|
||||
NodeInput::Value { tagged_value, .. } => match &**tagged_value {
|
||||
TaggedValue::TypeDefault(stored_type) if stored_type.name.contains("list::List<") && !tagged_value.is_no_paint() => {
|
||||
!matches!(definition_input, NodeInput::Value { tagged_value, .. } if matches!(&**tagged_value, TaggedValue::TypeDefault(definition_type) if definition_type == stored_type))
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
});
|
||||
|
||||
if stale_list_default {
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, index), definition_input.clone(), network_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================================
|
||||
// PUT ALL MIGRATIONS ABOVE THIS LINE
|
||||
// ==================================
|
||||
|
||||
Reference in New Issue
Block a user