mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 05:48:12 +08:00
Bridge legacy seams and convert the transform family
This commit is contained in:
@@ -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 {
|
let Some(fill_node_id) = self.existing_proto_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, true) else {
|
||||||
return;
|
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);
|
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);
|
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
|
// Skip the rerender on all but the last input so the whole update triggers a single graph run
|
||||||
self.set_input_with_refresh(
|
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),
|
NodeInput::value(TaggedValue::Gradient(gradient), false),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
@@ -715,7 +715,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
return;
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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),
|
implementation: DocumentNodeImplementation::ProtoNode(graphic::to_graphic::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// Secondary (left) input type coercion
|
// Collection of the coerced content (node 6) into the layer's group
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::import(generic!(T), 1)],
|
inputs: vec![NodeInput::node(NodeId(6), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::wrap_graphic::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(graphic::wrap_graphic::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -191,6 +191,12 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
|
||||||
..Default::default()
|
..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()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -256,6 +262,14 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..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()
|
.into_iter()
|
||||||
.enumerate()
|
.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_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_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_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| {
|
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]
|
[regular_polygon::IDENTIFIER, star::IDENTIFIER, arc::IDENTIFIER, spiral::IDENTIFIER, grid::IDENTIFIER, arrow::IDENTIFIER]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -2457,13 +2457,13 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
Other,
|
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);
|
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)
|
// 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)];
|
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() {
|
let fill = match input_type.compiled_nested_type() {
|
||||||
Some(ty) if ty == &concrete!(List<Color>) => {
|
Some(ty) if ty == &concrete!(List<Color>) => {
|
||||||
if let Ok(document_node) = get_document_node(node_id, context) {
|
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,
|
Some(&TaggedValue::Color(c)) => c,
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
@@ -2525,7 +2525,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
let reverse_button = IconButton::new("Reverse", 24)
|
let reverse_button = IconButton::new("Reverse", 24)
|
||||||
.tooltip_label("Reverse Stops")
|
.tooltip_label("Reverse Stops")
|
||||||
.tooltip_description("Reverse the gradient color 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();
|
.widget_instance();
|
||||||
widgets_first_row.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
|
widgets_first_row.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
|
||||||
widgets_first_row.push(reverse_button);
|
widgets_first_row.push(reverse_button);
|
||||||
@@ -2549,7 +2549,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
messages: Box::new([
|
messages: Box::new([
|
||||||
NodeGraphMessage::SetInputValue {
|
NodeGraphMessage::SetInputValue {
|
||||||
node_id,
|
node_id,
|
||||||
input_index: FillInput::<List<Graphic>>::INDEX,
|
input_index: FillInput::<Graphic>::INDEX,
|
||||||
value: TaggedValue::Color(color),
|
value: TaggedValue::Color(color),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
@@ -2566,7 +2566,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
messages: Box::new([
|
messages: Box::new([
|
||||||
NodeGraphMessage::SetInputValue {
|
NodeGraphMessage::SetInputValue {
|
||||||
node_id,
|
node_id,
|
||||||
input_index: FillInput::<List<Graphic>>::INDEX,
|
input_index: FillInput::<Graphic>::INDEX,
|
||||||
value: TaggedValue::Gradient(gradient.clone()),
|
value: TaggedValue::Gradient(gradient.clone()),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
@@ -2607,11 +2607,11 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
let entries = vec![
|
let entries = vec![
|
||||||
RadioEntryData::new("solid")
|
RadioEntryData::new("solid")
|
||||||
.label("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),
|
.on_commit(commit_value),
|
||||||
RadioEntryData::new("gradient")
|
RadioEntryData::new("gradient")
|
||||||
.label("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),
|
.on_commit(commit_value),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -2717,14 +2717,14 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
|
|||||||
_ => &StrokeJoin::Miter,
|
_ => &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(),
|
Some(TaggedValue::F64Array(values)) => values.is_empty(),
|
||||||
_ => true,
|
_ => true,
|
||||||
};
|
};
|
||||||
let miter_limit_disabled = join_value != &StrokeJoin::Miter;
|
let miter_limit_disabled = join_value != &StrokeJoin::Miter;
|
||||||
|
|
||||||
let color = color_widget(
|
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(),
|
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.));
|
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();
|
.property_row();
|
||||||
let disabled_number_input = NumberInput::default().unit(" px").disabled(has_dash_lengths);
|
let disabled_number_input = NumberInput::default().unit(" px").disabled(has_dash_lengths);
|
||||||
let dash_lengths = array_of_number_widget(
|
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),
|
TextInput::default().centered(true),
|
||||||
);
|
);
|
||||||
let number_input = disabled_number_input;
|
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:?}"))
|
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
|
/// 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.
|
/// iterate every node addressable from the metadata side.
|
||||||
pub fn node_paths(interface: &NodeNetworkInterface) -> Vec<(Vec<NodeId>, NodeId)> {
|
pub fn node_paths(interface: &NodeNetworkInterface) -> Vec<(Vec<NodeId>, NodeId)> {
|
||||||
|
|||||||
@@ -275,14 +275,14 @@ pub fn get_viewport_center(layer: LayerNodeIdentifier, network_interface: &NodeN
|
|||||||
pub fn get_fill_node_id_with_direct_fill_input(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
pub fn get_fill_node_id_with_direct_fill_input(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
||||||
let fill_node_id = NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?;
|
let fill_node_id = NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?;
|
||||||
let fill_node = network_interface.document_network().nodes.get(&fill_node_id)?;
|
let fill_node = network_interface.document_network().nodes.get(&fill_node_id)?;
|
||||||
matches!(fill_node.inputs.get(graphene_std::vector::fill::FillInput::<List<Graphic>>::INDEX)?, NodeInput::Value { .. }).then_some(fill_node_id)
|
matches!(fill_node.inputs.get(graphene_std::vector::fill::FillInput::<Graphic>::INDEX)?, NodeInput::Value { .. }).then_some(fill_node_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine the input connector where the gradient chain enters the layer.
|
/// Determine the input connector where the gradient chain enters the layer.
|
||||||
/// Returns Fill's fill input if the layer has a "Fill" node, otherwise returns the layer's content input.
|
/// Returns Fill's fill input if the layer has a "Fill" node, otherwise returns the layer's content input.
|
||||||
pub fn gradient_chain_target_input(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> InputConnector {
|
pub fn gradient_chain_target_input(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> InputConnector {
|
||||||
if let Some(fill_node_id) = NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER)) {
|
if let Some(fill_node_id) = NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER)) {
|
||||||
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)
|
||||||
} else {
|
} else {
|
||||||
InputConnector::node(layer.to_node(), 1)
|
InputConnector::node(layer.to_node(), 1)
|
||||||
}
|
}
|
||||||
@@ -303,7 +303,7 @@ pub fn get_upstream_gradient_value_node_id(layer: LayerNodeIdentifier, network_i
|
|||||||
pub fn get_fill_input_node_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
pub fn get_fill_input_node_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
||||||
let fill_node_id = NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?;
|
let fill_node_id = NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?;
|
||||||
let fill_node = network_interface.document_network().nodes.get(&fill_node_id)?;
|
let fill_node = network_interface.document_network().nodes.get(&fill_node_id)?;
|
||||||
let NodeInput::Node { node_id, .. } = fill_node.inputs.get(graphene_std::vector::fill::FillInput::<List<Graphic>>::INDEX)? else {
|
let NodeInput::Node { node_id, .. } = fill_node.inputs.get(graphene_std::vector::fill::FillInput::<Graphic>::INDEX)? else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
Some(*node_id)
|
Some(*node_id)
|
||||||
@@ -317,7 +317,7 @@ pub fn get_gradient_stops(layer: LayerNodeIdentifier, network_interface: &NodeNe
|
|||||||
.document_network()
|
.document_network()
|
||||||
.nodes
|
.nodes
|
||||||
.get(&fill_node_id)
|
.get(&fill_node_id)
|
||||||
.and_then(|node| node.inputs.get(graphene_std::vector::fill::FillInput::<List<Graphic>>::INDEX))
|
.and_then(|node| node.inputs.get(graphene_std::vector::fill::FillInput::<Graphic>::INDEX))
|
||||||
.and_then(|input| input.as_value())
|
.and_then(|input| input.as_value())
|
||||||
.and_then(|value| if let TaggedValue::Gradient(gradient) = value { Some(gradient.clone()) } else { None });
|
.and_then(|value| if let TaggedValue::Gradient(gradient) = value { Some(gradient.clone()) } else { None });
|
||||||
}
|
}
|
||||||
@@ -363,7 +363,7 @@ pub fn gradient_orientation_rightward(transform: glam::DAffine2) -> bool {
|
|||||||
/// Get the current fill of a layer from the closest "Fill" node.
|
/// Get the current fill of a layer from the closest "Fill" node.
|
||||||
pub fn get_fill_color(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Color> {
|
pub fn get_fill_color(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Color> {
|
||||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?;
|
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?;
|
||||||
let &TaggedValue::Color(color) = inputs.get(graphene_std::vector::fill::FillInput::<List<Graphic>>::INDEX)?.as_value()? else {
|
let &TaggedValue::Color(color) = inputs.get(graphene_std::vector::fill::FillInput::<Graphic>::INDEX)?.as_value()? else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
color
|
color
|
||||||
@@ -570,7 +570,7 @@ pub fn get_stroke_options(layer: LayerNodeIdentifier, network_interface: &NodeNe
|
|||||||
Some(TaggedValue::PaintOrder(value)) => *value,
|
Some(TaggedValue::PaintOrder(value)) => *value,
|
||||||
_ => PaintOrder::default(),
|
_ => PaintOrder::default(),
|
||||||
};
|
};
|
||||||
let dash_lengths = match read(graphene_std::vector::stroke::DashLengthsInput::<List<f64>>::INDEX) {
|
let dash_lengths = match read(graphene_std::vector::stroke::DashLengthsInput::INDEX) {
|
||||||
Some(TaggedValue::F64Array(value)) => value.clone(),
|
Some(TaggedValue::F64Array(value)) => value.clone(),
|
||||||
_ => Vec::new(),
|
_ => Vec::new(),
|
||||||
};
|
};
|
||||||
@@ -638,7 +638,7 @@ pub struct FillNodeGradient {
|
|||||||
pub fn read_fill_node_gradient(fill_node: &DocumentNode, bounding_box: impl FnOnce() -> [DVec2; 2]) -> Option<FillNodeGradient> {
|
pub fn read_fill_node_gradient(fill_node: &DocumentNode, bounding_box: impl FnOnce() -> [DVec2; 2]) -> Option<FillNodeGradient> {
|
||||||
use graphene_std::vector::fill;
|
use graphene_std::vector::fill;
|
||||||
|
|
||||||
let TaggedValue::Gradient(stops) = fill_node.inputs.get(fill::FillInput::<List<Graphic>>::INDEX)?.as_value()? else {
|
let TaggedValue::Gradient(stops) = fill_node.inputs.get(fill::FillInput::<Graphic>::INDEX)?.as_value()? else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let gradient_type = match fill_node.inputs.get(fill::GradientTypeInput::INDEX).and_then(|input| input.as_value()) {
|
let gradient_type = match fill_node.inputs.get(fill::GradientTypeInput::INDEX).and_then(|input| input.as_value()) {
|
||||||
@@ -665,7 +665,7 @@ pub fn read_fill_node_gradient(fill_node: &DocumentNode, bounding_box: impl FnOn
|
|||||||
}
|
}
|
||||||
/// Returns the stroke color from a layer's upstream Stroke node.
|
/// Returns the stroke color from a layer's upstream Stroke node.
|
||||||
pub fn get_stroke_color(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Option<Color>> {
|
pub fn get_stroke_color(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Option<Color>> {
|
||||||
let color_index = graphene_std::vector::stroke::PaintInput::<List<Graphic>>::INDEX;
|
let color_index = graphene_std::vector::stroke::PaintInput::<Graphic>::INDEX;
|
||||||
let tagged = NodeGraphLayer::new(layer, network_interface).find_input(&DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER), color_index)?;
|
let tagged = NodeGraphLayer::new(layer, network_interface).find_input(&DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER), color_index)?;
|
||||||
if let TaggedValue::Color(color) = tagged { Some(*color) } else { None }
|
if let TaggedValue::Color(color) = tagged { Some(*color) } else { None }
|
||||||
}
|
}
|
||||||
@@ -699,7 +699,7 @@ pub fn selected_fill_state(document: &DocumentMessageHandler) -> Option<Selected
|
|||||||
let fill_choice = (|| {
|
let fill_choice = (|| {
|
||||||
let fill_node = document.network_interface.document_network().nodes.get(&fill_node_id)?;
|
let fill_node = document.network_interface.document_network().nodes.get(&fill_node_id)?;
|
||||||
|
|
||||||
match fill_node.inputs.get(graphene_std::vector::fill::FillInput::<List<Graphic>>::INDEX)?.as_value()? {
|
match fill_node.inputs.get(graphene_std::vector::fill::FillInput::<Graphic>::INDEX)?.as_value()? {
|
||||||
&TaggedValue::Color(color) => Some(color.map_or(FillChoice::None, FillChoice::Solid)),
|
&TaggedValue::Color(color) => Some(color.map_or(FillChoice::None, FillChoice::Solid)),
|
||||||
TaggedValue::Gradient(stops) => Some(FillChoice::Gradient(stops.clone())),
|
TaggedValue::Gradient(stops) => Some(FillChoice::Gradient(stops.clone())),
|
||||||
_ => None,
|
_ => None,
|
||||||
@@ -825,7 +825,7 @@ pub fn set_stroke_color_for_selected_layers(color: Option<Color>, weight: f64, d
|
|||||||
let layers: Vec<_> = document.network_interface.selected_nodes().selected_layers_except_artboards(&document.network_interface).collect();
|
let layers: Vec<_> = document.network_interface.selected_nodes().selected_layers_except_artboards(&document.network_interface).collect();
|
||||||
for layer in layers {
|
for layer in layers {
|
||||||
if let Some(node_id) = get_stroke_id(layer, &document.network_interface) {
|
if let Some(node_id) = get_stroke_id(layer, &document.network_interface) {
|
||||||
let input_index = graphene_std::vector::stroke::PaintInput::<List<Graphic>>::INDEX;
|
let input_index = graphene_std::vector::stroke::PaintInput::<Graphic>::INDEX;
|
||||||
let value = TaggedValue::Color(color);
|
let value = TaggedValue::Color(color);
|
||||||
responses.add(NodeGraphMessage::SetInputValue { node_id, input_index, value });
|
responses.add(NodeGraphMessage::SetInputValue { node_id, input_index, value });
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ use crate::messages::tool::common_functionality::graph_modification_utils;
|
|||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graphene_std::NodeInputDecleration;
|
use graphene_std::NodeInputDecleration;
|
||||||
use graphene_std::choice_type::ChoiceTypeStatic;
|
use graphene_std::choice_type::ChoiceTypeStatic;
|
||||||
use graphene_std::list::List;
|
|
||||||
use graphene_std::vector::style::{PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
use graphene_std::vector::style::{PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
|
|
||||||
/// All non-color stroke-related options surfaced in the control bar popover.
|
/// All non-color stroke-related options surfaced in the control bar popover.
|
||||||
@@ -215,7 +214,7 @@ pub fn apply_paint_order(drawing: &mut DrawingToolState, order: PaintOrder, docu
|
|||||||
|
|
||||||
pub fn apply_dash_lengths(drawing: &mut DrawingToolState, lengths: Vec<f64>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
pub fn apply_dash_lengths(drawing: &mut DrawingToolState, lengths: Vec<f64>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
drawing.dash_lengths = Some(lengths.clone());
|
drawing.dash_lengths = Some(lengths.clone());
|
||||||
set_stroke_input_for_selected(document, graphene_std::vector::stroke::DashLengthsInput::<List<f64>>::INDEX, TaggedValue::F64Array(lengths), responses);
|
set_stroke_input_for_selected(document, graphene_std::vector::stroke::DashLengthsInput::INDEX, TaggedValue::F64Array(lengths), responses);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_dash_offset(drawing: &mut DrawingToolState, offset: f64, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
pub fn apply_dash_offset(drawing: &mut DrawingToolState, offset: f64, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
|
|||||||
@@ -579,7 +579,10 @@ mod test_artboard {
|
|||||||
Ok(instrumented) => instrumented,
|
Ok(instrumented) => instrumented,
|
||||||
Err(e) => panic!("Failed to evaluate graph: {e}"),
|
Err(e) => panic!("Failed to evaluate graph: {e}"),
|
||||||
};
|
};
|
||||||
instrumented.grab_all_input::<graphene_std::graphic::extend::NewInput<Artboard>>(&editor.runtime).flatten().collect()
|
instrumented
|
||||||
|
.grab_all_input::<graphene_std::graphic::extend::NewInput<Artboard>>(&editor.runtime)
|
||||||
|
.map(graphene_std::list::Item::new_from_element)
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
|||||||
@@ -2058,7 +2058,7 @@ mod test_gradient {
|
|||||||
let fill_node_id = get_fill_node_id_with_direct_fill_input(layer, &document.network_interface)?;
|
let fill_node_id = get_fill_node_id_with_direct_fill_input(layer, &document.network_interface)?;
|
||||||
let fill_node = document.network_interface.document_network().nodes.get(&fill_node_id)?;
|
let fill_node = document.network_interface.document_network().nodes.get(&fill_node_id)?;
|
||||||
|
|
||||||
let stops = match fill_node.inputs.get(fill::FillInput::<List<Graphic>>::INDEX)?.as_value()? {
|
let stops = match fill_node.inputs.get(fill::FillInput::<Graphic>::INDEX)?.as_value()? {
|
||||||
TaggedValue::Gradient(stops) => stops.clone(),
|
TaggedValue::Gradient(stops) => stops.clone(),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
@@ -2176,7 +2176,7 @@ mod test_gradient {
|
|||||||
editor
|
editor
|
||||||
.handle_message(NodeGraphMessage::CreateWire {
|
.handle_message(NodeGraphMessage::CreateWire {
|
||||||
output_connector: OutputConnector::node(gradient_node_id, 0),
|
output_connector: OutputConnector::node(gradient_node_id, 0),
|
||||||
input_connector: InputConnector::node(fill_node_id, fill::FillInput::<List<Graphic>>::INDEX),
|
input_connector: InputConnector::node(fill_node_id, fill::FillInput::<Graphic>::INDEX),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,22 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
|
|||||||
// MEMO NODES
|
// MEMO NODES
|
||||||
// ==========
|
// ==========
|
||||||
];
|
];
|
||||||
|
// The transform's value-typed rows, served by `transform_value` under the
|
||||||
|
// leveled transform's identifier.
|
||||||
|
node_types.extend(
|
||||||
|
graphene_std::transform_nodes::transform_nodes::transform_value_entries()
|
||||||
|
.into_iter()
|
||||||
|
.map(|entry| (graphene_std::transform_nodes::transform_nodes::transform::IDENTIFIER.clone(), entry)),
|
||||||
|
);
|
||||||
|
// The transitional level bridge: a leveled wire materializes into the legacy
|
||||||
|
// list an unconverted consumer expects. The rows are keyed under the legacy
|
||||||
|
// convert identifiers and die with the last legacy consumer.
|
||||||
|
node_types.extend(
|
||||||
|
graphene_std::graphic::level_to_list_entries()
|
||||||
|
.into_iter()
|
||||||
|
.zip(["List<Graphic>", "List<Vector>", "List<Raster<CPU>>", "List<Raster<GPU>>", "List<Color>", "List<GradientStops>", "List<String>"])
|
||||||
|
.map(|(entry, target)| (ProtoNodeIdentifier::with_owned_string(format!("graphene_core::ops::ConvertNode<{target}>")), entry)),
|
||||||
|
);
|
||||||
// =============
|
// =============
|
||||||
// CONVERT NODES
|
// CONVERT NODES
|
||||||
// =============
|
// =============
|
||||||
|
|||||||
@@ -581,15 +581,49 @@ fn wrap_graphic_extent(_content: ListIn<'_, Graphic>, _level: LevelIn) -> GPoll<
|
|||||||
GPoll::Final(Extent::Exactly(1))
|
GPoll::Final(Extent::Exactly(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the level's elements into `Graphic` elements. A `Graphic` level passes through unchanged.
|
/// Converts the level's elements into `Graphic` elements. A `Graphic` level passes through
|
||||||
|
/// unchanged. The legacy list rows accept an unconverted producer's list value as one element.
|
||||||
#[node_macro::node(category("General"))]
|
#[node_macro::node(category("General"))]
|
||||||
pub fn to_graphic<T: Into<Graphic> + Clone + Send + Sync + core_types::CacheHash + 'static>(
|
pub fn to_graphic<T: Into<Graphic> + Clone + Send + Sync + core_types::CacheHash + 'static>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
#[implementations(Graphic, Vector, Raster<CPU>, Raster<GPU>, Color, GradientStops, String)] content: T,
|
#[implementations(
|
||||||
|
Graphic,
|
||||||
|
Vector,
|
||||||
|
Raster<CPU>,
|
||||||
|
Raster<GPU>,
|
||||||
|
Color,
|
||||||
|
GradientStops,
|
||||||
|
String,
|
||||||
|
List<Graphic>,
|
||||||
|
List<Vector>,
|
||||||
|
List<Raster<CPU>>,
|
||||||
|
List<Raster<GPU>>,
|
||||||
|
List<Color>,
|
||||||
|
List<GradientStops>,
|
||||||
|
List<String>,
|
||||||
|
)]
|
||||||
|
content: T,
|
||||||
) -> Graphic {
|
) -> Graphic {
|
||||||
content.into()
|
content.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The transitional level bridge: the wire's records as the legacy list an
|
||||||
|
/// unconverted consumer expects, attributes copied through their erased
|
||||||
|
/// reads. Registered under the legacy convert identifiers; the rows die with
|
||||||
|
/// the last legacy consumer.
|
||||||
|
#[node_macro::node(category(""))]
|
||||||
|
pub fn level_to_list<T: Clone + Send + Sync + CacheHash + 'static>(
|
||||||
|
_: impl Ctx + ExtractIndex + InjectIndex + Copy,
|
||||||
|
#[implementations(Graphic, Vector, Raster<CPU>, Raster<GPU>, Color, GradientStops, String)] value: IList<T>,
|
||||||
|
_converter: (),
|
||||||
|
) -> List<T> {
|
||||||
|
// SAFETY: a materialized input's frames are arena-resident.
|
||||||
|
let item = unsafe { core_types::record::GroupItem::from_resident(value.batch()) };
|
||||||
|
graphic_types::graphic::run_to_render_list::<T>(&item).expect("the run holds the row's element type")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use _level_to_list_mod::level_to_list_entries;
|
||||||
|
|
||||||
/// Removes a level of nesting from a `Graphic[]`, or all nesting if "Fully Flatten" is enabled.
|
/// Removes a level of nesting from a `Graphic[]`, or all nesting if "Fully Flatten" is enabled.
|
||||||
#[node_macro::node(category("General"), extent(flatten_graphic_extent))]
|
#[node_macro::node(category("General"), extent(flatten_graphic_extent))]
|
||||||
pub fn flatten_graphic(ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, content: IList<Graphic>, fully_flatten: bool) -> Result<IList<(Graphic, Attr<TransformAttr>)>, Interrupt> {
|
pub fn flatten_graphic(ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, content: IList<Graphic>, fully_flatten: bool) -> Result<IList<(Graphic, Attr<TransformAttr>)>, Interrupt> {
|
||||||
|
|||||||
@@ -1,30 +1,48 @@
|
|||||||
use core::f64;
|
use core::f64;
|
||||||
|
use core_types::attribute::{Attr, Transform as TransformAttr};
|
||||||
use core_types::color::Color;
|
use core_types::color::Color;
|
||||||
use core_types::gpoll::Interrupt;
|
use core_types::extent::{ExtentIn, LevelIn, ValueIn};
|
||||||
use core_types::list::{List, ListDyn};
|
use core_types::gpoll::{Extent, GPoll, Interrupt};
|
||||||
use core_types::transform::{ApplyTransform, ScaleType, Transform};
|
use core_types::transform::{ApplyTransform, ScaleType, Transform};
|
||||||
use core_types::{ATTR_TRANSFORM, Context, Ctx, DeriveCtx, InjectFootprint, ModifyFootprint};
|
use core_types::{CacheHash, Context, Ctx, DeriveCtx, ExtractIndex, InjectFootprint, InjectIndex, ModifyFootprint};
|
||||||
use glam::{DAffine2, DMat2, DVec2};
|
use glam::{DAffine2, DMat2, DVec2};
|
||||||
use graphic_types::Graphic;
|
use graphic_types::Graphic;
|
||||||
use graphic_types::Vector;
|
use graphic_types::Vector;
|
||||||
use graphic_types::raster_types::{CPU, GPU, Raster};
|
use graphic_types::raster_types::{CPU, GPU, Raster};
|
||||||
use vector_types::GradientStops;
|
use vector_types::GradientStops;
|
||||||
|
|
||||||
/// Applies the specified transform to the input value, which may be a graphic type or another transform.
|
/// Applies the specified transform to each lane of the input wire, composing onto the lane's transform attribute.
|
||||||
#[node_macro::node(category("Math: Transform"))]
|
#[node_macro::node(category("Math: Transform"), extent(transform_extent))]
|
||||||
fn transform<T: ApplyTransform + 'static>(
|
fn transform<T>(
|
||||||
ctx: impl Ctx + DeriveCtx + ModifyFootprint,
|
ctx: impl Ctx + DeriveCtx + ModifyFootprint,
|
||||||
#[implementations(
|
content: impl Node<Context<'_>, Output = (T, Attr<TransformAttr>)>,
|
||||||
Context -> DAffine2,
|
#[widget(ParsedWidgetOverride::Custom = "transform_translation")] translation: DVec2,
|
||||||
Context -> DVec2,
|
#[widget(ParsedWidgetOverride::Custom = "transform_rotation")] rotation: f64,
|
||||||
Context -> List<Graphic>,
|
#[widget(ParsedWidgetOverride::Custom = "transform_scale")]
|
||||||
Context -> List<String>,
|
#[default(1., 1.)]
|
||||||
Context -> List<Vector>,
|
scale: DVec2,
|
||||||
Context -> List<Raster<CPU>>,
|
#[widget(ParsedWidgetOverride::Custom = "transform_skew")] skew: DVec2,
|
||||||
Context -> List<Raster<GPU>>,
|
) -> Result<(T, Attr<TransformAttr>), Interrupt> {
|
||||||
Context -> List<Color>,
|
let trs = DAffine2::from_scale_angle_translation(scale, rotation.to_radians(), translation);
|
||||||
Context -> List<GradientStops>,
|
let skew = DAffine2::from_cols_array(&[1., skew.y.to_radians().tan(), skew.x.to_radians().tan(), 1., 0., 0.]);
|
||||||
)]
|
let matrix = trs * skew;
|
||||||
|
|
||||||
|
let transformed = ctx.modify_footprint(|footprint| footprint.apply_transform(&matrix));
|
||||||
|
let (element, transform) = content.eval(&transformed.ctx())?;
|
||||||
|
|
||||||
|
Ok((element, Attr(matrix * *transform)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_extent(content: ExtentIn<'_>, _translation: ValueIn<'_, DVec2>, _rotation: ValueIn<'_, f64>, _scale: ValueIn<'_, DVec2>, _skew: ValueIn<'_, DVec2>, level: LevelIn) -> GPoll<Extent> {
|
||||||
|
content.at(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The transform applied to a plain transform or point value. Registered under the same identifier
|
||||||
|
/// as the leveled `transform`, serving its value-typed rows.
|
||||||
|
#[node_macro::node(category(""))]
|
||||||
|
fn transform_value<T: ApplyTransform + 'static>(
|
||||||
|
ctx: impl Ctx + DeriveCtx + ModifyFootprint,
|
||||||
|
#[implementations(Context -> DAffine2, Context -> DVec2)]
|
||||||
content: impl Node<Context<'_>, Output = T>,
|
content: impl Node<Context<'_>, Output = T>,
|
||||||
#[widget(ParsedWidgetOverride::Custom = "transform_translation")] translation: DVec2,
|
#[widget(ParsedWidgetOverride::Custom = "transform_translation")] translation: DVec2,
|
||||||
#[widget(ParsedWidgetOverride::Custom = "transform_rotation")] rotation: f64,
|
#[widget(ParsedWidgetOverride::Custom = "transform_rotation")] rotation: f64,
|
||||||
@@ -45,71 +63,55 @@ fn transform<T: ApplyTransform + 'static>(
|
|||||||
Ok(transform_target)
|
Ok(transform_target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub use _transform_value_mod::transform_value_entries;
|
||||||
|
|
||||||
/// Resets the desired components of the input transform to their default values. If all components are reset, the output will be set to the identity transform.
|
/// Resets the desired components of the input transform to their default values. If all components are reset, the output will be set to the identity transform.
|
||||||
/// Shear is represented jointly by rotation and scale, so resetting both will also remove any shear.
|
/// Shear is represented jointly by rotation and scale, so resetting both will also remove any shear.
|
||||||
#[node_macro::node(category("Math: Transform"))]
|
#[node_macro::node(category("Math: Transform"))]
|
||||||
fn reset_transform<T>(
|
fn reset_transform<T>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
#[implementations(
|
(element, transform): (T, Attr<TransformAttr>),
|
||||||
List<Graphic>,
|
|
||||||
List<Vector>,
|
|
||||||
List<Raster<CPU>>,
|
|
||||||
List<Raster<GPU>>,
|
|
||||||
List<Color>,
|
|
||||||
List<GradientStops>,
|
|
||||||
)]
|
|
||||||
mut content: List<T>,
|
|
||||||
#[default(true)] reset_translation: bool,
|
#[default(true)] reset_translation: bool,
|
||||||
reset_rotation: bool,
|
reset_rotation: bool,
|
||||||
reset_scale: bool,
|
reset_scale: bool,
|
||||||
) -> List<T> {
|
) -> (T, Attr<TransformAttr>) {
|
||||||
for row_transform in content.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
let mut row_transform = *transform;
|
||||||
if reset_translation {
|
if reset_translation {
|
||||||
row_transform.translation = DVec2::ZERO;
|
row_transform.translation = DVec2::ZERO;
|
||||||
}
|
|
||||||
|
|
||||||
match (reset_rotation, reset_scale) {
|
|
||||||
(true, true) => row_transform.matrix2 = DMat2::IDENTITY,
|
|
||||||
(true, false) => {
|
|
||||||
let scale = row_transform.scale_magnitudes();
|
|
||||||
row_transform.matrix2 = DMat2::from_diagonal(scale);
|
|
||||||
}
|
|
||||||
(false, true) => {
|
|
||||||
let rotation = row_transform.decompose_rotation();
|
|
||||||
row_transform.matrix2 = DMat2::from_angle(rotation);
|
|
||||||
}
|
|
||||||
(false, false) => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
content
|
|
||||||
|
match (reset_rotation, reset_scale) {
|
||||||
|
(true, true) => row_transform.matrix2 = DMat2::IDENTITY,
|
||||||
|
(true, false) => {
|
||||||
|
let scale = row_transform.scale_magnitudes();
|
||||||
|
row_transform.matrix2 = DMat2::from_diagonal(scale);
|
||||||
|
}
|
||||||
|
(false, true) => {
|
||||||
|
let rotation = row_transform.decompose_rotation();
|
||||||
|
row_transform.matrix2 = DMat2::from_angle(rotation);
|
||||||
|
}
|
||||||
|
(false, false) => {}
|
||||||
|
}
|
||||||
|
(element, Attr(row_transform))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Overwrites the transform of each item in the input `List` with the specified transform.
|
/// Overwrites the transform of each lane of the input wire with the specified transform.
|
||||||
#[node_macro::node(category("Math: Transform"))]
|
#[node_macro::node(category("Math: Transform"))]
|
||||||
fn replace_transform<T>(
|
fn replace_transform<T>(_: impl Ctx + InjectFootprint, (element, _content_transform): (T, Attr<TransformAttr>), transform: DAffine2) -> (T, Attr<TransformAttr>) {
|
||||||
_: impl Ctx + InjectFootprint,
|
(element, Attr(transform))
|
||||||
#[implementations(
|
|
||||||
List<Graphic>,
|
|
||||||
List<Vector>,
|
|
||||||
List<Raster<CPU>>,
|
|
||||||
List<Raster<GPU>>,
|
|
||||||
List<Color>,
|
|
||||||
List<GradientStops>,
|
|
||||||
)]
|
|
||||||
mut content: List<T>,
|
|
||||||
transform: DAffine2,
|
|
||||||
) -> List<T> {
|
|
||||||
for row_transform in content.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
|
||||||
*row_transform = transform.transform();
|
|
||||||
}
|
|
||||||
content
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Figure out how this node should behave once #2982 is implemented.
|
// TODO: Figure out how this node should behave once #2982 is implemented.
|
||||||
/// Obtains the transform of the first item in the input `List`, if present.
|
/// Obtains the transform of the first lane of the input wire, if present.
|
||||||
#[node_macro::node(category("Math: Transform"), path(core_types::vector))]
|
#[node_macro::node(category("Math: Transform"), path(core_types::vector))]
|
||||||
fn extract_transform(_: impl Ctx, content: ListDyn) -> DAffine2 {
|
fn extract_transform<T: Clone + Send + Sync + CacheHash + 'static>(
|
||||||
content.attribute::<DAffine2>(ATTR_TRANSFORM, 0).copied().unwrap_or_default()
|
_: impl Ctx + ExtractIndex + InjectIndex + Copy,
|
||||||
|
#[implementations(Graphic, Vector, Raster<CPU>, Raster<GPU>, Color, GradientStops)] content: IList<T>,
|
||||||
|
) -> DAffine2 {
|
||||||
|
match content.len() {
|
||||||
|
0 => DAffine2::default(),
|
||||||
|
_ => content.lane(0).attr::<TransformAttr>(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produces the inverse of the input transform, which is the transform that undoes the effect of the original transform.
|
/// Produces the inverse of the input transform, which is the transform that undoes the effect of the original transform.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use vector_types::vector::VectorModification;
|
|||||||
|
|
||||||
/// Applies a differential modification to a vector path, associating changes made by the Pen and Path tools to indices of edited points and segments.
|
/// Applies a differential modification to a vector path, associating changes made by the Pen and Path tools to indices of edited points and segments.
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
fn path_modify(_ctx: impl Ctx, mut vector: List<Vector>, modification: Box<VectorModification>, node_path: List<NodeId>) -> List<Vector> {
|
fn path_modify(_ctx: impl Ctx, mut vector: List<Vector>, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> List<Vector> {
|
||||||
use core_types::list::Item;
|
use core_types::list::Item;
|
||||||
|
|
||||||
if vector.is_empty() {
|
if vector.is_empty() {
|
||||||
@@ -23,7 +23,7 @@ fn path_modify(_ctx: impl Ctx, mut vector: List<Vector>, modification: Box<Vecto
|
|||||||
// matching the `path_of_subgraph` proto so editor tools can route data back to the parent layer.
|
// matching the `path_of_subgraph` proto so editor tools can route data back to the parent layer.
|
||||||
let subgraph_path: List<NodeId> = {
|
let subgraph_path: List<NodeId> = {
|
||||||
let len = node_path.len();
|
let len = node_path.len();
|
||||||
node_path.into_iter().take(len.saturating_sub(1)).collect()
|
node_path.into_iter().take(len.saturating_sub(1)).map(Item::new_from_element).collect()
|
||||||
};
|
};
|
||||||
let existing: List<NodeId> = vector.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0);
|
let existing: List<NodeId> = vector.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0);
|
||||||
vector.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, if existing.is_empty() { subgraph_path } else { existing });
|
vector.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, if existing.is_empty() { subgraph_path } else { existing });
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use core_types::list::{Item, ItemAttributeValues, List, ListDyn};
|
|||||||
use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLength, Progression, SeedValue};
|
use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLength, Progression, SeedValue};
|
||||||
use core_types::transform::{Footprint, Transform};
|
use core_types::transform::{Footprint, Transform};
|
||||||
use core_types::uuid::NodeId;
|
use core_types::uuid::NodeId;
|
||||||
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Color, Ctx, DeriveCtx};
|
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, CacheHash, Color, Ctx, DeriveCtx, ExtractIndex, InjectIndex};
|
||||||
use glam::{DAffine2, DMat2, DVec2};
|
use glam::{DAffine2, DMat2, DVec2};
|
||||||
use graphic_types::Vector;
|
use graphic_types::Vector;
|
||||||
use graphic_types::graphic::{bake_paint_transforms, graphic_list_at, has_paint_at, is_paint_present, set_paint_attribute_at};
|
use graphic_types::graphic::{bake_paint_transforms, graphic_list_at, has_paint_at, is_paint_present, set_paint_attribute_at};
|
||||||
@@ -155,8 +155,8 @@ where
|
|||||||
|
|
||||||
/// Applies a fill style to the vector content, giving an appearance to the area within the interior of the geometry.
|
/// Applies a fill style to the vector content, giving an appearance to the area within the interior of the geometry.
|
||||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))]
|
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))]
|
||||||
fn fill<V: VectorListIterMut + Send, F: IntoGraphicList + Send + 'static>(
|
fn fill<V: VectorListIterMut + Send, P: Clone + Send + Sync + CacheHash + 'static>(
|
||||||
_: impl Ctx,
|
_: impl Ctx + ExtractIndex + InjectIndex + Copy,
|
||||||
/// The content with vector paths to apply the fill style to.
|
/// The content with vector paths to apply the fill style to.
|
||||||
#[implementations(
|
#[implementations(
|
||||||
List<Vector>, List<Vector>, List<Vector>, List<Vector>, List<Vector>, List<Vector>,
|
List<Vector>, List<Vector>, List<Vector>, List<Vector>, List<Vector>, List<Vector>,
|
||||||
@@ -166,16 +166,20 @@ fn fill<V: VectorListIterMut + Send, F: IntoGraphicList + Send + 'static>(
|
|||||||
/// The fill to paint the path with.
|
/// The fill to paint the path with.
|
||||||
#[default(Color::BLACK)]
|
#[default(Color::BLACK)]
|
||||||
#[implementations(
|
#[implementations(
|
||||||
List<Graphic>, List<Vector>, List<Color>, List<GradientStops>, List<Raster<CPU>>, List<Raster<GPU>>,
|
Graphic, Vector, Color, GradientStops, Raster<CPU>, Raster<GPU>,
|
||||||
List<Graphic>, List<Vector>, List<Color>, List<GradientStops>, List<Raster<CPU>>, List<Raster<GPU>>,
|
Graphic, Vector, Color, GradientStops, Raster<CPU>, Raster<GPU>,
|
||||||
)]
|
)]
|
||||||
mut fill: F,
|
fill: IList<P>,
|
||||||
_backup_color: List<Color>,
|
_backup_color: IList<Color>,
|
||||||
_backup_gradient: List<GradientStops>,
|
_backup_gradient: IList<GradientStops>,
|
||||||
_gradient_type: GradientType,
|
_gradient_type: GradientType,
|
||||||
_spread_method: GradientSpreadMethod,
|
_spread_method: GradientSpreadMethod,
|
||||||
_transform: Option<DAffine2>,
|
_transform: Option<DAffine2>,
|
||||||
) -> V {
|
) -> V
|
||||||
|
where
|
||||||
|
List<P>: IntoGraphicList,
|
||||||
|
{
|
||||||
|
let mut fill: List<P> = legacy_list_of(fill);
|
||||||
if let Some(gradient) = (&mut fill as &mut dyn std::any::Any).downcast_mut::<List<GradientStops>>() {
|
if let Some(gradient) = (&mut fill as &mut dyn std::any::Any).downcast_mut::<List<GradientStops>>() {
|
||||||
if gradient.iter_attribute_values::<GradientType>(ATTR_GRADIENT_TYPE).is_none() {
|
if gradient.iter_attribute_values::<GradientType>(ATTR_GRADIENT_TYPE).is_none() {
|
||||||
for value in gradient.iter_attribute_values_mut_or_default::<GradientType>(ATTR_GRADIENT_TYPE) {
|
for value in gradient.iter_attribute_values_mut_or_default::<GradientType>(ATTR_GRADIENT_TYPE) {
|
||||||
@@ -250,41 +254,21 @@ impl IntoF64Vec for String {
|
|||||||
|
|
||||||
/// Applies a stroke style to the vector content, giving an appearance to the area within the outline of the geometry.
|
/// Applies a stroke style to the vector content, giving an appearance to the area within the outline of the geometry.
|
||||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("stroke_properties"))]
|
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("stroke_properties"))]
|
||||||
fn stroke<V, L: IntoF64Vec, P: IntoGraphicList + Send + 'static>(
|
fn stroke<V, P: Clone + Send + Sync + CacheHash + 'static>(
|
||||||
_: impl Ctx,
|
_: impl Ctx + ExtractIndex + InjectIndex + Copy,
|
||||||
/// The content with vector paths to apply the stroke style to.
|
/// The content with vector paths to apply the stroke style to.
|
||||||
#[implementations(
|
#[implementations(
|
||||||
List<Vector>, List<Vector>, List<Vector>,
|
List<Vector>, List<Vector>, List<Vector>, List<Vector>, List<Vector>, List<Vector>,
|
||||||
List<Vector>, List<Vector>, List<Vector>,
|
List<Graphic>, List<Graphic>, List<Graphic>, List<Graphic>, List<Graphic>, List<Graphic>,
|
||||||
List<Vector>, List<Vector>, List<Vector>,
|
|
||||||
List<Vector>, List<Vector>, List<Vector>,
|
|
||||||
List<Vector>, List<Vector>, List<Vector>,
|
|
||||||
List<Vector>, List<Vector>, List<Vector>,
|
|
||||||
List<Graphic>, List<Graphic>, List<Graphic>,
|
|
||||||
List<Graphic>, List<Graphic>, List<Graphic>,
|
|
||||||
List<Graphic>, List<Graphic>, List<Graphic>,
|
|
||||||
List<Graphic>, List<Graphic>, List<Graphic>,
|
|
||||||
List<Graphic>, List<Graphic>, List<Graphic>,
|
|
||||||
List<Graphic>, List<Graphic>, List<Graphic>,
|
|
||||||
)]
|
)]
|
||||||
mut content: List<V>,
|
mut content: List<V>,
|
||||||
/// The stroke paint.
|
/// The stroke paint.
|
||||||
#[default(Color::BLACK)]
|
#[default(Color::BLACK)]
|
||||||
#[implementations(
|
#[implementations(
|
||||||
List<Graphic>, List<Graphic>, List<Graphic>,
|
Graphic, Vector, Color, GradientStops, Raster<CPU>, Raster<GPU>,
|
||||||
List<Vector>, List<Vector>, List<Vector>,
|
Graphic, Vector, Color, GradientStops, Raster<CPU>, Raster<GPU>,
|
||||||
List<Color>, List<Color>, List<Color>,
|
|
||||||
List<GradientStops>, List<GradientStops>, List<GradientStops>,
|
|
||||||
List<Raster<CPU>>, List<Raster<CPU>>, List<Raster<CPU>>,
|
|
||||||
List<Raster<GPU>>, List<Raster<GPU>>, List<Raster<GPU>>,
|
|
||||||
List<Graphic>, List<Graphic>, List<Graphic>,
|
|
||||||
List<Vector>, List<Vector>, List<Vector>,
|
|
||||||
List<Color>, List<Color>, List<Color>,
|
|
||||||
List<GradientStops>, List<GradientStops>, List<GradientStops>,
|
|
||||||
List<Raster<CPU>>, List<Raster<CPU>>, List<Raster<CPU>>,
|
|
||||||
List<Raster<GPU>>, List<Raster<GPU>>, List<Raster<GPU>>,
|
|
||||||
)]
|
)]
|
||||||
paint: P,
|
paint: IList<P>,
|
||||||
/// The stroke thickness.
|
/// The stroke thickness.
|
||||||
#[unit(" px")]
|
#[unit(" px")]
|
||||||
#[default(2.)]
|
#[default(2.)]
|
||||||
@@ -302,29 +286,16 @@ fn stroke<V, L: IntoF64Vec, P: IntoGraphicList + Send + 'static>(
|
|||||||
/// The order to paint the stroke on top of the fill, or the fill on top of the stroke.
|
/// The order to paint the stroke on top of the fill, or the fill on top of the stroke.
|
||||||
paint_order: PaintOrder,
|
paint_order: PaintOrder,
|
||||||
/// The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.
|
/// The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.
|
||||||
#[implementations(
|
dash_lengths: IList<f64>,
|
||||||
List<f64>, f64, String,
|
|
||||||
List<f64>, f64, String,
|
|
||||||
List<f64>, f64, String,
|
|
||||||
List<f64>, f64, String,
|
|
||||||
List<f64>, f64, String,
|
|
||||||
List<f64>, f64, String,
|
|
||||||
List<f64>, f64, String,
|
|
||||||
List<f64>, f64, String,
|
|
||||||
List<f64>, f64, String,
|
|
||||||
List<f64>, f64, String,
|
|
||||||
List<f64>, f64, String,
|
|
||||||
List<f64>, f64, String,
|
|
||||||
)]
|
|
||||||
dash_lengths: L,
|
|
||||||
/// The phase offset distance from the starting point of the dash pattern.
|
/// The phase offset distance from the starting point of the dash pattern.
|
||||||
#[unit(" px")]
|
#[unit(" px")]
|
||||||
dash_offset: f64,
|
dash_offset: f64,
|
||||||
) -> List<V>
|
) -> List<V>
|
||||||
where
|
where
|
||||||
List<V>: VectorListIterMut + Send,
|
List<V>: VectorListIterMut + Send,
|
||||||
|
List<P>: IntoGraphicList,
|
||||||
{
|
{
|
||||||
let dash_lengths = dash_lengths.into_vec().into_iter().map(|length| length.max(0.)).collect();
|
let dash_lengths = (0..dash_lengths.len()).map(|index| dash_lengths.get(index).max(0.)).collect();
|
||||||
|
|
||||||
let stroke = Stroke {
|
let stroke = Stroke {
|
||||||
weight,
|
weight,
|
||||||
@@ -344,7 +315,7 @@ where
|
|||||||
vector.stroke = Some(stroke);
|
vector.stroke = Some(stroke);
|
||||||
});
|
});
|
||||||
|
|
||||||
let paint = paint.into_graphic_list();
|
let paint = legacy_list_of(paint).into_graphic_list();
|
||||||
content.for_each_vector_list_mut(|vector_list| {
|
content.for_each_vector_list_mut(|vector_list| {
|
||||||
// Broadcast the same paint to every item, scanning the attribute column once instead of per index
|
// Broadcast the same paint to every item, scanning the attribute column once instead of per index
|
||||||
for slot in vector_list.iter_attribute_values_mut_or_default::<List<Graphic>>(ATTR_STROKE) {
|
for slot in vector_list.iter_attribute_values_mut_or_default::<List<Graphic>>(ATTR_STROKE) {
|
||||||
@@ -354,6 +325,14 @@ where
|
|||||||
content
|
content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The transitional value bridge: a materialized level as the legacy list the
|
||||||
|
/// unconverted body consumes.
|
||||||
|
fn legacy_list_of<T: Clone + Send + Sync + 'static>(level: core_types::node::List<'_, T>) -> List<T> {
|
||||||
|
// SAFETY: a materialized input's frames are arena-resident.
|
||||||
|
let item = unsafe { core_types::record::GroupItem::from_resident(level.batch()) };
|
||||||
|
graphic_types::graphic::run_to_render_list::<T>(&item).expect("the run holds the row's element type")
|
||||||
|
}
|
||||||
|
|
||||||
#[node_macro::node(name("Copy to Points"), category("Repeat"), path(core_types::vector))]
|
#[node_macro::node(name("Copy to Points"), category("Repeat"), path(core_types::vector))]
|
||||||
fn copy_to_points<I: Send + Clone>(
|
fn copy_to_points<I: Send + Clone>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
|
|||||||
Reference in New Issue
Block a user