Bridge legacy seams and convert the transform family

This commit is contained in:
Dennis Kobert
2026-08-22 14:25:31 +00:00
parent 217f32eb88
commit fb71ea426d
14 changed files with 227 additions and 157 deletions
@@ -457,7 +457,7 @@ impl<'a> ModifyInputsContext<'a> {
let Some(fill_node_id) = self.existing_proto_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, true) else {
return;
};
let input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::<List<Graphic>>::INDEX);
let input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::<Graphic>::INDEX);
let backup_input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::BackupColorInput::INDEX);
self.set_input_with_refresh(backup_input_connector, NodeInput::value(TaggedValue::Color(color), false), true);
@@ -474,7 +474,7 @@ impl<'a> ModifyInputsContext<'a> {
// Skip the rerender on all but the last input so the whole update triggers a single graph run
self.set_input_with_refresh(
InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::<List<Graphic>>::INDEX),
InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::<Graphic>::INDEX),
NodeInput::value(TaggedValue::Gradient(gradient), false),
true,
);
@@ -715,7 +715,7 @@ impl<'a> ModifyInputsContext<'a> {
return;
};
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::PaintInput::<List<Graphic>>::INDEX);
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::PaintInput::<Graphic>::INDEX);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(color), false), true);
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::WeightInput::INDEX);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.weight), false), true);
@@ -729,7 +729,7 @@ impl<'a> ModifyInputsContext<'a> {
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.join_miter_limit), false), false);
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::PaintOrderInput::INDEX);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::PaintOrder(stroke.paint_order), false), false);
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashLengthsInput::<graphene_std::list::List<f64>>::INDEX);
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashLengthsInput::INDEX);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64Array(stroke.dash_lengths), false), true);
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashOffsetInput::INDEX);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.dash_offset), false), true);
@@ -159,9 +159,9 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
implementation: DocumentNodeImplementation::ProtoNode(graphic::to_graphic::IDENTIFIER),
..Default::default()
},
// Secondary (left) input type coercion
// Collection of the coerced content (node 6) into the layer's group
DocumentNode {
inputs: vec![NodeInput::import(generic!(T), 1)],
inputs: vec![NodeInput::node(NodeId(6), 0)],
implementation: DocumentNodeImplementation::ProtoNode(graphic::wrap_graphic::IDENTIFIER),
..Default::default()
},
@@ -191,6 +191,12 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
..Default::default()
},
// Secondary (left) input type coercion
DocumentNode {
inputs: vec![NodeInput::import(generic!(T), 1)],
implementation: DocumentNodeImplementation::ProtoNode(graphic::to_graphic::IDENTIFIER),
..Default::default()
},
]
.into_iter()
.enumerate()
@@ -256,6 +262,14 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
},
..Default::default()
},
// 6: to_graphic (secondary)
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-28, -1)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
@@ -1769,7 +1769,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
let is_text_node = reference.as_ref().is_some_and(|r| *r == DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER));
let is_stroke_node = reference.as_ref().is_some_and(|r| *r == DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER));
let is_fill_node = reference.as_ref().is_some_and(|r| *r == DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER));
let is_fill_input = is_fill_node && input_index == graphene_std::vector::fill::FillInput::<graphene_std::list::List<Graphic>>::INDEX;
let is_fill_input = is_fill_node && input_index == graphene_std::vector::fill::FillInput::<Graphic>::INDEX;
let is_shape_generator_node = reference.as_ref().is_some_and(|r| {
[regular_polygon::IDENTIFIER, star::IDENTIFIER, arc::IDENTIFIER, spiral::IDENTIFIER, grid::IDENTIFIER, arrow::IDENTIFIER]
.into_iter()
@@ -2457,13 +2457,13 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
Other,
}
let connector = InputConnector::node(node_id, FillInput::<List<Graphic>>::INDEX);
let connector = InputConnector::node(node_id, FillInput::<Graphic>::INDEX);
let input_type = context.network_interface.input_type(&connector, context.selection_network_path);
// Pass blank_assist=false because the assist slot is filled below ("Reverse Stops" button when in gradient mode)
let mut widgets_first_row = start_widgets(ParameterWidgetsInfo::new(node_id, FillInput::<List<Graphic>>::INDEX, false, context));
let mut widgets_first_row = start_widgets(ParameterWidgetsInfo::new(node_id, FillInput::<Graphic>::INDEX, false, context));
if get_document_node(node_id, context).is_ok_and(|node| node.inputs.get(FillInput::<List<Graphic>>::INDEX).is_some_and(|input| input.is_exposed())) {
if get_document_node(node_id, context).is_ok_and(|node| node.inputs.get(FillInput::<Graphic>::INDEX).is_some_and(|input| input.is_exposed())) {
return vec![LayoutGroup::row(widgets_first_row)];
}
@@ -2474,7 +2474,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
let fill = match input_type.compiled_nested_type() {
Some(ty) if ty == &concrete!(List<Color>) => {
if let Ok(document_node) = get_document_node(node_id, context) {
let color = match document_node.inputs[FillInput::<List<Graphic>>::INDEX].as_value() {
let color = match document_node.inputs[FillInput::<Graphic>::INDEX].as_value() {
Some(&TaggedValue::Color(c)) => c,
_ => None,
};
@@ -2525,7 +2525,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
let reverse_button = IconButton::new("Reverse", 24)
.tooltip_label("Reverse Stops")
.tooltip_description("Reverse the gradient color stops.")
.on_update(update_value(move |_| TaggedValue::Gradient(stops.reversed()), node_id, FillInput::<List<Graphic>>::INDEX))
.on_update(update_value(move |_| TaggedValue::Gradient(stops.reversed()), node_id, FillInput::<Graphic>::INDEX))
.widget_instance();
widgets_first_row.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
widgets_first_row.push(reverse_button);
@@ -2549,7 +2549,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
messages: Box::new([
NodeGraphMessage::SetInputValue {
node_id,
input_index: FillInput::<List<Graphic>>::INDEX,
input_index: FillInput::<Graphic>::INDEX,
value: TaggedValue::Color(color),
}
.into(),
@@ -2566,7 +2566,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
messages: Box::new([
NodeGraphMessage::SetInputValue {
node_id,
input_index: FillInput::<List<Graphic>>::INDEX,
input_index: FillInput::<Graphic>::INDEX,
value: TaggedValue::Gradient(gradient.clone()),
}
.into(),
@@ -2607,11 +2607,11 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
let entries = vec![
RadioEntryData::new("solid")
.label("Solid")
.on_update(update_value(move |_| TaggedValue::Color(backup_color), node_id, FillInput::<List<Graphic>>::INDEX))
.on_update(update_value(move |_| TaggedValue::Color(backup_color), node_id, FillInput::<Graphic>::INDEX))
.on_commit(commit_value),
RadioEntryData::new("gradient")
.label("Gradient")
.on_update(update_value(move |_| TaggedValue::Gradient(backup_gradient.clone()), node_id, FillInput::<List<Graphic>>::INDEX))
.on_update(update_value(move |_| TaggedValue::Gradient(backup_gradient.clone()), node_id, FillInput::<Graphic>::INDEX))
.on_commit(commit_value),
];
@@ -2717,14 +2717,14 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
_ => &StrokeJoin::Miter,
};
let has_dash_lengths = match &document_node.inputs[DashLengthsInput::<List<f64>>::INDEX].as_value() {
let has_dash_lengths = match &document_node.inputs[DashLengthsInput::INDEX].as_value() {
Some(TaggedValue::F64Array(values)) => values.is_empty(),
_ => true,
};
let miter_limit_disabled = join_value != &StrokeJoin::Miter;
let color = color_widget(
ParameterWidgetsInfo::new(node_id, PaintInput::<List<Graphic>>::INDEX, true, context),
ParameterWidgetsInfo::new(node_id, PaintInput::<Graphic>::INDEX, true, context),
crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(),
);
let weight = number_widget(ParameterWidgetsInfo::new(node_id, WeightInput::INDEX, true, context), NumberInput::default().unit(" px").min(0.));
@@ -2745,7 +2745,7 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
.property_row();
let disabled_number_input = NumberInput::default().unit(" px").disabled(has_dash_lengths);
let dash_lengths = array_of_number_widget(
ParameterWidgetsInfo::new(node_id, DashLengthsInput::<List<f64>>::INDEX, true, context),
ParameterWidgetsInfo::new(node_id, DashLengthsInput::INDEX, true, context),
TextInput::default().centered(true),
);
let number_input = disabled_number_input;
@@ -20,6 +20,29 @@ pub fn load_demo(file_name: &str) -> DocumentMessageHandler {
DocumentMessageHandler::deserialize_document(&content).unwrap_or_else(|e| panic!("Failed to deserialize {path}: {e:?}"))
}
/// Dev tool for the flip: runs each demo through the full open-time migration and writes the
/// re-saved form into the directory named by `DEMO_OUT`, so the CLI can render migrated copies
/// while the checked-in originals stay pristine until the flip's re-save.
#[test]
#[ignore = "dev tool: set DEMO_OUT and run explicitly"]
fn migrate_demo_artwork_into_demo_out() {
use crate::messages::portfolio::document_migration::{document_migration_reset_node_definition, document_migration_string_preprocessing, document_migration_upgrades};
let out_dir = std::env::var("DEMO_OUT").expect("set DEMO_OUT to the output directory");
for entry in std::fs::read_dir("../demo-artwork").unwrap() {
let path = entry.unwrap().path();
if path.extension().and_then(|extension| extension.to_str()) != Some("graphite") {
continue;
}
let content = std::fs::read_to_string(&path).unwrap();
let content = document_migration_string_preprocessing(content);
let reset = document_migration_reset_node_definition(&content);
let mut document = DocumentMessageHandler::deserialize_document(&content).unwrap_or_else(|e| panic!("Failed to deserialize {}: {e:?}", path.display()));
document_migration_upgrades(&mut document, reset);
let out = format!("{out_dir}/{}", path.file_name().unwrap().to_string_lossy());
std::fs::write(out, document.serialize_document()).unwrap();
}
}
/// Walk every node in every nested network and collect `(network_path, local_id)` pairs, so a test can
/// iterate every node addressable from the metadata side.
pub fn node_paths(interface: &NodeNetworkInterface) -> Vec<(Vec<NodeId>, NodeId)> {