mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 05:28:11 +08:00
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:
@@ -275,6 +275,8 @@ impl<T: TableRowLayout> TableRowLayout for Table<T> {
|
|||||||
Some(format_alpha_blending(value))
|
Some(format_alpha_blending(value))
|
||||||
} else if let Some(&value) = ty.downcast_ref::<Option<NodeId>>() {
|
} else if let Some(&value) = ty.downcast_ref::<Option<NodeId>>() {
|
||||||
Some(value.map_or_else(|| "-".to_string(), |id| id.to_string()))
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -458,17 +460,6 @@ impl TableRowLayout for Vector {
|
|||||||
TextLabel::new("Colinear Handle IDs").narrow(true).widget_instance(),
|
TextLabel::new("Colinear Handle IDs").narrow(true).widget_instance(),
|
||||||
TextLabel::new(colinear).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 => {
|
VectorTableTab::Points => {
|
||||||
table_rows.push(column_headings(&["", "position"]));
|
table_rows.push(column_headings(&["", "position"]));
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||||
exports: vec![NodeInput::node(NodeId(4), 0)],
|
exports: vec![NodeInput::node(NodeId(5), 0)],
|
||||||
nodes: [
|
nodes: [
|
||||||
// Primary (bottom) input type coercion
|
// Primary (bottom) input type coercion
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
@@ -233,22 +233,33 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::wrap_graphic::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(graphic::wrap_graphic::IDENTIFIER),
|
||||||
..Default::default()
|
..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 {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
|
inputs: vec![NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER),
|
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()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// The monitor node is used to display a thumbnail in the UI
|
// The monitor node is used to display a thumbnail in the UI
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
inputs: vec![NodeInput::node(NodeId(3), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||||
skip_deduplication: true,
|
skip_deduplication: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
call_argument: generic!(T),
|
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),
|
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -272,6 +283,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
network_metadata: Some(NodeNetworkMetadata {
|
network_metadata: Some(NodeNetworkMetadata {
|
||||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||||
node_metadata: [
|
node_metadata: [
|
||||||
|
// 0: to_graphic
|
||||||
DocumentNodeMetadata {
|
DocumentNodeMetadata {
|
||||||
persistent_metadata: DocumentNodePersistentMetadata {
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -3)),
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -3)),
|
||||||
@@ -279,6 +291,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
// 1: wrap_graphic
|
||||||
DocumentNodeMetadata {
|
DocumentNodeMetadata {
|
||||||
persistent_metadata: DocumentNodePersistentMetadata {
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -1)),
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -1)),
|
||||||
@@ -286,6 +299,15 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..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 {
|
DocumentNodeMetadata {
|
||||||
persistent_metadata: DocumentNodePersistentMetadata {
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -1)),
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -1)),
|
||||||
@@ -293,6 +315,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
// 4: monitor
|
||||||
DocumentNodeMetadata {
|
DocumentNodeMetadata {
|
||||||
persistent_metadata: DocumentNodePersistentMetadata {
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-7, -1)),
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-7, -1)),
|
||||||
@@ -300,6 +323,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
// 5: extend
|
||||||
DocumentNodeMetadata {
|
DocumentNodeMetadata {
|
||||||
persistent_metadata: DocumentNodePersistentMetadata {
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, -3)),
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, -3)),
|
||||||
@@ -328,7 +352,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||||
exports: vec![NodeInput::node(NodeId(3), 0)],
|
exports: vec![NodeInput::node(NodeId(4), 0)],
|
||||||
nodes: [
|
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
|
// 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 {
|
DocumentNode {
|
||||||
@@ -344,16 +368,27 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
],
|
],
|
||||||
..Default::default()
|
..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 {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
|
inputs: vec![NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER),
|
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()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// The monitor node is used to display a thumbnail in the UI.
|
// The monitor node is used to display a thumbnail in the UI.
|
||||||
// TODO: Check if thumbnail is reversed
|
// TODO: Check if thumbnail is reversed
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||||
call_argument: generic!(T),
|
call_argument: generic!(T),
|
||||||
skip_deduplication: true,
|
skip_deduplication: true,
|
||||||
@@ -362,7 +397,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::import(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(Table<Artboard>))), 0),
|
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),
|
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -418,6 +453,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
network_metadata: Some(NodeNetworkMetadata {
|
network_metadata: Some(NodeNetworkMetadata {
|
||||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||||
node_metadata: [
|
node_metadata: [
|
||||||
|
// 0: create_artboard
|
||||||
DocumentNodeMetadata {
|
DocumentNodeMetadata {
|
||||||
persistent_metadata: DocumentNodePersistentMetadata {
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -3)),
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -3)),
|
||||||
@@ -425,6 +461,15 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..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 {
|
DocumentNodeMetadata {
|
||||||
persistent_metadata: DocumentNodePersistentMetadata {
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -3)),
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -3)),
|
||||||
@@ -432,6 +477,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
// 3: monitor
|
||||||
DocumentNodeMetadata {
|
DocumentNodeMetadata {
|
||||||
persistent_metadata: DocumentNodePersistentMetadata {
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-7, -3)),
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-7, -3)),
|
||||||
@@ -439,6 +485,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
// 4: extend
|
||||||
DocumentNodeMetadata {
|
DocumentNodeMetadata {
|
||||||
persistent_metadata: DocumentNodePersistentMetadata {
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, -4)),
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, -4)),
|
||||||
|
|||||||
@@ -169,10 +169,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
|||||||
"graphene_core::graphic::LegacyLayerExtendNode",
|
"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 {
|
NodeReplacement {
|
||||||
node: graphene_std::graphic::to_graphic::IDENTIFIER,
|
node: graphene_std::graphic::to_graphic::IDENTIFIER,
|
||||||
aliases: &[
|
aliases: &[
|
||||||
@@ -1024,6 +1020,16 @@ pub fn document_migration_reset_node_definition(document_serialized_content: &st
|
|||||||
return true;
|
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
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Option<f64>]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Option<f64>]),
|
||||||
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Option<NodeId>]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<DVec2>]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<DVec2>]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<String>]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<String>]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => [f64; 4]]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => [f64; 4]]),
|
||||||
@@ -175,6 +176,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<f64>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<f64>]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<Color>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<Color>]),
|
||||||
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<NodeId>]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => [f64; 4]]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => [f64; 4]]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Graphic]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Graphic]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Vec2]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Vec2]),
|
||||||
|
|||||||
@@ -366,10 +366,7 @@ pub fn simplify_identifier_name(ty: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn make_type_user_readable(ty: &str) -> String {
|
pub fn make_type_user_readable(ty: &str) -> String {
|
||||||
ty.replace("Option<Arc<OwnedContextImpl>>", "Context")
|
ty.replace("Option<Arc<OwnedContextImpl>>", "Context").replace("Raster<CPU>", "Raster").replace("Raster<GPU>", "Raster")
|
||||||
.replace("Vector<Option<Table<Graphic>>>", "Vector")
|
|
||||||
.replace("Raster<CPU>", "Raster")
|
|
||||||
.replace("Raster<GPU>", "Raster")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Type {
|
impl std::fmt::Debug for Type {
|
||||||
|
|||||||
@@ -113,30 +113,11 @@ pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Re
|
|||||||
alpha_blending: Vec<AlphaBlending>,
|
alpha_blending: Vec<AlphaBlending>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
|
||||||
|
// to recover the elements. Per-row attribute values are populated at runtime by the node graph.
|
||||||
Ok(match ArtboardFormat::deserialize(deserializer)? {
|
Ok(match ArtboardFormat::deserialize(deserializer)? {
|
||||||
ArtboardFormat::ArtboardGroup(artboard_group) => {
|
ArtboardFormat::ArtboardGroup(artboard_group) => artboard_group.artboards.into_iter().map(|(artboard, _)| TableRow::new_from_element(artboard)).collect(),
|
||||||
let mut table = Table::new();
|
ArtboardFormat::OldArtboardTable(old_table) => old_table.element.into_iter().map(TableRow::new_from_element).collect(),
|
||||||
for (artboard, source_node_id) in artboard_group.artboards {
|
|
||||||
table.push(
|
|
||||||
TableRow::new_from_element(artboard)
|
|
||||||
.with_attribute("transform", DAffine2::IDENTITY)
|
|
||||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
|
||||||
.with_attribute("source_node_id", source_node_id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
table
|
|
||||||
}
|
|
||||||
ArtboardFormat::OldArtboardTable(old_table) => old_table
|
|
||||||
.element
|
|
||||||
.into_iter()
|
|
||||||
.zip(old_table.transform.into_iter().zip(old_table.alpha_blending))
|
|
||||||
.map(|(element, (transform, alpha_blending))| {
|
|
||||||
TableRow::new_from_element(element)
|
|
||||||
.with_attribute("transform", transform)
|
|
||||||
.with_attribute("alpha_blending", alpha_blending)
|
|
||||||
.with_attribute("source_node_id", None::<NodeId>)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
ArtboardFormat::ArtboardTable(artboard_table) => artboard_table,
|
ArtboardFormat::ArtboardTable(artboard_table) => artboard_table,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use raster_types::{CPU, GPU, Raster};
|
|||||||
use vector_types::GradientStops;
|
use vector_types::GradientStops;
|
||||||
// use vector_types::Vector;
|
// use vector_types::Vector;
|
||||||
|
|
||||||
pub type Vector = vector_types::Vector<Option<Table<Graphic>>>;
|
pub use vector_types::Vector;
|
||||||
|
|
||||||
/// The possible forms of graphical content that can be rendered by the Render node into either an image or SVG syntax.
|
/// The possible forms of graphical content that can be rendered by the Render node into either an image or SVG syntax.
|
||||||
#[derive(Clone, Debug, CacheHash, PartialEq, DynAny)]
|
#[derive(Clone, Debug, CacheHash, PartialEq, DynAny)]
|
||||||
@@ -141,7 +141,7 @@ fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic
|
|||||||
|
|
||||||
fn flatten_recursive<T>(output: &mut Table<T>, current_graphic_table: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) {
|
fn flatten_recursive<T>(output: &mut Table<T>, current_graphic_table: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) {
|
||||||
for current_graphic_row in current_graphic_table.into_iter() {
|
for current_graphic_row in current_graphic_table.into_iter() {
|
||||||
let source_node_id: Option<NodeId> = current_graphic_row.attribute_cloned_or_default("source_node_id");
|
let layer: Option<NodeId> = current_graphic_row.attribute_cloned_or_default("editor:layer");
|
||||||
let current_transform: DAffine2 = current_graphic_row.attribute_cloned_or_default("transform");
|
let current_transform: DAffine2 = current_graphic_row.attribute_cloned_or_default("transform");
|
||||||
let current_alpha_blending: AlphaBlending = current_graphic_row.attribute_cloned_or_default("alpha_blending");
|
let current_alpha_blending: AlphaBlending = current_graphic_row.attribute_cloned_or_default("alpha_blending");
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic
|
|||||||
|
|
||||||
attributes.insert("transform", current_transform * row_transform);
|
attributes.insert("transform", current_transform * row_transform);
|
||||||
attributes.insert("alpha_blending", compose_alpha_blending(current_alpha_blending, row_alpha_blending));
|
attributes.insert("alpha_blending", compose_alpha_blending(current_alpha_blending, row_alpha_blending));
|
||||||
attributes.insert("source_node_id", source_node_id);
|
attributes.insert("editor:layer", layer);
|
||||||
|
|
||||||
output.push(TableRow::from_parts(element, attributes));
|
output.push(TableRow::from_parts(element, attributes));
|
||||||
}
|
}
|
||||||
@@ -504,70 +504,32 @@ pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Res
|
|||||||
Table(serde_json::Value),
|
Table(serde_json::Value),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
|
||||||
|
// to recover the elements. Per-row attribute values are populated at runtime by the node graph.
|
||||||
Ok(match GraphicFormat::deserialize(deserializer)? {
|
Ok(match GraphicFormat::deserialize(deserializer)? {
|
||||||
GraphicFormat::OldGraphicGroup(old) => {
|
GraphicFormat::OldGraphicGroup(old) => old.elements.into_iter().map(|(graphic, _)| TableRow::new_from_element(graphic)).collect(),
|
||||||
let mut graphic_table = Table::new();
|
|
||||||
for (graphic, source_node_id) in old.elements {
|
|
||||||
graphic_table.push(
|
|
||||||
TableRow::new_from_element(graphic)
|
|
||||||
.with_attribute("transform", old.transform)
|
|
||||||
.with_attribute("alpha_blending", old.alpha_blending)
|
|
||||||
.with_attribute("source_node_id", source_node_id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
graphic_table
|
|
||||||
}
|
|
||||||
GraphicFormat::OlderTableOldGraphicGroup(old) => old
|
GraphicFormat::OlderTableOldGraphicGroup(old) => old
|
||||||
.element
|
.element
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|element| {
|
.flat_map(|element| element.elements.into_iter().map(|(graphic, _)| TableRow::new_from_element(graphic)))
|
||||||
element.elements.into_iter().map(move |(graphic, source_node_id)| {
|
|
||||||
TableRow::new_from_element(graphic)
|
|
||||||
.with_attribute("transform", element.transform)
|
|
||||||
.with_attribute("alpha_blending", element.alpha_blending)
|
|
||||||
.with_attribute("source_node_id", source_node_id)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect(),
|
.collect(),
|
||||||
GraphicFormat::OldTableOldGraphicGroup(old) => old
|
GraphicFormat::OldTableOldGraphicGroup(old) => old
|
||||||
.element
|
.element
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|element| {
|
.flat_map(|element| element.elements.into_iter().map(|(graphic, _)| TableRow::new_from_element(graphic)))
|
||||||
element.elements.into_iter().map(move |(graphic, source_node_id)| {
|
|
||||||
TableRow::new_from_element(graphic)
|
|
||||||
.with_attribute("transform", element.transform)
|
|
||||||
.with_attribute("alpha_blending", element.alpha_blending)
|
|
||||||
.with_attribute("source_node_id", source_node_id)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect(),
|
.collect(),
|
||||||
GraphicFormat::OldTableGraphicGroup(old) => old
|
GraphicFormat::OldTableGraphicGroup(old) => old
|
||||||
.element
|
.element
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|element| {
|
.flat_map(|element| element.elements.into_iter().map(|(graphic, _)| TableRow::new_from_element(graphic)))
|
||||||
element.elements.into_iter().map(move |(graphic, source_node_id)| {
|
|
||||||
TableRow::new_from_element(graphic)
|
|
||||||
.with_attribute("transform", DAffine2::IDENTITY)
|
|
||||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
|
||||||
.with_attribute("source_node_id", source_node_id)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect(),
|
.collect(),
|
||||||
GraphicFormat::Table(value) => {
|
GraphicFormat::Table(value) => {
|
||||||
// Try to deserialize as either table format
|
// Try to deserialize as either table format
|
||||||
if let Ok(old_table) = serde_json::from_value::<Table<GraphicGroup>>(value.clone()) {
|
if let Ok(old_table) = serde_json::from_value::<Table<GraphicGroup>>(value.clone()) {
|
||||||
let mut graphic_table = Table::new();
|
let mut graphic_table = Table::new();
|
||||||
for index in 0..old_table.len() {
|
for index in 0..old_table.len() {
|
||||||
let row_transform: DAffine2 = old_table.attribute_cloned_or_default("transform", index);
|
for (graphic, _) in &old_table.element(index).unwrap().elements {
|
||||||
let row_alpha_blending: AlphaBlending = old_table.attribute_cloned_or_default("alpha_blending", index);
|
graphic_table.push(TableRow::new_from_element(graphic.clone()));
|
||||||
|
|
||||||
for (graphic, source_node_id) in &old_table.element(index).unwrap().elements {
|
|
||||||
graphic_table.push(
|
|
||||||
TableRow::new_from_element(graphic.clone())
|
|
||||||
.with_attribute("transform", row_transform)
|
|
||||||
.with_attribute("alpha_blending", row_alpha_blending)
|
|
||||||
.with_attribute("source_node_id", *source_node_id),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
graphic_table
|
graphic_table
|
||||||
|
|||||||
@@ -72,40 +72,17 @@ pub mod migrations {
|
|||||||
|
|
||||||
Ok(match VectorFormat::deserialize(deserializer)? {
|
Ok(match VectorFormat::deserialize(deserializer)? {
|
||||||
VectorFormat::Vector(vector) => Table::new_from_element(vector),
|
VectorFormat::Vector(vector) => Table::new_from_element(vector),
|
||||||
VectorFormat::OldVectorData(old) => {
|
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
|
||||||
let mut vector_table = Table::new_from_element(Vector {
|
// to recover the elements. Per-row attribute values are populated at runtime by the node graph.
|
||||||
style: old.style,
|
VectorFormat::OldVectorData(old) => Table::new_from_element(Vector {
|
||||||
colinear_manipulators: old.colinear_manipulators,
|
style: old.style,
|
||||||
point_domain: old.point_domain,
|
colinear_manipulators: old.colinear_manipulators,
|
||||||
segment_domain: old.segment_domain,
|
point_domain: old.point_domain,
|
||||||
region_domain: old.region_domain,
|
segment_domain: old.segment_domain,
|
||||||
upstream_data: old.upstream_graphic_group,
|
region_domain: old.region_domain,
|
||||||
});
|
}),
|
||||||
vector_table.set_attribute("transform", 0, old.transform);
|
VectorFormat::OlderVectorTable(older_table) => older_table.element.into_iter().map(TableRow::new_from_element).collect(),
|
||||||
vector_table.set_attribute("alpha_blending", 0, old.alpha_blending);
|
VectorFormat::OldVectorTable(old_table) => old_table.element.into_iter().map(TableRow::new_from_element).collect(),
|
||||||
vector_table
|
|
||||||
}
|
|
||||||
VectorFormat::OlderVectorTable(older_table) => older_table
|
|
||||||
.element
|
|
||||||
.into_iter()
|
|
||||||
.map(|element| {
|
|
||||||
TableRow::new_from_element(element)
|
|
||||||
.with_attribute("transform", DAffine2::IDENTITY)
|
|
||||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
|
||||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
VectorFormat::OldVectorTable(old_table) => old_table
|
|
||||||
.element
|
|
||||||
.into_iter()
|
|
||||||
.zip(old_table.transform.into_iter().zip(old_table.alpha_blending))
|
|
||||||
.map(|(element, (transform, alpha_blending))| {
|
|
||||||
TableRow::new_from_element(element)
|
|
||||||
.with_attribute("transform", transform)
|
|
||||||
.with_attribute("alpha_blending", alpha_blending)
|
|
||||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
VectorFormat::VectorTable(vector_table) => vector_table,
|
VectorFormat::VectorTable(vector_table) => vector_table,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -319,31 +319,14 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
|||||||
Table::new_from_element(Raster::new_cpu(table.element(0).unwrap().clone()))
|
Table::new_from_element(Raster::new_cpu(table.element(0).unwrap().clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
|
||||||
|
// to recover the elements. Per-row attribute values are populated at runtime by the node graph.
|
||||||
fn old_table_to_new_table<T>(old_table: OldTable<T>) -> Table<T> {
|
fn old_table_to_new_table<T>(old_table: OldTable<T>) -> Table<T> {
|
||||||
old_table
|
old_table.element.into_iter().map(TableRow::new_from_element).collect()
|
||||||
.element
|
|
||||||
.into_iter()
|
|
||||||
.zip(old_table.transform.into_iter().zip(old_table.alpha_blending))
|
|
||||||
.map(|(element, (transform, alpha_blending))| {
|
|
||||||
TableRow::new_from_element(element)
|
|
||||||
.with_attribute("transform", transform)
|
|
||||||
.with_attribute("alpha_blending", alpha_blending)
|
|
||||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn older_table_to_new_table<T>(old_table: OlderTable<T>) -> Table<T> {
|
fn older_table_to_new_table<T>(old_table: OlderTable<T>) -> Table<T> {
|
||||||
old_table
|
old_table.element.into_iter().map(TableRow::new_from_element).collect()
|
||||||
.element
|
|
||||||
.into_iter()
|
|
||||||
.map(|element| {
|
|
||||||
TableRow::new_from_element(element)
|
|
||||||
.with_attribute("transform", DAffine2::IDENTITY)
|
|
||||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
|
||||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_image_frame_table(image_frame: Table<ImageFrame<Color>>) -> Table<Raster<CPU>> {
|
fn from_image_frame_table(image_frame: Table<ImageFrame<Color>>) -> Table<Raster<CPU>> {
|
||||||
@@ -447,19 +430,12 @@ pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D
|
|||||||
RasterTableRow(TableRow<Raster<CPU>>),
|
RasterTableRow(TableRow<Raster<CPU>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
|
||||||
|
// to recover the element. Per-row attribute values are populated at runtime by the node graph.
|
||||||
Ok(match FormatVersions::deserialize(deserializer)? {
|
Ok(match FormatVersions::deserialize(deserializer)? {
|
||||||
FormatVersions::Image(image) => TableRow::new_from_element(Raster::new_cpu(image))
|
FormatVersions::Image(image) => TableRow::new_from_element(Raster::new_cpu(image)),
|
||||||
.with_attribute("transform", DAffine2::IDENTITY)
|
FormatVersions::OldImageFrame(old) => TableRow::new_from_element(Raster::new_cpu(old.image)),
|
||||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
FormatVersions::ImageFrameTable(image_frame) => TableRow::new_from_element(Raster::new_cpu(image_frame.element(0).unwrap().image.clone())),
|
||||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>),
|
|
||||||
FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => TableRow::new_from_element(Raster::new_cpu(image_frame_with_transform_and_blending.image))
|
|
||||||
.with_attribute("transform", image_frame_with_transform_and_blending.transform)
|
|
||||||
.with_attribute("alpha_blending", image_frame_with_transform_and_blending.alpha_blending)
|
|
||||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>),
|
|
||||||
FormatVersions::ImageFrameTable(image_frame) => TableRow::new_from_element(Raster::new_cpu(image_frame.element(0).unwrap().image.clone()))
|
|
||||||
.with_attribute("transform", DAffine2::IDENTITY)
|
|
||||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
|
||||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>),
|
|
||||||
FormatVersions::RasterTable(image_frame_table) => image_frame_table.into_iter().next().unwrap_or_default(),
|
FormatVersions::RasterTable(image_frame_table) => image_frame_table.into_iter().next().unwrap_or_default(),
|
||||||
FormatVersions::RasterTableRow(image_table_row) => image_table_row,
|
FormatVersions::RasterTableRow(image_table_row) => image_table_row,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -412,10 +412,10 @@ impl Render for Graphic {
|
|||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
metadata.upstream_footprints.insert(element_id, footprint);
|
||||||
// TODO: Find a way to handle more than the first row
|
// TODO: Find a way to handle more than the first row
|
||||||
if !table.is_empty() {
|
if !table.is_empty() {
|
||||||
let source_node_id: Option<NodeId> = table.attribute_cloned_or_default("source_node_id", 0);
|
let layer: Option<NodeId> = table.attribute_cloned_or_default("editor:layer", 0);
|
||||||
let transform: DAffine2 = table.attribute_cloned_or_default("transform", 0);
|
let transform: DAffine2 = table.attribute_cloned_or_default("transform", 0);
|
||||||
|
|
||||||
metadata.first_element_source_id.insert(element_id, source_node_id);
|
metadata.first_element_source_id.insert(element_id, layer);
|
||||||
metadata.local_transforms.insert(element_id, transform);
|
metadata.local_transforms.insert(element_id, transform);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -655,8 +655,8 @@ impl Render for Table<Artboard> {
|
|||||||
|
|
||||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
|
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
|
||||||
for index in 0..self.len() {
|
for index in 0..self.len() {
|
||||||
let source_node_id: Option<NodeId> = self.attribute_cloned_or_default("source_node_id", index);
|
let layer: Option<NodeId> = self.attribute_cloned_or_default("editor:layer", index);
|
||||||
self.element(index).unwrap().collect_metadata(metadata, footprint, source_node_id);
|
self.element(index).unwrap().collect_metadata(metadata, footprint, layer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -805,16 +805,16 @@ impl Render for Table<Graphic> {
|
|||||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
||||||
for index in 0..self.len() {
|
for index in 0..self.len() {
|
||||||
let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||||
let source_node_id: Option<NodeId> = self.attribute_cloned_or_default("source_node_id", index);
|
let layer: Option<NodeId> = self.attribute_cloned_or_default("editor:layer", index);
|
||||||
let element = self.element(index).unwrap();
|
let element = self.element(index).unwrap();
|
||||||
|
|
||||||
let mut footprint = footprint;
|
let mut footprint = footprint;
|
||||||
footprint.transform *= row_transform;
|
footprint.transform *= row_transform;
|
||||||
|
|
||||||
if let Some(element_id) = source_node_id {
|
if let Some(element_id) = layer {
|
||||||
element.collect_metadata(metadata, footprint, Some(element_id));
|
element.collect_metadata(metadata, footprint, Some(element_id));
|
||||||
} else {
|
} else {
|
||||||
// Recurse through anonymous wrapper rows to reach nested content with source_node_ids
|
// Recurse through anonymous wrapper rows to reach nested content with editor:layer tags
|
||||||
element.collect_metadata(metadata, footprint, None);
|
element.collect_metadata(metadata, footprint, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -860,9 +860,9 @@ impl Render for Table<Graphic> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
|
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
|
||||||
let (elements, source_node_ids) = self.element_and_attribute_slices_mut::<Option<NodeId>>("source_node_id");
|
let (elements, layers) = self.element_and_attribute_slices_mut::<Option<NodeId>>("editor:layer");
|
||||||
for (element, source_node_id) in elements.iter_mut().zip(source_node_ids.iter()) {
|
for (element, layer) in elements.iter_mut().zip(layers.iter()) {
|
||||||
element.new_ids_from_hash(*source_node_id);
|
element.new_ids_from_hash(*layer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -938,8 +938,7 @@ impl Render for Table<Vector> {
|
|||||||
let vector_row = Table::new_from_row(
|
let vector_row = Table::new_from_row(
|
||||||
TableRow::new_from_element(cloned_vector)
|
TableRow::new_from_element(cloned_vector)
|
||||||
.with_attribute("transform", multiplied_transform)
|
.with_attribute("transform", multiplied_transform)
|
||||||
.with_attribute("alpha_blending", alpha_blending)
|
.with_attribute("alpha_blending", alpha_blending),
|
||||||
.with_attribute("source_node_id", None::<NodeId>),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
(id, mask_type, vector_row)
|
(id, mask_type, vector_row)
|
||||||
@@ -1256,8 +1255,7 @@ impl Render for Table<Vector> {
|
|||||||
let vector_table = Table::new_from_row(
|
let vector_table = Table::new_from_row(
|
||||||
TableRow::new_from_element(cloned_element)
|
TableRow::new_from_element(cloned_element)
|
||||||
.with_attribute("transform", row_transform)
|
.with_attribute("transform", row_transform)
|
||||||
.with_attribute("alpha_blending", alpha_blending)
|
.with_attribute("alpha_blending", alpha_blending),
|
||||||
.with_attribute("source_node_id", None::<NodeId>),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let bounds = element.bounding_box_with_transform(multiplied_transform).unwrap_or(layer_bounds);
|
let bounds = element.bounding_box_with_transform(multiplied_transform).unwrap_or(layer_bounds);
|
||||||
@@ -1329,10 +1327,10 @@ impl Render for Table<Vector> {
|
|||||||
for index in 0..self.len() {
|
for index in 0..self.len() {
|
||||||
let Some(vector) = self.element(index) else { continue };
|
let Some(vector) = self.element(index) else { continue };
|
||||||
let transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
let transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||||
let source_node_id: Option<NodeId> = self.attribute_cloned_or_default("source_node_id", index);
|
let layer: Option<NodeId> = self.attribute_cloned_or_default("editor:layer", index);
|
||||||
|
|
||||||
if let Some(element_id) = caller_element_id.or(source_node_id) {
|
if let Some(element_id) = caller_element_id.or(layer) {
|
||||||
// When recovering element_id from the row's source_node_id (because the caller
|
// When recovering element_id from the row's editor:layer tag (because the caller
|
||||||
// passed None), also store the transform metadata that Graphic::collect_metadata
|
// passed None), also store the transform metadata that Graphic::collect_metadata
|
||||||
// normally provides but skipped due to the None element_id.
|
// normally provides but skipped due to the None element_id.
|
||||||
if caller_element_id.is_none() {
|
if caller_element_id.is_none() {
|
||||||
@@ -1365,7 +1363,7 @@ impl Render for Table<Vector> {
|
|||||||
.stroke_bezier_paths()
|
.stroke_bezier_paths()
|
||||||
.map(fill)
|
.map(fill)
|
||||||
.map(|subpath| ClickTarget::new_with_subpath(subpath, stroke_width).into())
|
.map(|subpath| ClickTarget::new_with_subpath(subpath, stroke_width).into())
|
||||||
.chain(single_anchors_targets.into_iter())
|
.chain(single_anchors_targets)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
metadata.click_targets.entry(element_id).or_insert(click_targets);
|
metadata.click_targets.entry(element_id).or_insert(click_targets);
|
||||||
@@ -1373,7 +1371,11 @@ impl Render for Table<Vector> {
|
|||||||
metadata.vector_data.entry(element_id).or_insert_with(|| Arc::new(vector.clone()));
|
metadata.vector_data.entry(element_id).or_insert_with(|| Arc::new(vector.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(upstream_nested_layers) = &vector.upstream_data {
|
// If this row carries a snapshot of upstream graphic content (e.g. it was produced by Boolean Operation,
|
||||||
|
// Flatten Path, Morph, or any other destructive merge), recurse into that snapshot so the editor can
|
||||||
|
// surface the original child layers' click targets.
|
||||||
|
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>("editor:merged_layers", index);
|
||||||
|
if !upstream_nested_layers.is_empty() {
|
||||||
let mut upstream_footprint = footprint;
|
let mut upstream_footprint = footprint;
|
||||||
upstream_footprint.transform *= transform;
|
upstream_footprint.transform *= transform;
|
||||||
upstream_nested_layers.collect_metadata(metadata, upstream_footprint, None);
|
upstream_nested_layers.collect_metadata(metadata, upstream_footprint, None);
|
||||||
@@ -1571,7 +1573,19 @@ impl Render for Table<Raster<CPU>> {
|
|||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
metadata.upstream_footprints.insert(element_id, footprint);
|
||||||
// TODO: Find a way to handle more than one row of the raster table
|
// TODO: Find a way to handle more than one row of the raster table
|
||||||
if !self.is_empty() {
|
if !self.is_empty() {
|
||||||
metadata.local_transforms.insert(element_id, self.attribute_cloned_or_default("transform", 0));
|
let transform: DAffine2 = self.attribute_cloned_or_default("transform", 0);
|
||||||
|
metadata.local_transforms.insert(element_id, transform);
|
||||||
|
|
||||||
|
// If this raster carries a snapshot of upstream graphic content (e.g. it was produced by Rasterize,
|
||||||
|
// which destructively merges its inputs into pixels), recurse into that snapshot so the editor can
|
||||||
|
// surface the original child layers' click targets (the same mechanism Boolean Operation uses).
|
||||||
|
// The snapshot was captured before Rasterize shifted its input transforms to align with the rasterization
|
||||||
|
// area, so the children are already in the coordinate space matching `footprint` here — we must NOT
|
||||||
|
// multiply in `transform` (which is the rasterization area, not a layer-stack transform).
|
||||||
|
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>("editor:merged_layers", 0);
|
||||||
|
if !upstream_nested_layers.is_empty() {
|
||||||
|
upstream_nested_layers.collect_metadata(metadata, footprint, None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1649,7 +1663,19 @@ impl Render for Table<Raster<GPU>> {
|
|||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
metadata.upstream_footprints.insert(element_id, footprint);
|
||||||
// TODO: Find a way to handle more than one row of the raster table
|
// TODO: Find a way to handle more than one row of the raster table
|
||||||
if !self.is_empty() {
|
if !self.is_empty() {
|
||||||
metadata.local_transforms.insert(element_id, self.attribute_cloned_or_default("transform", 0));
|
let transform: DAffine2 = self.attribute_cloned_or_default("transform", 0);
|
||||||
|
metadata.local_transforms.insert(element_id, transform);
|
||||||
|
|
||||||
|
// If this raster carries a snapshot of upstream graphic content (e.g. it was produced by Rasterize,
|
||||||
|
// which destructively merges its inputs into pixels), recurse into that snapshot so the editor can
|
||||||
|
// surface the original child layers' click targets (the same mechanism Boolean Operation uses).
|
||||||
|
// The snapshot was captured before Rasterize shifted its input transforms to align with the rasterization
|
||||||
|
// area, so the children are already in the coordinate space matching `footprint` here — we must NOT
|
||||||
|
// multiply in `transform` (which is the rasterization area, not a layer-stack transform).
|
||||||
|
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>("editor:merged_layers", 0);
|
||||||
|
if !upstream_nested_layers.is_empty() {
|
||||||
|
upstream_nested_layers.collect_metadata(metadata, footprint, None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ pub trait MergeByDistanceExt {
|
|||||||
fn merge_by_distance_spatial(&mut self, transform: DAffine2, distance: f64);
|
fn merge_by_distance_spatial(&mut self, transform: DAffine2, distance: f64);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Upstream: 'static> MergeByDistanceExt for Vector<Upstream> {
|
impl MergeByDistanceExt for Vector {
|
||||||
fn merge_by_distance_topological(&mut self, distance: f64) {
|
fn merge_by_distance_topological(&mut self, distance: f64) {
|
||||||
// Treat self as an undirected graph
|
// Treat self as an undirected graph
|
||||||
let indices = VectorIndex::build_from(self);
|
let indices = VectorIndex::build_from(self);
|
||||||
@@ -237,7 +237,7 @@ pub struct VectorIndex {
|
|||||||
|
|
||||||
impl VectorIndex {
|
impl VectorIndex {
|
||||||
/// Construct a [`VectorIndex`] by building indexes from the given [`Vector`]. Takes `O(n)` time.
|
/// Construct a [`VectorIndex`] by building indexes from the given [`Vector`]. Takes `O(n)` time.
|
||||||
pub fn build_from<Upstream: 'static>(data: &Vector<Upstream>) -> Self {
|
pub fn build_from(data: &Vector) -> Self {
|
||||||
let point_to_offset = data.point_domain.ids().iter().copied().enumerate().map(|(a, b)| (b, a)).collect::<FxHashMap<_, _>>();
|
let point_to_offset = data.point_domain.ids().iter().copied().enumerate().map(|(a, b)| (b, a)).collect::<FxHashMap<_, _>>();
|
||||||
|
|
||||||
let mut point_to_node = FxHashMap::default();
|
let mut point_to_node = FxHashMap::default();
|
||||||
@@ -295,7 +295,7 @@ impl VectorIndex {
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Will panic if `id` isn't in the data.
|
/// Will panic if `id` isn't in the data.
|
||||||
pub fn point_position<Upstream: 'static>(&self, id: PointId, data: &Vector<Upstream>) -> DVec2 {
|
pub fn point_position(&self, id: PointId, data: &Vector) -> DVec2 {
|
||||||
let offset = self.point_to_offset[&id];
|
let offset = self.point_to_offset[&id];
|
||||||
data.point_domain.positions()[offset]
|
data.point_domain.positions()[offset]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -391,7 +391,7 @@ impl ManipulatorPointId {
|
|||||||
/// Attempt to retrieve the manipulator position in layer space (no transformation applied).
|
/// Attempt to retrieve the manipulator position in layer space (no transformation applied).
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn get_position<Upstream: 'static>(&self, vector: &Vector<Upstream>) -> Option<DVec2> {
|
pub fn get_position(&self, vector: &Vector) -> Option<DVec2> {
|
||||||
match self {
|
match self {
|
||||||
ManipulatorPointId::Anchor(id) => vector.point_domain.position_from_id(*id),
|
ManipulatorPointId::Anchor(id) => vector.point_domain.position_from_id(*id),
|
||||||
ManipulatorPointId::PrimaryHandle(id) => vector.segment_from_id(*id).and_then(|bezier| bezier.handle_start()),
|
ManipulatorPointId::PrimaryHandle(id) => vector.segment_from_id(*id).and_then(|bezier| bezier.handle_start()),
|
||||||
@@ -399,7 +399,7 @@ impl ManipulatorPointId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_anchor_position<Upstream: 'static>(&self, vector: &Vector<Upstream>) -> Option<DVec2> {
|
pub fn get_anchor_position(&self, vector: &Vector) -> Option<DVec2> {
|
||||||
match self {
|
match self {
|
||||||
ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_) => self.get_anchor(vector).and_then(|id| vector.point_domain.position_from_id(id)),
|
ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_) => self.get_anchor(vector).and_then(|id| vector.point_domain.position_from_id(id)),
|
||||||
_ => self.get_position(vector),
|
_ => self.get_position(vector),
|
||||||
@@ -408,7 +408,7 @@ impl ManipulatorPointId {
|
|||||||
|
|
||||||
/// Attempt to get a pair of handles. For an anchor this is the first two handles connected. For a handle it is self and the first opposing handle.
|
/// Attempt to get a pair of handles. For an anchor this is the first two handles connected. For a handle it is self and the first opposing handle.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_handle_pair<Upstream: 'static>(self, vector: &Vector<Upstream>) -> Option<[HandleId; 2]> {
|
pub fn get_handle_pair(self, vector: &Vector) -> Option<[HandleId; 2]> {
|
||||||
match self {
|
match self {
|
||||||
ManipulatorPointId::Anchor(point) => vector.all_connected(point).take(2).collect::<Vec<_>>().try_into().ok(),
|
ManipulatorPointId::Anchor(point) => vector.all_connected(point).take(2).collect::<Vec<_>>().try_into().ok(),
|
||||||
ManipulatorPointId::PrimaryHandle(segment) => {
|
ManipulatorPointId::PrimaryHandle(segment) => {
|
||||||
@@ -429,7 +429,7 @@ impl ManipulatorPointId {
|
|||||||
/// Finds all the connected handles of a point.
|
/// Finds all the connected handles of a point.
|
||||||
/// For an anchor it is all the connected handles.
|
/// For an anchor it is all the connected handles.
|
||||||
/// For a handle it is all the handles connected to its corresponding anchor other than the current handle.
|
/// For a handle it is all the handles connected to its corresponding anchor other than the current handle.
|
||||||
pub fn get_all_connected_handles<Upstream: 'static>(self, vector: &Vector<Upstream>) -> Option<Vec<HandleId>> {
|
pub fn get_all_connected_handles(self, vector: &Vector) -> Option<Vec<HandleId>> {
|
||||||
match self {
|
match self {
|
||||||
ManipulatorPointId::Anchor(point) => {
|
ManipulatorPointId::Anchor(point) => {
|
||||||
let connected = vector.all_connected(point).collect::<Vec<_>>();
|
let connected = vector.all_connected(point).collect::<Vec<_>>();
|
||||||
@@ -452,7 +452,7 @@ impl ManipulatorPointId {
|
|||||||
|
|
||||||
/// Attempt to find the closest anchor. If self is already an anchor then it is just self. If it is a start or end handle, then the start or end point is chosen.
|
/// Attempt to find the closest anchor. If self is already an anchor then it is just self. If it is a start or end handle, then the start or end point is chosen.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_anchor<Upstream: 'static>(self, vector: &Vector<Upstream>) -> Option<PointId> {
|
pub fn get_anchor(self, vector: &Vector) -> Option<PointId> {
|
||||||
match self {
|
match self {
|
||||||
ManipulatorPointId::Anchor(point) => Some(point),
|
ManipulatorPointId::Anchor(point) => Some(point),
|
||||||
ManipulatorPointId::PrimaryHandle(segment) => vector.segment_start_from_id(segment),
|
ManipulatorPointId::PrimaryHandle(segment) => vector.segment_start_from_id(segment),
|
||||||
@@ -538,7 +538,7 @@ impl HandleId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the magnitude of the handle from the anchor.
|
/// Calculate the magnitude of the handle from the anchor.
|
||||||
pub fn length<Upstream: 'static>(self, vector: &Vector<Upstream>) -> f64 {
|
pub fn length(self, vector: &Vector) -> f64 {
|
||||||
let Some(anchor_position) = self.to_manipulator_point().get_anchor_position(vector) else {
|
let Some(anchor_position) = self.to_manipulator_point().get_anchor_position(vector) else {
|
||||||
// TODO: This was previously an unwrap which was encountered, so this is a temporary way to avoid a crash
|
// TODO: This was previously an unwrap which was encountered, so this is a temporary way to avoid a crash
|
||||||
return 0.;
|
return 0.;
|
||||||
|
|||||||
@@ -846,14 +846,14 @@ struct Faces {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct FaceIterator<'a, Upstream> {
|
pub struct FaceIterator<'a> {
|
||||||
vector: &'a Vector<Upstream>,
|
vector: &'a Vector,
|
||||||
faces: Faces,
|
faces: Faces,
|
||||||
current_face: usize,
|
current_face: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Upstream> FaceIterator<'_, Upstream> {
|
impl FaceIterator<'_> {
|
||||||
fn new<'a>(faces: Faces, vector: &'a Vector<Upstream>) -> FaceIterator<'a, Upstream> {
|
fn new(faces: Faces, vector: &Vector) -> FaceIterator<'_> {
|
||||||
FaceIterator { vector, faces, current_face: 0 }
|
FaceIterator { vector, faces, current_face: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -862,7 +862,7 @@ impl<Upstream> FaceIterator<'_, Upstream> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Upstream> Iterator for FaceIterator<'_, Upstream> {
|
impl Iterator for FaceIterator<'_> {
|
||||||
type Item = kurbo::BezPath;
|
type Item = kurbo::BezPath;
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let start_side = self.faces.face_start.get(self.current_face).copied()?;
|
let start_side = self.faces.face_start.get(self.current_face).copied()?;
|
||||||
@@ -916,7 +916,7 @@ impl Faces {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Upstream> Vector<Upstream> {
|
impl Vector {
|
||||||
/// Construct a [`kurbo::PathSeg`] by resolving the points from their ids.
|
/// Construct a [`kurbo::PathSeg`] by resolving the points from their ids.
|
||||||
fn path_segment_from_index(&self, start: usize, end: usize, handles: BezierHandles) -> PathSeg {
|
fn path_segment_from_index(&self, start: usize, end: usize, handles: BezierHandles) -> PathSeg {
|
||||||
let start = dvec2_to_point(self.point_domain.positions()[start]);
|
let start = dvec2_to_point(self.point_domain.positions()[start]);
|
||||||
@@ -1131,7 +1131,7 @@ impl<Upstream> Vector<Upstream> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_stroke_path_iter(&self) -> StrokePathIter<'_, Upstream> {
|
pub fn build_stroke_path_iter(&self) -> StrokePathIter<'_> {
|
||||||
let mut points = vec![StrokePathIterPointMetadata::default(); self.point_domain.ids().len()];
|
let mut points = vec![StrokePathIterPointMetadata::default(); self.point_domain.ids().len()];
|
||||||
for (segment_index, (&start, &end)) in self.segment_domain.start_point.iter().zip(&self.segment_domain.end_point).enumerate() {
|
for (segment_index, (&start, &end)) in self.segment_domain.start_point.iter().zip(&self.segment_domain.end_point).enumerate() {
|
||||||
points[start].set(StrokePathIterPointSegmentMetadata::new(segment_index, false));
|
points[start].set(StrokePathIterPointSegmentMetadata::new(segment_index, false));
|
||||||
@@ -1239,7 +1239,7 @@ impl<Upstream> Vector<Upstream> {
|
|||||||
self.is_branching() && !self.has_regions()
|
self.is_branching() && !self.has_regions()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn construct_faces(&self) -> FaceIterator<'_, Upstream> {
|
pub fn construct_faces(&self) -> FaceIterator<'_> {
|
||||||
let mut adjacency: Vec<Vec<FaceSide>> = vec![Vec::new(); self.point_domain.len()];
|
let mut adjacency: Vec<Vec<FaceSide>> = vec![Vec::new(); self.point_domain.len()];
|
||||||
for (segment_index, (&start, &end)) in self.segment_domain.start_point.iter().zip(&self.segment_domain.end_point).enumerate() {
|
for (segment_index, (&start, &end)) in self.segment_domain.start_point.iter().zip(&self.segment_domain.end_point).enumerate() {
|
||||||
adjacency[start].push(FaceSide { segment_index, reversed: false });
|
adjacency[start].push(FaceSide { segment_index, reversed: false });
|
||||||
@@ -1361,14 +1361,14 @@ impl StrokePathIterPointMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct StrokePathIter<'a, Upstream> {
|
pub struct StrokePathIter<'a> {
|
||||||
vector: &'a Vector<Upstream>,
|
vector: &'a Vector,
|
||||||
points: Vec<StrokePathIterPointMetadata>,
|
points: Vec<StrokePathIterPointMetadata>,
|
||||||
skip: usize,
|
skip: usize,
|
||||||
done_one: bool,
|
done_one: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Upstream> Iterator for StrokePathIter<'_, Upstream> {
|
impl Iterator for StrokePathIter<'_> {
|
||||||
type Item = (Vec<ManipulatorGroup<PointId>>, bool);
|
type Item = (Vec<ManipulatorGroup<PointId>>, bool);
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ impl PointModification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].
|
/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].
|
||||||
pub fn create_from_vector<Upstream>(vector: &Vector<Upstream>) -> Self {
|
pub fn create_from_vector(vector: &Vector) -> Self {
|
||||||
Self {
|
Self {
|
||||||
add: vector.point_domain.ids().to_vec(),
|
add: vector.point_domain.ids().to_vec(),
|
||||||
remove: HashSet::new(),
|
remove: HashSet::new(),
|
||||||
@@ -211,7 +211,7 @@ impl SegmentModification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].
|
/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].
|
||||||
pub fn create_from_vector<Upstream>(vector: &Vector<Upstream>) -> Self {
|
pub fn create_from_vector(vector: &Vector) -> Self {
|
||||||
let point_id = |(&segment, &index)| (segment, vector.point_domain.ids()[index]);
|
let point_id = |(&segment, &index)| (segment, vector.point_domain.ids()[index]);
|
||||||
Self {
|
Self {
|
||||||
add: vector.segment_domain.ids().to_vec(),
|
add: vector.segment_domain.ids().to_vec(),
|
||||||
@@ -280,7 +280,7 @@ impl RegionModification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].
|
/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].
|
||||||
pub fn create_from_vector<Upstream>(vector: &Vector<Upstream>) -> Self {
|
pub fn create_from_vector(vector: &Vector) -> Self {
|
||||||
Self {
|
Self {
|
||||||
add: vector.region_domain.ids().to_vec(),
|
add: vector.region_domain.ids().to_vec(),
|
||||||
remove: HashSet::new(),
|
remove: HashSet::new(),
|
||||||
@@ -426,7 +426,7 @@ impl VectorModification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Apply this modification to the specified [`Vector`].
|
/// Apply this modification to the specified [`Vector`].
|
||||||
pub fn apply<Upstream>(&self, vector: &mut Vector<Upstream>) {
|
pub fn apply(&self, vector: &mut Vector) {
|
||||||
self.points.apply(&mut vector.point_domain, &mut vector.segment_domain);
|
self.points.apply(&mut vector.point_domain, &mut vector.segment_domain);
|
||||||
self.segments.apply(&mut vector.segment_domain, &vector.point_domain);
|
self.segments.apply(&mut vector.segment_domain, &vector.point_domain);
|
||||||
self.regions.apply(&mut vector.region_domain);
|
self.regions.apply(&mut vector.region_domain);
|
||||||
@@ -499,7 +499,7 @@ impl VectorModification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].
|
/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].
|
||||||
pub fn create_from_vector<Upstream>(vector: &Vector<Upstream>) -> Self {
|
pub fn create_from_vector(vector: &Vector) -> Self {
|
||||||
Self {
|
Self {
|
||||||
points: PointModification::create_from_vector(vector),
|
points: PointModification::create_from_vector(vector),
|
||||||
segments: SegmentModification::create_from_vector(vector),
|
segments: SegmentModification::create_from_vector(vector),
|
||||||
@@ -581,7 +581,7 @@ where
|
|||||||
deserializer.deserialize_seq(visitor)
|
deserializer.deserialize_seq(visitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppendBezpath<'a, Upstream: 'static> {
|
pub struct AppendBezpath<'a> {
|
||||||
first_point: Option<Point>,
|
first_point: Option<Point>,
|
||||||
last_point: Option<Point>,
|
last_point: Option<Point>,
|
||||||
first_point_index: Option<usize>,
|
first_point_index: Option<usize>,
|
||||||
@@ -590,11 +590,11 @@ pub struct AppendBezpath<'a, Upstream: 'static> {
|
|||||||
last_segment_id: Option<SegmentId>,
|
last_segment_id: Option<SegmentId>,
|
||||||
point_id: PointId,
|
point_id: PointId,
|
||||||
segment_id: SegmentId,
|
segment_id: SegmentId,
|
||||||
vector: &'a mut Vector<Upstream>,
|
vector: &'a mut Vector,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Upstream> AppendBezpath<'a, Upstream> {
|
impl<'a> AppendBezpath<'a> {
|
||||||
fn new(vector: &'a mut Vector<Upstream>) -> Self {
|
fn new(vector: &'a mut Vector) -> Self {
|
||||||
Self {
|
Self {
|
||||||
first_point: None,
|
first_point: None,
|
||||||
last_point: None,
|
last_point: None,
|
||||||
@@ -676,7 +676,7 @@ impl<'a, Upstream> AppendBezpath<'a, Upstream> {
|
|||||||
self.last_segment_id = None;
|
self.last_segment_id = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn append_bezpath(vector: &'a mut Vector<Upstream>, bezpath: BezPath) {
|
pub fn append_bezpath(vector: &'a mut Vector, bezpath: BezPath) {
|
||||||
let mut this = Self::new(vector);
|
let mut this = Self::new(vector);
|
||||||
let mut elements = bezpath.elements().iter().peekable();
|
let mut elements = bezpath.elements().iter().peekable();
|
||||||
|
|
||||||
@@ -726,7 +726,7 @@ pub trait VectorExt {
|
|||||||
fn append_bezpath(&mut self, bezpath: BezPath);
|
fn append_bezpath(&mut self, bezpath: BezPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Upstream: 'static> VectorExt for Vector<Upstream> {
|
impl VectorExt for Vector {
|
||||||
fn append_bezpath(&mut self, bezpath: BezPath) {
|
fn append_bezpath(&mut self, bezpath: BezPath) {
|
||||||
AppendBezpath::append_bezpath(self, bezpath);
|
AppendBezpath::append_bezpath(self, bezpath);
|
||||||
}
|
}
|
||||||
@@ -758,7 +758,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn modify_new() {
|
fn modify_new() {
|
||||||
let vector: Vector<()> = Vector::from_subpaths([Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE), Subpath::new_rectangle(DVec2::NEG_ONE, DVec2::ZERO)], false);
|
let vector: Vector = Vector::from_subpaths([Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE), Subpath::new_rectangle(DVec2::NEG_ONE, DVec2::ZERO)], false);
|
||||||
|
|
||||||
let modify = VectorModification::create_from_vector(&vector);
|
let modify = VectorModification::create_from_vector(&vector);
|
||||||
|
|
||||||
@@ -780,7 +780,7 @@ mod tests {
|
|||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
let mut vector: Vector<()> = Vector::from_subpaths(subpaths, false);
|
let mut vector: Vector = Vector::from_subpaths(subpaths, false);
|
||||||
|
|
||||||
let mut modify_new = VectorModification::create_from_vector(&vector);
|
let mut modify_new = VectorModification::create_from_vector(&vector);
|
||||||
let mut modify_original = VectorModification::default();
|
let mut modify_original = VectorModification::default();
|
||||||
|
|||||||
@@ -16,13 +16,9 @@ use kurbo::{Affine, BezPath, Rect, Shape};
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// Represents vector graphics data, composed of Bézier curves in a path or mesh arrangement.
|
/// Represents vector graphics data, composed of Bézier curves in a path or mesh arrangement.
|
||||||
///
|
|
||||||
/// Generic over `Upstream` to avoid circular dependency with the Graphic type.
|
|
||||||
/// - Use `Vector<()>` for basic vectors without upstream tracking
|
|
||||||
/// - Use `Vector<Option<Table<Graphic>>>` in the graphic crate for vectors with upstream layers
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct Vector<Upstream> {
|
pub struct Vector {
|
||||||
pub style: PathStyle,
|
pub style: PathStyle,
|
||||||
|
|
||||||
/// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another).
|
/// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another).
|
||||||
@@ -32,17 +28,12 @@ pub struct Vector<Upstream> {
|
|||||||
pub point_domain: PointDomain,
|
pub point_domain: PointDomain,
|
||||||
pub segment_domain: SegmentDomain,
|
pub segment_domain: SegmentDomain,
|
||||||
pub region_domain: RegionDomain,
|
pub region_domain: RegionDomain,
|
||||||
|
|
||||||
/// Used to store the upstream group/folder of nested layers during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved for the child layers.
|
|
||||||
/// Without this, the tools would be working with a collapsed version of the data which has no reference to the original child layers that were booleaned together, resulting in the inner layers not being editable.
|
|
||||||
#[cfg_attr(feature = "serde", serde(alias = "upstream_group"))]
|
|
||||||
pub upstream_data: Upstream,
|
|
||||||
}
|
}
|
||||||
unsafe impl<Upstream: 'static> StaticType for Vector<Upstream> {
|
unsafe impl StaticType for Vector {
|
||||||
type Static = Self;
|
type Static = Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Upstream: Default + 'static> Default for Vector<Upstream> {
|
impl Default for Vector {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None),
|
style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None),
|
||||||
@@ -50,23 +41,21 @@ impl<Upstream: Default + 'static> Default for Vector<Upstream> {
|
|||||||
point_domain: PointDomain::new(),
|
point_domain: PointDomain::new(),
|
||||||
segment_domain: SegmentDomain::new(),
|
segment_domain: SegmentDomain::new(),
|
||||||
region_domain: RegionDomain::new(),
|
region_domain: RegionDomain::new(),
|
||||||
upstream_data: Upstream::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Upstream> graphene_hash::CacheHash for Vector<Upstream> {
|
impl graphene_hash::CacheHash for Vector {
|
||||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.point_domain.cache_hash(state);
|
self.point_domain.cache_hash(state);
|
||||||
self.segment_domain.cache_hash(state);
|
self.segment_domain.cache_hash(state);
|
||||||
self.region_domain.cache_hash(state);
|
self.region_domain.cache_hash(state);
|
||||||
self.style.cache_hash(state);
|
self.style.cache_hash(state);
|
||||||
self.colinear_manipulators.cache_hash(state);
|
self.colinear_manipulators.cache_hash(state);
|
||||||
// We don't hash the upstream_data intentionally
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Upstream> Vector<Upstream> {
|
impl Vector {
|
||||||
/// Add a subpath to this vector path.
|
/// Add a subpath to this vector path.
|
||||||
pub fn append_subpath(&mut self, subpath: impl Borrow<Subpath<PointId>>, preserve_id: bool) {
|
pub fn append_subpath(&mut self, subpath: impl Borrow<Subpath<PointId>>, preserve_id: bool) {
|
||||||
let subpath: &Subpath<PointId> = subpath.borrow();
|
let subpath: &Subpath<PointId> = subpath.borrow();
|
||||||
@@ -140,28 +129,19 @@ impl<Upstream> Vector<Upstream> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Construct some new vector path from a single subpath with an identity transform and black fill.
|
/// Construct some new vector path from a single subpath with an identity transform and black fill.
|
||||||
pub fn from_subpath(subpath: impl Borrow<Subpath<PointId>>) -> Self
|
pub fn from_subpath(subpath: impl Borrow<Subpath<PointId>>) -> Self {
|
||||||
where
|
|
||||||
Upstream: Default + 'static,
|
|
||||||
{
|
|
||||||
Self::from_subpaths([subpath], false)
|
Self::from_subpaths([subpath], false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct some new vector path from a single [`BezPath`] with an identity transform and black fill.
|
/// Construct some new vector path from a single [`BezPath`] with an identity transform and black fill.
|
||||||
pub fn from_bezpath(bezpath: BezPath) -> Self
|
pub fn from_bezpath(bezpath: BezPath) -> Self {
|
||||||
where
|
|
||||||
Upstream: Default + 'static,
|
|
||||||
{
|
|
||||||
let mut vector = Self::default();
|
let mut vector = Self::default();
|
||||||
vector.append_bezpath(bezpath);
|
vector.append_bezpath(bezpath);
|
||||||
vector
|
vector
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct some new vector path from subpaths with an identity transform and black fill.
|
/// Construct some new vector path from subpaths with an identity transform and black fill.
|
||||||
pub fn from_subpaths(subpaths: impl IntoIterator<Item = impl Borrow<Subpath<PointId>>>, preserve_id: bool) -> Self
|
pub fn from_subpaths(subpaths: impl IntoIterator<Item = impl Borrow<Subpath<PointId>>>, preserve_id: bool) -> Self {
|
||||||
where
|
|
||||||
Upstream: Default + 'static,
|
|
||||||
{
|
|
||||||
let mut vector = Self::default();
|
let mut vector = Self::default();
|
||||||
|
|
||||||
for subpath in subpaths.into_iter() {
|
for subpath in subpaths.into_iter() {
|
||||||
@@ -171,10 +151,7 @@ impl<Upstream> Vector<Upstream> {
|
|||||||
vector
|
vector
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_target_types(target_types: impl IntoIterator<Item = impl Borrow<ClickTargetType>>, preserve_id: bool) -> Self
|
pub fn from_target_types(target_types: impl IntoIterator<Item = impl Borrow<ClickTargetType>>, preserve_id: bool) -> Self {
|
||||||
where
|
|
||||||
Upstream: Default + 'static,
|
|
||||||
{
|
|
||||||
let mut vector = Self::default();
|
let mut vector = Self::default();
|
||||||
|
|
||||||
for target_type in target_types.into_iter() {
|
for target_type in target_types.into_iter() {
|
||||||
@@ -387,10 +364,7 @@ impl<Upstream> Vector<Upstream> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn other_colinear_handle(&self, handle: HandleId) -> Option<HandleId>
|
pub fn other_colinear_handle(&self, handle: HandleId) -> Option<HandleId> {
|
||||||
where
|
|
||||||
Upstream: 'static,
|
|
||||||
{
|
|
||||||
let pair = self.colinear_manipulators.iter().find(|pair| pair.contains(&handle))?;
|
let pair = self.colinear_manipulators.iter().find(|pair| pair.contains(&handle))?;
|
||||||
let other = pair.iter().copied().find(|&val| val != handle)?;
|
let other = pair.iter().copied().find(|&val| val != handle)?;
|
||||||
if handle.to_manipulator_point().get_anchor(self) == other.to_manipulator_point().get_anchor(self) {
|
if handle.to_manipulator_point().get_anchor(self) == other.to_manipulator_point().get_anchor(self) {
|
||||||
@@ -471,7 +445,7 @@ impl<Upstream> Vector<Upstream> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Upstream> BoundingBox for Vector<Upstream> {
|
impl BoundingBox for Vector {
|
||||||
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
|
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
|
||||||
if !include_stroke {
|
if !include_stroke {
|
||||||
// Just use the path bounds without stroke
|
// Just use the path bounds without stroke
|
||||||
@@ -496,7 +470,7 @@ impl<Upstream> BoundingBox for Vector<Upstream> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Upstream> RenderComplexity for Vector<Upstream> {
|
impl RenderComplexity for Vector {
|
||||||
fn render_complexity(&self) -> usize {
|
fn render_complexity(&self) -> usize {
|
||||||
self.segment_domain.ids().len()
|
self.segment_domain.ids().len()
|
||||||
}
|
}
|
||||||
@@ -526,7 +500,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn construct_closed_subpath() {
|
fn construct_closed_subpath() {
|
||||||
let circle = Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
|
let circle = Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
|
||||||
let vector: Vector<()> = Vector::from_subpath(&circle);
|
let vector: Vector = Vector::from_subpath(&circle);
|
||||||
assert_eq!(vector.point_domain.ids().len(), 4);
|
assert_eq!(vector.point_domain.ids().len(), 4);
|
||||||
let bezier_paths = vector.segment_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
let bezier_paths = vector.segment_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
||||||
assert_eq!(bezier_paths.len(), 4);
|
assert_eq!(bezier_paths.len(), 4);
|
||||||
@@ -540,7 +514,7 @@ mod tests {
|
|||||||
fn construct_open_subpath() {
|
fn construct_open_subpath() {
|
||||||
let bezier = PathSeg::Cubic(CubicBez::new(Point::ZERO, Point::new(-1., -1.), Point::new(1., 1.), Point::new(1., 0.)));
|
let bezier = PathSeg::Cubic(CubicBez::new(Point::ZERO, Point::new(-1., -1.), Point::new(1., 1.), Point::new(1., 0.)));
|
||||||
let subpath = Subpath::from_bezier(bezier);
|
let subpath = Subpath::from_bezier(bezier);
|
||||||
let vector: Vector<()> = Vector::from_subpath(&subpath);
|
let vector: Vector = Vector::from_subpath(&subpath);
|
||||||
assert_eq!(vector.point_domain.ids().len(), 2);
|
assert_eq!(vector.point_domain.ids().len(), 2);
|
||||||
let bezier_paths = vector.segment_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
let bezier_paths = vector.segment_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
||||||
assert_eq!(bezier_paths, vec![bezier]);
|
assert_eq!(bezier_paths, vec![bezier]);
|
||||||
@@ -555,7 +529,7 @@ mod tests {
|
|||||||
let curve = Subpath::from_bezier(curve);
|
let curve = Subpath::from_bezier(curve);
|
||||||
let circle = Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
|
let circle = Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
|
||||||
|
|
||||||
let vector: Vector<()> = Vector::from_subpaths([&curve, &circle], false);
|
let vector: Vector = Vector::from_subpaths([&curve, &circle], false);
|
||||||
assert_eq!(vector.point_domain.ids().len(), 6);
|
assert_eq!(vector.point_domain.ids().len(), 6);
|
||||||
|
|
||||||
let bezier_paths = vector.segment_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
let bezier_paths = vector.segment_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
||||||
|
|||||||
@@ -276,10 +276,7 @@ async fn brush(
|
|||||||
let has_erase_or_restore_strokes = strokes.iter().any(|s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore));
|
let has_erase_or_restore_strokes = strokes.iter().any(|s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore));
|
||||||
if has_erase_or_restore_strokes {
|
if has_erase_or_restore_strokes {
|
||||||
let opaque_image = Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE);
|
let opaque_image = Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE);
|
||||||
let mut erase_restore_mask = TableRow::new_from_element(Raster::new_cpu(opaque_image))
|
let mut erase_restore_mask = TableRow::new_from_element(Raster::new_cpu(opaque_image)).with_attribute("transform", background_bounds);
|
||||||
.with_attribute("transform", background_bounds)
|
|
||||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
|
||||||
.with_attribute("source_node_id", None::<NodeId>);
|
|
||||||
|
|
||||||
for stroke in strokes {
|
for stroke in strokes {
|
||||||
let mut brush_texture = cache.get_cached_brush(&stroke.style);
|
let mut brush_texture = cache.get_cached_brush(&stroke.style);
|
||||||
@@ -313,12 +310,12 @@ async fn brush(
|
|||||||
|
|
||||||
let transform: DAffine2 = actual_image.attribute_cloned_or_default("transform");
|
let transform: DAffine2 = actual_image.attribute_cloned_or_default("transform");
|
||||||
let alpha_blending: AlphaBlending = actual_image.attribute_cloned_or_default("alpha_blending");
|
let alpha_blending: AlphaBlending = actual_image.attribute_cloned_or_default("alpha_blending");
|
||||||
let source_node_id: Option<NodeId> = actual_image.attribute_cloned_or_default("source_node_id");
|
let layer: Option<NodeId> = actual_image.attribute_cloned_or_default("editor:layer");
|
||||||
|
|
||||||
*image.element_mut(0).unwrap() = actual_image.into_element();
|
*image.element_mut(0).unwrap() = actual_image.into_element();
|
||||||
image.set_attribute("transform", 0, transform);
|
image.set_attribute("transform", 0, transform);
|
||||||
image.set_attribute("alpha_blending", 0, alpha_blending);
|
image.set_attribute("alpha_blending", 0, alpha_blending);
|
||||||
image.set_attribute("source_node_id", 0, source_node_id);
|
image.set_attribute("editor:layer", 0, layer);
|
||||||
|
|
||||||
image
|
image
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,10 +64,8 @@ impl BrushCacheImpl {
|
|||||||
background = std::mem::take(&mut self.blended_image);
|
background = std::mem::take(&mut self.blended_image);
|
||||||
|
|
||||||
// Check if the first non-blended stroke is an extension of the last one.
|
// Check if the first non-blended stroke is an extension of the last one.
|
||||||
let mut first_stroke_texture = TableRow::new_from_element(Raster::<CPU>::default())
|
// Transform is set to ZERO (not the default IDENTITY) as a sentinel to mark this row as uninitialized.
|
||||||
.with_attribute("transform", glam::DAffine2::ZERO)
|
let mut first_stroke_texture = TableRow::new_from_element(Raster::<CPU>::default()).with_attribute("transform", glam::DAffine2::ZERO);
|
||||||
.with_attribute("alpha_blending", core_types::AlphaBlending::default())
|
|
||||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>);
|
|
||||||
let mut first_stroke_point_skip = 0;
|
let mut first_stroke_point_skip = 0;
|
||||||
let strokes = input[num_blended_strokes..].to_vec();
|
let strokes = input[num_blended_strokes..].to_vec();
|
||||||
if !strokes.is_empty() && self.prev_input.len() > num_blended_strokes {
|
if !strokes.is_empty() && self.prev_input.len() > num_blended_strokes {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ async fn context_modification<T>(
|
|||||||
Context -> Footprint,
|
Context -> Footprint,
|
||||||
Context -> DVec2,
|
Context -> DVec2,
|
||||||
Context -> Vec<DVec2>,
|
Context -> Vec<DVec2>,
|
||||||
|
Context -> Option<NodeId>,
|
||||||
Context -> Vec<NodeId>,
|
Context -> Vec<NodeId>,
|
||||||
Context -> Vec<f64>,
|
Context -> Vec<f64>,
|
||||||
Context -> Vec<f32>,
|
Context -> Vec<f32>,
|
||||||
|
|||||||
@@ -177,33 +177,53 @@ where
|
|||||||
result_table
|
result_table
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs internal editor record-keeping that enables tools to target this network's layer.
|
/// Returns the NodeId of the user-facing parent layer node that encapsulates this sub-network.
|
||||||
/// This node associates the ID of the network's parent layer to every element of output data.
|
/// Used as the value source for stamping the `editor:layer` attribute on each row of a layer's output,
|
||||||
/// This technical detail may be ignored by users, and will be phased out in the future.
|
/// which lets editor tools (e.g. selection, click target routing) trace data back to its owning layer.
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
pub async fn source_node_id<T: 'n + Send + Clone>(
|
pub fn parent_layer(_: impl Ctx, node_path: Vec<NodeId>) -> Option<NodeId> {
|
||||||
_: impl Ctx,
|
|
||||||
#[implementations(
|
|
||||||
Table<Artboard>,
|
|
||||||
Table<Graphic>,
|
|
||||||
Table<Vector>,
|
|
||||||
Table<Raster<CPU>>,
|
|
||||||
Table<Raster<GPU>>,
|
|
||||||
Table<Color>,
|
|
||||||
Table<GradientStops>,
|
|
||||||
)]
|
|
||||||
content: Table<T>,
|
|
||||||
node_path: Vec<NodeId>,
|
|
||||||
) -> Table<T> {
|
|
||||||
// Get the penultimate element of the node path, or None if the path is too short
|
// Get the penultimate element of the node path, or None if the path is too short
|
||||||
// This is used to get the ID of the user-facing parent layer node (whose network contains this internal node).
|
node_path.get(node_path.len().wrapping_sub(2)).copied()
|
||||||
let source_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
|
}
|
||||||
|
|
||||||
let mut content = content;
|
/// Writes a per-row attribute column on the input table. The value-producing input is evaluated once per row,
|
||||||
for source_id in content.iter_attribute_values_mut_or_default::<Option<NodeId>>("source_node_id") {
|
/// with the row's element index and the row itself (as a single-row table vararg) passed via context, so the
|
||||||
*source_id = source_node_id;
|
/// upstream pipeline can return a different value per row that may be derived from the row's own data.
|
||||||
|
/// If the column already exists, its values are replaced; if not, the column is created.
|
||||||
|
#[node_macro::node(category("General"))]
|
||||||
|
async fn write_attribute<T: AnyHash + Clone + Send + Sync + core_types::CacheHash, U: Clone + Send + Sync + Default + std::fmt::Debug + 'static>(
|
||||||
|
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||||
|
/// The table whose rows will gain or replace the named attribute column.
|
||||||
|
#[implementations(
|
||||||
|
Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>, Table<Artboard>,
|
||||||
|
Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>, Table<Graphic>,
|
||||||
|
Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>, Table<Vector>,
|
||||||
|
Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>, Table<Raster<CPU>>,
|
||||||
|
Table<Raster<GPU>>, Table<Raster<GPU>>, Table<Raster<GPU>>, Table<Raster<GPU>>, Table<Raster<GPU>>, Table<Raster<GPU>>, Table<Raster<GPU>>,
|
||||||
|
Table<Color>, Table<Color>, Table<Color>, Table<Color>, Table<Color>, Table<Color>, Table<Color>,
|
||||||
|
Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>, Table<GradientStops>,
|
||||||
|
)]
|
||||||
|
mut content: Table<T>,
|
||||||
|
/// The attribute name (column key) to write or replace.
|
||||||
|
name: String,
|
||||||
|
/// The node that produces the per-row value. Called once per row with the row index in context.
|
||||||
|
#[implementations(
|
||||||
|
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>,
|
||||||
|
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>,
|
||||||
|
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>,
|
||||||
|
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>,
|
||||||
|
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>,
|
||||||
|
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>,
|
||||||
|
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>,
|
||||||
|
)]
|
||||||
|
value: impl Node<'n, Context<'static>, Output = U>,
|
||||||
|
) -> Table<T> {
|
||||||
|
for index in 0..content.len() {
|
||||||
|
let row = content.clone_row(index).expect("index is within bounds");
|
||||||
|
let owned_ctx = OwnedContextImpl::from(ctx.clone()).with_vararg(Box::new(Table::new_from_row(row))).with_index(index);
|
||||||
|
let v = value.eval(owned_ctx.into_context()).await;
|
||||||
|
content.set_attribute(&name, index, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,11 +259,11 @@ pub async fn legacy_layer_extend<T: 'n + Send + Clone>(
|
|||||||
) -> Table<T> {
|
) -> Table<T> {
|
||||||
// Get the penultimate element of the node path, or None if the path is too short
|
// Get the penultimate element of the node path, or None if the path is too short
|
||||||
// This is used to get the ID of the user-facing parent layer-style node (which encapsulates this internal node).
|
// This is used to get the ID of the user-facing parent layer-style node (which encapsulates this internal node).
|
||||||
let source_node_id = nested_node_path.get(nested_node_path.len().wrapping_sub(2)).copied();
|
let layer = nested_node_path.get(nested_node_path.len().wrapping_sub(2)).copied();
|
||||||
|
|
||||||
let mut base = base;
|
let mut base = base;
|
||||||
for mut row in new.into_iter() {
|
for mut row in new.into_iter() {
|
||||||
row.set_attribute("source_node_id", source_node_id);
|
row.set_attribute("editor:layer", layer);
|
||||||
base.push(row);
|
base.push(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ pub use graphene_canvas_utils as canvas_utils;
|
|||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
use graphic_types::Graphic;
|
use graphic_types::Graphic;
|
||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
|
use graphic_types::IntoGraphicTable;
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
use graphic_types::Vector;
|
use graphic_types::Vector;
|
||||||
use graphic_types::raster_types::Image;
|
use graphic_types::raster_types::Image;
|
||||||
use graphic_types::raster_types::{CPU, Raster};
|
use graphic_types::raster_types::{CPU, Raster};
|
||||||
@@ -170,7 +172,7 @@ async fn create_canvas(_: impl Ctx) -> CanvasHandle {
|
|||||||
/// Renders a view of the input graphic within an area defined by the *Footprint*.
|
/// Renders a view of the input graphic within an area defined by the *Footprint*.
|
||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn rasterize<T: WasmNotSend + 'n>(
|
async fn rasterize<T: WasmNotSend + Clone + 'n>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
Table<Vector>,
|
Table<Vector>,
|
||||||
@@ -184,7 +186,7 @@ async fn rasterize<T: WasmNotSend + 'n>(
|
|||||||
mut canvas: CanvasHandle,
|
mut canvas: CanvasHandle,
|
||||||
) -> Table<Raster<CPU>>
|
) -> Table<Raster<CPU>>
|
||||||
where
|
where
|
||||||
Table<T>: Render,
|
Table<T>: Render + Clone + graphic_types::IntoGraphicTable,
|
||||||
{
|
{
|
||||||
use core_types::table::TableRow;
|
use core_types::table::TableRow;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
@@ -194,6 +196,10 @@ where
|
|||||||
return Table::new();
|
return Table::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Snapshot the input as a Table<Graphic> so the renderer can recurse into the original child layers
|
||||||
|
// when collecting metadata, exposing their click targets to editor tools (same mechanism as Boolean Operation).
|
||||||
|
let upstream_graphic_table = data.clone().into_graphic_table();
|
||||||
|
|
||||||
let mut render = SvgRender::new();
|
let mut render = SvgRender::new();
|
||||||
let aabb = Bbox::from_transform(footprint.transform).to_axis_aligned_bbox();
|
let aabb = Bbox::from_transform(footprint.transform).to_axis_aligned_bbox();
|
||||||
let size = aabb.size();
|
let size = aabb.size();
|
||||||
@@ -229,5 +235,9 @@ where
|
|||||||
let rasterized = context.get_image_data(0., 0., resolution.x as f64, resolution.y as f64).unwrap();
|
let rasterized = context.get_image_data(0., 0., resolution.x as f64, resolution.y as f64).unwrap();
|
||||||
|
|
||||||
let image = Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32);
|
let image = Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32);
|
||||||
Table::new_from_row(TableRow::new_from_element(Raster::new_cpu(image)).with_attribute("transform", footprint.transform))
|
Table::new_from_row(
|
||||||
|
TableRow::new_from_element(Raster::new_cpu(image))
|
||||||
|
.with_attribute("transform", footprint.transform)
|
||||||
|
.with_attribute("editor:merged_layers", upstream_graphic_table),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,10 @@ async fn boolean_operation<I: graphic_types::IntoGraphicTable + 'n + Send + Clon
|
|||||||
let result_vector = result_vector_table.element_mut(0).unwrap();
|
let result_vector = result_vector_table.element_mut(0).unwrap();
|
||||||
Vector::transform(result_vector, transform);
|
Vector::transform(result_vector, transform);
|
||||||
result_vector.style.set_stroke_transform(DAffine2::IDENTITY);
|
result_vector.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||||
result_vector.upstream_data = Some(content.clone());
|
|
||||||
|
// Snapshot the input layers as the `editor:merged_layers` row attribute so the renderer can recurse into them
|
||||||
|
// for editor click-target preservation.
|
||||||
|
result_vector_table.set_attribute("editor:merged_layers", 0, content.clone());
|
||||||
|
|
||||||
// Clean up the boolean operation result by merging duplicated points
|
// Clean up the boolean operation result by merging duplicated points
|
||||||
let merge_transform: DAffine2 = result_vector_table.attribute_cloned_or_default("transform", 0);
|
let merge_transform: DAffine2 = result_vector_table.attribute_cloned_or_default("transform", 0);
|
||||||
@@ -127,7 +130,6 @@ fn boolean_operation_on_vector_table(vector: &Table<Vector>, boolean_operation:
|
|||||||
let copy_from = vector.element(index).unwrap();
|
let copy_from = vector.element(index).unwrap();
|
||||||
let element = Vector {
|
let element = Vector {
|
||||||
style: copy_from.style.clone(),
|
style: copy_from.style.clone(),
|
||||||
upstream_data: copy_from.upstream_data.clone(),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
TableRow::from_parts(element, attributes)
|
TableRow::from_parts(element, attributes)
|
||||||
@@ -177,7 +179,7 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
|||||||
}
|
}
|
||||||
Graphic::RasterCPU(image) => {
|
Graphic::RasterCPU(image) => {
|
||||||
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
|
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
|
||||||
let make_row = |transform, source_node_id, alpha_blending| {
|
let make_row = |transform, layer, alpha_blending| {
|
||||||
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
||||||
subpath.apply_transform(transform);
|
subpath.apply_transform(transform);
|
||||||
|
|
||||||
@@ -186,24 +188,24 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
|||||||
|
|
||||||
TableRow::new_from_element(element)
|
TableRow::new_from_element(element)
|
||||||
.with_attribute("alpha_blending", alpha_blending)
|
.with_attribute("alpha_blending", alpha_blending)
|
||||||
.with_attribute("source_node_id", source_node_id)
|
.with_attribute("editor:layer", layer)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply the parent graphic's transform to each raster element, preserving each row's source_node_id
|
// Apply the parent graphic's transform to each raster element, preserving each row's layer
|
||||||
// and alpha_blending so the boolean op downstream can route clicks (and inherit blending state)
|
// and alpha_blending so the boolean op downstream can route clicks (and inherit blending state)
|
||||||
// back to the originating raster layer
|
// back to the originating raster layer
|
||||||
(0..image.len())
|
(0..image.len())
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let row_transform: DAffine2 = image.attribute_cloned_or_default("transform", i);
|
let row_transform: DAffine2 = image.attribute_cloned_or_default("transform", i);
|
||||||
let source_node_id: Option<NodeId> = image.attribute_cloned_or_default("source_node_id", i);
|
let layer: Option<NodeId> = image.attribute_cloned_or_default("editor:layer", i);
|
||||||
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default("alpha_blending", i);
|
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default("alpha_blending", i);
|
||||||
make_row(parent_transform * row_transform, source_node_id, alpha_blending)
|
make_row(parent_transform * row_transform, layer, alpha_blending)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
Graphic::RasterGPU(image) => {
|
Graphic::RasterGPU(image) => {
|
||||||
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
|
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
|
||||||
let make_row = |transform, source_node_id, alpha_blending| {
|
let make_row = |transform, layer, alpha_blending| {
|
||||||
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
||||||
subpath.apply_transform(transform);
|
subpath.apply_transform(transform);
|
||||||
|
|
||||||
@@ -212,18 +214,18 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
|||||||
|
|
||||||
TableRow::new_from_element(element)
|
TableRow::new_from_element(element)
|
||||||
.with_attribute("alpha_blending", alpha_blending)
|
.with_attribute("alpha_blending", alpha_blending)
|
||||||
.with_attribute("source_node_id", source_node_id)
|
.with_attribute("editor:layer", layer)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply the parent graphic's transform to each raster element, preserving each row's source_node_id
|
// Apply the parent graphic's transform to each raster element, preserving each row's layer
|
||||||
// and alpha_blending so the boolean op downstream can route clicks (and inherit blending state)
|
// and alpha_blending so the boolean op downstream can route clicks (and inherit blending state)
|
||||||
// back to the originating raster layer
|
// back to the originating raster layer
|
||||||
(0..image.len())
|
(0..image.len())
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let row_transform: DAffine2 = image.attribute_cloned_or_default("transform", i);
|
let row_transform: DAffine2 = image.attribute_cloned_or_default("transform", i);
|
||||||
let source_node_id: Option<NodeId> = image.attribute_cloned_or_default("source_node_id", i);
|
let layer: Option<NodeId> = image.attribute_cloned_or_default("editor:layer", i);
|
||||||
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default("alpha_blending", i);
|
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default("alpha_blending", i);
|
||||||
make_row(parent_transform * row_transform, source_node_id, alpha_blending)
|
make_row(parent_transform * row_transform, layer, alpha_blending)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -335,6 +335,8 @@ pub fn noise_pattern(
|
|||||||
return Table::new();
|
return Table::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let transform = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
|
||||||
|
|
||||||
let footprint_scale = footprint.scale();
|
let footprint_scale = footprint.scale();
|
||||||
let width = (size.x * footprint_scale.x) as u32;
|
let width = (size.x * footprint_scale.x) as u32;
|
||||||
let height = (size.y * footprint_scale.y) as u32;
|
let height = (size.y * footprint_scale.y) as u32;
|
||||||
@@ -376,12 +378,7 @@ pub fn noise_pattern(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Table::new_from_row(
|
return Table::new_from_row(TableRow::new_from_element(Raster::new_cpu(image)).with_attribute("transform", transform));
|
||||||
TableRow::new_from_element(Raster::new_cpu(image))
|
|
||||||
.with_attribute("transform", DAffine2::from_translation(offset) * DAffine2::from_scale(size))
|
|
||||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
|
||||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
noise.set_noise_type(Some(noise_type));
|
noise.set_noise_type(Some(noise_type));
|
||||||
@@ -439,12 +436,7 @@ pub fn noise_pattern(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Table::new_from_row(
|
Table::new_from_row(TableRow::new_from_element(Raster::new_cpu(image)).with_attribute("transform", transform))
|
||||||
TableRow::new_from_element(Raster::new_cpu(image))
|
|
||||||
.with_attribute("transform", DAffine2::from_translation(offset) * DAffine2::from_scale(size))
|
|
||||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
|
||||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Raster: Pattern"))]
|
#[node_macro::node(category("Raster: Pattern"))]
|
||||||
@@ -489,9 +481,7 @@ pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> Table<Raster<CPU>> {
|
|||||||
data,
|
data,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}))
|
}))
|
||||||
.with_attribute("transform", DAffine2::from_translation(offset) * DAffine2::from_scale(size))
|
.with_attribute("transform", DAffine2::from_translation(offset) * DAffine2::from_scale(size)),
|
||||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
|
||||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use core_types::AlphaBlending;
|
|
||||||
use core_types::table::{Table, TableRow};
|
use core_types::table::{Table, TableRow};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use parley::GlyphRun;
|
use parley::GlyphRun;
|
||||||
@@ -10,16 +9,16 @@ use skrifa::{MetadataProvider, OutlineGlyph};
|
|||||||
use vector_types::subpath::{ManipulatorGroup, Subpath};
|
use vector_types::subpath::{ManipulatorGroup, Subpath};
|
||||||
use vector_types::vector::{PointId, Vector};
|
use vector_types::vector::{PointId, Vector};
|
||||||
|
|
||||||
pub struct PathBuilder<Upstream> {
|
pub struct PathBuilder {
|
||||||
current_subpath: Subpath<PointId>,
|
current_subpath: Subpath<PointId>,
|
||||||
origin: DVec2,
|
origin: DVec2,
|
||||||
glyph_subpaths: Vec<Subpath<PointId>>,
|
glyph_subpaths: Vec<Subpath<PointId>>,
|
||||||
pub vector_table: Table<Vector<Upstream>>,
|
pub vector_table: Table<Vector>,
|
||||||
scale: f64,
|
scale: f64,
|
||||||
id: PointId,
|
id: PointId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Upstream: Default + 'static> PathBuilder<Upstream> {
|
impl PathBuilder {
|
||||||
pub fn new(per_glyph_instances: bool, scale: f64) -> Self {
|
pub fn new(per_glyph_instances: bool, scale: f64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
current_subpath: Subpath::new(Vec::new(), false),
|
current_subpath: Subpath::new(Vec::new(), false),
|
||||||
@@ -52,12 +51,8 @@ impl<Upstream: Default + 'static> PathBuilder<Upstream> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if per_glyph_instances {
|
if per_glyph_instances {
|
||||||
self.vector_table.push(
|
self.vector_table
|
||||||
TableRow::new_from_element(Vector::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false))
|
.push(TableRow::new_from_element(Vector::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false)).with_attribute("transform", DAffine2::from_translation(glyph_offset)));
|
||||||
.with_attribute("transform", DAffine2::from_translation(glyph_offset))
|
|
||||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
|
||||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
for subpath in self.glyph_subpaths.drain(..) {
|
for subpath in self.glyph_subpaths.drain(..) {
|
||||||
// Unwrapping here is ok because `self.vector_table` is initialized with a single `Vector` table element
|
// Unwrapping here is ok because `self.vector_table` is initialized with a single `Vector` table element
|
||||||
@@ -120,7 +115,7 @@ impl<Upstream: Default + 'static> PathBuilder<Upstream> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finalize(mut self) -> Table<Vector<Upstream>> {
|
pub fn finalize(mut self) -> Table<Vector> {
|
||||||
if self.vector_table.is_empty() {
|
if self.vector_table.is_empty() {
|
||||||
self.vector_table = Table::new_from_element(Vector::default());
|
self.vector_table = Table::new_from_element(Vector::default());
|
||||||
}
|
}
|
||||||
@@ -128,7 +123,7 @@ impl<Upstream: Default + 'static> PathBuilder<Upstream> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Upstream: Default + 'static> OutlinePen for PathBuilder<Upstream> {
|
impl OutlinePen for PathBuilder {
|
||||||
fn move_to(&mut self, x: f32, y: f32) {
|
fn move_to(&mut self, x: f32, y: f32) {
|
||||||
if !self.current_subpath.is_empty() {
|
if !self.current_subpath.is_empty() {
|
||||||
self.glyph_subpaths.push(std::mem::replace(&mut self.current_subpath, Subpath::new(Vec::new(), false)));
|
self.glyph_subpaths.push(std::mem::replace(&mut self.current_subpath, Subpath::new(Vec::new(), false)));
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ impl TextContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert text to vector paths using the specified font and typesetting configuration
|
/// Convert text to vector paths using the specified font and typesetting configuration
|
||||||
pub fn to_path<Upstream: Default + 'static>(&mut self, text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<Vector<Upstream>> {
|
pub fn to_path(&mut self, text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<Vector> {
|
||||||
let Some(layout) = self.layout_text(text, font, font_cache, typesetting) else {
|
let Some(layout) = self.layout_text(text, font, font_cache, typesetting) else {
|
||||||
return Table::new_from_element(Vector::default());
|
return Table::new_from_element(Vector::default());
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use parley::fontique::Blob;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use vector_types::Vector;
|
use vector_types::Vector;
|
||||||
|
|
||||||
pub fn to_path<Upstream: Default + 'static>(text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<Vector<Upstream>> {
|
pub fn to_path(text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<Vector> {
|
||||||
TextContext::with_thread_local(|ctx| ctx.to_path(text, font, font_cache, typesetting, per_glyph_instances))
|
TextContext::with_thread_local(|ctx| ctx.to_path(text, font, font_cache, typesetting, per_glyph_instances))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ async fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Bo
|
|||||||
|
|
||||||
// Update the source node id
|
// Update the source node id
|
||||||
let this_node_path = node_path.iter().rev().nth(1).copied();
|
let this_node_path = node_path.iter().rev().nth(1).copied();
|
||||||
let existing: Option<NodeId> = vector.attribute_cloned_or_default("source_node_id", 0);
|
let existing: Option<NodeId> = vector.attribute_cloned_or_default("editor:layer", 0);
|
||||||
vector.set_attribute("source_node_id", 0, existing.or(this_node_path));
|
vector.set_attribute("editor:layer", 0, existing.or(this_node_path));
|
||||||
|
|
||||||
if vector.len() > 1 {
|
if vector.len() > 1 {
|
||||||
warn!("The path modify ran on {} vector rows. Only the first can be modified.", vector.len());
|
warn!("The path modify ran on {} vector rows. Only the first can be modified.", vector.len());
|
||||||
|
|||||||
@@ -354,8 +354,6 @@ async fn round_corners(
|
|||||||
let attributes = source.clone_row_attributes(index);
|
let attributes = source.clone_row_attributes(index);
|
||||||
let source = source.element(index).unwrap();
|
let source = source.element(index).unwrap();
|
||||||
|
|
||||||
let upstream_nested_layers = source.upstream_data.clone();
|
|
||||||
|
|
||||||
// Flip the roundness to help with user intuition
|
// Flip the roundness to help with user intuition
|
||||||
let roundness = 1. - roundness;
|
let roundness = 1. - roundness;
|
||||||
// Convert 0-100 to 0-0.5
|
// Convert 0-100 to 0-0.5
|
||||||
@@ -439,8 +437,6 @@ async fn round_corners(
|
|||||||
result.append_bezpath(rounded_subpath);
|
result.append_bezpath(rounded_subpath);
|
||||||
}
|
}
|
||||||
|
|
||||||
result.upstream_data = upstream_nested_layers;
|
|
||||||
|
|
||||||
TableRow::from_parts(result, attributes)
|
TableRow::from_parts(result, attributes)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
@@ -1300,7 +1296,7 @@ pub async fn flatten_path<T: IntoGraphicTable + 'n + Send>(_: impl Ctx, #[implem
|
|||||||
// Concatenate every vector element's subpaths into the single output compound path
|
// Concatenate every vector element's subpaths into the single output compound path
|
||||||
for index in 0..flattened.len() {
|
for index in 0..flattened.len() {
|
||||||
let Some(element) = flattened.element(index) else { continue };
|
let Some(element) = flattened.element(index) else { continue };
|
||||||
let node_id: Option<NodeId> = flattened.attribute_cloned_or_default("source_node_id", index);
|
let node_id: Option<NodeId> = flattened.attribute_cloned_or_default("editor:layer", index);
|
||||||
let node_id = node_id.map(|node_id| node_id.0).unwrap_or_default();
|
let node_id = node_id.map(|node_id| node_id.0).unwrap_or_default();
|
||||||
|
|
||||||
let mut hasher = DefaultHasher::new();
|
let mut hasher = DefaultHasher::new();
|
||||||
@@ -1317,13 +1313,13 @@ pub async fn flatten_path<T: IntoGraphicTable + 'n + Send>(_: impl Ctx, #[implem
|
|||||||
// Preserve a reference to the original upstream graphic table so the renderer can recurse into it
|
// Preserve a reference to the original upstream graphic table so the renderer can recurse into it
|
||||||
// when collecting metadata, exposing the original child layers' click targets to editor tools.
|
// when collecting metadata, exposing the original child layers' click targets to editor tools.
|
||||||
// This is the same mechanism Boolean Operation uses to keep its inputs editable after the merge.
|
// This is the same mechanism Boolean Operation uses to keep its inputs editable after the merge.
|
||||||
output.upstream_data = Some(graphic_table);
|
output_table.set_attribute("editor:merged_layers", 0, graphic_table);
|
||||||
|
|
||||||
// Adopt the last input row's source_node_id so the editor can also bucket clicks under a contributing child layer
|
// Adopt the last input row's layer so the editor can also bucket clicks under a contributing child layer
|
||||||
if !flattened.is_empty() {
|
if !flattened.is_empty() {
|
||||||
let primary = flattened.len() - 1;
|
let primary = flattened.len() - 1;
|
||||||
let source_node_id: Option<NodeId> = flattened.attribute_cloned_or_default("source_node_id", primary);
|
let layer: Option<NodeId> = flattened.attribute_cloned_or_default("editor:layer", primary);
|
||||||
output_table.set_attribute("source_node_id", 0, source_node_id);
|
output_table.set_attribute("editor:layer", 0, layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
output_table
|
output_table
|
||||||
@@ -1351,7 +1347,6 @@ async fn sample_polyline(
|
|||||||
region_domain: Default::default(),
|
region_domain: Default::default(),
|
||||||
colinear_manipulators: Default::default(),
|
colinear_manipulators: Default::default(),
|
||||||
style: std::mem::take(&mut row.element_mut().style),
|
style: std::mem::take(&mut row.element_mut().style),
|
||||||
upstream_data: std::mem::take(&mut row.element_mut().upstream_data),
|
|
||||||
};
|
};
|
||||||
// Transfer the stroke transform from the input vector content to the result.
|
// Transfer the stroke transform from the input vector content to the result.
|
||||||
result.style.set_stroke_transform(row.attribute_cloned_or_default("transform"));
|
result.style.set_stroke_transform(row.attribute_cloned_or_default("transform"));
|
||||||
@@ -1441,7 +1436,6 @@ async fn simplify(
|
|||||||
|
|
||||||
let mut result = Vector {
|
let mut result = Vector {
|
||||||
style: std::mem::take(&mut row.element_mut().style),
|
style: std::mem::take(&mut row.element_mut().style),
|
||||||
upstream_data: std::mem::take(&mut row.element_mut().upstream_data),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1538,7 +1532,6 @@ async fn decimate(
|
|||||||
|
|
||||||
let mut result = Vector {
|
let mut result = Vector {
|
||||||
style: std::mem::take(&mut row.element_mut().style),
|
style: std::mem::take(&mut row.element_mut().style),
|
||||||
upstream_data: std::mem::take(&mut row.element_mut().upstream_data),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2382,13 +2375,13 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
|
|||||||
trs * skew
|
trs * skew
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pre-compensate upstream_data transforms so that when collect_metadata applies
|
// Pre-compensate merged_layers transforms so that when collect_metadata applies
|
||||||
// the row transform (which will be group_transform * lerped_transform after the
|
// the row transform (which will be group_transform * lerped_transform after the
|
||||||
// pipeline's Transform node runs), the lerped_transform cancels out and children
|
// pipeline's Transform node runs), the lerped_transform cancels out and children
|
||||||
// get the correct footprint: parent * group_transform * child_transform.
|
// get the correct footprint: parent * group_transform * child_transform.
|
||||||
// Only pre-compensate if the lerped transform is invertible (non-zero determinant).
|
// Only pre-compensate if the lerped transform is invertible (non-zero determinant).
|
||||||
// A zero determinant can occur when interpolated scale passes through zero (e.g., flipped axes),
|
// A zero determinant can occur when interpolated scale passes through zero (e.g., flipped axes),
|
||||||
// in which case we skip pre-compensation to avoid propagating NaN through upstream_data transforms.
|
// in which case we skip pre-compensation to avoid propagating NaN through merged_layers transforms.
|
||||||
if lerped_transform.matrix2.determinant().abs() > f64::EPSILON {
|
if lerped_transform.matrix2.determinant().abs() > f64::EPSILON {
|
||||||
let lerped_inverse = lerped_transform.inverse();
|
let lerped_inverse = lerped_transform.inverse();
|
||||||
for transform in graphic_table_content.iter_attribute_values_mut_or_default::<DAffine2>("transform") {
|
for transform in graphic_table_content.iter_attribute_values_mut_or_default::<DAffine2>("transform") {
|
||||||
@@ -2404,21 +2397,15 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
|
|||||||
|
|
||||||
let mut attributes = content.clone_row_attributes(endpoint_index);
|
let mut attributes = content.clone_row_attributes(endpoint_index);
|
||||||
attributes.insert("transform", lerped_transform);
|
attributes.insert("transform", lerped_transform);
|
||||||
|
attributes.insert("editor:merged_layers", graphic_table_content);
|
||||||
|
|
||||||
return Table::new_from_row(TableRow::from_parts(
|
return Table::new_from_row(TableRow::from_parts(endpoint_element.clone(), attributes));
|
||||||
Vector {
|
|
||||||
upstream_data: Some(graphic_table_content),
|
|
||||||
..endpoint_element.clone()
|
|
||||||
},
|
|
||||||
attributes,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut vector = Vector {
|
let mut vector = Vector {
|
||||||
upstream_data: Some(graphic_table_content),
|
style: source_element.style.lerp(&target_element.style, time),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
vector.style = source_element.style.lerp(&target_element.style, time);
|
|
||||||
|
|
||||||
// Work directly with manipulator groups, bypassing the BezPath intermediate representation.
|
// Work directly with manipulator groups, bypassing the BezPath intermediate representation.
|
||||||
// This avoids the full Vector → BezPath → interpolate → BezPath → Vector roundtrip each frame.
|
// This avoids the full Vector → BezPath → interpolate → BezPath → Vector roundtrip each frame.
|
||||||
@@ -2566,13 +2553,14 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
|
|||||||
// The result is a synthesis of source and target, so adopt whichever endpoint the result is closer to as
|
// The result is a synthesis of source and target, so adopt whichever endpoint the result is closer to as
|
||||||
// the click-target identity (so the editor can route clicks back to one of the contributing layers)
|
// the click-target identity (so the editor can route clicks back to one of the contributing layers)
|
||||||
let primary_index = if time < 0.5 { source_index } else { target_index };
|
let primary_index = if time < 0.5 { source_index } else { target_index };
|
||||||
let source_node_id: Option<NodeId> = content.attribute_cloned_or_default("source_node_id", primary_index);
|
let layer: Option<NodeId> = content.attribute_cloned_or_default("editor:layer", primary_index);
|
||||||
|
|
||||||
Table::new_from_row(
|
Table::new_from_row(
|
||||||
TableRow::new_from_element(vector)
|
TableRow::new_from_element(vector)
|
||||||
.with_attribute("transform", lerped_transform)
|
.with_attribute("transform", lerped_transform)
|
||||||
.with_attribute("alpha_blending", vector_alpha_blending)
|
.with_attribute("alpha_blending", vector_alpha_blending)
|
||||||
.with_attribute("source_node_id", source_node_id),
|
.with_attribute("editor:layer", layer)
|
||||||
|
.with_attribute("editor:merged_layers", graphic_table_content),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3047,7 +3035,6 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: impl Node<
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use core_types::AlphaBlending;
|
|
||||||
use core_types::Node;
|
use core_types::Node;
|
||||||
use kurbo::{CubicBez, Ellipse, Point, Rect};
|
use kurbo::{CubicBez, Ellipse, Point, Rect};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
@@ -3073,10 +3060,7 @@ mod test {
|
|||||||
fn create_vector_row(bezpath: BezPath, transform: DAffine2) -> TableRow<Vector> {
|
fn create_vector_row(bezpath: BezPath, transform: DAffine2) -> TableRow<Vector> {
|
||||||
let mut row = Vector::default();
|
let mut row = Vector::default();
|
||||||
row.append_bezpath(bezpath);
|
row.append_bezpath(bezpath);
|
||||||
TableRow::new_from_element(row)
|
TableRow::new_from_element(row).with_attribute("transform", transform)
|
||||||
.with_attribute("transform", transform)
|
|
||||||
.with_attribute("alpha_blending", AlphaBlending::default())
|
|
||||||
.with_attribute("source_node_id", None::<core_types::uuid::NodeId>)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
Reference in New Issue
Block a user