Rename the "Table" type to "List" everywhere (#4133)

* Rename the "Table" type to "List" everywhere

* Fix a few missed ones

* Re-save demo artwork
This commit is contained in:
Keavon Chambers
2026-05-09 01:33:39 -07:00
committed by GitHub
parent 6b3e4757de
commit a28b9437aa
79 changed files with 1571 additions and 1591 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -8,9 +8,9 @@ use glam::{Affine2, DAffine2, Vec2};
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
use graphene_std::blending::BlendMode; use graphene_std::blending::BlendMode;
use graphene_std::gradient::GradientStops; use graphene_std::gradient::GradientStops;
use graphene_std::list::List;
use graphene_std::memo::IORecord; use graphene_std::memo::IORecord;
use graphene_std::raster_types::{CPU, GPU, Raster}; use graphene_std::raster_types::{CPU, GPU, Raster};
use graphene_std::table::Table;
use graphene_std::vector::Vector; use graphene_std::vector::Vector;
use graphene_std::vector::style::{Fill, FillChoice, GradientSpreadMethod, GradientType}; use graphene_std::vector::style::{Fill, FillChoice, GradientSpreadMethod, GradientType};
use graphene_std::{Artboard, Color, Context, Graphic}; use graphene_std::{Artboard, Color, Context, Graphic};
@@ -155,7 +155,7 @@ struct LayoutData<'a> {
desired_path: &'a mut Vec<PathStep>, desired_path: &'a mut Vec<PathStep>,
network_interface: &'a NodeNetworkInterface, network_interface: &'a NodeNetworkInterface,
/// The `network_path` to use when resolving a `NodeId` against the network interface. /// The `network_path` to use when resolving a `NodeId` against the network interface.
/// Defaults to root (`&[]`); `Table<NodeId>` rendering temporarily sets it to the path's prefix so nested /// Defaults to root (`&[]`); `List<NodeId>` rendering temporarily sets it to the path's prefix so nested
/// layers (e.g. inside a Ctrl+M-merged custom subgraph) resolve correctly. /// layers (e.g. inside a Ctrl+M-merged custom subgraph) resolve correctly.
node_lookup_network_path: Vec<NodeId>, node_lookup_network_path: Vec<NodeId>,
breadcrumbs: Vec<String>, breadcrumbs: Vec<String>,
@@ -175,27 +175,27 @@ macro_rules! generate_layout_downcast {
} }
// TODO: We simply try all these types sequentially. Find a better strategy. // TODO: We simply try all these types sequentially. Find a better strategy.
fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> { fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
// `Table<NodeId>` is interpreted as a path (e.g. the value produced by `path_of_subgraph`), shown as a // `List<NodeId>` is interpreted as a path (e.g. the value produced by `path_of_subgraph`), shown as a
// `Table` where each item's NodeId resolves against the prefix made up of the items above it. // `List` where each item's NodeId resolves against the prefix made up of the items above it.
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<NodeId>>>() { if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, List<NodeId>>>() {
return Some(table_node_id_path_layout_with_breadcrumb(&io.output, data)); return Some(table_node_id_path_layout_with_breadcrumb(&io.output, data));
} }
generate_layout_downcast!(introspected_data, data, [ generate_layout_downcast!(introspected_data, data, [
Table<Artboard>, List<Artboard>,
Table<Graphic>, List<Graphic>,
Table<Vector>, List<Vector>,
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Raster<GPU>>, List<Raster<GPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
Table<String>, List<String>,
Table<f64>, List<f64>,
Table<u8>, List<u8>,
Table<bool>, List<bool>,
Table<DAffine2>, List<DAffine2>,
Table<BlendMode>, List<BlendMode>,
Table<GradientType>, List<GradientType>,
Table<GradientSpreadMethod>, List<GradientSpreadMethod>,
GradientStops, GradientStops,
f64, f64,
u32, u32,
@@ -227,7 +227,7 @@ trait TableItemLayout {
data.breadcrumbs.push(self.identifier()); data.breadcrumbs.push(self.identifier());
self.value_page(data) self.value_page(data)
} }
/// Renders this value as a single inline widget inside an item of a Table. /// Renders this value as a single inline widget inside an item of a `List`.
/// `target` is the [`PathStep`] to push when the widget is clicked to drill into the value. /// `target` is the [`PathStep`] to push when the widget is clicked to drill into the value.
/// `data` provides shared context (notably `network_interface`) for types whose label or content /// `data` provides shared context (notably `network_interface`) for types whose label or content
/// depends on lookup beyond their own value (e.g. `NodeId` resolving a node's display name). /// depends on lookup beyond their own value (e.g. `NodeId` resolving a node's display name).
@@ -245,9 +245,9 @@ trait TableItemLayout {
} }
} }
impl<T: TableItemLayout> TableItemLayout for Table<T> { impl<T: TableItemLayout> TableItemLayout for List<T> {
fn type_name() -> &'static str { fn type_name() -> &'static str {
"Table" "List"
} }
fn identifier(&self) -> String { fn identifier(&self) -> String {
format!("{}[] ({} item{})", T::type_name(), self.len(), if self.len() == 1 { "" } else { "s" }) format!("{}[] ({} item{})", T::type_name(), self.len(), if self.len() == 1 { "" } else { "s" })
@@ -312,14 +312,14 @@ impl TableItemLayout for Artboard {
"Artboard" "Artboard"
} }
fn identifier(&self) -> String { fn identifier(&self) -> String {
self.as_graphic_table().identifier() self.as_graphic_list().identifier()
} }
// Don't put a breadcrumb for Artboard // Don't put a breadcrumb for Artboard
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> { fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
self.value_page(data) self.value_page(data)
} }
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> { fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
self.as_graphic_table().layout_with_breadcrumb(data) self.as_graphic_list().layout_with_breadcrumb(data)
} }
} }
@@ -329,12 +329,12 @@ impl TableItemLayout for Graphic {
} }
fn identifier(&self) -> String { fn identifier(&self) -> String {
match self { match self {
Self::Graphic(table) => table.identifier(), Self::Graphic(list) => list.identifier(),
Self::Vector(table) => table.identifier(), Self::Vector(list) => list.identifier(),
Self::RasterCPU(table) => table.identifier(), Self::RasterCPU(list) => list.identifier(),
Self::RasterGPU(table) => table.identifier(), Self::RasterGPU(list) => list.identifier(),
Self::Color(table) => table.identifier(), Self::Color(list) => list.identifier(),
Self::Gradient(table) => table.identifier(), Self::Gradient(list) => list.identifier(),
} }
} }
// Don't put a breadcrumb for Graphic // Don't put a breadcrumb for Graphic
@@ -343,12 +343,12 @@ impl TableItemLayout for Graphic {
} }
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> { fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
match self { match self {
Self::Graphic(table) => table.layout_with_breadcrumb(data), Self::Graphic(list) => list.layout_with_breadcrumb(data),
Self::Vector(table) => table.layout_with_breadcrumb(data), Self::Vector(list) => list.layout_with_breadcrumb(data),
Self::RasterCPU(table) => table.layout_with_breadcrumb(data), Self::RasterCPU(list) => list.layout_with_breadcrumb(data),
Self::RasterGPU(table) => table.layout_with_breadcrumb(data), Self::RasterGPU(list) => list.layout_with_breadcrumb(data),
Self::Color(table) => table.layout_with_breadcrumb(data), Self::Color(list) => list.layout_with_breadcrumb(data),
Self::Gradient(table) => table.layout_with_breadcrumb(data), Self::Gradient(list) => list.layout_with_breadcrumb(data),
} }
} }
} }
@@ -834,7 +834,7 @@ impl TableItemLayout for NodeId {
} }
// The value's label resolves the node's display name via the network interface so the button reads as the name shown // The value's label resolves the node's display name via the network interface so the button reads as the name shown
// in the Node Graph / Layers panels. The lookup uses `data.node_lookup_network_path` (set by the enclosing // in the Node Graph / Layers panels. The lookup uses `data.node_lookup_network_path` (set by the enclosing
// `Table<NodeId>` if rendering a path) so the resolution succeeds at any nesting depth. The button's icon // `List<NodeId>` if rendering a path) so the resolution succeeds at any nesting depth. The button's icon
// signals layer-vs-node kind. Falls back to "Node {id}" with no icon if the lookup misses. // signals layer-vs-node kind. Falls back to "Node {id}" with no icon if the lookup misses.
fn value_widget(&self, target: PathStep, data: &LayoutData) -> WidgetInstance { fn value_widget(&self, target: PathStep, data: &LayoutData) -> WidgetInstance {
let label = node_id_display_label(*self, data.network_interface, &data.node_lookup_network_path); let label = node_id_display_label(*self, data.network_interface, &data.node_lookup_network_path);
@@ -935,20 +935,20 @@ impl TableItemLayout for NodeId {
/// Invokes another macro with the full list of `TableItemLayout`-implementing types whose values may appear /// Invokes another macro with the full list of `TableItemLayout`-implementing types whose values may appear
/// as attribute values. Both the value-rendering and drilldown-navigation dispatchers iterate this list, /// as attribute values. Both the value-rendering and drilldown-navigation dispatchers iterate this list,
/// so adding a new attribute-displayable type is a single edit here. /// so adding a new attribute-displayable type is a single edit here.
macro_rules! known_table_row_types { macro_rules! known_item_types {
($apply:ident) => { ($apply:ident) => {
$apply!( $apply!(
Table<Artboard>, List<Artboard>,
Table<Graphic>, List<Graphic>,
Table<Vector>, List<Vector>,
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Raster<GPU>>, List<Raster<GPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
Table<String>, List<String>,
Table<NodeId>, List<NodeId>,
Table<f64>, List<f64>,
Table<u8>, List<u8>,
GradientStops, GradientStops,
Color, Color,
NodeId, NodeId,
@@ -983,7 +983,7 @@ fn display_value_override(any: &dyn Any) -> Option<String> {
None None
} }
/// Type-dispatched widget for displaying an attribute value in a `Table<T>` item. /// Type-dispatched widget for displaying an attribute value in a `List<T>` item.
/// Delegates to [`TableItemLayout::value_widget`] so the same widget code is shared between /// Delegates to [`TableItemLayout::value_widget`] so the same widget code is shared between
/// element-column rendering and attribute-column rendering. Returns `None` for unrecognized /// element-column rendering and attribute-column rendering. Returns `None` for unrecognized
/// types so the caller can fall back to a debug-formatted [`TextLabel`]. /// types so the caller can fall back to a debug-formatted [`TextLabel`].
@@ -997,16 +997,16 @@ fn dispatch_value_widget(any: &dyn Any, target: PathStep, data: &LayoutData) ->
)* )*
}; };
} }
known_table_row_types!(check); known_item_types!(check);
None None
} }
/// Renders a `Table<NodeId>` as a path: the standard table view, but each item's `NodeId` value is resolved /// Renders a `List<NodeId>` as a path: the standard table view, but each item's `NodeId` value is resolved
/// against the network path made up of all preceding items. So for a path `[outer, middle, leaf]`, item 0 /// against the network path made up of all preceding items. So for a path `[outer, middle, leaf]`, item 0
/// resolves at root, item 1 resolves at `[outer]`, and item 2 resolves at `[outer, middle]` — letting deeply /// resolves at root, item 1 resolves at `[outer]`, and item 2 resolves at `[outer, middle]` — letting deeply
/// nested layers display each step's correct name. Drilling into an item drops into that node's value page /// nested layers display each step's correct name. Drilling into an item drops into that node's value page
/// using the same prefix as `network_path`. /// using the same prefix as `network_path`.
fn table_node_id_path_layout_with_breadcrumb(path: &Table<NodeId>, data: &mut LayoutData) -> Vec<LayoutGroup> { fn table_node_id_path_layout_with_breadcrumb(path: &List<NodeId>, data: &mut LayoutData) -> Vec<LayoutGroup> {
data.breadcrumbs.push(path.identifier()); data.breadcrumbs.push(path.identifier());
if let Some(step) = data.desired_path.get(data.current_depth).cloned() { if let Some(step) = data.desired_path.get(data.current_depth).cloned() {
@@ -1044,9 +1044,9 @@ fn table_node_id_path_layout_with_breadcrumb(path: &Table<NodeId>, data: &mut La
/// Mirrors [`dispatch_value_widget`] but routes to [`TableItemLayout::layout_with_breadcrumb`]. /// Mirrors [`dispatch_value_widget`] but routes to [`TableItemLayout::layout_with_breadcrumb`].
/// Returns `None` for unrecognized types. /// Returns `None` for unrecognized types.
fn drilldown_attribute_layout(any: &dyn Any, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> { fn drilldown_attribute_layout(any: &dyn Any, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
// `Table<NodeId>` is interpreted as a path (e.g. the `editor:layer_path` attribute), so each item's NodeId value // `List<NodeId>` is interpreted as a path (e.g. the `editor:layer_path` attribute), so each item's NodeId value
// resolves against the prefix made up of preceding items. Handled before the generic `Table<T>` blanket impl. // resolves against the prefix made up of preceding items. Handled before the generic `List<T>` blanket impl.
if let Some(path) = any.downcast_ref::<Table<NodeId>>() { if let Some(path) = any.downcast_ref::<List<NodeId>>() {
return Some(table_node_id_path_layout_with_breadcrumb(path, data)); return Some(table_node_id_path_layout_with_breadcrumb(path, data));
} }
macro_rules! check { macro_rules! check {
@@ -1058,7 +1058,7 @@ fn drilldown_attribute_layout(any: &dyn Any, data: &mut LayoutData) -> Option<Ve
)* )*
}; };
} }
known_table_row_types!(check); known_item_types!(check);
None None
} }
@@ -2427,7 +2427,7 @@ impl DocumentMessageHandler {
}); });
if layer_to_move.parent(self.metadata()) != Some(parent) { if layer_to_move.parent(self.metadata()) != Some(parent) {
// TODO: Fix this so it works when dragging a layer into a group parent which has a Transform node, which used to work before #2689 caused this regression by removing the empty `Table<Vector>` item. // TODO: Fix this so it works when dragging a layer into a group parent which has a Transform node, which used to work before #2689 caused this regression by removing the empty `List<Vector>` item.
// TODO: See #2688 for this issue. // TODO: See #2688 for this issue.
let layer_local_transform = self.network_interface.document_metadata().transform_to_viewport(layer_to_move); let layer_local_transform = self.network_interface.document_metadata().transform_to_viewport(layer_to_move);
let undo_transform = self.network_interface.document_metadata().transform_to_viewport(parent).inverse(); let undo_transform = self.network_interface.document_metadata().transform_to_viewport(parent).inverse();
@@ -3345,7 +3345,7 @@ impl DocumentMessageHandler {
/// Create a network interface with a single export /// Create a network interface with a single export
fn default_document_network_interface() -> NodeNetworkInterface { fn default_document_network_interface() -> NodeNetworkInterface {
let mut network_interface = NodeNetworkInterface::default(); let mut network_interface = NodeNetworkInterface::default();
network_interface.add_export(TaggedValue::TypeDefault(descriptor!(graphene_std::table::Table<graphene_std::Artboard>)), -1, "", &[]); network_interface.add_export(TaggedValue::TypeDefault(descriptor!(graphene_std::list::List<graphene_std::Artboard>)), -1, "", &[]);
network_interface network_interface
} }
@@ -10,9 +10,9 @@ use crate::messages::tool::common_functionality::graph_modification_utils::get_c
use glam::{DAffine2, DVec2, IVec2}; use glam::{DAffine2, DVec2, IVec2};
use graph_craft::descriptor; use graph_craft::descriptor;
use graph_craft::document::{NodeId, NodeInput}; use graph_craft::document::{NodeId, NodeInput};
use graphene_std::list::List;
use graphene_std::renderer::Quad; use graphene_std::renderer::Quad;
use graphene_std::renderer::convert_usvg_path::convert_usvg_path; use graphene_std::renderer::convert_usvg_path::convert_usvg_path;
use graphene_std::table::Table;
use graphene_std::text::{Font, TypesettingConfig}; use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::style::{Fill, Gradient, GradientSpreadMethod, GradientStop, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin}; use graphene_std::vector::style::{Fill, Gradient, GradientSpreadMethod, GradientStop, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
use graphene_std::{Artboard, Color}; use graphene_std::{Artboard, Color};
@@ -170,7 +170,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
} }
// Set the bottom input of the artboard back to artboard // Set the bottom input of the artboard back to artboard
let bottom_input = NodeInput::type_default(descriptor!(Table<Artboard>), true); let bottom_input = NodeInput::type_default(descriptor!(List<Artboard>), true);
network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 0), bottom_input, &[]); network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 0), bottom_input, &[]);
} else { } else {
// We have some non layers (e.g. just a rectangle node). We disconnect the bottom input and connect it to the left input. // We have some non layers (e.g. just a rectangle node). We disconnect the bottom input and connect it to the left input.
@@ -178,7 +178,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 1), primary_input, &[]); network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 1), primary_input, &[]);
// Set the bottom input of the artboard back to artboard // Set the bottom input of the artboard back to artboard
let bottom_input = NodeInput::type_default(descriptor!(Table<Artboard>), true); let bottom_input = NodeInput::type_default(descriptor!(List<Artboard>), true);
network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 0), bottom_input, &[]); network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 0), bottom_input, &[]);
} }
} }
@@ -8,10 +8,10 @@ use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput}; use graph_craft::document::{NodeId, NodeInput};
use graph_craft::{ProtoNodeIdentifier, concrete, descriptor}; use graph_craft::{ProtoNodeIdentifier, concrete, descriptor};
use graphene_std::brush::brush_stroke::BrushStroke; use graphene_std::brush::brush_stroke::BrushStroke;
use graphene_std::list::List;
use graphene_std::raster::BlendMode; use graphene_std::raster::BlendMode;
use graphene_std::raster_types::Image; use graphene_std::raster_types::Image;
use graphene_std::subpath::Subpath; use graphene_std::subpath::Subpath;
use graphene_std::table::Table;
use graphene_std::text::{Font, TypesettingConfig}; use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::style::{Fill, GradientSpreadMethod, GradientType, Stroke}; use graphene_std::vector::style::{Fill, GradientSpreadMethod, GradientType, Stroke};
use graphene_std::vector::{GradientStops, PointId, Vector, VectorModification, VectorModificationType}; use graphene_std::vector::{GradientStops, PointId, Vector, VectorModification, VectorModificationType};
@@ -132,8 +132,8 @@ impl<'a> ModifyInputsContext<'a> {
/// Creates an artboard as the primary export for the document network. /// Creates an artboard as the primary export for the document network.
pub fn create_artboard(&mut self, new_id: NodeId, location: DVec2, dimensions: DVec2, background: Color, clip: bool) -> LayerNodeIdentifier { pub fn create_artboard(&mut self, new_id: NodeId, location: DVec2, dimensions: DVec2, background: Color, clip: bool) -> LayerNodeIdentifier {
let artboard_node_template = resolve_network_node_type("Artboard").expect("Node").node_template_input_override([ let artboard_node_template = resolve_network_node_type("Artboard").expect("Node").node_template_input_override([
Some(NodeInput::type_default(descriptor!(Table<Artboard>), true)), Some(NodeInput::type_default(descriptor!(List<Artboard>), true)),
Some(NodeInput::type_default(descriptor!(Table<Graphic>), true)), Some(NodeInput::type_default(descriptor!(List<Graphic>), true)),
Some(NodeInput::value(TaggedValue::DVec2(location), false)), Some(NodeInput::value(TaggedValue::DVec2(location), false)),
Some(NodeInput::value(TaggedValue::DVec2(dimensions), false)), Some(NodeInput::value(TaggedValue::DVec2(dimensions), false)),
Some(NodeInput::value(TaggedValue::Color(Some(background)), false)), Some(NodeInput::value(TaggedValue::Color(Some(background)), false)),
@@ -147,7 +147,7 @@ impl<'a> ModifyInputsContext<'a> {
let boolean = resolve_proto_node_type(graphene_std::path_bool_nodes::boolean_operation::IDENTIFIER) let boolean = resolve_proto_node_type(graphene_std::path_bool_nodes::boolean_operation::IDENTIFIER)
.expect("Boolean node does not exist") .expect("Boolean node does not exist")
.node_template_input_override([ .node_template_input_override([
Some(NodeInput::type_default(descriptor!(Table<Graphic>), true)), Some(NodeInput::type_default(descriptor!(List<Graphic>), true)),
Some(NodeInput::value(TaggedValue::BooleanOperation(operation), false)), Some(NodeInput::value(TaggedValue::BooleanOperation(operation), false)),
]); ]);
@@ -159,7 +159,7 @@ impl<'a> ModifyInputsContext<'a> {
pub fn insert_blend_data(&mut self, layer: LayerNodeIdentifier, count: f64) -> NodeId { pub fn insert_blend_data(&mut self, layer: LayerNodeIdentifier, count: f64) -> NodeId {
let blend = resolve_network_node_type("Blend") let blend = resolve_network_node_type("Blend")
.expect("Blend node does not exist") .expect("Blend node does not exist")
.node_template_input_override([Some(NodeInput::type_default(descriptor!(Table<Graphic>), true)), Some(NodeInput::value(TaggedValue::F64(count), false))]); .node_template_input_override([Some(NodeInput::type_default(descriptor!(List<Graphic>), true)), Some(NodeInput::value(TaggedValue::F64(count), false))]);
let blend_id = NodeId::new(); let blend_id = NodeId::new();
self.network_interface.insert_node(blend_id, blend, &[]); self.network_interface.insert_node(blend_id, blend, &[]);
@@ -171,7 +171,7 @@ impl<'a> ModifyInputsContext<'a> {
pub fn insert_morph_data(&mut self, layer: LayerNodeIdentifier) -> NodeId { pub fn insert_morph_data(&mut self, layer: LayerNodeIdentifier) -> NodeId {
let morph = resolve_proto_node_type(graphene_std::vector::morph::IDENTIFIER) let morph = resolve_proto_node_type(graphene_std::vector::morph::IDENTIFIER)
.expect("Morph node does not exist") .expect("Morph node does not exist")
.node_template_input_override([Some(NodeInput::type_default(descriptor!(Table<Graphic>), true)), Some(NodeInput::value(TaggedValue::F64(0.5), false))]); .node_template_input_override([Some(NodeInput::type_default(descriptor!(List<Graphic>), true)), Some(NodeInput::value(TaggedValue::F64(0.5), false))]);
let morph_id = NodeId::new(); let morph_id = NodeId::new();
self.network_interface.insert_node(morph_id, morph, &[]); self.network_interface.insert_node(morph_id, morph, &[]);
@@ -390,10 +390,10 @@ impl<'a> ModifyInputsContext<'a> {
}; };
// If inserting a 'Path' node, insert a 'Flatten Path' node if the type is `Graphic`. // If inserting a 'Path' node, insert a 'Flatten Path' node if the type is `Graphic`.
// TODO: Allow the 'Path' node to operate on `Table` data by utilizing the reference (index or ID?) for each item. // TODO: Allow the 'Path' node to operate on `List` data by utilizing the reference (index or ID?) for each item.
if node_definition.identifier == "Path" { if node_definition.identifier == "Path" {
let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]); let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]);
if layer_input_type.compiled_nested_type() == Some(&concrete!(Table<Graphic>)) { if layer_input_type.compiled_nested_type() == Some(&concrete!(List<Graphic>)) {
let Some(flatten_path_definition) = resolve_proto_node_type(graphene_std::vector_nodes::flatten_path::IDENTIFIER) else { let Some(flatten_path_definition) = resolve_proto_node_type(graphene_std::vector_nodes::flatten_path::IDENTIFIER) else {
log::error!("Flatten Path does not exist in ModifyInputsContext::existing_node_id"); log::error!("Flatten Path does not exist in ModifyInputsContext::existing_node_id");
return None; return None;
@@ -489,7 +489,7 @@ impl<'a> ModifyInputsContext<'a> {
); );
} }
/// Set the stops table on the 'Gradient Value' node, creating it if necessary. /// Set the GradientStops list on the 'Gradient Value' node, creating it if necessary.
pub fn gradient_stops_set(&mut self, stops: GradientStops) { pub fn gradient_stops_set(&mut self, stops: GradientStops) {
let Some(gradient_node_id) = self.existing_proto_node_id(graphene_std::math_nodes::gradient_value::IDENTIFIER, true) else { let Some(gradient_node_id) = self.existing_proto_node_id(graphene_std::math_nodes::gradient_value::IDENTIFIER, true) else {
return; return;
@@ -612,7 +612,7 @@ impl<'a> ModifyInputsContext<'a> {
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.join_miter_limit), false), false); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.join_miter_limit), false), false);
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::PaintOrderInput::INDEX); let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::PaintOrderInput::INDEX);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::PaintOrder(stroke.paint_order), false), false); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::PaintOrder(stroke.paint_order), false), false);
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashLengthsInput::<graphene_std::table::Table<f64>>::INDEX); let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashLengthsInput::<graphene_std::list::List<f64>>::INDEX);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64Array(stroke.dash_lengths), false), true); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64Array(stroke.dash_lengths), false), true);
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashOffsetInput::INDEX); let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashOffsetInput::INDEX);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.dash_offset), false), true); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.dash_offset), false), true);
@@ -17,9 +17,9 @@ use graph_craft::document::value::*;
use graph_craft::document::*; use graph_craft::document::*;
use graph_craft::{concrete, descriptor}; use graph_craft::{concrete, descriptor};
use graphene_std::extract_xy::XY; use graphene_std::extract_xy::XY;
use graphene_std::list::List;
use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha}; use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha};
use graphene_std::raster_types::{CPU, Raster}; use graphene_std::raster_types::{CPU, Raster};
use graphene_std::table::Table;
#[allow(unused_imports)] #[allow(unused_imports)]
use graphene_std::transform::Footprint; use graphene_std::transform::Footprint;
use graphene_std::vector::Vector; use graphene_std::vector::Vector;
@@ -205,7 +205,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
.collect(), .collect(),
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::type_default(descriptor!(Table<Graphic>), true), NodeInput::type_default(descriptor!(Table<Graphic>), true)], inputs: vec![NodeInput::type_default(descriptor!(List<Graphic>), true), NodeInput::type_default(descriptor!(List<Graphic>), true)],
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@@ -325,7 +325,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!(List<Artboard>))), 0),
NodeInput::node(NodeId(3), 0), NodeInput::node(NodeId(3), 0),
], ],
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
@@ -339,8 +339,8 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::type_default(descriptor!(Table<Artboard>), true), NodeInput::type_default(descriptor!(List<Artboard>), true),
NodeInput::type_default(descriptor!(Table<Graphic>), true), NodeInput::type_default(descriptor!(List<Graphic>), true),
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false), NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
NodeInput::value(TaggedValue::DVec2(DVec2::new(1920., 1080.)), false), NodeInput::value(TaggedValue::DVec2(DVec2::new(1920., 1080.)), false),
NodeInput::value(TaggedValue::Color(Some(Color::WHITE)), false), NodeInput::value(TaggedValue::Color(Some(Color::WHITE)), false),
@@ -573,11 +573,11 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::type_default(descriptor!(Table<Vector>), true), NodeInput::type_default(descriptor!(List<Vector>), true),
NodeInput::value(TaggedValue::F64(10.), false), NodeInput::value(TaggedValue::F64(10.), false),
NodeInput::value(TaggedValue::Bool(Default::default()), false), NodeInput::value(TaggedValue::Bool(Default::default()), false),
NodeInput::value(TaggedValue::InterpolationDistribution(Default::default()), false), NodeInput::value(TaggedValue::InterpolationDistribution(Default::default()), false),
NodeInput::type_default(descriptor!(Table<Vector>), false), NodeInput::type_default(descriptor!(List<Vector>), false),
], ],
..Default::default() ..Default::default()
}, },
@@ -824,7 +824,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
.collect(), .collect(),
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::type_default(descriptor!(Table<Vector>), true)], inputs: vec![NodeInput::type_default(descriptor!(List<Vector>), true)],
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@@ -1009,7 +1009,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::type_default(descriptor!(Table<Vector>), true), NodeInput::type_default(descriptor!(List<Vector>), true),
NodeInput::value( NodeInput::value(
TaggedValue::Footprint(Footprint { TaggedValue::Footprint(Footprint {
transform: DAffine2::from_scale_angle_translation(DVec2::new(1000., 1000.), 0., DVec2::new(0., 0.)), transform: DAffine2::from_scale_angle_translation(DVec2::new(1000., 1000.), 0., DVec2::new(0., 0.)),
@@ -1079,7 +1079,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::import(concrete!(Table<Raster<CPU>>), 0), NodeInput::import(concrete!(List<Raster<CPU>>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
@@ -1088,7 +1088,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
}, },
DocumentNode { DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::import(concrete!(Table<Raster<CPU>>), 0), NodeInput::import(concrete!(List<Raster<CPU>>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
@@ -1097,7 +1097,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
}, },
DocumentNode { DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::import(concrete!(Table<Raster<CPU>>), 0), NodeInput::import(concrete!(List<Raster<CPU>>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
@@ -1106,7 +1106,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
}, },
DocumentNode { DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::import(concrete!(Table<Raster<CPU>>), 0), NodeInput::import(concrete!(List<Raster<CPU>>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
@@ -1120,7 +1120,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
.collect(), .collect(),
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::type_default(descriptor!(Table<Raster<CPU>>), true)], inputs: vec![NodeInput::type_default(descriptor!(List<Raster<CPU>>), true)],
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@@ -1257,7 +1257,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::import(concrete!(Table<Raster<CPU>>), 0), NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::import(concrete!(List<Raster<CPU>>), 0), NodeInput::node(NodeId(0), 0)],
call_argument: generic!(T), call_argument: generic!(T),
implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::texture_conversion::upload_texture::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::texture_conversion::upload_texture::IDENTIFIER),
..Default::default() ..Default::default()
@@ -1275,7 +1275,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
.collect(), .collect(),
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::type_default(descriptor!(Table<Raster<CPU>>), true)], inputs: vec![NodeInput::type_default(descriptor!(List<Raster<CPU>>), true)],
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@@ -1346,11 +1346,11 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
exports: vec![ exports: vec![
// Primary output: the whole match (String) // Primary output: the whole match (String)
NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(1), 0),
// Secondary output: capture groups (Table<String>), each item carries `start`/`end`/`name` attributes from `regex_find` // Secondary output: capture groups (List<String>), each item carries `start`/`end`/`name` attributes from `regex_find`
NodeInput::node(NodeId(2), 0), NodeInput::node(NodeId(2), 0),
], ],
nodes: [ nodes: [
// Node 0: regex_find proto node — returns Table<String> of [whole_match, ...capture_groups] // Node 0: regex_find proto node — returns List<String> of [whole_match, ...capture_groups]
DocumentNode { DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::import(concrete!(String), 0), NodeInput::import(concrete!(String), 0),
@@ -1368,7 +1368,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
implementation: DocumentNodeImplementation::ProtoNode(graphic::extract_element::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(graphic::extract_element::IDENTIFIER),
..Default::default() ..Default::default()
}, },
// Node 2: omit_element at index 0, returns the capture group items as a Table<String>, preserving each item's start/end/name attributes // Node 2: omit_element at index 0, returns the capture group items as a List<String>, preserving each item's start/end/name attributes
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::value(TaggedValue::F64(0.), false)], inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::value(TaggedValue::F64(0.), false)],
implementation: DocumentNodeImplementation::ProtoNode(graphic::omit_element::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(graphic::omit_element::IDENTIFIER),
@@ -1453,7 +1453,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
exports: vec![NodeInput::node(NodeId(1), 0)], exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: vec![ nodes: vec![
DocumentNode { DocumentNode {
inputs: vec![NodeInput::import(concrete!(Table<Vector>), 0)], inputs: vec![NodeInput::import(concrete!(List<Vector>), 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,
@@ -1477,7 +1477,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::type_default(descriptor!(Table<Vector>), true), NodeInput::type_default(descriptor!(List<Vector>), true),
NodeInput::value(TaggedValue::VectorModification(Default::default()), false), NodeInput::value(TaggedValue::VectorModification(Default::default()), false),
], ],
..Default::default() ..Default::default()
@@ -19,12 +19,12 @@ use graphene_std::NodeInputDecleration;
use graphene_std::animation::RealTimeMode; use graphene_std::animation::RealTimeMode;
use graphene_std::brush::brush_stroke::BrushStroke; use graphene_std::brush::brush_stroke::BrushStroke;
use graphene_std::extract_xy::XY; use graphene_std::extract_xy::XY;
use graphene_std::list::List;
use graphene_std::raster::{ use graphene_std::raster::{
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute, BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
SelectiveColorChoice, SelectiveColorChoice,
}; };
use graphene_std::raster_types::Image; use graphene_std::raster_types::Image;
use graphene_std::table::Table;
use graphene_std::text::{Font, TextAlign}; use graphene_std::text::{Font, TextAlign};
use graphene_std::text_nodes::StringCapitalization; use graphene_std::text_nodes::StringCapitalization;
use graphene_std::transform::{Footprint, ReferencePoint, ScaleType, Transform}; use graphene_std::transform::{Footprint, ReferencePoint, ScaleType, Transform};
@@ -212,13 +212,13 @@ pub(crate) fn property_from_type(
Some(x) if x == TypeId::of::<String>() => text_widget(default_info).into(), Some(x) if x == TypeId::of::<String>() => text_widget(default_info).into(),
Some(x) if x == TypeId::of::<DVec2>() => vec2_widget(default_info, "X", "Y", "", None, false), Some(x) if x == TypeId::of::<DVec2>() => vec2_widget(default_info, "X", "Y", "", None, false),
Some(x) if x == TypeId::of::<DAffine2>() => transform_widget(default_info, &mut extra_widgets), Some(x) if x == TypeId::of::<DAffine2>() => transform_widget(default_info, &mut extra_widgets),
// =========== // ==========
// TABLE TYPES // LIST TYPES
// =========== // ==========
Some(x) if x == TypeId::of::<Table<f64>>() => array_of_number_widget(default_info, TextInput::default()).into(), Some(x) if x == TypeId::of::<List<f64>>() => array_of_number_widget(default_info, TextInput::default()).into(),
Some(x) if x == TypeId::of::<Table<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)), Some(x) if x == TypeId::of::<List<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)),
Some(x) if x == TypeId::of::<Table<GradientStops>>() => color_widget(default_info, ColorInput::default().allow_none(false)), Some(x) if x == TypeId::of::<List<GradientStops>>() => color_widget(default_info, ColorInput::default().allow_none(false)),
Some(x) if x == TypeId::of::<Table<BrushStroke>>() => brush_strokes_widget(default_info).into(), Some(x) if x == TypeId::of::<List<BrushStroke>>() => brush_strokes_widget(default_info).into(),
// ============ // ============
// STRUCT TYPES // STRUCT TYPES
// ============ // ============
@@ -2681,7 +2681,7 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
_ => &StrokeJoin::Miter, _ => &StrokeJoin::Miter,
}; };
let has_dash_lengths = match &document_node.inputs[DashLengthsInput::<Table<f64>>::INDEX].as_value() { let has_dash_lengths = match &document_node.inputs[DashLengthsInput::<List<f64>>::INDEX].as_value() {
Some(TaggedValue::F64Array(values)) => values.is_empty(), Some(TaggedValue::F64Array(values)) => values.is_empty(),
_ => true, _ => true,
}; };
@@ -2709,7 +2709,7 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
.property_row(); .property_row();
let disabled_number_input = NumberInput::default().unit(" px").disabled(has_dash_lengths); let disabled_number_input = NumberInput::default().unit(" px").disabled(has_dash_lengths);
let dash_lengths = array_of_number_widget( let dash_lengths = array_of_number_widget(
ParameterWidgetsInfo::new(node_id, DashLengthsInput::<Table<f64>>::INDEX, true, context), ParameterWidgetsInfo::new(node_id, DashLengthsInput::<List<f64>>::INDEX, true, context),
TextInput::default().centered(true), TextInput::default().centered(true),
); );
let number_input = disabled_number_input; let number_input = disabled_number_input;
@@ -1,8 +1,8 @@
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graphene_std::Type; use graphene_std::Type;
use graphene_std::list::List;
use graphene_std::raster_types::{CPU, Raster}; use graphene_std::raster_types::{CPU, Raster};
use graphene_std::table::Table;
use graphene_std::vector::Vector; use graphene_std::vector::Vector;
use graphene_std::{Artboard, Graphic}; use graphene_std::{Artboard, Graphic};
@@ -31,11 +31,11 @@ impl FrontendGraphDataType {
TaggedValue::String(_) => Self::Typography, TaggedValue::String(_) => Self::Typography,
// Types whose `TaggedValue` variant has been removed are routed through `TypeDefault` and identified by the descriptor's type name. // Types whose `TaggedValue` variant has been removed are routed through `TypeDefault` and identified by the descriptor's type name.
TaggedValue::TypeDefault(td) => match td.name.as_ref() { TaggedValue::TypeDefault(td) => match td.name.as_ref() {
n if n == std::any::type_name::<Table<Graphic>>() => Self::Graphic, n if n == std::any::type_name::<List<Graphic>>() => Self::Graphic,
n if n == std::any::type_name::<Table<Artboard>>() => Self::Artboard, n if n == std::any::type_name::<List<Artboard>>() => Self::Artboard,
n if n == std::any::type_name::<Table<Raster<CPU>>>() => Self::Raster, n if n == std::any::type_name::<List<Raster<CPU>>>() => Self::Raster,
n if n == std::any::type_name::<Table<Vector>>() => Self::Vector, n if n == std::any::type_name::<List<Vector>>() => Self::Vector,
n if n == std::any::type_name::<Table<String>>() => Self::Typography, n if n == std::any::type_name::<List<String>>() => Self::Typography,
_ => Self::General, _ => Self::General,
}, },
_ => Self::General, _ => Self::General,
@@ -11,9 +11,9 @@ use core::borrow::Borrow;
use core::f64::consts::{FRAC_PI_2, PI, TAU}; use core::f64::consts::{FRAC_PI_2, PI, TAU};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graphene_std::ATTR_TRANSFORM; use graphene_std::ATTR_TRANSFORM;
use graphene_std::list::List;
use graphene_std::math::quad::Quad; use graphene_std::math::quad::Quad;
use graphene_std::subpath::{self, Subpath}; use graphene_std::subpath::{self, Subpath};
use graphene_std::table::Table;
use graphene_std::text::{Font, TextAlign, TypesettingConfig}; use graphene_std::text::{Font, TextAlign, TypesettingConfig};
use graphene_std::vector::click_target::ClickTargetType; use graphene_std::vector::click_target::ClickTargetType;
use graphene_std::vector::misc::point_to_dvec2; use graphene_std::vector::misc::point_to_dvec2;
@@ -1129,7 +1129,7 @@ impl OverlayContextInternal {
let text_bounds = kurbo::Rect::new(0., 0., text_width, text_height); let text_bounds = kurbo::Rect::new(0., 0., text_width, text_height);
// Convert text to vector paths for rendering // Convert text to vector paths for rendering
let text_table = text_context.to_path(text, &font, &GLOBAL_FONT_CACHE, typesetting, false); let text_list = text_context.to_path(text, &font, &GLOBAL_FONT_CACHE, typesetting, false);
// Calculate position based on pivot // Calculate position based on pivot
let mut position = DVec2::ZERO; let mut position = DVec2::ZERO;
@@ -1161,20 +1161,20 @@ impl OverlayContextInternal {
} }
// Render the actual text paths // Render the actual text paths
self.render_text_paths(&text_table, font_color, vello_transform); self.render_text_paths(&text_list, font_color, vello_transform);
} }
// Render text paths to the vello scene using existing infrastructure // Render text paths to the vello scene using existing infrastructure
fn render_text_paths(&mut self, text_table: &Table<Vector>, font_color: &str, base_transform: kurbo::Affine) { fn render_text_paths(&mut self, text_list: &List<Vector>, font_color: &str, base_transform: kurbo::Affine) {
let color = Self::parse_color(font_color); let color = Self::parse_color(font_color);
for index in 0..text_table.len() { for index in 0..text_list.len() {
// Use the existing bezier_to_path infrastructure to convert Vector to BezPath // Use the existing bezier_to_path infrastructure to convert Vector to BezPath
let mut path = BezPath::new(); let mut path = BezPath::new();
let mut last_point = None; let mut last_point = None;
let transform: DAffine2 = text_table.attribute_cloned_or_default(ATTR_TRANSFORM, index); let transform: DAffine2 = text_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let Some(element) = text_table.element(index) else { continue }; let Some(element) = text_list.element(index) else { continue };
for (_, bezier, start_id, end_id) in element.segment_iter() { for (_, bezier, start_id, end_id) in element.segment_iter() {
let move_to = last_point != Some(start_id); let move_to = last_point != Some(start_id);
last_point = Some(end_id); last_point = Some(end_id);
@@ -4,8 +4,8 @@ use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNodeImplementation, InlineRust, NodeInput}; use graph_craft::document::{DocumentNodeImplementation, InlineRust, NodeInput};
use graph_craft::proto::{GraphErrorType, GraphErrors}; use graph_craft::proto::{GraphErrorType, GraphErrors};
use graph_craft::{Type, concrete}; use graph_craft::{Type, concrete};
use graphene_std::list::List;
use graphene_std::raster_types::{CPU, Raster}; use graphene_std::raster_types::{CPU, Raster};
use graphene_std::table::Table;
use graphene_std::uuid::NodeId; use graphene_std::uuid::NodeId;
use graphene_std::vector::Vector; use graphene_std::vector::Vector;
use graphene_std::{Artboard, Graphic}; use graphene_std::{Artboard, Graphic};
@@ -65,11 +65,11 @@ impl TypeSource {
TaggedValue::String(_) => FrontendGraphDataType::Typography, TaggedValue::String(_) => FrontendGraphDataType::Typography,
// Types whose `TaggedValue` variant has been removed are routed through `TypeDefault` and identified by the descriptor's type name. // Types whose `TaggedValue` variant has been removed are routed through `TypeDefault` and identified by the descriptor's type name.
TaggedValue::TypeDefault(td) => match td.name.as_ref() { TaggedValue::TypeDefault(td) => match td.name.as_ref() {
n if n == std::any::type_name::<Table<Graphic>>() => FrontendGraphDataType::Graphic, n if n == std::any::type_name::<List<Graphic>>() => FrontendGraphDataType::Graphic,
n if n == std::any::type_name::<Table<Artboard>>() => FrontendGraphDataType::Artboard, n if n == std::any::type_name::<List<Artboard>>() => FrontendGraphDataType::Artboard,
n if n == std::any::type_name::<Table<Raster<CPU>>>() => FrontendGraphDataType::Raster, n if n == std::any::type_name::<List<Raster<CPU>>>() => FrontendGraphDataType::Raster,
n if n == std::any::type_name::<Table<Vector>>() => FrontendGraphDataType::Vector, n if n == std::any::type_name::<List<Vector>>() => FrontendGraphDataType::Vector,
n if n == std::any::type_name::<Table<String>>() => FrontendGraphDataType::Typography, n if n == std::any::type_name::<List<String>>() => FrontendGraphDataType::Typography,
_ => FrontendGraphDataType::General, _ => FrontendGraphDataType::General,
}, },
_ => FrontendGraphDataType::General, _ => FrontendGraphDataType::General,
@@ -28,6 +28,7 @@ const TEXT_REPLACEMENTS: &[(&str, &str)] = &[
("graphene_core::transform::Footprint", "graphene_core::transform::Footprint"), ("graphene_core::transform::Footprint", "graphene_core::transform::Footprint"),
("\"OptionalF64\":", "\"F64\":"), ("\"OptionalF64\":", "\"F64\":"),
("\"path_bool_nodes::BooleanOperation\"", "\"vector_types::vector::misc::BooleanOperation\""), ("\"path_bool_nodes::BooleanOperation\"", "\"vector_types::vector::misc::BooleanOperation\""),
("\"core_types::table::Table<", "\"core_types::list::List<"),
]; ];
pub struct NodeReplacement<'a> { pub struct NodeReplacement<'a> {
@@ -1803,20 +1804,20 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::U32(0), false), network_path); .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::U32(0), false), network_path);
} }
// Migrate from the old source/target v1 "Morph" node to the new `Table<Vector>`-based v2 "Morph" node. // Migrate from the old source/target v1 "Morph" node to the new `List<Vector>`-based v2 "Morph" node.
// This doesn't produce exactly equivalent results in cases involving input `Table<Vector>` values with multiple items. // This doesn't produce exactly equivalent results in cases involving input `List<Vector>` values with multiple items.
// The old version would zip the source and target items, interpolating each pair together. // The old version would zip the source and target items, interpolating each pair together.
// The migrated version will instead deeply flatten both merged `Table`s and morph sequentially between all source vectors and all target vector elements. // The migrated version will instead deeply flatten both merged `List`s and morph sequentially between all source vectors and all target vector elements.
// This migration assumes most usages didn't involve multiple parallel vector elements, and instead morphed from a single source to a single target vector element. // This migration assumes most usages didn't involve multiple parallel vector elements, and instead morphed from a single source to a single target vector element.
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::morph::IDENTIFIER) && (inputs_count == 3 || inputs_count == 4) { if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::morph::IDENTIFIER) && (inputs_count == 3 || inputs_count == 4) {
// 3 inputs - old signature (#3405): // 3 inputs - old signature (#3405):
// async fn morph(_: impl Ctx, source: Table<Vector>, #[expose] target: Table<Vector>, #[default(0.5)] time: Fraction) -> Table<Vector> { ... } // async fn morph(_: impl Ctx, source: List<Vector>, #[expose] target: List<Vector>, #[default(0.5)] time: Fraction) -> List<Vector> { ... }
// //
// 4 inputs - even older signature (commit 80b8df8d4298b6669f124b929ce61bfabfc44e41): // 4 inputs - even older signature (commit 80b8df8d4298b6669f124b929ce61bfabfc44e41):
// async fn morph(_: impl Ctx, source: Table<Vector>, #[expose] target: Table<Vector>, #[default(0.5)] time: Fraction, start_index: u32) -> Table<Vector> { ... } // async fn morph(_: impl Ctx, source: List<Vector>, #[expose] target: List<Vector>, #[default(0.5)] time: Fraction, start_index: u32) -> List<Vector> { ... }
// //
// v2 signature: // v2 signature:
// async fn morph<I: IntoGraphicTable>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Vector>)] content: I, progression: Progression) -> Table<Vector> { ... } // async fn morph<I: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Vector>)] content: I, progression: Progression) -> List<Vector> { ... }
let mut node_template = resolve_document_node_type(&reference)?.default_node_template(); let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
@@ -1873,7 +1874,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
return None; return None;
}; };
// Create Count Elements node: counts content `Table` items → N // Create Count Elements node: counts content `List` items → N
let Some(count_elements_def) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::vector::count_elements::IDENTIFIER)) else { let Some(count_elements_def) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::vector::count_elements::IDENTIFIER)) else {
log::error!("Could not get count_elements node from definition when upgrading morph"); log::error!("Could not get count_elements node from definition when upgrading morph");
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
@@ -2102,7 +2103,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
document.network_interface.set_input( document.network_interface.set_input(
&InputConnector::node(*node_id, 0), &InputConnector::node(*node_id, 0),
NodeInput::type_default(descriptor!(graphene_std::table::Table<graphene_std::vector::Vector>), true), NodeInput::type_default(descriptor!(graphene_std::list::List<graphene_std::vector::Vector>), true),
network_path, network_path,
); );
@@ -9,10 +9,10 @@ use graph_craft::document::{NodeId, NodeInput};
use graph_craft::{ProtoNodeIdentifier, concrete}; use graph_craft::{ProtoNodeIdentifier, concrete};
use graphene_std::Color; use graphene_std::Color;
use graphene_std::NodeInputDecleration; use graphene_std::NodeInputDecleration;
use graphene_std::list::List;
use graphene_std::raster::BlendMode; use graphene_std::raster::BlendMode;
use graphene_std::raster_types::{CPU, GPU, Image, Raster}; use graphene_std::raster_types::{CPU, GPU, Image, Raster};
use graphene_std::subpath::Subpath; use graphene_std::subpath::Subpath;
use graphene_std::table::Table;
use graphene_std::text::{Font, TypesettingConfig}; use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::misc::ManipulatorPointId; use graphene_std::vector::misc::ManipulatorPointId;
use graphene_std::vector::style::{Fill, Gradient}; use graphene_std::vector::style::{Fill, Gradient};
@@ -291,15 +291,15 @@ pub fn get_gradient_stops(layer: LayerNodeIdentifier, network_interface: &NodeNe
Some(stops.clone()) Some(stops.clone())
} }
/// Compute the transform from a gradient's local space to viewport space for the given layer. For a `Table<GradientStops>` /// Compute the transform from a gradient's local space to viewport space for the given layer. For a `List<GradientStops>`
/// layer this is the layer's incoming footprint transform; for the legacy `Fill::Gradient` path it composes the layer's /// layer this is the layer's incoming footprint transform; for the legacy `Fill::Gradient` path it composes the layer's
/// viewport transform with the [0,1]² → bounding-box mapping. /// viewport transform with the [0,1]² → bounding-box mapping.
pub fn gradient_space_transform(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> glam::DAffine2 { pub fn gradient_space_transform(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> glam::DAffine2 {
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier; use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
let metadata = network_interface.document_metadata(); let metadata = network_interface.document_metadata();
let is_gradient_table = is_layer_fed_by_node_of_name(layer, network_interface, &DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::gradient_value::IDENTIFIER)); let is_gradient_list = is_layer_fed_by_node_of_name(layer, network_interface, &DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::gradient_value::IDENTIFIER));
if is_gradient_table { if is_gradient_list {
return metadata return metadata
.upstream_footprints .upstream_footprints
.get(&layer.to_node()) .get(&layer.to_node())
@@ -637,6 +637,6 @@ impl<'a> NodeGraphLayer<'a> {
pub fn is_raster_layer(layer: LayerNodeIdentifier, network_interface: &mut NodeNetworkInterface) -> bool { pub fn is_raster_layer(layer: LayerNodeIdentifier, network_interface: &mut NodeNetworkInterface) -> bool {
let layer_input_type = network_interface.input_type(&InputConnector::node(layer.to_node(), 1), &[]); let layer_input_type = network_interface.input_type(&InputConnector::node(layer.to_node(), 1), &[]);
layer_input_type.compiled_nested_type() == Some(&concrete!(Table<Raster<CPU>>)) || layer_input_type.compiled_nested_type() == Some(&concrete!(Table<Raster<GPU>>)) layer_input_type.compiled_nested_type() == Some(&concrete!(List<Raster<CPU>>)) || layer_input_type.compiled_nested_type() == Some(&concrete!(List<Raster<GPU>>))
} }
} }
@@ -13,9 +13,9 @@ use crate::messages::tool::utility_types::ToolType;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graph_craft::concrete; use graph_craft::concrete;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graphene_std::list::List;
use graphene_std::renderer::Quad; use graphene_std::renderer::Quad;
use graphene_std::subpath::{Bezier, BezierHandles}; use graphene_std::subpath::{Bezier, BezierHandles};
use graphene_std::table::Table;
use graphene_std::text::FontCache; use graphene_std::text::FontCache;
use graphene_std::vector::algorithms::bezpath_algorithms::pathseg_compute_lookup_table; use graphene_std::vector::algorithms::bezpath_algorithms::pathseg_compute_lookup_table;
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point}; use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point};
@@ -568,11 +568,11 @@ pub fn make_path_editable_is_allowed(network_interface: &mut NodeNetworkInterfac
} }
for _ in selected_layers {} for _ in selected_layers {}
// Must be a layer of type Table<Vector> // Must be a layer of type List<Vector>
let node_id = NodeGraphLayer::new(first_layer, network_interface).horizontal_layer_flow().nth(1)?; let node_id = NodeGraphLayer::new(first_layer, network_interface).horizontal_layer_flow().nth(1)?;
let output_type = network_interface.output_type(&OutputConnector::node(node_id, 0), &[]); let output_type = network_interface.output_type(&OutputConnector::node(node_id, 0), &[]);
if output_type.compiled_nested_type() != Some(&concrete!(Table<Vector>)) { if output_type.compiled_nested_type() != Some(&concrete!(List<Vector>)) {
return None; return None;
} }
@@ -612,9 +612,9 @@ impl Fsm for ArtboardToolFsmState {
mod test_artboard { mod test_artboard {
pub use crate::test_utils::test_prelude::*; pub use crate::test_utils::test_prelude::*;
use graphene_std::Artboard; use graphene_std::Artboard;
use graphene_std::table::Table; use graphene_std::list::List;
async fn get_artboards(editor: &mut EditorTestUtils) -> Table<Artboard> { async fn get_artboards(editor: &mut EditorTestUtils) -> List<Artboard> {
let instrumented = match editor.eval_graph().await { let instrumented = match editor.eval_graph().await {
Ok(instrumented) => instrumented, Ok(instrumented) => instrumented,
Err(e) => panic!("Failed to evaluate graph: {e}"), Err(e) => panic!("Failed to evaluate graph: {e}"),
@@ -459,8 +459,8 @@ struct SelectedGradient {
gradient: Gradient, gradient: Gradient,
dragging: GradientDragTarget, dragging: GradientDragTarget,
initial_gradient: Gradient, initial_gradient: Gradient,
// TODO: Remove (and the matching branches in `render_gradient` / pointer-up) once `Table<GradientStops>` replaces legacy `Fill::Gradient` // TODO: Remove (and the matching branches in `render_gradient` / pointer-up) once `List<GradientStops>` replaces legacy `Fill::Gradient`
is_gradient_table: bool, is_gradient_list: bool,
} }
fn calculate_insertion(start: DVec2, end: DVec2, stops: &GradientStops, mouse: DVec2) -> Option<f64> { fn calculate_insertion(start: DVec2, end: DVec2, stops: &GradientStops, mouse: DVec2) -> Option<f64> {
@@ -504,14 +504,14 @@ fn calculate_insertion(start: DVec2, end: DVec2, stops: &GradientStops, mouse: D
impl SelectedGradient { impl SelectedGradient {
pub fn new(gradient: Gradient, layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Self { pub fn new(gradient: Gradient, layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Self {
let transform = gradient_space_transform(layer, document); let transform = gradient_space_transform(layer, document);
let is_gradient_table = get_gradient_stops(layer, &document.network_interface).is_some(); let is_gradient_list = get_gradient_stops(layer, &document.network_interface).is_some();
Self { Self {
layer: Some(layer), layer: Some(layer),
transform, transform,
gradient: gradient.clone(), gradient: gradient.clone(),
dragging: GradientDragTarget::End, dragging: GradientDragTarget::End,
initial_gradient: gradient, initial_gradient: gradient,
is_gradient_table, is_gradient_list,
} }
} }
@@ -727,8 +727,8 @@ impl SelectedGradient {
/// Update the layer fill to the current gradient /// Update the layer fill to the current gradient
pub fn render_gradient(&mut self, responses: &mut VecDeque<Message>) { pub fn render_gradient(&mut self, responses: &mut VecDeque<Message>) {
if let Some(layer) = self.layer { if let Some(layer) = self.layer {
// TODO: Drop the `Fill::Gradient` branch when all gradients become `Table<GradientStops>` // TODO: Drop the `Fill::Gradient` branch when all gradients become `List<GradientStops>`
if self.is_gradient_table { if self.is_gradient_list {
dispatch_gradient_writes(layer, &self.gradient, responses); dispatch_gradient_writes(layer, &self.gradient, responses);
} else { } else {
responses.add(GraphOperationMessage::FillSet { responses.add(GraphOperationMessage::FillSet {
@@ -1144,9 +1144,9 @@ impl Fsm for GradientToolFsmState {
}; };
// The gradient has only one point and so should become a fill // The gradient has only one point and so should become a fill
// TODO: Drop the legacy `Fill::Solid` branch when all gradients become `Table<GradientStops>` // TODO: Drop the legacy `Fill::Solid` branch when all gradients become `List<GradientStops>`
if selected_gradient.gradient.stops.len() == 1 { if selected_gradient.gradient.stops.len() == 1 {
if selected_gradient.is_gradient_table { if selected_gradient.is_gradient_list {
selected_gradient.render_gradient(responses); selected_gradient.render_gradient(responses);
} else if let Some(layer) = selected_gradient.layer { } else if let Some(layer) = selected_gradient.layer {
responses.add(GraphOperationMessage::FillSet { responses.add(GraphOperationMessage::FillSet {
@@ -1242,7 +1242,7 @@ impl Fsm for GradientToolFsmState {
for layer in document.network_interface.selected_nodes().selected_visible_layers(&document.network_interface) { for layer in document.network_interface.selected_nodes().selected_visible_layers(&document.network_interface) {
let Some(gradient) = get_gradient(layer, &document.network_interface) else { continue }; let Some(gradient) = get_gradient(layer, &document.network_interface) else { continue };
let transform = gradient_space_transform(layer, document); let transform = gradient_space_transform(layer, document);
let is_gradient_table = get_gradient_stops(layer, &document.network_interface).is_some(); let is_gradient_list = get_gradient_stops(layer, &document.network_interface).is_some();
// Check for dragging a midpoint diamond // Check for dragging a midpoint diamond
if drag_hint.is_none() { if drag_hint.is_none() {
@@ -1270,7 +1270,7 @@ impl Fsm for GradientToolFsmState {
gradient: gradient.clone(), gradient: gradient.clone(),
dragging: GradientDragTarget::Midpoint(i), dragging: GradientDragTarget::Midpoint(i),
initial_gradient: gradient.clone(), initial_gradient: gradient.clone(),
is_gradient_table, is_gradient_list,
}); });
break; break;
@@ -1311,7 +1311,7 @@ impl Fsm for GradientToolFsmState {
gradient: gradient.clone(), gradient: gradient.clone(),
dragging: drag_target, dragging: drag_target,
initial_gradient: gradient.clone(), initial_gradient: gradient.clone(),
is_gradient_table, is_gradient_list,
}); });
} }
} }
@@ -1328,7 +1328,7 @@ impl Fsm for GradientToolFsmState {
gradient: gradient.clone(), gradient: gradient.clone(),
dragging: dragging_target, dragging: dragging_target,
initial_gradient: gradient.clone(), initial_gradient: gradient.clone(),
is_gradient_table, is_gradient_list,
}) })
} }
} }
@@ -1377,8 +1377,8 @@ impl Fsm for GradientToolFsmState {
GradientToolFsmState::Drawing { drag_hint: hint } GradientToolFsmState::Drawing { drag_hint: hint }
} else { } else {
let document_mouse = document.metadata().document_to_viewport.inverse().transform_point2(mouse); let document_mouse = document.metadata().document_to_viewport.inverse().transform_point2(mouse);
// Table-based gradients render no geometry, so a click on empty canvas yields no layer. Fall back to a // List-based gradients render no geometry, so a click on empty canvas yields no layer.
// selected gradient-table layer so the user can drag a fresh gradient line anywhere. // Fall back to a selected gradient list layer so the user can drag a fresh gradient line anywhere.
let selected_layer = document.click_based_on_position(document_mouse).or_else(|| { let selected_layer = document.click_based_on_position(document_mouse).or_else(|| {
document document
.network_interface .network_interface
@@ -1747,8 +1747,8 @@ fn apply_gradient_update(
} }
update(&mut gradient); update(&mut gradient);
// Only check for the gradient table once we know we'll write back, since this is a graph traversal per layer // Only check for the gradient list once we know we'll write back, since this is a graph traversal per layer
// TODO: Drop the `Fill::Gradient` branch when all gradients become `Table<GradientStops>` // TODO: Drop the `Fill::Gradient` branch when all gradients become `List<GradientStops>`
if get_gradient_stops(layer, &context.document.network_interface).is_some() { if get_gradient_stops(layer, &context.document.network_interface).is_some() {
dispatch_gradient_writes(layer, &gradient, responses); dispatch_gradient_writes(layer, &gradient, responses);
} else { } else {
@@ -1932,7 +1932,7 @@ mod test_gradient {
} }
} }
async fn create_gradient_table_layer(editor: &mut EditorTestUtils) -> LayerNodeIdentifier { async fn create_gradient_list_layer(editor: &mut EditorTestUtils) -> LayerNodeIdentifier {
editor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await; editor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;
let document = editor.active_document(); let document = editor.active_document();
let layer = document.metadata().all_layers().next().unwrap(); let layer = document.metadata().all_layers().next().unwrap();
@@ -2335,10 +2335,10 @@ mod test_gradient {
} }
#[tokio::test] #[tokio::test]
async fn gradient_table_drag_endpoint() { async fn gradient_list_drag_endpoint() {
let mut editor = EditorTestUtils::create(); let mut editor = EditorTestUtils::create();
editor.new_document().await; editor.new_document().await;
let layer = create_gradient_table_layer(&mut editor).await; let layer = create_gradient_list_layer(&mut editor).await;
// Create original transform for the control geometry and apply it // Create original transform for the control geometry and apply it
let initial_start = DVec2::new(10., 50.); let initial_start = DVec2::new(10., 50.);
@@ -2407,10 +2407,10 @@ mod test_gradient {
} }
#[tokio::test] #[tokio::test]
async fn gradient_table_preserves_stops() { async fn gradient_list_preserves_stops() {
let mut editor = EditorTestUtils::create(); let mut editor = EditorTestUtils::create();
editor.new_document().await; editor.new_document().await;
let layer = create_gradient_table_layer(&mut editor).await; let layer = create_gradient_list_layer(&mut editor).await;
// Set up a 3-stop gradient with distinct colors // Set up a 3-stop gradient with distinct colors
let original_stops = GradientStops::new([ let original_stops = GradientStops::new([
+7 -7
View File
@@ -9,13 +9,13 @@ use graph_craft::proto::GraphErrors;
use graph_craft::{ProtoNodeIdentifier, concrete}; use graph_craft::{ProtoNodeIdentifier, concrete};
use graphene_std::application_io::{ApplicationIo, ExportFormat, ImageTexture, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig}; use graphene_std::application_io::{ApplicationIo, ExportFormat, ImageTexture, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
use graphene_std::bounds::{BoundingBox, RenderBoundingBox}; use graphene_std::bounds::{BoundingBox, RenderBoundingBox};
use graphene_std::list::List;
use graphene_std::memo::IORecord; use graphene_std::memo::IORecord;
use graphene_std::ops::Convert; use graphene_std::ops::Convert;
#[cfg(all(target_family = "wasm", feature = "gpu", feature = "wasm"))] #[cfg(all(target_family = "wasm", feature = "gpu", feature = "wasm"))]
use graphene_std::platform_application_io::canvas_utils::{Canvas, CanvasSurface, CanvasSurfaceHandle}; use graphene_std::platform_application_io::canvas_utils::{Canvas, CanvasSurface, CanvasSurfaceHandle};
use graphene_std::raster_types::Raster; use graphene_std::raster_types::Raster;
use graphene_std::renderer::{Render, RenderParams, RenderSvgSegmentList, SvgRender, SvgSegment}; use graphene_std::renderer::{Render, RenderParams, RenderSvgSegmentList, SvgRender, SvgSegment};
use graphene_std::table::Table;
use graphene_std::text::FontCache; use graphene_std::text::FontCache;
use graphene_std::transform::RenderQuality; use graphene_std::transform::RenderQuality;
use graphene_std::vector::Vector; use graphene_std::vector::Vector;
@@ -432,8 +432,8 @@ impl NodeRuntime {
continue; continue;
}; };
// Graphic table: thumbnail // Graphic list: thumbnail
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Graphic>>>() { if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, List<Graphic>>>() {
if update_thumbnails { if update_thumbnails {
let bounds = io.output.thumbnail_bounding_box(DAffine2::IDENTITY, true); let bounds = io.output.thumbnail_bounding_box(DAffine2::IDENTITY, true);
Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, bounds, responses) Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, bounds, responses)
@@ -441,14 +441,14 @@ impl NodeRuntime {
} }
// Artboard thumbnail bounds come from the clipping rectangles, not the content union, since the renderer // Artboard thumbnail bounds come from the clipping rectangles, not the content union, since the renderer
// clips content to those rectangles so anything outside isn't visible // clips content to those rectangles so anything outside isn't visible
else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Artboard>>>() { else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, List<Artboard>>>() {
if update_thumbnails { if update_thumbnails {
let bounds = artboard_clip_bounds(&io.output); let bounds = artboard_clip_bounds(&io.output);
Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, bounds, responses) Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, bounds, responses)
} }
} }
// Vector table: vector modifications // Vector list: vector modifications
else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Vector>>>() { else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, List<Vector>>>() {
// Insert the vector modify // Insert the vector modify
self.vector_modify.insert(parent_network_node_id, io.output.element(0).cloned().unwrap_or_default()); self.vector_modify.insert(parent_network_node_id, io.output.element(0).cloned().unwrap_or_default());
} }
@@ -522,7 +522,7 @@ impl NodeRuntime {
/// Returns the union of the artboards' clipping rectangles, used as the thumbnail bounds for an artboard layer so the /// Returns the union of the artboards' clipping rectangles, used as the thumbnail bounds for an artboard layer so the
/// framing matches what's actually visible after clipping rather than the unclipped content extents. /// framing matches what's actually visible after clipping rather than the unclipped content extents.
fn artboard_clip_bounds(artboards: &Table<Artboard>) -> RenderBoundingBox { fn artboard_clip_bounds(artboards: &List<Artboard>) -> RenderBoundingBox {
let mut combined: Option<[DVec2; 2]> = None; let mut combined: Option<[DVec2; 2]> = None;
for index in 0..artboards.len() { for index in 0..artboards.len() {
let location: DVec2 = artboards.attribute_cloned_or_default(graphene_std::ATTR_LOCATION, index); let location: DVec2 = artboards.attribute_cloned_or_default(graphene_std::ATTR_LOCATION, index);
+1 -1
View File
@@ -220,7 +220,7 @@ pub enum DocumentNodeMetadata {
impl DocumentNodeMetadata { impl DocumentNodeMetadata {
pub fn ty(&self) -> Type { pub fn ty(&self) -> Type {
match self { match self {
DocumentNodeMetadata::DocumentNodePath => concrete!(core_types::table::Table<NodeId>), DocumentNodeMetadata::DocumentNodePath => concrete!(core_types::list::List<NodeId>),
} }
} }
} }
+50 -50
View File
@@ -2,7 +2,7 @@ use super::DocumentNode;
use crate::application_io::PlatformEditorApi; use crate::application_io::PlatformEditorApi;
use crate::proto::{Any as DAny, FutureAny}; use crate::proto::{Any as DAny, FutureAny};
use brush_nodes::brush_stroke::BrushStroke; use brush_nodes::brush_stroke::BrushStroke;
use core_types::table::Table; use core_types::list::List;
use core_types::transform::Footprint; use core_types::transform::Footprint;
use core_types::uuid::NodeId; use core_types::uuid::NodeId;
use core_types::{CacheHash, Color, ContextFeatures, MemoHash, Node, Type, TypeDescriptor}; use core_types::{CacheHash, Color, ContextFeatures, MemoHash, Node, Type, TypeDescriptor};
@@ -29,11 +29,11 @@ pub struct TaggedValueTypeError;
/// Consumed by [`TaggedValue::from_type`] (which creates `TypeDefault` values) and [`TaggedValue::to_dynany`]/[`TaggedValue::to_any`] (which unwrap them into real default values). /// Consumed by [`TaggedValue::from_type`] (which creates `TypeDefault` values) and [`TaggedValue::to_dynany`]/[`TaggedValue::to_any`] (which unwrap them into real default values).
macro_rules! for_each_type_default { macro_rules! for_each_type_default {
($action:ident) => { ($action:ident) => {
$action!(Table<Graphic>); $action!(List<Graphic>);
$action!(Table<Artboard>); $action!(List<Artboard>);
$action!(Table<Raster<CPU>>); $action!(List<Raster<CPU>>);
$action!(Table<Vector>); $action!(List<Vector>);
$action!(Table<String>); $action!(List<String>);
$action!(DocumentNode); $action!(DocumentNode);
}; };
} }
@@ -52,20 +52,20 @@ macro_rules! tagged_value {
/// Stores a type, from which its `Default::default()` value can be obtained, rather than storing an actual type's value. /// Stores a type, from which its `Default::default()` value can be obtained, rather than storing an actual type's value.
/// Example: `TaggedValue::TypeDefault(descriptor!(String))` stores the type `String` but no specific string value. /// Example: `TaggedValue::TypeDefault(descriptor!(String))` stores the type `String` but no specific string value.
TypeDefault(TypeDescriptor), TypeDefault(TypeDescriptor),
/// Stored compactly as a `Vec<f64>`, materializes as `Table<f64>` at runtime via `to_dynany`/`to_any`. Aliases recover legacy on-disk shapes. /// Stored compactly as a `Vec<f64>`, materializes as `List<f64>` at runtime via `to_dynany`/`to_any`. Aliases recover legacy on-disk shapes.
#[serde(deserialize_with = "core_types::misc::migrate_to_f64_array")] // TODO: Eventually remove this migration document upgrade code #[serde(deserialize_with = "core_types::misc::migrate_to_f64_array")] // TODO: Eventually remove this migration document upgrade code
#[serde(alias = "F64Table", alias = "VecF64", alias = "VecF32", alias = "F64Array4")] #[serde(alias = "F64Table", alias = "VecF64", alias = "VecF32", alias = "F64Array4")]
F64Array(Vec<f64>), F64Array(Vec<f64>),
/// Stored compactly as an `Option<Color>`, materializes as `Table<Color>` at runtime via `to_dynany`/`to_any`. Aliases recover legacy on-disk shapes. /// Stored compactly as an `Option<Color>`, materializes as `List<Color>` at runtime via `to_dynany`/`to_any`. Aliases recover legacy on-disk shapes.
#[serde(deserialize_with = "core_types::misc::migrate_to_optional_color")] // TODO: Eventually remove this migration document upgrade code #[serde(deserialize_with = "core_types::misc::migrate_to_optional_color")] // TODO: Eventually remove this migration document upgrade code
#[serde(alias = "ColorTable", alias = "OptionalColor", alias = "ColorNotInTable")] #[serde(alias = "ColorTable", alias = "OptionalColor", alias = "ColorNotInTable")]
Color(Option<Color>), Color(Option<Color>),
/// Stored compactly as a `GradientStops`, materializes as a single-row `Table<GradientStops>` at runtime via `to_dynany`/`to_any`. Aliases recover legacy on-disk shapes. /// Stored compactly as a `GradientStops`, materializes as a single-row `List<GradientStops>` at runtime via `to_dynany`/`to_any`. Aliases recover legacy on-disk shapes.
/// (Old documents that stored a full `Gradient` struct under this same `"Gradient"` tag are routed to `FillGradient` by `deserialize_tagged_value_with_legacy_migration`.) /// (Old documents that stored a full `Gradient` struct under this same `"Gradient"` tag are routed to `FillGradient` by `deserialize_tagged_value_with_legacy_migration`.)
#[serde(deserialize_with = "graphic_types::vector_types::gradient::migrate_to_gradient_stops")] // TODO: Eventually remove this migration document upgrade code #[serde(deserialize_with = "graphic_types::vector_types::gradient::migrate_to_gradient_stops")] // TODO: Eventually remove this migration document upgrade code
#[serde(alias = "GradientTable", alias = "GradientPositions")] #[serde(alias = "GradientTable", alias = "GradientPositions")]
Gradient(GradientStops), Gradient(GradientStops),
/// Stored compactly as a `Vec<BrushStroke>`, materializes as `Table<BrushStroke>` at runtime via `to_dynany`/`to_any`. Aliases recover legacy on-disk shapes. /// Stored compactly as a `Vec<BrushStroke>`, materializes as `List<BrushStroke>` at runtime via `to_dynany`/`to_any`. Aliases recover legacy on-disk shapes.
#[serde(deserialize_with = "brush_nodes::migrations::migrate_to_brush_strokes")] // TODO: Eventually remove this migration document upgrade code #[serde(deserialize_with = "brush_nodes::migrations::migrate_to_brush_strokes")] // TODO: Eventually remove this migration document upgrade code
#[serde(alias = "BrushStrokeTable")] #[serde(alias = "BrushStrokeTable")]
BrushStrokes(Vec<BrushStroke>), BrushStrokes(Vec<BrushStroke>),
@@ -78,7 +78,7 @@ macro_rules! tagged_value {
// ======================= // =======================
#[serde(skip)] #[serde(skip)]
RenderOutput(RenderOutput), RenderOutput(RenderOutput),
/// Path to the consumer of a `NodeInput::Reflection(DocumentNodePath)`. Materializes a `Table<NodeId>` at runtime via `to_dynany`/`to_any` during graph flattening. /// Path to the consumer of a `NodeInput::Reflection(DocumentNodePath)`. Materializes a `List<NodeId>` at runtime via `to_dynany`/`to_any` during graph flattening.
#[serde(skip)] #[serde(skip)]
NodeIdPath(Vec<NodeId>), NodeIdPath(Vec<NodeId>),
/// The `DocumentNode` value carried by an `Extract` proto node, populated at flatten time by `resolve_extract_nodes`. The on-disk placeholder uses `TypeDefault(descriptor!(DocumentNode))`. /// The `DocumentNode` value carried by an `Extract` proto node, populated at flatten time by `resolve_extract_nodes`. The on-disk placeholder uses `TypeDefault(descriptor!(DocumentNode))`.
@@ -142,17 +142,17 @@ macro_rules! tagged_value {
Self::from_type_or_none(&Type::Concrete(td)).to_dynany() Self::from_type_or_none(&Type::Concrete(td)).to_dynany()
} }
Self::F64Array(values) => { Self::F64Array(values) => {
let table: Table<f64> = values.into_iter().map(core_types::table::Item::new_from_element).collect(); let list: List<f64> = values.into_iter().map(core_types::list::Item::new_from_element).collect();
Box::new(table) Box::new(list)
} }
Self::Color(color) => { Self::Color(color) => {
let table: Table<Color> = color.into_iter().map(core_types::table::Item::new_from_element).collect(); let list: List<Color> = color.into_iter().map(core_types::list::Item::new_from_element).collect();
Box::new(table) Box::new(list)
} }
Self::Gradient(stops) => Box::new(Table::<GradientStops>::new_from_element(stops)), Self::Gradient(stops) => Box::new(List::<GradientStops>::new_from_element(stops)),
Self::BrushStrokes(strokes) => { Self::BrushStrokes(strokes) => {
let table: Table<BrushStroke> = strokes.into_iter().map(core_types::table::Item::new_from_element).collect(); let list: List<BrushStroke> = strokes.into_iter().map(core_types::list::Item::new_from_element).collect();
Box::new(table) Box::new(list)
} }
// ======================= // =======================
// AUTO-GENERATED VARIANTS // AUTO-GENERATED VARIANTS
@@ -163,8 +163,8 @@ macro_rules! tagged_value {
// ======================= // =======================
Self::RenderOutput(x) => Box::new(x), Self::RenderOutput(x) => Box::new(x),
Self::NodeIdPath(path) => { Self::NodeIdPath(path) => {
let table: Table<NodeId> = path.into_iter().map(core_types::table::Item::new_from_element).collect(); let list: List<NodeId> = path.into_iter().map(core_types::list::Item::new_from_element).collect();
Box::new(table) Box::new(list)
} }
Self::DocumentNode(node) => Box::new(node), Self::DocumentNode(node) => Box::new(node),
Self::ContextFeatures(features) => Box::new(features), Self::ContextFeatures(features) => Box::new(features),
@@ -191,17 +191,17 @@ macro_rules! tagged_value {
Self::from_type_or_none(&Type::Concrete(td)).to_any() Self::from_type_or_none(&Type::Concrete(td)).to_any()
} }
Self::F64Array(values) => { Self::F64Array(values) => {
let table: Table<f64> = values.into_iter().map(core_types::table::Item::new_from_element).collect(); let list: List<f64> = values.into_iter().map(core_types::list::Item::new_from_element).collect();
Arc::new(table) Arc::new(list)
} }
Self::Color(color) => { Self::Color(color) => {
let table: Table<Color> = color.into_iter().map(core_types::table::Item::new_from_element).collect(); let list: List<Color> = color.into_iter().map(core_types::list::Item::new_from_element).collect();
Arc::new(table) Arc::new(list)
} }
Self::Gradient(stops) => Arc::new(Table::<GradientStops>::new_from_element(stops)), Self::Gradient(stops) => Arc::new(List::<GradientStops>::new_from_element(stops)),
Self::BrushStrokes(strokes) => { Self::BrushStrokes(strokes) => {
let table: Table<BrushStroke> = strokes.into_iter().map(core_types::table::Item::new_from_element).collect(); let list: List<BrushStroke> = strokes.into_iter().map(core_types::list::Item::new_from_element).collect();
Arc::new(table) Arc::new(list)
} }
// ======================= // =======================
// AUTO-GENERATED VARIANTS // AUTO-GENERATED VARIANTS
@@ -212,8 +212,8 @@ macro_rules! tagged_value {
// ======================= // =======================
Self::RenderOutput(x) => Arc::new(x), Self::RenderOutput(x) => Arc::new(x),
Self::NodeIdPath(path) => { Self::NodeIdPath(path) => {
let table: Table<NodeId> = path.into_iter().map(core_types::table::Item::new_from_element).collect(); let list: List<NodeId> = path.into_iter().map(core_types::list::Item::new_from_element).collect();
Arc::new(table) Arc::new(list)
} }
Self::DocumentNode(node) => Arc::new(node), Self::DocumentNode(node) => Arc::new(node),
Self::ContextFeatures(features) => Arc::new(features), Self::ContextFeatures(features) => Arc::new(features),
@@ -229,10 +229,10 @@ macro_rules! tagged_value {
// =============== // ===============
Self::None => concrete!(()), Self::None => concrete!(()),
Self::TypeDefault(td) => Type::Concrete(td.clone()), Self::TypeDefault(td) => Type::Concrete(td.clone()),
Self::F64Array(_) => concrete!(Table<f64>), Self::F64Array(_) => concrete!(List<f64>),
Self::Color(_) => concrete!(Table<Color>), Self::Color(_) => concrete!(List<Color>),
Self::Gradient(_) => concrete!(Table<GradientStops>), Self::Gradient(_) => concrete!(List<GradientStops>),
Self::BrushStrokes(_) => concrete!(Table<BrushStroke>), Self::BrushStrokes(_) => concrete!(List<BrushStroke>),
// ======================= // =======================
// AUTO-GENERATED VARIANTS // AUTO-GENERATED VARIANTS
// ======================= // =======================
@@ -241,7 +241,7 @@ macro_rules! tagged_value {
// NON-SERIALIZED VARIANTS // NON-SERIALIZED VARIANTS
// ======================= // =======================
Self::RenderOutput(_) => concrete!(RenderOutput), Self::RenderOutput(_) => concrete!(RenderOutput),
Self::NodeIdPath(_) => concrete!(Table<NodeId>), Self::NodeIdPath(_) => concrete!(List<NodeId>),
Self::DocumentNode(_) => concrete!(DocumentNode), Self::DocumentNode(_) => concrete!(DocumentNode),
Self::ContextFeatures(_) => concrete!(ContextFeatures), Self::ContextFeatures(_) => concrete!(ContextFeatures),
Self::EditorApi(_) => concrete!(&PlatformEditorApi), Self::EditorApi(_) => concrete!(&PlatformEditorApi),
@@ -303,12 +303,12 @@ macro_rules! tagged_value {
// TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types // TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types
// Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned. // Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned.
if name == std::any::type_name::<()>() { return Some(TaggedValue::None) } if name == std::any::type_name::<()>() { return Some(TaggedValue::None) }
// Table-wrapped types need a single-item default with the element's default, not an empty table // List-wrapped types need a single-item default with the element's default, not an empty list
if name == std::any::type_name::<Table<Color>>() { return Some(TaggedValue::Color(Some(Color::default()))) } if name == std::any::type_name::<List<Color>>() { return Some(TaggedValue::Color(Some(Color::default()))) }
if name == std::any::type_name::<Table<GradientStops>>() { return Some(TaggedValue::Gradient(GradientStops::default())) } if name == std::any::type_name::<List<GradientStops>>() { return Some(TaggedValue::Gradient(GradientStops::default())) }
$( if name == std::any::type_name::<$ty>() { return Some(TaggedValue::$identifier(Default::default())) } )* $( if name == std::any::type_name::<$ty>() { return Some(TaggedValue::$identifier(Default::default())) } )*
if name == std::any::type_name::<Table<f64>>() { return Some(TaggedValue::F64Array(Vec::new())) } if name == std::any::type_name::<List<f64>>() { return Some(TaggedValue::F64Array(Vec::new())) }
if name == std::any::type_name::<Table<BrushStroke>>() { return Some(TaggedValue::BrushStrokes(Vec::new())) } if name == std::any::type_name::<List<BrushStroke>>() { return Some(TaggedValue::BrushStrokes(Vec::new())) }
// Types whose `TaggedValue` variant has been removed. They route through `TypeDefault` instead, with `to_dynany`/`to_any` constructing the actual default at execution time. // Types whose `TaggedValue` variant has been removed. They route through `TypeDefault` instead, with `to_dynany`/`to_any` constructing the actual default at execution time.
macro_rules! check { macro_rules! check {
($type_default:ty) => { ($type_default:ty) => {
@@ -567,10 +567,10 @@ impl TaggedValue {
() if ty == TypeId::of::<u32>() => FromStr::from_str(string).map(TaggedValue::U32).ok()?, () if ty == TypeId::of::<u32>() => FromStr::from_str(string).map(TaggedValue::U32).ok()?,
() if ty == TypeId::of::<DVec2>() => to_dvec2(string).map(TaggedValue::DVec2)?, () if ty == TypeId::of::<DVec2>() => to_dvec2(string).map(TaggedValue::DVec2)?,
() if ty == TypeId::of::<bool>() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?, () if ty == TypeId::of::<bool>() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?,
// `Color` (not in a table) is still currently needed by `BlackAndWhiteNode` and `ColorOverlayNode` GPU `shader_node(PerPixelAdjust)` variants // `Color` (not in a `List`) is still currently needed by `BlackAndWhiteNode` and `ColorOverlayNode` GPU `shader_node(PerPixelAdjust)` variants
() if ty == TypeId::of::<Color>() => to_color(string).map(|color| TaggedValue::Color(Some(color)))?, () if ty == TypeId::of::<Color>() => to_color(string).map(|color| TaggedValue::Color(Some(color)))?,
() if ty == TypeId::of::<Table<Color>>() => to_color(string).map(|color| TaggedValue::Color(Some(color)))?, () if ty == TypeId::of::<List<Color>>() => to_color(string).map(|color| TaggedValue::Color(Some(color)))?,
() if ty == TypeId::of::<Table<GradientStops>>() => to_gradient(string).map(TaggedValue::Gradient)?, () if ty == TypeId::of::<List<GradientStops>>() => to_gradient(string).map(TaggedValue::Gradient)?,
() if ty == TypeId::of::<Fill>() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?, () if ty == TypeId::of::<Fill>() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?,
() if ty == TypeId::of::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?, () if ty == TypeId::of::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?,
_ => return None, _ => return None,
@@ -595,14 +595,14 @@ impl TaggedValue {
/// Routes legacy variant names into modern variants, in typed Rust. Each legacy name is also matched against the historical `#[serde(alias = "...")]` spellings the deleted variant accepted, so old-shape inner payloads are caught: /// Routes legacy variant names into modern variants, in typed Rust. Each legacy name is also matched against the historical `#[serde(alias = "...")]` spellings the deleted variant accepted, so old-shape inner payloads are caught:
/// ///
/// - `BrushCache` → `TaggedValue::None` (purely runtime cache; no payload to preserve) /// - `BrushCache` → `TaggedValue::None` (purely runtime cache; no payload to preserve)
/// - `Graphic` (or alias `GraphicGroup`/`Group`) → `TaggedValue::TypeDefault(descriptor!(Table<Graphic>))` /// - `Graphic` (or alias `GraphicGroup`/`Group`) → `TaggedValue::TypeDefault(descriptor!(List<Graphic>))`
/// - `Artboard` (or alias `ArtboardGroup`) → `TaggedValue::TypeDefault(descriptor!(Table<Artboard>))` /// - `Artboard` (or alias `ArtboardGroup`) → `TaggedValue::TypeDefault(descriptor!(List<Artboard>))`
/// - `Raster` (or alias `ImageFrame`/`RasterData`/`Image`): /// - `Raster` (or alias `ImageFrame`/`RasterData`/`Image`):
/// - non-empty (the legacy `image` proto's input 1, where the inner `Raster<CPU>` serializes as the embedded `Image<Color>`) → `TaggedValue::ImageData(<inner Image<Color>>)` /// - non-empty (the legacy `image` proto's input 1, where the inner `Raster<CPU>` serializes as the embedded `Image<Color>`) → `TaggedValue::ImageData(<inner Image<Color>>)`
/// - empty → `TaggedValue::TypeDefault(descriptor!(Table<Raster<CPU>>))` /// - empty → `TaggedValue::TypeDefault(descriptor!(List<Raster<CPU>>))`
/// - `Vector` (or alias `VectorData`): /// - `Vector` (or alias `VectorData`):
/// - non-empty → `TaggedValue::VectorModification(<built from first element>)` (the document_migration's Path pass disambiguates this between SVG-import legacy and a discardable modern baked value via the input's `exposed` flag) /// - non-empty → `TaggedValue::VectorModification(<built from first element>)` (the document_migration's Path pass disambiguates this between SVG-import legacy and a discardable modern baked value via the input's `exposed` flag)
/// - empty → `TaggedValue::TypeDefault(descriptor!(Table<Vector>))` /// - empty → `TaggedValue::TypeDefault(descriptor!(List<Vector>))`
/// ///
/// All other tags (including ones with the modern shape) fall through to the standard derived `Deserialize` for `TaggedValue`. /// All other tags (including ones with the modern shape) fall through to the standard derived `Deserialize` for `TaggedValue`.
// TODO: Eventually remove this migration document upgrade code // TODO: Eventually remove this migration document upgrade code
@@ -617,15 +617,15 @@ pub fn deserialize_tagged_value_with_legacy_migration<'de, D: serde::Deserialize
{ {
match tag.as_str() { match tag.as_str() {
"BrushCache" => return Ok(MemoHash::new(TaggedValue::None)), "BrushCache" => return Ok(MemoHash::new(TaggedValue::None)),
"Graphic" | "GraphicGroup" | "Group" => return Ok(MemoHash::new(TaggedValue::TypeDefault(descriptor!(Table<Graphic>)))), "Graphic" | "GraphicGroup" | "Group" => return Ok(MemoHash::new(TaggedValue::TypeDefault(descriptor!(List<Graphic>)))),
"Artboard" | "ArtboardGroup" => return Ok(MemoHash::new(TaggedValue::TypeDefault(descriptor!(Table<Artboard>)))), "Artboard" | "ArtboardGroup" => return Ok(MemoHash::new(TaggedValue::TypeDefault(descriptor!(List<Artboard>)))),
"Raster" | "ImageFrame" | "RasterData" | "Image" => { "Raster" | "ImageFrame" | "RasterData" | "Image" => {
let first_element = content.as_object().and_then(|c| c.get("element")).and_then(|e| e.as_array()).and_then(|arr| arr.first()); let first_element = content.as_object().and_then(|c| c.get("element")).and_then(|e| e.as_array()).and_then(|arr| arr.first());
if let Some(image_value) = first_element { if let Some(image_value) = first_element {
let image: Image<Color> = serde_json::from_value(image_value.clone()).map_err(serde::de::Error::custom)?; let image: Image<Color> = serde_json::from_value(image_value.clone()).map_err(serde::de::Error::custom)?;
return Ok(MemoHash::new(TaggedValue::ImageData(image))); return Ok(MemoHash::new(TaggedValue::ImageData(image)));
} }
return Ok(MemoHash::new(TaggedValue::TypeDefault(descriptor!(Table<Raster<CPU>>)))); return Ok(MemoHash::new(TaggedValue::TypeDefault(descriptor!(List<Raster<CPU>>))));
} }
"Vector" | "VectorData" => { "Vector" | "VectorData" => {
let vector = graphic_types::migrations::migrate_to_optional_vector(content.clone()).map_err(serde::de::Error::custom)?; let vector = graphic_types::migrations::migrate_to_optional_vector(content.clone()).map_err(serde::de::Error::custom)?;
@@ -633,7 +633,7 @@ pub fn deserialize_tagged_value_with_legacy_migration<'de, D: serde::Deserialize
let modification = Box::new(VectorModification::create_from_vector(&vector)); let modification = Box::new(VectorModification::create_from_vector(&vector));
return Ok(MemoHash::new(TaggedValue::VectorModification(modification))); return Ok(MemoHash::new(TaggedValue::VectorModification(modification)));
} }
return Ok(MemoHash::new(TaggedValue::TypeDefault(descriptor!(Table<Vector>)))); return Ok(MemoHash::new(TaggedValue::TypeDefault(descriptor!(List<Vector>))));
} }
// The `Gradient` tag was reused: it used to carry a full `Gradient` struct (now `FillGradient`), and now carries an `Option<GradientStops>`. // The `Gradient` tag was reused: it used to carry a full `Gradient` struct (now `FillGradient`), and now carries an `Option<GradientStops>`.
// Disambiguate by payload shape: a Gradient struct has `start`/`end` keys; a `GradientStops` has none of those (it has `position`/`midpoint`/`color`). // Disambiguate by payload shape: a Gradient struct has `start`/`end` keys; a `GradientStops` has none of those (it has `position`/`midpoint`/`color`).
@@ -8,6 +8,7 @@ use graphene_std::any::DynAnyNode;
use graphene_std::application_io::ImageTexture; use graphene_std::application_io::ImageTexture;
use graphene_std::brush::brush_stroke::BrushStroke; use graphene_std::brush::brush_stroke::BrushStroke;
use graphene_std::gradient::GradientStops; use graphene_std::gradient::GradientStops;
use graphene_std::list::{AttributeDyn, AttributeValueDyn, List, ListDyn};
#[cfg(target_family = "wasm")] #[cfg(target_family = "wasm")]
use graphene_std::platform_application_io::canvas_utils::CanvasHandle; use graphene_std::platform_application_io::canvas_utils::CanvasHandle;
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
@@ -16,7 +17,6 @@ use graphene_std::raster::color::Color;
use graphene_std::raster::*; use graphene_std::raster::*;
use graphene_std::raster::{CPU, Raster}; use graphene_std::raster::{CPU, Raster};
use graphene_std::render_node::RenderIntermediate; use graphene_std::render_node::RenderIntermediate;
use graphene_std::table::{AttributeDyn, AttributeValueDyn, Table, TableDyn};
use graphene_std::transform::Footprint; use graphene_std::transform::Footprint;
use graphene_std::uuid::NodeId; use graphene_std::uuid::NodeId;
use graphene_std::vector::Vector; use graphene_std::vector::Vector;
@@ -32,47 +32,47 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
// ========== // ==========
// INTO NODES // INTO NODES
// ========== // ==========
into_node!(from: Table<Graphic>, to: Table<Graphic>), into_node!(from: List<Graphic>, to: List<Graphic>),
into_node!(from: Table<Vector>, to: Table<Vector>), into_node!(from: List<Vector>, to: List<Vector>),
into_node!(from: Table<Raster<CPU>>, to: Table<Raster<CPU>>), into_node!(from: List<Raster<CPU>>, to: List<Raster<CPU>>),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
into_node!(from: Table<Raster<GPU>>, to: Table<Raster<GPU>>), into_node!(from: List<Raster<GPU>>, to: List<Raster<GPU>>),
convert_node!(from: Table<Vector>, to: Table<Graphic>), convert_node!(from: List<Vector>, to: List<Graphic>),
convert_node!(from: Table<Raster<CPU>>, to: Table<Graphic>), convert_node!(from: List<Raster<CPU>>, to: List<Graphic>),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
convert_node!(from: Table<Raster<GPU>>, to: Table<Graphic>), convert_node!(from: List<Raster<GPU>>, to: List<Graphic>),
// Type-erased attribute column conversions for the `Attach Attribute` node, so it monomorphizes only over the destination table type. // Type-erased attribute conversions for the `Attach Attribute` node, so it monomorphizes only over the destination `List` type.
convert_node!(from: Table<Artboard>, to: AttributeDyn), convert_node!(from: List<Artboard>, to: AttributeDyn),
convert_node!(from: Table<Graphic>, to: AttributeDyn), convert_node!(from: List<Graphic>, to: AttributeDyn),
convert_node!(from: Table<Vector>, to: AttributeDyn), convert_node!(from: List<Vector>, to: AttributeDyn),
convert_node!(from: Table<Raster<CPU>>, to: AttributeDyn), convert_node!(from: List<Raster<CPU>>, to: AttributeDyn),
convert_node!(from: Table<Color>, to: AttributeDyn), convert_node!(from: List<Color>, to: AttributeDyn),
convert_node!(from: Table<GradientStops>, to: AttributeDyn), convert_node!(from: List<GradientStops>, to: AttributeDyn),
convert_node!(from: Table<f64>, to: AttributeDyn), convert_node!(from: List<f64>, to: AttributeDyn),
convert_node!(from: Table<bool>, to: AttributeDyn), convert_node!(from: List<bool>, to: AttributeDyn),
convert_node!(from: Table<String>, to: AttributeDyn), convert_node!(from: List<String>, to: AttributeDyn),
convert_node!(from: Table<DAffine2>, to: AttributeDyn), convert_node!(from: List<DAffine2>, to: AttributeDyn),
convert_node!(from: Table<BlendMode>, to: AttributeDyn), convert_node!(from: List<BlendMode>, to: AttributeDyn),
convert_node!(from: Table<graphene_std::vector::style::GradientType>, to: AttributeDyn), convert_node!(from: List<graphene_std::vector::style::GradientType>, to: AttributeDyn),
convert_node!(from: Table<graphene_std::vector::style::GradientSpreadMethod>, to: AttributeDyn), convert_node!(from: List<graphene_std::vector::style::GradientSpreadMethod>, to: AttributeDyn),
convert_node!(from: Table<Artboard>, to: TableDyn), convert_node!(from: List<Artboard>, to: ListDyn),
convert_node!(from: Table<Graphic>, to: TableDyn), convert_node!(from: List<Graphic>, to: ListDyn),
convert_node!(from: Table<Vector>, to: TableDyn), convert_node!(from: List<Vector>, to: ListDyn),
convert_node!(from: Table<Raster<CPU>>, to: TableDyn), convert_node!(from: List<Raster<CPU>>, to: ListDyn),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
convert_node!(from: Table<Raster<GPU>>, to: TableDyn), convert_node!(from: List<Raster<GPU>>, to: ListDyn),
convert_node!(from: Table<Color>, to: TableDyn), convert_node!(from: List<Color>, to: ListDyn),
convert_node!(from: Table<GradientStops>, to: TableDyn), convert_node!(from: List<GradientStops>, to: ListDyn),
convert_node!(from: Table<f64>, to: TableDyn), convert_node!(from: List<f64>, to: ListDyn),
convert_node!(from: Table<bool>, to: TableDyn), convert_node!(from: List<bool>, to: ListDyn),
convert_node!(from: Table<String>, to: TableDyn), convert_node!(from: List<String>, to: ListDyn),
convert_node!(from: Table<u8>, to: TableDyn), convert_node!(from: List<u8>, to: ListDyn),
convert_node!(from: Table<NodeId>, to: TableDyn), convert_node!(from: List<NodeId>, to: ListDyn),
convert_node!(from: Table<DAffine2>, to: TableDyn), convert_node!(from: List<DAffine2>, to: ListDyn),
convert_node!(from: Table<BlendMode>, to: TableDyn), convert_node!(from: List<BlendMode>, to: ListDyn),
convert_node!(from: Table<graphene_std::vector::style::GradientType>, to: TableDyn), convert_node!(from: List<graphene_std::vector::style::GradientType>, to: ListDyn),
convert_node!(from: Table<graphene_std::vector::style::GradientSpreadMethod>, to: TableDyn), convert_node!(from: List<graphene_std::vector::style::GradientSpreadMethod>, to: ListDyn),
// Type-erased attribute value conversions for the `Write Attribute` node, so it monomorphizes only over the destination table type. // Type-erased attribute value conversions for the `Write Attribute` node, so it monomorphizes only over the destination `List` type.
convert_node!(from: f64, to: AttributeValueDyn), convert_node!(from: f64, to: AttributeValueDyn),
convert_node!(from: u32, to: AttributeValueDyn), convert_node!(from: u32, to: AttributeValueDyn),
convert_node!(from: u64, to: AttributeValueDyn), convert_node!(from: u64, to: AttributeValueDyn),
@@ -84,12 +84,12 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
convert_node!(from: BlendMode, to: AttributeValueDyn), convert_node!(from: BlendMode, to: AttributeValueDyn),
convert_node!(from: graphene_std::vector::style::GradientType, to: AttributeValueDyn), convert_node!(from: graphene_std::vector::style::GradientType, to: AttributeValueDyn),
convert_node!(from: graphene_std::vector::style::GradientSpreadMethod, to: AttributeValueDyn), convert_node!(from: graphene_std::vector::style::GradientSpreadMethod, to: AttributeValueDyn),
convert_node!(from: Table<String>, to: AttributeValueDyn), convert_node!(from: List<String>, to: AttributeValueDyn),
convert_node!(from: Table<NodeId>, to: AttributeValueDyn), convert_node!(from: List<NodeId>, to: AttributeValueDyn),
convert_node!(from: Table<Color>, to: AttributeValueDyn), convert_node!(from: List<Color>, to: AttributeValueDyn),
convert_node!(from: Table<GradientStops>, to: AttributeValueDyn), convert_node!(from: List<GradientStops>, to: AttributeValueDyn),
convert_node!(from: Table<Graphic>, to: AttributeValueDyn), convert_node!(from: List<Graphic>, to: AttributeValueDyn),
// into_node!(from: Table<Raster<CPU>>, to: Table<Raster<SRGBA8>>), // into_node!(from: List<Raster<CPU>>, to: List<Raster<SRGBA8>>),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
into_node!(from: &PlatformEditorApi, to: &WgpuExecutor), into_node!(from: &PlatformEditorApi, to: &WgpuExecutor),
convert_node!(from: DVec2, to: DVec2), convert_node!(from: DVec2, to: DVec2),
@@ -99,25 +99,25 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
convert_node!(from: IVec2, to: String), convert_node!(from: IVec2, to: String),
convert_node!(from: DAffine2, to: String), convert_node!(from: DAffine2, to: String),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
convert_node!(from: Table<Raster<CPU>>, to: Table<Raster<CPU>>, converter: &WgpuExecutor), convert_node!(from: List<Raster<CPU>>, to: List<Raster<CPU>>, converter: &WgpuExecutor),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
convert_node!(from: Table<Raster<CPU>>, to: Table<Raster<GPU>>, converter: &WgpuExecutor), convert_node!(from: List<Raster<CPU>>, to: List<Raster<GPU>>, converter: &WgpuExecutor),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
convert_node!(from: Table<Raster<GPU>>, to: Table<Raster<GPU>>, converter: &WgpuExecutor), convert_node!(from: List<Raster<GPU>>, to: List<Raster<GPU>>, converter: &WgpuExecutor),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
convert_node!(from: Table<Raster<GPU>>, to: Table<Raster<CPU>>, converter: &WgpuExecutor), convert_node!(from: List<Raster<GPU>>, to: List<Raster<CPU>>, converter: &WgpuExecutor),
// ============= // =============
// MONITOR NODES // MONITOR NODES
// ============= // =============
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ()]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ()]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Artboard>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<Artboard>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Graphic>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<Graphic>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Vector>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<Vector>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<Raster<CPU>>]),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<Raster<GPU>>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Color>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<Color>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<GradientStops>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<GradientStops>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Image<Color>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Image<Color>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => String]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => String]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => IVec2]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => IVec2]),
@@ -142,21 +142,21 @@ 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 => Table<String>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<String>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<NodeId>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<NodeId>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<f64>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<f64>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<u8>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<u8>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<bool>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<bool>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<DAffine2>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<DAffine2>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<BlendMode>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<BlendMode>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<graphene_std::vector::style::GradientType>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<graphene_std::vector::style::GradientType>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<graphene_std::vector::style::GradientSpreadMethod>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<graphene_std::vector::style::GradientSpreadMethod>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => AttributeDyn]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => AttributeDyn]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => AttributeValueDyn]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => AttributeValueDyn]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => TableDyn]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ListDyn]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Graphic]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Graphic]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::text::Font]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::text::Font]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<BrushStroke>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<BrushStroke>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => DocumentNode]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => DocumentNode]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::transform::Footprint]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::transform::Footprint]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::blending::BlendMode]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::blending::BlendMode]),
@@ -192,7 +192,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderOutput, Context => graphene_std::ContextFeatures]), async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderOutput, Context => graphene_std::ContextFeatures]),
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => AttributeDyn, Context => graphene_std::ContextFeatures]), async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => AttributeDyn, Context => graphene_std::ContextFeatures]),
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => AttributeValueDyn, Context => graphene_std::ContextFeatures]), async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => AttributeValueDyn, Context => graphene_std::ContextFeatures]),
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => TableDyn, Context => graphene_std::ContextFeatures]), async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => ListDyn, Context => graphene_std::ContextFeatures]),
#[cfg(target_family = "wasm")] #[cfg(target_family = "wasm")]
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => CanvasHandle, Context => graphene_std::ContextFeatures]), async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => CanvasHandle, Context => graphene_std::ContextFeatures]),
// ========== // ==========
@@ -200,25 +200,25 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
// ========== // ==========
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => ()]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => ()]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => bool]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => bool]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<Artboard>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<Artboard>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<Graphic>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<Graphic>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<Vector>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<Vector>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<Raster<CPU>>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<Color>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<Color>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Image<Color>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<GradientStops>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<GradientStops>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<String>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<String>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<NodeId>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<NodeId>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<f64>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<f64>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<u8>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<u8>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<bool>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<bool>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<DAffine2>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<DAffine2>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<BlendMode>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<BlendMode>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<graphene_std::vector::style::GradientType>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<graphene_std::vector::style::GradientType>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<graphene_std::vector::style::GradientSpreadMethod>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<graphene_std::vector::style::GradientSpreadMethod>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => AttributeDyn]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => AttributeDyn]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => AttributeValueDyn]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => AttributeValueDyn]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => TableDyn]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => ListDyn]),
#[cfg(target_family = "wasm")] #[cfg(target_family = "wasm")]
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => CanvasHandle]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => CanvasHandle]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => f64]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => f64]),
@@ -232,7 +232,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => RenderOutput]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => RenderOutput]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => &PlatformEditorApi]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => &PlatformEditorApi]),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<Raster<GPU>>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Option<f64>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Option<f64>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Option<Color>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Option<Color>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Graphic]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Graphic]),
@@ -241,7 +241,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Stroke]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Stroke]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Gradient]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Gradient]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::Font]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::Font]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<BrushStroke>]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<BrushStroke>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => DocumentNode]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => DocumentNode]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::ContextFeatures]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::ContextFeatures]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::Footprint]), async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::Footprint]),
@@ -15,7 +15,7 @@ pub trait BoundingBox {
/// Returns the bounding box to use when sizing this value's thumbnail in the Layers panel. /// Returns the bounding box to use when sizing this value's thumbnail in the Layers panel.
/// ///
/// Diverges from `bounding_box` for types where the rendering bounds wouldn't make a useful thumbnail frame. /// Diverges from `bounding_box` for types where the rendering bounds wouldn't make a useful thumbnail frame.
/// For instance, `GradientStops` is `Infinite` for rendering but returns the line's AABB here, so a `Table<Graphic>` /// For instance, `GradientStops` is `Infinite` for rendering but returns the line's AABB here, so a `List<Graphic>`
/// group of a gradient and a vector frames around the vector's geometry rather than infinity. /// group of a gradient and a vector frames around the vector's geometry rather than infinity.
/// Types with no meaningful contribution (e.g., `Color`) return `Infinite` from both; the runtime substitutes a /// Types with no meaningful contribution (e.g., `Color`) return `Infinite` from both; the runtime substitutes a
/// small fallback rectangle at the end if no finite bounds remain after combining. /// small fallback rectangle at the end if no finite bounds remain after combining.
+5 -5
View File
@@ -4,13 +4,13 @@ pub mod bounds;
pub mod consts; pub mod consts;
pub mod context; pub mod context;
pub mod generic; pub mod generic;
pub mod list;
pub mod math; pub mod math;
pub mod memo; pub mod memo;
pub mod misc; pub mod misc;
pub mod ops; pub mod ops;
pub mod registry; pub mod registry;
pub mod render_complexity; pub mod render_complexity;
pub mod table;
pub mod transform; pub mod transform;
pub mod uuid; pub mod uuid;
pub mod value; pub mod value;
@@ -23,6 +23,10 @@ pub use ctor;
pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync}; pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync};
pub use graphene_hash; pub use graphene_hash;
pub use graphene_hash::CacheHash; pub use graphene_hash::CacheHash;
pub use list::{
ATTR_BACKGROUND, ATTR_BLEND_MODE, ATTR_CLIP, ATTR_CLIPPING_MASK, ATTR_DIMENSIONS, ATTR_EDITOR_CLICK_TARGET, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_EDITOR_TEXT_FRAME, ATTR_END,
ATTR_GRADIENT_TYPE, ATTR_LOCATION, ATTR_NAME, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_SPREAD_METHOD, ATTR_START, ATTR_TRANSFORM, ATTR_TYPE,
};
pub use memo::MemoHash; pub use memo::MemoHash;
pub use no_std_types::AsU32; pub use no_std_types::AsU32;
pub use no_std_types::blending; pub use no_std_types::blending;
@@ -33,10 +37,6 @@ pub use num_traits;
use std::any::TypeId; use std::any::TypeId;
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
pub use table::{
ATTR_BACKGROUND, ATTR_BLEND_MODE, ATTR_CLIP, ATTR_CLIPPING_MASK, ATTR_DIMENSIONS, ATTR_EDITOR_CLICK_TARGET, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_EDITOR_TEXT_FRAME, ATTR_END,
ATTR_GRADIENT_TYPE, ATTR_LOCATION, ATTR_NAME, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_SPREAD_METHOD, ATTR_START, ATTR_TRANSFORM, ATTR_TYPE,
};
#[cfg(feature = "wasm")] #[cfg(feature = "wasm")]
pub use tsify; pub use tsify;
pub use types::Cow; pub use types::Cow;
@@ -27,11 +27,11 @@ pub const ATTR_OPACITY_FILL: &str = "opacity_fill";
/// `bool` for whether an item inherits the alpha of the content beneath it (clipping mask). /// `bool` for whether an item inherits the alpha of the content beneath it (clipping mask).
pub const ATTR_CLIPPING_MASK: &str = "clipping_mask"; pub const ATTR_CLIPPING_MASK: &str = "clipping_mask";
/// `Table<NodeId>` path from the root network to the layer node owning this item. /// `List<NodeId>` path from the root network to the layer node owning this item.
/// Used by editor tools to route clicks/selection back to the originating layer. /// Used by editor tools to route clicks/selection back to the originating layer.
pub const ATTR_EDITOR_LAYER_PATH: &str = "editor:layer_path"; pub const ATTR_EDITOR_LAYER_PATH: &str = "editor:layer_path";
/// `Table<Graphic>` snapshot of the upstream content that fed into a destructive merge /// `List<Graphic>` snapshot of the upstream content that fed into a destructive merge
/// (Boolean Operation, Rasterize, etc.), so the editor can still surface click targets for /// (Boolean Operation, Rasterize, etc.), so the editor can still surface click targets for
/// the original child layers after their content has been collapsed. /// the original child layers after their content has been collapsed.
pub const ATTR_EDITOR_MERGED_LAYERS: &str = "editor:merged_layers"; pub const ATTR_EDITOR_MERGED_LAYERS: &str = "editor:merged_layers";
@@ -147,7 +147,7 @@ impl Clone for Box<dyn AnyAttributeValue> {
// TRAIT: AnyAttribute // TRAIT: AnyAttribute
// =================== // ===================
/// Enables type-erased storage for parallel attribute lists in a [`Table`]. /// Enables type-erased storage for parallel attribute lists in a [`List`].
pub trait AnyAttribute: std::any::Any + Send + Sync { pub trait AnyAttribute: std::any::Any + Send + Sync {
/// Clones this attribute into a new boxed trait object. /// Clones this attribute into a new boxed trait object.
fn clone_box(&self) -> Box<dyn AnyAttribute>; fn clone_box(&self) -> Box<dyn AnyAttribute>;
@@ -224,7 +224,7 @@ impl Clone for Box<dyn AnyAttribute> {
// Attribute<T> // Attribute<T>
// ============ // ============
/// Wraps a Vec<T> for attribute storage in a [`Table`]. /// Wraps a Vec<T> for attribute storage in a [`List`].
pub struct Attribute<T>(pub Vec<T>); pub struct Attribute<T>(pub Vec<T>);
impl<T: Clone + Send + Sync + Default + Debug + PartialEq + CacheHash + 'static> AnyAttribute for Attribute<T> { impl<T: Clone + Send + Sync + Default + Debug + PartialEq + CacheHash + 'static> AnyAttribute for Attribute<T> {
@@ -329,7 +329,7 @@ impl<T: Clone + Send + Sync + Default + Debug + PartialEq + CacheHash + 'static>
// ============ // ============
/// Type-erased list of attribute values, used as a node graph parameter type. /// Type-erased list of attribute values, used as a node graph parameter type.
/// Lets a node accept any `Table<U>` source via the auto-inserted `Convert<AttributeDyn, ()>` /// Lets a node accept any `List<U>` source via the auto-inserted `Convert<AttributeDyn, ()>`
/// without monomorphizing over `U` (so the cartesian product of `(content T, source U)` collapses to just `T`). /// without monomorphizing over `U` (so the cartesian product of `(content T, source U)` collapses to just `T`).
pub struct AttributeDyn(pub Box<dyn AnyAttribute>); pub struct AttributeDyn(pub Box<dyn AnyAttribute>);
@@ -439,26 +439,26 @@ unsafe impl StaticType for AttributeValueDyn {
type Static = Self; type Static = Self;
} }
// ======== // =======
// TableDyn // ListDyn
// ======== // =======
/// Type-erased view of a `Table<T>` exposing only its attributes and item count, used as a node graph parameter type. /// Type-erased view of a `List<T>` exposing only its attributes and item count, used as a node graph parameter type.
/// Lets a node accept any `Table<U>` source via the auto-inserted `Convert<TableDyn, ()>` without monomorphizing over `U`, /// Lets a node accept any `List<U>` source via the auto-inserted `Convert<ListDyn, ()>` without monomorphizing over `U`,
/// for cases where the element type is irrelevant (such as nodes that read out a named attribute regardless of the carrier table). /// for cases where the element type is irrelevant (such as nodes that read out a named attribute regardless of the carrier `List`).
#[derive(Default)] #[derive(Default)]
pub struct TableDyn { pub struct ListDyn {
attributes: Vec<(String, Box<dyn AnyAttribute>)>, attributes: Vec<(String, Box<dyn AnyAttribute>)>,
len: usize, len: usize,
} }
impl TableDyn { impl ListDyn {
/// Number of items in the underlying table. /// Number of items in the underlying `List`.
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.len self.len
} }
/// Whether the underlying table has zero items. /// Whether the underlying `List` has zero items.
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.len == 0 self.len == 0
} }
@@ -471,16 +471,16 @@ impl TableDyn {
} }
} }
impl<T> From<Table<T>> for TableDyn { impl<T> From<List<T>> for ListDyn {
fn from(table: Table<T>) -> Self { fn from(list: List<T>) -> Self {
Self { Self {
attributes: table.attributes.attributes, attributes: list.attributes.attributes,
len: table.attributes.len, len: list.attributes.len,
} }
} }
} }
impl Clone for TableDyn { impl Clone for ListDyn {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
attributes: self.attributes.iter().map(|(key, attribute)| (key.clone(), attribute.clone_box())).collect(), attributes: self.attributes.iter().map(|(key, attribute)| (key.clone(), attribute.clone_box())).collect(),
@@ -489,14 +489,14 @@ impl Clone for TableDyn {
} }
} }
impl Debug for TableDyn { impl Debug for ListDyn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let keys: Vec<&str> = self.attributes.iter().map(|(k, _)| k.as_str()).collect(); let keys: Vec<&str> = self.attributes.iter().map(|(k, _)| k.as_str()).collect();
f.debug_struct("TableDyn").field("keys", &keys).field("len", &self.len).finish() f.debug_struct("ListDyn").field("keys", &keys).field("len", &self.len).finish()
} }
} }
impl PartialEq for TableDyn { impl PartialEq for ListDyn {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.len == other.len self.len == other.len
&& self.attributes.len() == other.attributes.len() && self.attributes.len() == other.attributes.len()
@@ -508,7 +508,7 @@ impl PartialEq for TableDyn {
} }
} }
impl CacheHash for TableDyn { impl CacheHash for ListDyn {
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) { fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.len.cache_hash(state); self.len.cache_hash(state);
for (key, attribute) in &self.attributes { for (key, attribute) in &self.attributes {
@@ -518,7 +518,7 @@ impl CacheHash for TableDyn {
} }
} }
unsafe impl StaticType for TableDyn { unsafe impl StaticType for ListDyn {
type Static = Self; type Static = Self;
} }
@@ -631,7 +631,7 @@ impl ItemAttributeValues {
/// The storage data structure for attributes. /// The storage data structure for attributes.
/// ///
/// A collection of type-erased parallel attributes, keyed by string name. /// A collection of type-erased parallel attributes, keyed by string name.
/// All access goes through [`Table`] and [`Item`] since internals are private. /// All access goes through [`List`] and [`Item`] since internals are private.
/// Invariant: every attribute in `attributes` has exactly `len` elements. /// Invariant: every attribute in `attributes` has exactly `len` elements.
#[derive(Clone, Default)] #[derive(Clone, Default)]
struct Attributes { struct Attributes {
@@ -842,9 +842,9 @@ impl Attributes {
} }
} }
// ======== // =======
// Table<T> // List<T>
// ======== // =======
/// A struct-of-arrays collection where each item holds an element of type `T` alongside /// A struct-of-arrays collection where each item holds an element of type `T` alongside
/// a set of type-erased, dynamically-typed attributes stored in parallel attributes. /// a set of type-erased, dynamically-typed attributes stored in parallel attributes.
@@ -853,18 +853,18 @@ impl Attributes {
/// [`Attributes`] store that keeps one attribute per attribute key. Items are accessed by /// [`Attributes`] store that keeps one attribute per attribute key. Items are accessed by
/// index through element/attribute accessor methods, or consumed as owned [`Item`]s via iteration. /// index through element/attribute accessor methods, or consumed as owned [`Item`]s via iteration.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Table<T> { pub struct List<T> {
element: Vec<T>, element: Vec<T>,
attributes: Attributes, attributes: Attributes,
} }
impl<T> Table<T> { impl<T> List<T> {
/// Creates an empty table with no items. /// Creates an empty list with no items.
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
/// Creates an empty table with pre-allocated capacity for the given number of items. /// Creates an empty list with pre-allocated capacity for the given number of items.
pub fn with_capacity(capacity: usize) -> Self { pub fn with_capacity(capacity: usize) -> Self {
Self { Self {
element: Vec::with_capacity(capacity), element: Vec::with_capacity(capacity),
@@ -872,7 +872,7 @@ impl<T> Table<T> {
} }
} }
/// Creates a table containing a single item with the given element and no attributes. /// Creates a list containing a single item with the given element and no attributes.
pub fn new_from_element(element: T) -> Self { pub fn new_from_element(element: T) -> Self {
Self { Self {
element: vec![element], element: vec![element],
@@ -880,7 +880,7 @@ impl<T> Table<T> {
} }
} }
/// Creates a table containing a single item from the given [`Item`], preserving its attributes. /// Creates a list containing a single item from the given [`Item`], preserving its attributes.
pub fn new_from_item(item: Item<T>) -> Self { pub fn new_from_item(item: Item<T>) -> Self {
let mut attributes = Attributes::new(); let mut attributes = Attributes::new();
attributes.push_item(item.attributes); attributes.push_item(item.attributes);
@@ -890,29 +890,29 @@ impl<T> Table<T> {
} }
} }
/// Appends an item to the end of this table. /// Appends an item to the end of this list.
pub fn push(&mut self, item: Item<T>) { pub fn push(&mut self, item: Item<T>) {
self.element.push(item.element); self.element.push(item.element);
self.attributes.push_item(item.attributes); self.attributes.push_item(item.attributes);
} }
/// Appends all items from another table into this one. /// Appends all items from another list into this one.
pub fn extend(&mut self, table: Table<T>) { pub fn extend(&mut self, list: List<T>) {
self.element.extend(table.element); self.element.extend(list.element);
self.attributes.extend(table.attributes); self.attributes.extend(list.attributes);
} }
/// Returns the number of items in this table. /// Returns the number of items in this list.
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.element.len() self.element.len()
} }
/// Returns `true` if this table contains no items. /// Returns `true` if this list contains no items.
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.element.is_empty() self.element.is_empty()
} }
/// Returns an iterator over all attribute keys in this table, in insertion order. /// Returns an iterator over all attribute keys in this list, in insertion order.
pub fn attribute_keys(&self) -> impl Iterator<Item = &str> { pub fn attribute_keys(&self) -> impl Iterator<Item = &str> {
self.attributes.keys() self.attributes.keys()
} }
@@ -991,7 +991,7 @@ impl<T> Table<T> {
self.attributes.set_value(key, index, value); self.attributes.set_value(key, index, value);
} }
/// Replaces (or adds) an attribute from a type-erased source. The source is wrapped or truncated to match this table's item count. /// Replaces (or adds) an attribute from a type-erased source. The source is wrapped or truncated to match this list's item count.
pub fn set_attribute_dyn(&mut self, key: impl Into<String>, source: AttributeDyn) { pub fn set_attribute_dyn(&mut self, key: impl Into<String>, source: AttributeDyn) {
let key = key.into(); let key = key.into();
self.attributes.attributes.retain(|(k, _)| k != &key); self.attributes.attributes.retain(|(k, _)| k != &key);
@@ -999,7 +999,7 @@ impl<T> Table<T> {
self.attributes.attributes.push((key, new_attribute)); self.attributes.attributes.push((key, new_attribute));
} }
/// Sets a single type-erased attribute value at the given index, creating the attribute from the value's underlying type if it doesn't exist (padded with defaults to match the table's length). /// Sets a single type-erased attribute value at the given index, creating the attribute from the value's underlying type if it doesn't exist (padded with defaults to match the list's length).
/// Falls back to default if the value's type doesn't match an existing attribute. /// Falls back to default if the value's type doesn't match an existing attribute.
pub fn set_attribute_value_dyn(&mut self, key: impl Into<String>, index: usize, value: AttributeValueDyn) { pub fn set_attribute_value_dyn(&mut self, key: impl Into<String>, index: usize, value: AttributeValueDyn) {
let key = key.into(); let key = key.into();
@@ -1069,7 +1069,7 @@ impl<T> Table<T> {
} }
} }
impl<T: BoundingBox> BoundingBox for Table<T> { impl<T: BoundingBox> BoundingBox for List<T> {
/// Computes the combined bounding box of all items, composing each item's transform attribute with the given transform. /// Computes the combined bounding box of all items, composing each item's transform attribute with the given transform.
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox { fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
let mut combined_bounds = None; let mut combined_bounds = None;
@@ -1115,11 +1115,11 @@ impl<T: BoundingBox> BoundingBox for Table<T> {
} }
} }
impl<T> IntoIterator for Table<T> { impl<T> IntoIterator for List<T> {
type Item = Item<T>; type Item = Item<T>;
type IntoIter = ItemIter<T>; type IntoIter = ItemIter<T>;
/// Consumes a [`Table`] and returns an iterator of [`Item`]s, each containing the owned data of the respective item from the original table. /// Consumes a [`List`] and returns an iterator of [`Item`]s, each containing the owned data of the respective item from the original list.
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
let attributes = self.attributes.into_item_vec(); let attributes = self.attributes.into_item_vec();
ItemIter { ItemIter {
@@ -1129,7 +1129,7 @@ impl<T> IntoIterator for Table<T> {
} }
} }
impl<T> Default for Table<T> { impl<T> Default for List<T> {
fn default() -> Self { fn default() -> Self {
Self { Self {
element: Vec::new(), element: Vec::new(),
@@ -1138,7 +1138,7 @@ impl<T> Default for Table<T> {
} }
} }
impl<T: CacheHash> CacheHash for Table<T> { impl<T: CacheHash> CacheHash for List<T> {
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) { fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.element.cache_hash(state); self.element.cache_hash(state);
@@ -1151,7 +1151,7 @@ impl<T: CacheHash> CacheHash for Table<T> {
} }
} }
impl<T: PartialEq> PartialEq for Table<T> { impl<T: PartialEq> PartialEq for List<T> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
// Attributes participate in equality so the `a == b` ⇒ `hash(a) == hash(b)` contract holds with `cache_hash` // Attributes participate in equality so the `a == b` ⇒ `hash(a) == hash(b)` contract holds with `cache_hash`
self.element == other.element self.element == other.element
@@ -1165,7 +1165,7 @@ impl<T: PartialEq> PartialEq for Table<T> {
} }
} }
impl<T> ApplyTransform for Table<T> { impl<T> ApplyTransform for List<T> {
/// Right-multiplies the modification into each item's transform attribute. /// Right-multiplies the modification into each item's transform attribute.
fn apply_transform(&mut self, modification: &DAffine2) { fn apply_transform(&mut self, modification: &DAffine2) {
for transform in self.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) { for transform in self.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
@@ -1181,22 +1181,22 @@ impl<T> ApplyTransform for Table<T> {
} }
} }
unsafe impl<T: StaticTypeSized> StaticType for Table<T> { unsafe impl<T: StaticTypeSized> StaticType for List<T> {
type Static = Table<T::Static>; type Static = List<T::Static>;
} }
impl<T> FromIterator<Item<T>> for Table<T> { impl<T> FromIterator<Item<T>> for List<T> {
/// Collects an iterator of [`Item`]s into a [`Table`], pre-allocating based on the iterator's size hint. /// Collects an iterator of [`Item`]s into a [`List`], pre-allocating based on the iterator's size hint.
fn from_iter<I: IntoIterator<Item = Item<T>>>(iter: I) -> Self { fn from_iter<I: IntoIterator<Item = Item<T>>>(iter: I) -> Self {
let iter = iter.into_iter(); let iter = iter.into_iter();
let (lower_bound, _) = iter.size_hint(); let (lower_bound, _) = iter.size_hint();
let mut table = Self::with_capacity(lower_bound); let mut list = Self::with_capacity(lower_bound);
for item in iter { for item in iter {
table.push(item); list.push(item);
} }
table list
} }
} }
@@ -1206,7 +1206,7 @@ impl<T> FromIterator<Item<T>> for Table<T> {
/// An owned item containing an element of type `T` and a set of type-erased scalar attributes. /// An owned item containing an element of type `T` and a set of type-erased scalar attributes.
/// ///
/// Used to build individual items before pushing them into a [`Table`], or when consuming items out of a table via [`IntoIterator`]. /// Used to build individual items before pushing them into a [`List`], or when consuming items out of a list via [`IntoIterator`].
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Item<T> { pub struct Item<T> {
element: T, element: T,
@@ -1317,9 +1317,9 @@ impl<T> Item<T> {
// ItemIter<T> // ItemIter<T>
// =========== // ===========
/// Owning iterator over the items of a consumed [`Table`], yielding [`Item`]s. /// Owning iterator over the items of a consumed [`List`], yielding [`Item`]s.
/// ///
/// Created by [`Table::into_iter`]. The table's attributes are converted into per-item /// Created by [`List::into_iter`]. The list's attributes are converted into per-item
/// scalar [`ItemAttributeValues`] during construction so each yielded item is self-contained. /// scalar [`ItemAttributeValues`] during construction so each yielded item is self-contained.
pub struct ItemIter<T> { pub struct ItemIter<T> {
element: std::vec::IntoIter<T>, element: std::vec::IntoIter<T>,
+4 -4
View File
@@ -77,12 +77,12 @@ pub fn migrate_to_optional_color<'de, D: serde::Deserializer<'de>>(deserializer:
#[cfg_attr(feature = "serde", serde(untagged))] #[cfg_attr(feature = "serde", serde(untagged))]
enum ColorFormat { enum ColorFormat {
OptionalColor(Option<Color>), OptionalColor(Option<Color>),
Table(LegacyTable<Color>), List(LegacyTable<Color>),
} }
Ok(match ColorFormat::deserialize(deserializer)? { Ok(match ColorFormat::deserialize(deserializer)? {
ColorFormat::OptionalColor(color) => color, ColorFormat::OptionalColor(color) => color,
ColorFormat::Table(table) => table.element.into_iter().next(), ColorFormat::List(list) => list.element.into_iter().next(),
}) })
} }
@@ -94,11 +94,11 @@ pub fn migrate_to_f64_array<'de, D: serde::Deserializer<'de>>(deserializer: D) -
#[cfg_attr(feature = "serde", serde(untagged))] #[cfg_attr(feature = "serde", serde(untagged))]
enum F64ArrayFormat { enum F64ArrayFormat {
Array(Vec<f64>), Array(Vec<f64>),
Table(LegacyTable<f64>), List(LegacyTable<f64>),
} }
Ok(match F64ArrayFormat::deserialize(deserializer)? { Ok(match F64ArrayFormat::deserialize(deserializer)? {
F64ArrayFormat::Array(values) => values, F64ArrayFormat::Array(values) => values,
F64ArrayFormat::Table(table) => table.element, F64ArrayFormat::List(list) => list.element,
}) })
} }
+16 -16
View File
@@ -1,5 +1,5 @@
use crate::Node; use crate::Node;
use crate::table::{Attribute, AttributeDyn, AttributeValueDyn, Item, Table, TableDyn}; use crate::list::{Attribute, AttributeDyn, AttributeValueDyn, Item, List, ListDyn};
use crate::transform::Footprint; use crate::transform::Footprint;
use glam::DVec2; use glam::DVec2;
use graphene_hash::CacheHash; use graphene_hash::CacheHash;
@@ -55,27 +55,27 @@ impl<T: ToString + Send> Convert<String, ()> for T {
} }
} }
pub trait TableConvert<U> { pub trait ListConvert<U> {
fn convert_row(self) -> U; fn convert_row(self) -> U;
} }
impl<U, T: TableConvert<U> + Send> Convert<Table<U>, ()> for Table<T> { impl<U, T: ListConvert<U> + Send> Convert<List<U>, ()> for List<T> {
async fn convert(self, _: Footprint, _: ()) -> Table<U> { async fn convert(self, _: Footprint, _: ()) -> List<U> {
let table: Table<U> = self let list: List<U> = self
.into_iter() .into_iter()
.map(|row| { .map(|row| {
let (element, attributes) = row.into_parts(); let (element, attributes) = row.into_parts();
Item::from_parts(element.convert_row(), attributes) Item::from_parts(element.convert_row(), attributes)
}) })
.collect(); .collect();
table list
} }
} }
/// Wraps each row's element into a type-erased column. Lets nodes that accept a source attribute /// Wraps each row's element into a type-erased attribute. Lets nodes that accept a source attribute
/// from any `Table<U>` express their signature as `AttributeColumnDyn` and avoid monomorphizing /// from any `List<U>` express their signature as `AttributeDyn` and avoid monomorphizing
/// over `U`; the compiler inserts this convert to bridge concrete-typed graph wires to the dyn input. /// over `U`; the compiler inserts this convert to bridge concrete-typed graph wires to the dyn input.
impl<T: Clone + Send + Sync + Default + std::fmt::Debug + PartialEq + CacheHash + 'static> Convert<AttributeDyn, ()> for Table<T> { impl<T: Clone + Send + Sync + Default + std::fmt::Debug + PartialEq + CacheHash + 'static> Convert<AttributeDyn, ()> for List<T> {
async fn convert(self, _: Footprint, _: ()) -> AttributeDyn { async fn convert(self, _: Footprint, _: ()) -> AttributeDyn {
let values: Vec<T> = self.into_iter().map(|row| row.into_element()).collect(); let values: Vec<T> = self.into_iter().map(|row| row.into_element()).collect();
AttributeDyn(Box::new(Attribute(values))) AttributeDyn(Box::new(Attribute(values)))
@@ -83,7 +83,7 @@ impl<T: Clone + Send + Sync + Default + std::fmt::Debug + PartialEq + CacheHash
} }
/// Wraps a value into a type-erased attribute value. Lets nodes that take a per-item value source /// Wraps a value into a type-erased attribute value. Lets nodes that take a per-item value source
/// (such as `write_attribute`'s value-producing input) be generic over the destination table type /// (such as `write_attribute`'s value-producing input) be generic over the destination list type
/// alone, with the compiler-inserted convert handling each concrete value type at the wire level. /// alone, with the compiler-inserted convert handling each concrete value type at the wire level.
impl<T: Clone + Send + Sync + Default + std::fmt::Debug + PartialEq + CacheHash + 'static> Convert<AttributeValueDyn, ()> for T { impl<T: Clone + Send + Sync + Default + std::fmt::Debug + PartialEq + CacheHash + 'static> Convert<AttributeValueDyn, ()> for T {
async fn convert(self, _: Footprint, _: ()) -> AttributeValueDyn { async fn convert(self, _: Footprint, _: ()) -> AttributeValueDyn {
@@ -91,11 +91,11 @@ impl<T: Clone + Send + Sync + Default + std::fmt::Debug + PartialEq + CacheHash
} }
} }
/// Erases a `Table<T>`'s element type, exposing only its attributes and row count. Lets nodes that /// Erases a `List<T>`'s element type, exposing only its attributes and row count. Lets nodes that
/// only need attribute access (such as the `read_attribute_*` family) take a single `TableDyn` input /// only need attribute access (such as the `read_attribute_*` family) take a single `ListDyn` input
/// instead of monomorphizing over every possible carrier table type. /// instead of monomorphizing over every possible carrier list type.
impl<T: Send> Convert<TableDyn, ()> for Table<T> { impl<T: Send> Convert<ListDyn, ()> for List<T> {
async fn convert(self, _: Footprint, _: ()) -> TableDyn { async fn convert(self, _: Footprint, _: ()) -> ListDyn {
self.into() self.into()
} }
} }
@@ -106,7 +106,7 @@ impl Convert<DVec2, ()> for DVec2 {
} }
} }
// TODO: Add a DVec2 to Table<Vector> anchor point conversion implementation to replace the 'Vec2 to Point' node // TODO: Add a DVec2 to List<Vector> anchor point conversion implementation to replace the 'Vec2 to Point' node
/// Implements the [`Convert`] trait for conversion between the cartesian product of Rust's primitive numeric types. /// Implements the [`Convert`] trait for conversion between the cartesian product of Rust's primitive numeric types.
macro_rules! impl_convert { macro_rules! impl_convert {
@@ -1,6 +1,6 @@
// Raster types moved to raster-types crate // Raster types moved to raster-types crate
use crate::Color; use crate::Color;
use crate::table::Table; use crate::list::List;
pub trait RenderComplexity { pub trait RenderComplexity {
fn render_complexity(&self) -> usize { fn render_complexity(&self) -> usize {
@@ -8,7 +8,7 @@ pub trait RenderComplexity {
} }
} }
impl<T: RenderComplexity> RenderComplexity for Table<T> { impl<T: RenderComplexity> RenderComplexity for List<T> {
fn render_complexity(&self) -> usize { fn render_complexity(&self) -> usize {
self.iter_element_values().map(|element| element.render_complexity()).fold(0, usize::saturating_add) self.iter_element_values().map(|element| element.render_complexity()).fold(0, usize::saturating_add)
} }
@@ -1,44 +1,44 @@
use crate::graphic::Graphic; use crate::graphic::Graphic;
use core_types::bounds::{BoundingBox, RenderBoundingBox}; use core_types::bounds::{BoundingBox, RenderBoundingBox};
use core_types::graphene_hash::CacheHash; use core_types::graphene_hash::CacheHash;
use core_types::list::List;
use core_types::render_complexity::RenderComplexity; use core_types::render_complexity::RenderComplexity;
use core_types::table::Table;
use dyn_any::DynAny; use dyn_any::DynAny;
use glam::DAffine2; use glam::DAffine2;
/// Nominal wrapper around `Table<Graphic>` representing a single artboard's content. /// Nominal wrapper around `List<Graphic>` representing a single artboard's content.
/// ///
/// Per-artboard metadata (location, dimensions, background, clip) lives as row attributes on the /// Per-artboard metadata (location, dimensions, background, clip) lives as row attributes on the
/// enclosing `Table<Artboard>`, not as fields here. This keeps `Artboard` a pure type-system boundary /// enclosing `List<Artboard>`, not as fields here. This keeps `Artboard` a pure type-system boundary
/// that prevents arbitrary `Table<Table<...<Graphic>>>` nesting. /// that prevents arbitrary `List<List<...<Graphic>>>` nesting.
#[derive(Clone, Debug, Default, CacheHash, PartialEq, DynAny)] #[derive(Clone, Debug, Default, CacheHash, PartialEq, DynAny)]
pub struct Artboard(Table<Graphic>); pub struct Artboard(List<Graphic>);
impl Artboard { impl Artboard {
pub fn new(content: Table<Graphic>) -> Self { pub fn new(content: List<Graphic>) -> Self {
Self(content) Self(content)
} }
pub fn as_graphic_table(&self) -> &Table<Graphic> { pub fn as_graphic_list(&self) -> &List<Graphic> {
&self.0 &self.0
} }
pub fn as_graphic_table_mut(&mut self) -> &mut Table<Graphic> { pub fn as_graphic_list_mut(&mut self) -> &mut List<Graphic> {
&mut self.0 &mut self.0
} }
pub fn into_graphic_table(self) -> Table<Graphic> { pub fn into_graphic_list(self) -> List<Graphic> {
self.0 self.0
} }
} }
impl From<Table<Graphic>> for Artboard { impl From<List<Graphic>> for Artboard {
fn from(content: Table<Graphic>) -> Self { fn from(content: List<Graphic>) -> Self {
Self(content) Self(content)
} }
} }
impl From<Artboard> for Table<Graphic> { impl From<Artboard> for List<Graphic> {
fn from(artboard: Artboard) -> Self { fn from(artboard: Artboard) -> Self {
artboard.0 artboard.0
} }
+124 -146
View File
@@ -1,8 +1,8 @@
use core_types::bounds::{BoundingBox, RenderBoundingBox}; use core_types::bounds::{BoundingBox, RenderBoundingBox};
use core_types::graphene_hash::CacheHash; use core_types::graphene_hash::CacheHash;
use core_types::ops::TableConvert; use core_types::list::List;
use core_types::ops::ListConvert;
use core_types::render_complexity::RenderComplexity; use core_types::render_complexity::RenderComplexity;
use core_types::table::Table;
use core_types::uuid::NodeId; use core_types::uuid::NodeId;
use core_types::{ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Color}; use core_types::{ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Color};
use dyn_any::DynAny; use dyn_any::DynAny;
@@ -16,45 +16,23 @@ 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)]
pub enum Graphic { pub enum Graphic {
Graphic(Table<Graphic>), Graphic(List<Graphic>),
Vector(Table<Vector>), Vector(List<Vector>),
RasterCPU(Table<Raster<CPU>>), RasterCPU(List<Raster<CPU>>),
RasterGPU(Table<Raster<GPU>>), RasterGPU(List<Raster<GPU>>),
Color(Table<Color>), Color(List<Color>),
Gradient(Table<GradientStops>), Gradient(List<GradientStops>),
} }
impl Default for Graphic { impl Default for Graphic {
fn default() -> Self { fn default() -> Self {
Self::Graphic(Table::new()) Self::Graphic(List::new())
} }
} }
// Explicit `Send`/`Sync` impls. All fields are themselves `Send`/`Sync`, so these would normally
// be inferred, but the type participates in two mutually recursive cycles through `Table<Graphic>`
// and `Table<Vector>` (where `Vector = vector_types::Vector<Option<Table<Graphic>>>`). The second
// path, wrapped in `Option<_>` and a generic type parameter, produces a distinct auto-trait
// obligation that the solver cannot recognize as the same cycle node, causing
// `overflow evaluating the requirement` errors at the workspace's `once_cell::sync::Lazy` statics.
// Providing these impls explicitly anchors the proof and lets the coinductive cache close both cycles.
//
// These can be removed (reverting to auto-derived `Send`/`Sync`) once any of the following holds:
// - We remove the TaggedValue or its variants that contain tables.
// - The `Vector` alias no longer references `Graphic` through a generic type parameter, breaking
// the second cycle so only the direct `Table<Graphic>` self-cycle remains (which the solver
// already handles on its own).
// - `Graphic` stops containing `Table<Graphic>` directly, e.g. by boxing children through a trait
// object or opaque handle so the recursion is no longer structural.
// - A future rustc release improves the auto-trait solver to recognize cycles across generic-
// parameter substitutions. Try deleting these impls and running:
// `cargo check --tests -p graphite-editor`
// If no `overflow evaluating the requirement` errors appear, they're no longer needed).
unsafe impl Send for Graphic {}
unsafe impl Sync for Graphic {}
// Graphic // Graphic
impl From<Table<Graphic>> for Graphic { impl From<List<Graphic>> for Graphic {
fn from(graphic: Table<Graphic>) -> Self { fn from(graphic: List<Graphic>) -> Self {
Graphic::Graphic(graphic) Graphic::Graphic(graphic)
} }
} }
@@ -62,113 +40,113 @@ impl From<Table<Graphic>> for Graphic {
// Vector // Vector
impl From<Vector> for Graphic { impl From<Vector> for Graphic {
fn from(vector: Vector) -> Self { fn from(vector: Vector) -> Self {
Graphic::Vector(Table::new_from_element(vector)) Graphic::Vector(List::new_from_element(vector))
} }
} }
impl From<Table<Vector>> for Graphic { impl From<List<Vector>> for Graphic {
fn from(vector: Table<Vector>) -> Self { fn from(vector: List<Vector>) -> Self {
Graphic::Vector(vector) Graphic::Vector(vector)
} }
} }
// Note: Table<Vector> -> Table<Graphic> conversion handled by blanket impl in gcore // Note: List<Vector> -> List<Graphic> conversion handled by blanket impl in gcore
// Raster<CPU> // Raster<CPU>
impl From<Raster<CPU>> for Graphic { impl From<Raster<CPU>> for Graphic {
fn from(raster: Raster<CPU>) -> Self { fn from(raster: Raster<CPU>) -> Self {
Graphic::RasterCPU(Table::new_from_element(raster)) Graphic::RasterCPU(List::new_from_element(raster))
} }
} }
impl From<Table<Raster<CPU>>> for Graphic { impl From<List<Raster<CPU>>> for Graphic {
fn from(raster: Table<Raster<CPU>>) -> Self { fn from(raster: List<Raster<CPU>>) -> Self {
Graphic::RasterCPU(raster) Graphic::RasterCPU(raster)
} }
} }
// Note: Table conversions handled by blanket impl in gcore // Note: List conversions handled by blanket impl in gcore
// Raster<GPU> // Raster<GPU>
impl From<Raster<GPU>> for Graphic { impl From<Raster<GPU>> for Graphic {
fn from(raster: Raster<GPU>) -> Self { fn from(raster: Raster<GPU>) -> Self {
Graphic::RasterGPU(Table::new_from_element(raster)) Graphic::RasterGPU(List::new_from_element(raster))
} }
} }
impl From<Table<Raster<GPU>>> for Graphic { impl From<List<Raster<GPU>>> for Graphic {
fn from(raster: Table<Raster<GPU>>) -> Self { fn from(raster: List<Raster<GPU>>) -> Self {
Graphic::RasterGPU(raster) Graphic::RasterGPU(raster)
} }
} }
// Note: Table conversions handled by blanket impl in gcore // Note: List conversions handled by blanket impl in gcore
// Color // Color
impl From<Color> for Graphic { impl From<Color> for Graphic {
fn from(color: Color) -> Self { fn from(color: Color) -> Self {
Graphic::Color(Table::new_from_element(color)) Graphic::Color(List::new_from_element(color))
} }
} }
impl From<Table<Color>> for Graphic { impl From<List<Color>> for Graphic {
fn from(color: Table<Color>) -> Self { fn from(color: List<Color>) -> Self {
Graphic::Color(color) Graphic::Color(color)
} }
} }
// Note: Table conversions handled by blanket impl in gcore // Note: List conversions handled by blanket impl in gcore
// Note: Table<Color> -> Option<Color> is in gcore (Color is defined there) // Note: List<Color> -> Option<Color> is in gcore (Color is defined there)
// GradientStops // GradientStops
impl From<GradientStops> for Graphic { impl From<GradientStops> for Graphic {
fn from(gradient: GradientStops) -> Self { fn from(gradient: GradientStops) -> Self {
Graphic::Gradient(Table::new_from_element(gradient)) Graphic::Gradient(List::new_from_element(gradient))
} }
} }
impl From<Table<GradientStops>> for Graphic { impl From<List<GradientStops>> for Graphic {
fn from(gradient: Table<GradientStops>) -> Self { fn from(gradient: List<GradientStops>) -> Self {
Graphic::Gradient(gradient) Graphic::Gradient(gradient)
} }
} }
/// Deeply flattens a `Table<Graphic>`, collecting only elements matching a specific variant (extracted by `extract_variant`) /// Deeply flattens a `List<Graphic>`, collecting only elements matching a specific variant (extracted by `extract_variant`)
/// and discarding all other non-matching content. Recursion through `Graphic::Graphic` sub-`Table`s composes transforms and opacity. /// and discarding all other non-matching content. Recursion through `Graphic::Graphic` sub-`List`s composes transforms and opacity.
fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) -> Table<T> { fn flatten_graphic_list<T>(content: List<Graphic>, extract_variant: fn(Graphic) -> Option<List<T>>) -> List<T> {
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 List<T>, current_graphic_list: List<Graphic>, extract_variant: fn(Graphic) -> Option<List<T>>) {
for current_graphic_row in current_graphic_table.into_iter() { for current_graphic_row in current_graphic_list.into_iter() {
let layer_path: Table<NodeId> = current_graphic_row.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH); let layer_path: List<NodeId> = current_graphic_row.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH);
let current_transform: DAffine2 = current_graphic_row.attribute_cloned_or_default(ATTR_TRANSFORM); let current_transform: DAffine2 = current_graphic_row.attribute_cloned_or_default(ATTR_TRANSFORM);
let current_opacity: f64 = current_graphic_row.attribute_cloned_or(ATTR_OPACITY, 1.); let current_opacity: f64 = current_graphic_row.attribute_cloned_or(ATTR_OPACITY, 1.);
let current_fill: f64 = current_graphic_row.attribute_cloned_or(ATTR_OPACITY_FILL, 1.); let current_fill: f64 = current_graphic_row.attribute_cloned_or(ATTR_OPACITY_FILL, 1.);
match current_graphic_row.into_element() { match current_graphic_row.into_element() {
// Compose the parent's transform, opacity, and fill onto each child row // Compose the parent's transform, opacity, and fill onto each child row
Graphic::Graphic(mut sub_table) => { Graphic::Graphic(mut sub_list) => {
// Identity default means a missing column still composes correctly // Identity default means a missing attribute still composes correctly
for v in sub_table.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) { for v in sub_list.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
*v = current_transform * *v; *v = current_transform * *v;
} }
// f64 defaults to 0, but opacity/fill default to 1, so missing columns must be set rather than multiplied // f64 defaults to 0, but opacity/fill default to 1, so missing attributes must be set rather than multiplied
if let Some(values) = sub_table.iter_attribute_values_mut::<f64>(ATTR_OPACITY) { if let Some(values) = sub_list.iter_attribute_values_mut::<f64>(ATTR_OPACITY) {
for v in values { for v in values {
*v *= current_opacity; *v *= current_opacity;
} }
} else { } else {
for v in sub_table.iter_attribute_values_mut_or_default::<f64>(ATTR_OPACITY) { for v in sub_list.iter_attribute_values_mut_or_default::<f64>(ATTR_OPACITY) {
*v = current_opacity; *v = current_opacity;
} }
} }
if let Some(values) = sub_table.iter_attribute_values_mut::<f64>(ATTR_OPACITY_FILL) { if let Some(values) = sub_list.iter_attribute_values_mut::<f64>(ATTR_OPACITY_FILL) {
for v in values { for v in values {
*v *= current_fill; *v *= current_fill;
} }
} else { } else {
for v in sub_table.iter_attribute_values_mut_or_default::<f64>(ATTR_OPACITY_FILL) { for v in sub_list.iter_attribute_values_mut_or_default::<f64>(ATTR_OPACITY_FILL) {
*v = current_fill; *v = current_fill;
} }
} }
flatten_recursive(output, sub_table, extract_variant); flatten_recursive(output, sub_list, extract_variant);
} }
// Extract the target variant and push its items with composed transform, opacity, and fill // Extract the target variant and push its items with composed transform, opacity, and fill
other => { other => {
if let Some(typed_table) = extract_variant(other) { if let Some(typed_list) = extract_variant(other) {
for mut item in typed_table.into_iter() { for mut item in typed_list.into_iter() {
let row_transform: DAffine2 = item.attribute_cloned_or_default(ATTR_TRANSFORM); let row_transform: DAffine2 = item.attribute_cloned_or_default(ATTR_TRANSFORM);
let row_opacity: f64 = item.attribute_cloned_or(ATTR_OPACITY, 1.); let row_opacity: f64 = item.attribute_cloned_or(ATTR_OPACITY, 1.);
let row_fill: f64 = item.attribute_cloned_or(ATTR_OPACITY_FILL, 1.); let row_fill: f64 = item.attribute_cloned_or(ATTR_OPACITY_FILL, 1.);
@@ -186,100 +164,100 @@ fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic
} }
} }
let mut output = Table::new(); let mut output = List::new();
flatten_recursive(&mut output, content, extract_variant); flatten_recursive(&mut output, content, extract_variant);
output output
} }
/// Maps from a concrete element type to its corresponding `Graphic` enum variant, /// Maps from a concrete element type to its corresponding `Graphic` enum variant,
/// enabling type-directed casting of typed `Table`s from a `Graphic` value. /// enabling type-directed casting of typed `List`s from a `Graphic` value.
pub trait TryFromGraphic: Clone + Sized { pub trait TryFromGraphic: Clone + Sized {
fn try_from_graphic(graphic: Graphic) -> Option<Table<Self>>; fn try_from_graphic(graphic: Graphic) -> Option<List<Self>>;
} }
impl TryFromGraphic for Vector { impl TryFromGraphic for Vector {
fn try_from_graphic(graphic: Graphic) -> Option<Table<Self>> { fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
if let Graphic::Vector(t) = graphic { Some(t) } else { None } if let Graphic::Vector(t) = graphic { Some(t) } else { None }
} }
} }
impl TryFromGraphic for Raster<CPU> { impl TryFromGraphic for Raster<CPU> {
fn try_from_graphic(graphic: Graphic) -> Option<Table<Self>> { fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
if let Graphic::RasterCPU(t) = graphic { Some(t) } else { None } if let Graphic::RasterCPU(t) = graphic { Some(t) } else { None }
} }
} }
impl TryFromGraphic for Color { impl TryFromGraphic for Color {
fn try_from_graphic(graphic: Graphic) -> Option<Table<Self>> { fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
if let Graphic::Color(t) = graphic { Some(t) } else { None } if let Graphic::Color(t) = graphic { Some(t) } else { None }
} }
} }
impl TryFromGraphic for GradientStops { impl TryFromGraphic for GradientStops {
fn try_from_graphic(graphic: Graphic) -> Option<Table<Self>> { fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
if let Graphic::Gradient(t) = graphic { Some(t) } else { None } if let Graphic::Gradient(t) = graphic { Some(t) } else { None }
} }
} }
// Local trait to convert types to Table<Graphic> (avoids orphan rule issues) // Local trait to convert types to List<Graphic> (avoids orphan rule issues)
pub trait IntoGraphicTable { pub trait IntoGraphicList {
fn into_graphic_table(self) -> Table<Graphic>; fn into_graphic_list(self) -> List<Graphic>;
/// Deeply flattens any content of type `T` within a `Table<Graphic>`, discarding all other content, and returning a flat `Table<T>`. /// Deeply flattens any content of type `T` within a `List<Graphic>`, discarding all other content, and returning a flat `List<T>`.
fn into_flattened_table<T: TryFromGraphic>(self) -> Table<T> fn into_flattened_list<T: TryFromGraphic>(self) -> List<T>
where where
Self: std::marker::Sized, Self: std::marker::Sized,
{ {
flatten_graphic_table(self.into_graphic_table(), T::try_from_graphic) flatten_graphic_list(self.into_graphic_list(), T::try_from_graphic)
} }
} }
impl IntoGraphicTable for Table<Graphic> { impl IntoGraphicList for List<Graphic> {
fn into_graphic_table(self) -> Table<Graphic> { fn into_graphic_list(self) -> List<Graphic> {
self self
} }
} }
impl IntoGraphicTable for Table<Vector> { impl IntoGraphicList for List<Vector> {
fn into_graphic_table(self) -> Table<Graphic> { fn into_graphic_list(self) -> List<Graphic> {
// Propagate `editor:layer_path` from item 0 onto the wrapper Graphic row so a subsequent // Propagate `editor:layer_path` from item 0 onto the wrapper Graphic row so a subsequent
// `flatten_graphic_table` doesn't overwrite the inner Vector's stamp with an empty value // `flatten_graphic_list` doesn't overwrite the inner Vector's stamp with an empty value
let layer_path: Table<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0); let layer_path: List<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0);
let mut graphic_table = Table::new_from_element(Graphic::Vector(self)); let mut graphic_list = List::new_from_element(Graphic::Vector(self));
if !layer_path.is_empty() { if !layer_path.is_empty() {
graphic_table.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer_path); graphic_list.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer_path);
} }
graphic_table graphic_list
} }
} }
impl IntoGraphicTable for Table<Raster<CPU>> { impl IntoGraphicList for List<Raster<CPU>> {
fn into_graphic_table(self) -> Table<Graphic> { fn into_graphic_list(self) -> List<Graphic> {
Table::new_from_element(Graphic::RasterCPU(self)) List::new_from_element(Graphic::RasterCPU(self))
} }
} }
impl IntoGraphicTable for Table<Raster<GPU>> { impl IntoGraphicList for List<Raster<GPU>> {
fn into_graphic_table(self) -> Table<Graphic> { fn into_graphic_list(self) -> List<Graphic> {
Table::new_from_element(Graphic::RasterGPU(self)) List::new_from_element(Graphic::RasterGPU(self))
} }
} }
impl IntoGraphicTable for Table<Color> { impl IntoGraphicList for List<Color> {
fn into_graphic_table(self) -> Table<Graphic> { fn into_graphic_list(self) -> List<Graphic> {
Table::new_from_element(Graphic::Color(self)) List::new_from_element(Graphic::Color(self))
} }
} }
impl IntoGraphicTable for Table<GradientStops> { impl IntoGraphicList for List<GradientStops> {
fn into_graphic_table(self) -> Table<Graphic> { fn into_graphic_list(self) -> List<Graphic> {
Table::new_from_element(Graphic::Gradient(self)) List::new_from_element(Graphic::Gradient(self))
} }
} }
impl IntoGraphicTable for DAffine2 { impl IntoGraphicList for DAffine2 {
fn into_graphic_table(self) -> Table<Graphic> { fn into_graphic_list(self) -> List<Graphic> {
Table::new_from_element(Graphic::default()) List::new_from_element(Graphic::default())
} }
} }
@@ -289,45 +267,45 @@ impl From<DAffine2> for Graphic {
Graphic::default() Graphic::default()
} }
} }
// Note: Table conversions handled by blanket impl in gcore // Note: List conversions handled by blanket impl in gcore
impl Graphic { impl Graphic {
pub fn as_graphic(&self) -> Option<&Table<Graphic>> { pub fn as_graphic(&self) -> Option<&List<Graphic>> {
match self { match self {
Graphic::Graphic(graphic) => Some(graphic), Graphic::Graphic(graphic) => Some(graphic),
_ => None, _ => None,
} }
} }
pub fn as_graphic_mut(&mut self) -> Option<&mut Table<Graphic>> { pub fn as_graphic_mut(&mut self) -> Option<&mut List<Graphic>> {
match self { match self {
Graphic::Graphic(graphic) => Some(graphic), Graphic::Graphic(graphic) => Some(graphic),
_ => None, _ => None,
} }
} }
pub fn as_vector(&self) -> Option<&Table<Vector>> { pub fn as_vector(&self) -> Option<&List<Vector>> {
match self { match self {
Graphic::Vector(vector) => Some(vector), Graphic::Vector(vector) => Some(vector),
_ => None, _ => None,
} }
} }
pub fn as_vector_mut(&mut self) -> Option<&mut Table<Vector>> { pub fn as_vector_mut(&mut self) -> Option<&mut List<Vector>> {
match self { match self {
Graphic::Vector(vector) => Some(vector), Graphic::Vector(vector) => Some(vector),
_ => None, _ => None,
} }
} }
pub fn as_raster(&self) -> Option<&Table<Raster<CPU>>> { pub fn as_raster(&self) -> Option<&List<Raster<CPU>>> {
match self { match self {
Graphic::RasterCPU(raster) => Some(raster), Graphic::RasterCPU(raster) => Some(raster),
_ => None, _ => None,
} }
} }
pub fn as_raster_mut(&mut self) -> Option<&mut Table<Raster<CPU>>> { pub fn as_raster_mut(&mut self) -> Option<&mut List<Raster<CPU>>> {
match self { match self {
Graphic::RasterCPU(raster) => Some(raster), Graphic::RasterCPU(raster) => Some(raster),
_ => None, _ => None,
@@ -335,17 +313,17 @@ impl Graphic {
} }
pub fn had_clip_enabled(&self) -> bool { pub fn had_clip_enabled(&self) -> bool {
fn all_clipped<T>(table: &Table<T>) -> bool { fn all_clipped<T>(list: &List<T>) -> bool {
table.iter_attribute_values_or_default::<bool>(ATTR_CLIPPING_MASK).all(|clip| clip) list.iter_attribute_values_or_default::<bool>(ATTR_CLIPPING_MASK).all(|clip| clip)
} }
match self { match self {
Graphic::Vector(table) => all_clipped(table), Graphic::Vector(list) => all_clipped(list),
Graphic::Graphic(table) => all_clipped(table), Graphic::Graphic(list) => all_clipped(list),
Graphic::RasterCPU(table) => all_clipped(table), Graphic::RasterCPU(list) => all_clipped(list),
Graphic::RasterGPU(table) => all_clipped(table), Graphic::RasterGPU(list) => all_clipped(list),
Graphic::Color(table) => all_clipped(table), Graphic::Color(list) => all_clipped(list),
Graphic::Gradient(table) => all_clipped(table), Graphic::Gradient(list) => all_clipped(list),
} }
} }
@@ -364,12 +342,12 @@ impl Graphic {
impl BoundingBox for Graphic { impl BoundingBox for Graphic {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox { fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
match self { match self {
Graphic::Vector(table) => table.bounding_box(transform, include_stroke), Graphic::Vector(list) => list.bounding_box(transform, include_stroke),
Graphic::RasterCPU(table) => table.bounding_box(transform, include_stroke), Graphic::RasterCPU(list) => list.bounding_box(transform, include_stroke),
Graphic::RasterGPU(table) => table.bounding_box(transform, include_stroke), Graphic::RasterGPU(list) => list.bounding_box(transform, include_stroke),
Graphic::Graphic(table) => table.bounding_box(transform, include_stroke), Graphic::Graphic(list) => list.bounding_box(transform, include_stroke),
Graphic::Color(table) => table.bounding_box(transform, include_stroke), Graphic::Color(list) => list.bounding_box(transform, include_stroke),
Graphic::Gradient(table) => table.bounding_box(transform, include_stroke), Graphic::Gradient(list) => list.bounding_box(transform, include_stroke),
} }
} }
@@ -385,31 +363,31 @@ impl BoundingBox for Graphic {
} }
} }
impl TableConvert<Graphic> for Vector { impl ListConvert<Graphic> for Vector {
fn convert_row(self) -> Graphic { fn convert_row(self) -> Graphic {
Graphic::Vector(Table::new_from_element(self)) Graphic::Vector(List::new_from_element(self))
} }
} }
impl TableConvert<Graphic> for Raster<CPU> { impl ListConvert<Graphic> for Raster<CPU> {
fn convert_row(self) -> Graphic { fn convert_row(self) -> Graphic {
Graphic::RasterCPU(Table::new_from_element(self)) Graphic::RasterCPU(List::new_from_element(self))
} }
} }
impl TableConvert<Graphic> for Raster<GPU> { impl ListConvert<Graphic> for Raster<GPU> {
fn convert_row(self) -> Graphic { fn convert_row(self) -> Graphic {
Graphic::RasterGPU(Table::new_from_element(self)) Graphic::RasterGPU(List::new_from_element(self))
} }
} }
impl RenderComplexity for Graphic { impl RenderComplexity for Graphic {
fn render_complexity(&self) -> usize { fn render_complexity(&self) -> usize {
match self { match self {
Self::Graphic(table) => table.render_complexity(), Self::Graphic(list) => list.render_complexity(),
Self::Vector(table) => table.render_complexity(), Self::Vector(list) => list.render_complexity(),
Self::RasterCPU(table) => table.render_complexity(), Self::RasterCPU(list) => list.render_complexity(),
Self::RasterGPU(table) => table.render_complexity(), Self::RasterGPU(list) => list.render_complexity(),
Self::Color(table) => table.render_complexity(), Self::Color(list) => list.render_complexity(),
Self::Gradient(table) => table.render_complexity(), Self::Gradient(list) => list.render_complexity(),
} }
} }
} }
@@ -432,14 +410,14 @@ impl<T: Clone> AtIndex for Vec<T> {
if index == 0 || index > self.len() { None } else { self.get(self.len() - index).cloned() } if index == 0 || index > self.len() { None } else { self.get(self.len() - index).cloned() }
} }
} }
impl<T: Clone> AtIndex for Table<T> { impl<T: Clone> AtIndex for List<T> {
type Output = Table<T>; type Output = List<T>;
fn at_index(&self, index: usize) -> Option<Self::Output> { fn at_index(&self, index: usize) -> Option<Self::Output> {
self.clone_item(index).map(|row| { self.clone_item(index).map(|row| {
let mut result_table = Self::default(); let mut result_list = Self::default();
result_table.push(row); result_list.push(row);
result_table result_list
}) })
} }
@@ -464,7 +442,7 @@ impl<T: Clone> OmitIndex for Vec<T> {
self.omit_index(self.len() - index) self.omit_index(self.len() - index)
} }
} }
impl<T: Clone> OmitIndex for Table<T> { impl<T: Clone> OmitIndex for List<T> {
fn omit_index(&self, index: usize) -> Self { fn omit_index(&self, index: usize) -> Self {
let mut result = Self::default(); let mut result = Self::default();
for i in 0..self.len() { for i in 0..self.len() {
@@ -8,7 +8,7 @@ pub use vector_types;
// Re-export commonly used types at the crate root // Re-export commonly used types at the crate root
pub use artboard::Artboard; pub use artboard::Artboard;
pub use graphic::{Graphic, IntoGraphicTable, TryFromGraphic, Vector}; pub use graphic::{Graphic, IntoGraphicList, TryFromGraphic, Vector};
pub mod migrations { pub mod migrations {
use vector_types::vector::{PathStyle, PointDomain, RegionDomain, SegmentDomain, misc::HandleId}; use vector_types::vector::{PathStyle, PointDomain, RegionDomain, SegmentDomain, misc::HandleId};
@@ -16,11 +16,11 @@ pub mod migrations {
use crate::Vector; use crate::Vector;
// TODO: Eventually remove this migration document upgrade code // TODO: Eventually remove this migration document upgrade code
/// Returns the first `Vector` recovered from any of the legacy on-disk shapes (a single `Vector`, the old `OldVectorData` flat struct, or any of the historical `Table<Vector>` variants). /// Returns the first `Vector` recovered from any of the legacy on-disk shapes (a single `Vector`, the old `OldVectorData` flat struct, or any of the historical `List<Vector>` variants).
pub fn migrate_to_optional_vector<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Option<Vector>, D::Error> { pub fn migrate_to_optional_vector<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Option<Vector>, D::Error> {
use serde::Deserialize; use serde::Deserialize;
/// Old documents stored a `Vector` flattened with table attributes (`transform`, `alpha_blending`, `upstream_graphic_group`); only the geometry fields are recovered. /// Old documents stored a `Vector` flattened with list attributes (`transform`, `alpha_blending`, `upstream_graphic_group`); only the geometry fields are recovered.
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
struct OldVectorData { struct OldVectorData {
style: PathStyle, style: PathStyle,
@@ -42,7 +42,7 @@ pub mod migrations {
enum VectorFormat { enum VectorFormat {
Vector(Vector), Vector(Vector),
OldVectorData(OldVectorData), OldVectorData(OldVectorData),
Table(LegacyTable), List(LegacyTable),
} }
Ok(match VectorFormat::deserialize(deserializer)? { Ok(match VectorFormat::deserialize(deserializer)? {
@@ -54,7 +54,7 @@ pub mod migrations {
segment_domain: old.segment_domain, segment_domain: old.segment_domain,
region_domain: old.region_domain, region_domain: old.region_domain,
}), }),
VectorFormat::Table(table) => table.element.into_iter().next(), VectorFormat::List(list) => list.element.into_iter().next(),
}) })
} }
} }
+86 -86
View File
@@ -5,9 +5,9 @@ use core_types::blending::BlendMode;
use core_types::bounds::BoundingBox; use core_types::bounds::BoundingBox;
use core_types::bounds::RenderBoundingBox; use core_types::bounds::RenderBoundingBox;
use core_types::color::Color; use core_types::color::Color;
use core_types::list::{Item, List};
use core_types::math::quad::Quad; use core_types::math::quad::Quad;
use core_types::render_complexity::RenderComplexity; use core_types::render_complexity::RenderComplexity;
use core_types::table::{Item, Table};
use core_types::transform::Footprint; use core_types::transform::Footprint;
use core_types::uuid::{NodeId, generate_uuid}; use core_types::uuid::{NodeId, generate_uuid};
use core_types::{ use core_types::{
@@ -402,7 +402,7 @@ pub trait Render: BoundingBox + RenderComplexity {
/// The upstream click targets for each layer are collected during the render so that they do not have to be calculated for each click detection. /// The upstream click targets for each layer are collected during the render so that they do not have to be calculated for each click detection.
fn add_upstream_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {} fn add_upstream_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
/// Like `add_upstream_click_targets` but for visual outlines. `Table<Vector>` overrides this to ignore `editor:click_target` so outlines reflect the actual geometry. /// Like `add_upstream_click_targets` but for visual outlines. `List<Vector>` overrides this to ignore `editor:click_target` so outlines reflect the actual geometry.
fn add_upstream_outline_targets(&self, outlines: &mut Vec<ClickTarget>) { fn add_upstream_outline_targets(&self, outlines: &mut Vec<ClickTarget>) {
self.add_upstream_click_targets(outlines); self.add_upstream_click_targets(outlines);
} }
@@ -423,23 +423,23 @@ pub trait Render: BoundingBox + RenderComplexity {
impl Render for Graphic { impl Render for Graphic {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
match self { match self {
Graphic::Graphic(table) => table.render_svg(render, render_params), Graphic::Graphic(list) => list.render_svg(render, render_params),
Graphic::Vector(table) => table.render_svg(render, render_params), Graphic::Vector(list) => list.render_svg(render, render_params),
Graphic::RasterCPU(table) => table.render_svg(render, render_params), Graphic::RasterCPU(list) => list.render_svg(render, render_params),
Graphic::RasterGPU(_) => (), Graphic::RasterGPU(_) => (),
Graphic::Color(table) => table.render_svg(render, render_params), Graphic::Color(list) => list.render_svg(render, render_params),
Graphic::Gradient(table) => table.render_svg(render, render_params), Graphic::Gradient(list) => list.render_svg(render, render_params),
} }
} }
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) { fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
match self { match self {
Graphic::Graphic(table) => table.render_to_vello(scene, transform, context, render_params), Graphic::Graphic(list) => list.render_to_vello(scene, transform, context, render_params),
Graphic::Vector(table) => table.render_to_vello(scene, transform, context, render_params), Graphic::Vector(list) => list.render_to_vello(scene, transform, context, render_params),
Graphic::RasterCPU(table) => table.render_to_vello(scene, transform, context, render_params), Graphic::RasterCPU(list) => list.render_to_vello(scene, transform, context, render_params),
Graphic::RasterGPU(table) => table.render_to_vello(scene, transform, context, render_params), Graphic::RasterGPU(list) => list.render_to_vello(scene, transform, context, render_params),
Graphic::Color(table) => table.render_to_vello(scene, transform, context, render_params), Graphic::Color(list) => list.render_to_vello(scene, transform, context, render_params),
Graphic::Gradient(table) => table.render_to_vello(scene, transform, context, render_params), Graphic::Gradient(list) => list.render_to_vello(scene, transform, context, render_params),
} }
} }
@@ -449,100 +449,100 @@ impl Render for Graphic {
Graphic::Graphic(_) => { Graphic::Graphic(_) => {
metadata.upstream_footprints.insert(element_id, footprint); metadata.upstream_footprints.insert(element_id, footprint);
} }
Graphic::Vector(table) => { Graphic::Vector(list) => {
metadata.upstream_footprints.insert(element_id, footprint); metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than the first item // TODO: Find a way to handle more than the first item
if !table.is_empty() { if !list.is_empty() {
let layer_path: Table<NodeId> = table.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0); let layer_path: List<NodeId> = list.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0);
let layer = layer_path.iter_element_values().next_back().copied(); let layer = layer_path.iter_element_values().next_back().copied();
let transform: DAffine2 = table.attribute_cloned_or_default(ATTR_TRANSFORM, 0); let transform: DAffine2 = list.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
metadata.first_element_source_id.insert(element_id, layer); metadata.first_element_source_id.insert(element_id, layer);
metadata.local_transforms.insert(element_id, transform); metadata.local_transforms.insert(element_id, transform);
} }
} }
Graphic::RasterCPU(table) => { Graphic::RasterCPU(list) => {
metadata.upstream_footprints.insert(element_id, footprint); metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than the first item // TODO: Find a way to handle more than the first item
if !table.is_empty() { if !list.is_empty() {
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0)); metadata.local_transforms.insert(element_id, list.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
} }
} }
Graphic::RasterGPU(table) => { Graphic::RasterGPU(list) => {
metadata.upstream_footprints.insert(element_id, footprint); metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than the first item // TODO: Find a way to handle more than the first item
if !table.is_empty() { if !list.is_empty() {
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0)); metadata.local_transforms.insert(element_id, list.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
} }
} }
Graphic::Color(table) => { Graphic::Color(list) => {
metadata.upstream_footprints.insert(element_id, footprint); metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than the first item // TODO: Find a way to handle more than the first item
if !table.is_empty() { if !list.is_empty() {
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0)); metadata.local_transforms.insert(element_id, list.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
} }
} }
Graphic::Gradient(table) => { Graphic::Gradient(list) => {
metadata.upstream_footprints.insert(element_id, footprint); metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than the first item // TODO: Find a way to handle more than the first item
if !table.is_empty() { if !list.is_empty() {
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0)); metadata.local_transforms.insert(element_id, list.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
} }
} }
} }
} }
match self { match self {
Graphic::Graphic(table) => table.collect_metadata(metadata, footprint, element_id), Graphic::Graphic(list) => list.collect_metadata(metadata, footprint, element_id),
Graphic::Vector(table) => table.collect_metadata(metadata, footprint, element_id), Graphic::Vector(list) => list.collect_metadata(metadata, footprint, element_id),
Graphic::RasterCPU(table) => table.collect_metadata(metadata, footprint, element_id), Graphic::RasterCPU(list) => list.collect_metadata(metadata, footprint, element_id),
Graphic::RasterGPU(table) => table.collect_metadata(metadata, footprint, element_id), Graphic::RasterGPU(list) => list.collect_metadata(metadata, footprint, element_id),
Graphic::Color(table) => table.collect_metadata(metadata, footprint, element_id), Graphic::Color(list) => list.collect_metadata(metadata, footprint, element_id),
Graphic::Gradient(table) => table.collect_metadata(metadata, footprint, element_id), Graphic::Gradient(list) => list.collect_metadata(metadata, footprint, element_id),
} }
} }
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
match self { match self {
Graphic::Graphic(table) => table.add_upstream_click_targets(click_targets), Graphic::Graphic(list) => list.add_upstream_click_targets(click_targets),
Graphic::Vector(table) => table.add_upstream_click_targets(click_targets), Graphic::Vector(list) => list.add_upstream_click_targets(click_targets),
Graphic::RasterCPU(table) => table.add_upstream_click_targets(click_targets), Graphic::RasterCPU(list) => list.add_upstream_click_targets(click_targets),
Graphic::RasterGPU(table) => table.add_upstream_click_targets(click_targets), Graphic::RasterGPU(list) => list.add_upstream_click_targets(click_targets),
Graphic::Color(table) => table.add_upstream_click_targets(click_targets), Graphic::Color(list) => list.add_upstream_click_targets(click_targets),
Graphic::Gradient(table) => table.add_upstream_click_targets(click_targets), Graphic::Gradient(list) => list.add_upstream_click_targets(click_targets),
} }
} }
fn add_upstream_outline_targets(&self, outlines: &mut Vec<ClickTarget>) { fn add_upstream_outline_targets(&self, outlines: &mut Vec<ClickTarget>) {
match self { match self {
Graphic::Graphic(table) => table.add_upstream_outline_targets(outlines), Graphic::Graphic(list) => list.add_upstream_outline_targets(outlines),
Graphic::Vector(table) => table.add_upstream_outline_targets(outlines), Graphic::Vector(list) => list.add_upstream_outline_targets(outlines),
Graphic::RasterCPU(table) => table.add_upstream_outline_targets(outlines), Graphic::RasterCPU(list) => list.add_upstream_outline_targets(outlines),
Graphic::RasterGPU(table) => table.add_upstream_outline_targets(outlines), Graphic::RasterGPU(list) => list.add_upstream_outline_targets(outlines),
Graphic::Color(table) => table.add_upstream_outline_targets(outlines), Graphic::Color(list) => list.add_upstream_outline_targets(outlines),
Graphic::Gradient(table) => table.add_upstream_outline_targets(outlines), Graphic::Gradient(list) => list.add_upstream_outline_targets(outlines),
} }
} }
fn contains_artboard(&self) -> bool { fn contains_artboard(&self) -> bool {
match self { match self {
Graphic::Graphic(table) => table.contains_artboard(), Graphic::Graphic(list) => list.contains_artboard(),
Graphic::Vector(table) => table.contains_artboard(), Graphic::Vector(list) => list.contains_artboard(),
Graphic::RasterCPU(table) => table.contains_artboard(), Graphic::RasterCPU(list) => list.contains_artboard(),
Graphic::RasterGPU(table) => table.contains_artboard(), Graphic::RasterGPU(list) => list.contains_artboard(),
Graphic::Color(table) => table.contains_artboard(), Graphic::Color(list) => list.contains_artboard(),
Graphic::Gradient(table) => table.contains_artboard(), Graphic::Gradient(list) => list.contains_artboard(),
} }
} }
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) { fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
match self { match self {
Graphic::Graphic(table) => table.new_ids_from_hash(reference), Graphic::Graphic(list) => list.new_ids_from_hash(reference),
Graphic::Vector(table) => table.new_ids_from_hash(reference), Graphic::Vector(list) => list.new_ids_from_hash(reference),
Graphic::RasterCPU(_) => (), Graphic::RasterCPU(_) => (),
Graphic::RasterGPU(_) => (), Graphic::RasterGPU(_) => (),
Graphic::Color(_) => (), Graphic::Color(_) => (),
@@ -551,19 +551,19 @@ impl Render for Graphic {
} }
} }
/// Reads the artboard metadata for the item at `index` from a `Table<Artboard>`. /// Reads the artboard metadata for the item at `index` from a `List<Artboard>`.
fn read_artboard_attributes(table: &Table<Artboard>, index: usize) -> (DVec2, DVec2, Color, bool) { fn read_artboard_attributes(list: &List<Artboard>, index: usize) -> (DVec2, DVec2, Color, bool) {
let location: DVec2 = table.attribute_cloned_or_default(ATTR_LOCATION, index); let location: DVec2 = list.attribute_cloned_or_default(ATTR_LOCATION, index);
let dimensions: DVec2 = table.attribute_cloned_or_default(ATTR_DIMENSIONS, index); let dimensions: DVec2 = list.attribute_cloned_or_default(ATTR_DIMENSIONS, index);
let background: Color = table.attribute_cloned_or_default(ATTR_BACKGROUND, index); let background: Color = list.attribute_cloned_or_default(ATTR_BACKGROUND, index);
let clip: bool = table.attribute_cloned_or_default(ATTR_CLIP, index); let clip: bool = list.attribute_cloned_or_default(ATTR_CLIP, index);
(location, dimensions, background, clip) (location, dimensions, background, clip)
} }
impl Render for Table<Artboard> { impl Render for List<Artboard> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for index in 0..self.len() { for index in 0..self.len() {
let Some(content) = self.element(index).map(Artboard::as_graphic_table) else { continue }; let Some(content) = self.element(index).map(Artboard::as_graphic_list) else { continue };
let (location, dimensions, background, clip) = read_artboard_attributes(self, index); let (location, dimensions, background, clip) = read_artboard_attributes(self, index);
let x = location.x.min(location.x + dimensions.x); let x = location.x.min(location.x + dimensions.x);
@@ -621,7 +621,7 @@ impl Render for Table<Artboard> {
use vello::peniko; use vello::peniko;
for index in 0..self.len() { for index in 0..self.len() {
let Some(content) = self.element(index).map(Artboard::as_graphic_table) else { continue }; let Some(content) = self.element(index).map(Artboard::as_graphic_list) else { continue };
let (location, dimensions, background, clip) = read_artboard_attributes(self, index); let (location, dimensions, background, clip) = read_artboard_attributes(self, index);
let [a, b] = [location, location + dimensions]; let [a, b] = [location, location + dimensions];
@@ -651,10 +651,10 @@ 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 Some(content) = self.element(index).map(Artboard::as_graphic_table) else { continue }; let Some(content) = self.element(index).map(Artboard::as_graphic_list) else { continue };
let (location, dimensions, _background, clip) = read_artboard_attributes(self, index); let (location, dimensions, _background, clip) = read_artboard_attributes(self, index);
let layer_path: Table<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index); let layer_path: List<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index);
let element_id = layer_path.iter_element_values().next_back().copied(); let element_id = layer_path.iter_element_values().next_back().copied();
if let Some(element_id) = element_id { if let Some(element_id) = element_id {
@@ -688,7 +688,7 @@ impl Render for Table<Artboard> {
} }
} }
impl Render for Table<Graphic> { impl Render for List<Graphic> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
let mut mask_state = None; let mut mask_state = None;
@@ -826,7 +826,7 @@ 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 item_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index); let item_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let layer_path: Table<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index); let layer_path: List<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index);
let layer = layer_path.iter_element_values().next_back().copied(); let layer = layer_path.iter_element_values().next_back().copied();
let element = self.element(index).unwrap(); let element = self.element(index).unwrap();
@@ -908,14 +908,14 @@ 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, layers) = self.element_and_attribute_slices_mut::<Table<NodeId>>(ATTR_EDITOR_LAYER_PATH); let (elements, layers) = self.element_and_attribute_slices_mut::<List<NodeId>>(ATTR_EDITOR_LAYER_PATH);
for (element, layer) in elements.iter_mut().zip(layers.iter()) { for (element, layer) in elements.iter_mut().zip(layers.iter()) {
element.new_ids_from_hash(layer.iter_element_values().next_back().copied()); element.new_ids_from_hash(layer.iter_element_values().next_back().copied());
} }
} }
} }
impl Render for Table<Vector> { impl Render for List<Vector> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
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 };
@@ -987,7 +987,7 @@ impl Render for Table<Vector> {
// The mask must draw at full alpha so the SVG `<mask>`/`<clipPath>` fully zeroes the path interior. // The mask must draw at full alpha so the SVG `<mask>`/`<clipPath>` fully zeroes the path interior.
// The wrapping SVG group (above) handles the user-set opacity. // The wrapping SVG group (above) handles the user-set opacity.
let vector_item = Table::new_from_item(Item::new_from_element(cloned_vector).with_attribute(ATTR_TRANSFORM, multiplied_transform)); let vector_item = List::new_from_item(Item::new_from_element(cloned_vector).with_attribute(ATTR_TRANSFORM, multiplied_transform));
(id, mask_type, vector_item) (id, mask_type, vector_item)
}); });
@@ -1312,7 +1312,7 @@ impl Render for Table<Vector> {
// The mask must draw at full alpha so `SrcOut` fully zeroes the path interior. // The mask must draw at full alpha so `SrcOut` fully zeroes the path interior.
// The outer opacity/blend layer (above) handles the user-set opacity. // The outer opacity/blend layer (above) handles the user-set opacity.
let vector_table = Table::new_from_item(Item::new_from_element(cloned_element).with_attribute(ATTR_TRANSFORM, item_transform)); let vector_list = List::new_from_item(Item::new_from_element(cloned_element).with_attribute(ATTR_TRANSFORM, item_transform));
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);
// This branch is gated on `can_draw_aligned_stroke`, which already requires every subpath is closed // This branch is gated on `can_draw_aligned_stroke`, which already requires every subpath is closed
@@ -1330,7 +1330,7 @@ impl Render for Table<Vector> {
if wants_stroke_below { if wants_stroke_below {
scene.push_layer(peniko::Fill::NonZero, peniko::Mix::Normal, 1., kurbo::Affine::IDENTITY, &rect); scene.push_layer(peniko::Fill::NonZero, peniko::Mix::Normal, 1., kurbo::Affine::IDENTITY, &rect);
vector_table.render_to_vello(scene, parent_transform, _context, &render_params.for_alignment(applied_stroke_transform)); vector_list.render_to_vello(scene, parent_transform, _context, &render_params.for_alignment(applied_stroke_transform));
scene.push_layer(peniko::Fill::NonZero, peniko::BlendMode::new(peniko::Mix::Normal, compose), 1., kurbo::Affine::IDENTITY, &rect); scene.push_layer(peniko::Fill::NonZero, peniko::BlendMode::new(peniko::Mix::Normal, compose), 1., kurbo::Affine::IDENTITY, &rect);
do_stroke(scene, 2.); do_stroke(scene, 2.);
@@ -1344,7 +1344,7 @@ impl Render for Table<Vector> {
do_fill(scene); do_fill(scene);
scene.push_layer(peniko::Fill::NonZero, peniko::Mix::Normal, 1., kurbo::Affine::IDENTITY, &rect); scene.push_layer(peniko::Fill::NonZero, peniko::Mix::Normal, 1., kurbo::Affine::IDENTITY, &rect);
vector_table.render_to_vello(scene, parent_transform, _context, &render_params.for_alignment(applied_stroke_transform)); vector_list.render_to_vello(scene, parent_transform, _context, &render_params.for_alignment(applied_stroke_transform));
scene.push_layer(peniko::Fill::NonZero, peniko::BlendMode::new(peniko::Mix::Normal, compose), 1., kurbo::Affine::IDENTITY, &rect); scene.push_layer(peniko::Fill::NonZero, peniko::BlendMode::new(peniko::Mix::Normal, compose), 1., kurbo::Affine::IDENTITY, &rect);
do_stroke(scene, 2.); do_stroke(scene, 2.);
@@ -1382,7 +1382,7 @@ impl Render for Table<Vector> {
} }
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, caller_element_id: Option<NodeId>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, caller_element_id: Option<NodeId>) {
// Aggregate all items' targets per element_id so multi-item tables (e.g. 'Text' node with "Separate Glyphs" active) produce hit areas for every glyph. // Aggregate all items' targets per element_id so multi-item lists (e.g. 'Text' node with "Separate Glyphs" active) produce hit areas for every glyph.
// Targets are baked relative to item 0's transform since `Graphic::collect_metadata` records that as `local_transforms[element_id]`. // Targets are baked relative to item 0's transform since `Graphic::collect_metadata` records that as `local_transforms[element_id]`.
let item_zero_transform: DAffine2 = if !self.is_empty() { let item_zero_transform: DAffine2 = if !self.is_empty() {
self.attribute_cloned_or_default(ATTR_TRANSFORM, 0) self.attribute_cloned_or_default(ATTR_TRANSFORM, 0)
@@ -1401,7 +1401,7 @@ impl Render for Table<Vector> {
for index in 0..self.len() { for index in 0..self.len() {
let Some(source) = self.element(index) else { continue }; let Some(source) = self.element(index) else { continue };
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index); let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let layer_path: Table<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index); let layer_path: List<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index);
let layer = layer_path.iter_element_values().next_back().copied(); let layer = layer_path.iter_element_values().next_back().copied();
if let Some(element_id) = caller_element_id.or(layer) { if let Some(element_id) = caller_element_id.or(layer) {
@@ -1440,7 +1440,7 @@ impl Render for Table<Vector> {
// If this item carries a snapshot of upstream graphic content (e.g. it was produced by Boolean Operation, // If this item 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 // Flatten Path, Morph, or any other destructive merge), recurse into that snapshot so the editor can
// surface the original child layers' click targets. // surface the original child layers' click targets.
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, index); let upstream_nested_layers = self.attribute_cloned_or_default::<List<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, index);
if !upstream_nested_layers.is_empty() { if !upstream_nested_layers.is_empty() {
let mut upstream_footprint = footprint; let mut upstream_footprint = footprint;
upstream_footprint.transform *= transform; upstream_footprint.transform *= transform;
@@ -1524,7 +1524,7 @@ fn extend_free_point_targets(vector: &Vector, transform: DAffine2) -> impl Itera
}) })
} }
impl Render for Table<Raster<CPU>> { impl Render for List<Raster<CPU>> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for index in 0..self.len() { for index in 0..self.len() {
let Some(image) = self.element(index) else { continue }; let Some(image) = self.element(index) else { continue };
@@ -1673,7 +1673,7 @@ impl Render for Table<Raster<CPU>> {
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]); metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);
metadata.upstream_footprints.insert(element_id, footprint); metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one item of the `Table<Raster<...>>` // TODO: Find a way to handle more than one item of the `List<Raster<...>>`
if !self.is_empty() { if !self.is_empty() {
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0); let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
metadata.local_transforms.insert(element_id, transform); metadata.local_transforms.insert(element_id, transform);
@@ -1684,7 +1684,7 @@ impl Render for Table<Raster<CPU>> {
// The snapshot was captured before Rasterize shifted its input transforms to align with the rasterization // 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 // 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). // multiply in `transform` (which is the rasterization area, not a layer-stack transform).
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, 0); let upstream_nested_layers = self.attribute_cloned_or_default::<List<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, 0);
if !upstream_nested_layers.is_empty() { if !upstream_nested_layers.is_empty() {
upstream_nested_layers.collect_metadata(metadata, footprint, None); upstream_nested_layers.collect_metadata(metadata, footprint, None);
} }
@@ -1699,7 +1699,7 @@ impl Render for Table<Raster<CPU>> {
static LAZY_ARC_VEC_ZERO_U8: LazyLock<Arc<Vec<u8>>> = LazyLock::new(|| Arc::new(Vec::new())); static LAZY_ARC_VEC_ZERO_U8: LazyLock<Arc<Vec<u8>>> = LazyLock::new(|| Arc::new(Vec::new()));
impl Render for Table<Raster<GPU>> { impl Render for List<Raster<GPU>> {
fn render_svg(&self, _render: &mut SvgRender, _render_params: &RenderParams) { fn render_svg(&self, _render: &mut SvgRender, _render_params: &RenderParams) {
log::warn!("tried to render texture as an svg"); log::warn!("tried to render texture as an svg");
} }
@@ -1768,7 +1768,7 @@ impl Render for Table<Raster<GPU>> {
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]); metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);
metadata.upstream_footprints.insert(element_id, footprint); metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one item of the `Table<Raster<...>>` // TODO: Find a way to handle more than one item of the `List<Raster<...>>`
if !self.is_empty() { if !self.is_empty() {
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0); let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
metadata.local_transforms.insert(element_id, transform); metadata.local_transforms.insert(element_id, transform);
@@ -1779,7 +1779,7 @@ impl Render for Table<Raster<GPU>> {
// The snapshot was captured before Rasterize shifted its input transforms to align with the rasterization // 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 // 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). // multiply in `transform` (which is the rasterization area, not a layer-stack transform).
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, 0); let upstream_nested_layers = self.attribute_cloned_or_default::<List<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, 0);
if !upstream_nested_layers.is_empty() { if !upstream_nested_layers.is_empty() {
upstream_nested_layers.collect_metadata(metadata, footprint, None); upstream_nested_layers.collect_metadata(metadata, footprint, None);
} }
@@ -1798,7 +1798,7 @@ impl Render for Table<Raster<GPU>> {
// For SVG, this is is achived by creating a truly giant rectangle. // For SVG, this is is achived by creating a truly giant rectangle.
// For Vello, we create a layer with a placeholder transform which we // For Vello, we create a layer with a placeholder transform which we
// later replace with the current viewport transform before each render. // later replace with the current viewport transform before each render.
impl Render for Table<Color> { impl Render for List<Color> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for (index, color) in self.iter_element_values().enumerate() { for (index, color) in self.iter_element_values().enumerate() {
let blend_mode: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index); let blend_mode: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
@@ -1858,7 +1858,7 @@ impl Render for Table<Color> {
} }
} }
impl Render for Table<GradientStops> { impl Render for List<GradientStops> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
// For thumbnails the gradient fills a finite rect at the footprint's document space bounds, with a 1-unit margin to cover the `as u32` truncation of `Footprint::resolution`. // For thumbnails the gradient fills a finite rect at the footprint's document space bounds, with a 1-unit margin to cover the `as u32` truncation of `Footprint::resolution`.
// The viewBox crops the overshoot. Canvas rendering keeps the polyline path since Chrome rejects rects larger than ~20 million. // The viewBox crops the overshoot. Canvas rendering keeps the polyline path since Chrome rejects rects larger than ~20 million.
@@ -2017,7 +2017,7 @@ impl Render for Table<GradientStops> {
let mut layer = false; let mut layer = false;
if opacity < 1. || blend_mode_attr != BlendMode::default() { if opacity < 1. || blend_mode_attr != BlendMode::default() {
let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver); let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);
// See implementation in `Table<Color>` for more detail // See implementation in `List<Color>` for more detail
scene.push_layer(peniko::Fill::NonZero, blending, opacity, kurbo::Affine::scale(f64::INFINITY), &rect); scene.push_layer(peniko::Fill::NonZero, blending, opacity, kurbo::Affine::scale(f64::INFINITY), &rect);
layer = true; layer = true;
} }
@@ -539,12 +539,12 @@ pub fn migrate_to_gradient_stops<'de, D: serde::Deserializer<'de>>(deserializer:
#[cfg_attr(feature = "serde", serde(untagged))] #[cfg_attr(feature = "serde", serde(untagged))]
enum GradientStopsFormat { enum GradientStopsFormat {
Stops(GradientStops), Stops(GradientStops),
Table(LegacyTable), List(LegacyTable),
} }
Ok(match GradientStopsFormat::deserialize(deserializer)? { Ok(match GradientStopsFormat::deserialize(deserializer)? {
GradientStopsFormat::Stops(stops) => stops, GradientStopsFormat::Stops(stops) => stops,
GradientStopsFormat::Table(table) => table.element.into_iter().next().unwrap_or_default(), GradientStopsFormat::List(list) => list.element.into_iter().next().unwrap_or_default(),
}) })
} }
@@ -4,7 +4,7 @@ pub use crate::gradient::*;
use core_types::ATTR_OPACITY; use core_types::ATTR_OPACITY;
use core_types::Color; use core_types::Color;
use core_types::color::Alpha; use core_types::color::Alpha;
use core_types::table::Table; use core_types::list::List;
use core_types::transform::Transform; use core_types::transform::Transform;
use dyn_any::DynAny; use dyn_any::DynAny;
use glam::DAffine2; use glam::DAffine2;
@@ -133,16 +133,16 @@ impl From<Option<Color>> for Fill {
} }
} }
impl From<Table<Color>> for Fill { impl From<List<Color>> for Fill {
fn from(color: Table<Color>) -> Fill { fn from(color: List<Color>) -> Fill {
let alpha: f64 = color.attribute_cloned_or(ATTR_OPACITY, 0, 1.); let alpha: f64 = color.attribute_cloned_or(ATTR_OPACITY, 0, 1.);
let color = color.element(0).copied(); let color = color.element(0).copied();
Fill::solid_or_none(color.map(|c| c.with_alpha(c.alpha() * alpha as f32))) Fill::solid_or_none(color.map(|c| c.with_alpha(c.alpha() * alpha as f32)))
} }
} }
impl From<Table<GradientStops>> for Fill { impl From<List<GradientStops>> for Fill {
fn from(gradient: Table<GradientStops>) -> Fill { fn from(gradient: List<GradientStops>) -> Fill {
Fill::Gradient(Gradient { Fill::Gradient(Gradient {
stops: gradient.element(0).cloned().unwrap_or_default(), stops: gradient.element(0).cloned().unwrap_or_default(),
..Default::default() ..Default::default()
@@ -556,7 +556,7 @@ impl RenderComplexity for Vector {
} }
} }
// Note: BoundingBox for Table<Vector> is handled by blanket impl in gcore // Note: BoundingBox for List<Vector> is handled by blanket impl in gcore
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@@ -1,7 +1,7 @@
use crate::WgpuContext; use crate::WgpuContext;
use crate::shader_runtime::{FULLSCREEN_VERTEX_SHADER_NAME, ShaderRuntime}; use crate::shader_runtime::{FULLSCREEN_VERTEX_SHADER_NAME, ShaderRuntime};
use core_types::list::{Item, List};
use core_types::shaders::buffer_struct::BufferStruct; use core_types::shaders::buffer_struct::BufferStruct;
use core_types::table::{Item, Table};
use futures::lock::Mutex; use futures::lock::Mutex;
use raster_types::{GPU, Raster}; use raster_types::{GPU, Raster};
use std::borrow::Cow; use std::borrow::Cow;
@@ -33,7 +33,7 @@ impl PerPixelAdjustShaderRuntime {
} }
impl ShaderRuntime { impl ShaderRuntime {
pub async fn run_per_pixel_adjust<T: BufferStruct>(&self, shaders: &Shaders<'_>, textures: Table<Raster<GPU>>, args: Option<&T>) -> Table<Raster<GPU>> { pub async fn run_per_pixel_adjust<T: BufferStruct>(&self, shaders: &Shaders<'_>, textures: List<Raster<GPU>>, args: Option<&T>) -> List<Raster<GPU>> {
let mut cache = self.per_pixel_adjust.pipeline_cache.lock().await; let mut cache = self.per_pixel_adjust.pipeline_cache.lock().await;
let pipeline = cache let pipeline = cache
.entry(shaders.fragment_shader_name.to_owned()) .entry(shaders.fragment_shader_name.to_owned())
@@ -160,7 +160,7 @@ impl PerPixelAdjustGraphicsPipeline {
} }
} }
pub fn dispatch(&self, context: &WgpuContext, textures: Table<Raster<GPU>>, arg_buffer: Option<Buffer>) -> Table<Raster<GPU>> { pub fn dispatch(&self, context: &WgpuContext, textures: List<Raster<GPU>>, arg_buffer: Option<Buffer>) -> List<Raster<GPU>> {
assert_eq!(self.has_uniform, arg_buffer.is_some()); assert_eq!(self.has_uniform, arg_buffer.is_some());
let device = &context.device; let device = &context.device;
let name = self.name.as_str(); let name = self.name.as_str();
@@ -236,7 +236,7 @@ impl PerPixelAdjustGraphicsPipeline {
let attributes = textures.clone_item_attributes(index); let attributes = textures.clone_item_attributes(index);
Item::from_parts(Raster::new(GPU { texture: tex_out }), attributes) Item::from_parts(Raster::new(GPU { texture: tex_out }), attributes)
}) })
.collect::<Table<_>>(); .collect::<List<_>>();
context.queue.submit([cmd.finish()]); context.queue.submit([cmd.finish()]);
out out
} }
@@ -2,8 +2,8 @@ use crate::WgpuExecutor;
use core_types::Color; use core_types::Color;
use core_types::Ctx; use core_types::Ctx;
use core_types::color::SRGBA8; use core_types::color::SRGBA8;
use core_types::list::{Item, List};
use core_types::ops::Convert; use core_types::ops::Convert;
use core_types::table::{Item, Table};
use core_types::transform::Footprint; use core_types::transform::Footprint;
use raster_types::Image; use raster_types::Image;
use raster_types::{CPU, GPU, Raster}; use raster_types::{CPU, GPU, Raster};
@@ -137,19 +137,19 @@ impl RasterGpuToRasterCpuConverter {
} }
} }
/// Passthrough conversion for GPU `Table`s - no conversion needed /// Passthrough conversion for GPU `List`s - no conversion needed
impl<'i> Convert<Table<Raster<GPU>>, &'i WgpuExecutor> for Table<Raster<GPU>> { impl<'i> Convert<List<Raster<GPU>>, &'i WgpuExecutor> for List<Raster<GPU>> {
async fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> Table<Raster<GPU>> { async fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> List<Raster<GPU>> {
self self
} }
} }
/// Converts a `Table<Raster<CPU>>` to `Table<Raster<GPU>>` by uploading each image to a texture /// Converts a `List<Raster<CPU>>` to `List<Raster<GPU>>` by uploading each image to a texture
impl<'i> Convert<Table<Raster<GPU>>, &'i WgpuExecutor> for Table<Raster<CPU>> { impl<'i> Convert<List<Raster<GPU>>, &'i WgpuExecutor> for List<Raster<CPU>> {
async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Table<Raster<GPU>> { async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> List<Raster<GPU>> {
let device = &executor.context.device; let device = &executor.context.device;
let queue = &executor.context.queue; let queue = &executor.context.queue;
let table = self let list = self
.into_iter() .into_iter()
.map(|row| { .map(|row| {
let (image, attributes) = row.into_parts(); let (image, attributes) = row.into_parts();
@@ -160,7 +160,7 @@ impl<'i> Convert<Table<Raster<GPU>>, &'i WgpuExecutor> for Table<Raster<CPU>> {
.collect(); .collect();
queue.submit([]); queue.submit([]);
table list
} }
} }
@@ -176,16 +176,16 @@ impl<'i> Convert<Raster<GPU>, &'i WgpuExecutor> for Raster<CPU> {
} }
} }
/// Passthrough conversion for CPU `Table`s - no conversion needed /// Passthrough conversion for CPU `List`s - no conversion needed
impl<'i> Convert<Table<Raster<CPU>>, &'i WgpuExecutor> for Table<Raster<CPU>> { impl<'i> Convert<List<Raster<CPU>>, &'i WgpuExecutor> for List<Raster<CPU>> {
async fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> Table<Raster<CPU>> { async fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> List<Raster<CPU>> {
self self
} }
} }
/// Converts a `Table<Raster<GPU>>` to `Table<Raster<CPU>>` by downloading texture data in one go then asynchronously maps all buffers and processes the results. /// Converts a `List<Raster<GPU>>` to `List<Raster<CPU>>` by downloading texture data in one go then asynchronously maps all buffers and processes the results.
impl<'i> Convert<Table<Raster<CPU>>, &'i WgpuExecutor> for Table<Raster<GPU>> { impl<'i> Convert<List<Raster<CPU>>, &'i WgpuExecutor> for List<Raster<GPU>> {
async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Table<Raster<CPU>> { async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> List<Raster<CPU>> {
let device = &executor.context.device; let device = &executor.context.device;
let queue = &executor.context.queue; let queue = &executor.context.queue;
@@ -245,12 +245,12 @@ impl<'i> Convert<Raster<CPU>, &'i WgpuExecutor> for Raster<GPU> {
/// Uploads an raster texture from the CPU to the GPU. This is now deprecated and the Convert node should be used in the future. /// Uploads an raster texture from the CPU to the GPU. This is now deprecated and the Convert node should be used in the future.
/// ///
/// Accepts either individual raster data or a `Table` of raster elements and converts it to the GPU format using the WgpuExecutor's device and queue. /// Accepts either individual raster data or a `List` of raster elements and converts it to the GPU format using the WgpuExecutor's device and queue.
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
pub async fn upload_texture<'a: 'n, T: Convert<Table<Raster<GPU>>, &'a WgpuExecutor>>( pub async fn upload_texture<'a: 'n, T: Convert<List<Raster<GPU>>, &'a WgpuExecutor>>(
_: impl Ctx, _: impl Ctx,
#[implementations(Table<Raster<CPU>>, Table<Raster<GPU>>)] input: T, #[implementations(List<Raster<CPU>>, List<Raster<GPU>>)] input: T,
executor: &'a WgpuExecutor, executor: &'a WgpuExecutor,
) -> Table<Raster<GPU>> { ) -> List<Raster<GPU>> {
input.convert(Footprint::DEFAULT, executor).await input.convert(Footprint::DEFAULT, executor).await
} }
+12 -12
View File
@@ -1235,7 +1235,7 @@ mod tests {
fn test_node_with_implementations() { fn test_node_with_implementations() {
let attr = quote!(category("Raster: Adjustment")); let attr = quote!(category("Raster: Adjustment"));
let input = quote!( let input = quote!(
fn levels<P: Pixel>(image: Table<Raster<P>>, #[implementations(f32, f64)] shadows: f64) -> Table<Raster<P>> { fn levels<P: Pixel>(image: List<Raster<P>>, #[implementations(f32, f64)] shadows: f64) -> List<Raster<P>> {
// Implementation details... // Implementation details...
} }
); );
@@ -1261,11 +1261,11 @@ mod tests {
where_clause: None, where_clause: None,
input: Input { input: Input {
pat_ident: pat_ident("image"), pat_ident: pat_ident("image"),
ty: parse_quote!(Table<Raster<P>>), ty: parse_quote!(List<Raster<P>>),
implementations: Punctuated::new(), implementations: Punctuated::new(),
context_features: vec![], context_features: vec![],
}, },
output_type: parse_quote!(Table<Raster<P>>), output_type: parse_quote!(List<Raster<P>>),
is_async: false, is_async: false,
fields: vec![ParsedField { fields: vec![ParsedField {
pat_ident: pat_ident("shadows"), pat_ident: pat_ident("shadows"),
@@ -1377,7 +1377,7 @@ mod tests {
fn test_async_node() { fn test_async_node() {
let attr = quote!(category("IO")); let attr = quote!(category("IO"));
let input = quote!( let input = quote!(
async fn load_image(api: &PlatformEditorApi, #[expose] path: String) -> Table<Raster<CPU>> { async fn load_image(api: &PlatformEditorApi, #[expose] path: String) -> List<Raster<CPU>> {
// Implementation details... // Implementation details...
} }
); );
@@ -1407,7 +1407,7 @@ mod tests {
implementations: Punctuated::new(), implementations: Punctuated::new(),
context_features: vec![], context_features: vec![],
}, },
output_type: parse_quote!(Table<Raster<CPU>>), output_type: parse_quote!(List<Raster<CPU>>),
is_async: true, is_async: true,
fields: vec![ParsedField { fields: vec![ParsedField {
pat_ident: pat_ident("path"), pat_ident: pat_ident("path"),
@@ -1534,7 +1534,7 @@ mod tests {
fn test_invalid_implementation_syntax() { fn test_invalid_implementation_syntax() {
let attr = quote!(category("Test")); let attr = quote!(category("Test"));
let input = quote!( let input = quote!(
fn test_node(_: (), #[implementations((Footprint, Color), (Footprint, Table<Raster<CPU>>))] input: impl Node<Footprint, Output = T>) -> T { fn test_node(_: (), #[implementations((Footprint, Color), (Footprint, List<Raster<CPU>>))] input: impl Node<Footprint, Output = T>) -> T {
// Implementation details... // Implementation details...
} }
); );
@@ -1560,12 +1560,12 @@ mod tests {
#[implementations((), #tuples, Footprint)] #[implementations((), #tuples, Footprint)]
footprint: F, footprint: F,
#[implementations( #[implementations(
() -> Table<Raster<CPU>>, () -> List<Raster<CPU>>,
() -> Table<Color>, () -> List<Color>,
() -> Table<GradientStops>, () -> List<GradientStops>,
Footprint -> Table<Raster<CPU>>, Footprint -> List<Raster<CPU>>,
Footprint -> Table<Color>, Footprint -> List<Color>,
Footprint -> Table<GradientStops>, Footprint -> List<GradientStops>,
)] )]
image: impl Node<F, Output = T>, image: impl Node<F, Output = T>,
) -> T { ) -> T {
@@ -186,7 +186,7 @@ impl PerPixelAdjustCodegen<'_> {
let wgpu_executor = self.crate_ident.wgpu_executor()?; let wgpu_executor = self.crate_ident.wgpu_executor()?;
// adapt fields for gpu node // adapt fields for gpu node
let raster_gpu: Type = parse_quote!(#gcore::table::Table<#raster_types::Raster<#raster_types::GPU>>); let raster_gpu: Type = parse_quote!(#gcore::list::List<#raster_types::Raster<#raster_types::GPU>>);
let mut fields = self let mut fields = self
.parsed .parsed
.fields .fields
+66 -66
View File
@@ -1,5 +1,5 @@
use core_types::list::List;
use core_types::registry::types::Percentage; use core_types::registry::types::Percentage;
use core_types::table::Table;
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_OPACITY, ATTR_OPACITY_FILL, BlendMode, Color, Ctx}; use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_OPACITY, ATTR_OPACITY_FILL, BlendMode, Color, Ctx};
use graphic_types::Graphic; use graphic_types::Graphic;
use graphic_types::Vector; use graphic_types::Vector;
@@ -16,41 +16,41 @@ impl MultiplyAlpha for Color {
} }
} }
fn multiply_table_attribute<T>(table: &mut Table<T>, key: &str, factor: f64) { fn multiply_list_attribute<T>(list: &mut List<T>, key: &str, factor: f64) {
if let Some(values) = table.iter_attribute_values_mut::<f64>(key) { if let Some(values) = list.iter_attribute_values_mut::<f64>(key) {
for v in values { for v in values {
*v *= factor; *v *= factor;
} }
} else { } else {
for v in table.iter_attribute_values_mut_or_default::<f64>(key) { for v in list.iter_attribute_values_mut_or_default::<f64>(key) {
*v = factor; *v = factor;
} }
} }
} }
impl MultiplyAlpha for Table<Vector> { impl MultiplyAlpha for List<Vector> {
fn multiply_alpha(&mut self, factor: f64) { fn multiply_alpha(&mut self, factor: f64) {
multiply_table_attribute(self, ATTR_OPACITY, factor); multiply_list_attribute(self, ATTR_OPACITY, factor);
} }
} }
impl MultiplyAlpha for Table<Graphic> { impl MultiplyAlpha for List<Graphic> {
fn multiply_alpha(&mut self, factor: f64) { fn multiply_alpha(&mut self, factor: f64) {
multiply_table_attribute(self, ATTR_OPACITY, factor); multiply_list_attribute(self, ATTR_OPACITY, factor);
} }
} }
impl MultiplyAlpha for Table<Raster<CPU>> { impl MultiplyAlpha for List<Raster<CPU>> {
fn multiply_alpha(&mut self, factor: f64) { fn multiply_alpha(&mut self, factor: f64) {
multiply_table_attribute(self, ATTR_OPACITY, factor); multiply_list_attribute(self, ATTR_OPACITY, factor);
} }
} }
impl MultiplyAlpha for Table<Color> { impl MultiplyAlpha for List<Color> {
fn multiply_alpha(&mut self, factor: f64) { fn multiply_alpha(&mut self, factor: f64) {
multiply_table_attribute(self, ATTR_OPACITY, factor); multiply_list_attribute(self, ATTR_OPACITY, factor);
} }
} }
impl MultiplyAlpha for Table<GradientStops> { impl MultiplyAlpha for List<GradientStops> {
fn multiply_alpha(&mut self, factor: f64) { fn multiply_alpha(&mut self, factor: f64) {
multiply_table_attribute(self, ATTR_OPACITY, factor); multiply_list_attribute(self, ATTR_OPACITY, factor);
} }
} }
@@ -62,29 +62,29 @@ impl MultiplyFill for Color {
*self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.)) *self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.))
} }
} }
impl MultiplyFill for Table<Vector> { impl MultiplyFill for List<Vector> {
fn multiply_fill(&mut self, factor: f64) { fn multiply_fill(&mut self, factor: f64) {
multiply_table_attribute(self, ATTR_OPACITY_FILL, factor); multiply_list_attribute(self, ATTR_OPACITY_FILL, factor);
} }
} }
impl MultiplyFill for Table<Graphic> { impl MultiplyFill for List<Graphic> {
fn multiply_fill(&mut self, factor: f64) { fn multiply_fill(&mut self, factor: f64) {
multiply_table_attribute(self, ATTR_OPACITY_FILL, factor); multiply_list_attribute(self, ATTR_OPACITY_FILL, factor);
} }
} }
impl MultiplyFill for Table<Raster<CPU>> { impl MultiplyFill for List<Raster<CPU>> {
fn multiply_fill(&mut self, factor: f64) { fn multiply_fill(&mut self, factor: f64) {
multiply_table_attribute(self, ATTR_OPACITY_FILL, factor); multiply_list_attribute(self, ATTR_OPACITY_FILL, factor);
} }
} }
impl MultiplyFill for Table<Color> { impl MultiplyFill for List<Color> {
fn multiply_fill(&mut self, factor: f64) { fn multiply_fill(&mut self, factor: f64) {
multiply_table_attribute(self, ATTR_OPACITY_FILL, factor); multiply_list_attribute(self, ATTR_OPACITY_FILL, factor);
} }
} }
impl MultiplyFill for Table<GradientStops> { impl MultiplyFill for List<GradientStops> {
fn multiply_fill(&mut self, factor: f64) { fn multiply_fill(&mut self, factor: f64) {
multiply_table_attribute(self, ATTR_OPACITY_FILL, factor); multiply_list_attribute(self, ATTR_OPACITY_FILL, factor);
} }
} }
@@ -92,35 +92,35 @@ trait SetBlendMode {
fn set_blend_mode(&mut self, blend_mode: BlendMode); fn set_blend_mode(&mut self, blend_mode: BlendMode);
} }
fn set_table_blend_mode<T>(table: &mut Table<T>, blend_mode: BlendMode) { fn set_list_blend_mode<T>(list: &mut List<T>, blend_mode: BlendMode) {
for v in table.iter_attribute_values_mut_or_default::<BlendMode>(ATTR_BLEND_MODE) { for v in list.iter_attribute_values_mut_or_default::<BlendMode>(ATTR_BLEND_MODE) {
*v = blend_mode; *v = blend_mode;
} }
} }
impl SetBlendMode for Table<Vector> { impl SetBlendMode for List<Vector> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) { fn set_blend_mode(&mut self, blend_mode: BlendMode) {
set_table_blend_mode(self, blend_mode); set_list_blend_mode(self, blend_mode);
} }
} }
impl SetBlendMode for Table<Graphic> { impl SetBlendMode for List<Graphic> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) { fn set_blend_mode(&mut self, blend_mode: BlendMode) {
set_table_blend_mode(self, blend_mode); set_list_blend_mode(self, blend_mode);
} }
} }
impl SetBlendMode for Table<Raster<CPU>> { impl SetBlendMode for List<Raster<CPU>> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) { fn set_blend_mode(&mut self, blend_mode: BlendMode) {
set_table_blend_mode(self, blend_mode); set_list_blend_mode(self, blend_mode);
} }
} }
impl SetBlendMode for Table<Color> { impl SetBlendMode for List<Color> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) { fn set_blend_mode(&mut self, blend_mode: BlendMode) {
set_table_blend_mode(self, blend_mode); set_list_blend_mode(self, blend_mode);
} }
} }
impl SetBlendMode for Table<GradientStops> { impl SetBlendMode for List<GradientStops> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) { fn set_blend_mode(&mut self, blend_mode: BlendMode) {
set_table_blend_mode(self, blend_mode); set_list_blend_mode(self, blend_mode);
} }
} }
@@ -128,35 +128,35 @@ trait SetClip {
fn set_clip(&mut self, clip: bool); fn set_clip(&mut self, clip: bool);
} }
fn set_table_clip<T>(table: &mut Table<T>, clip: bool) { fn set_list_clip<T>(list: &mut List<T>, clip: bool) {
for v in table.iter_attribute_values_mut_or_default::<bool>(ATTR_CLIPPING_MASK) { for v in list.iter_attribute_values_mut_or_default::<bool>(ATTR_CLIPPING_MASK) {
*v = clip; *v = clip;
} }
} }
impl SetClip for Table<Vector> { impl SetClip for List<Vector> {
fn set_clip(&mut self, clip: bool) { fn set_clip(&mut self, clip: bool) {
set_table_clip(self, clip); set_list_clip(self, clip);
} }
} }
impl SetClip for Table<Graphic> { impl SetClip for List<Graphic> {
fn set_clip(&mut self, clip: bool) { fn set_clip(&mut self, clip: bool) {
set_table_clip(self, clip); set_list_clip(self, clip);
} }
} }
impl SetClip for Table<Raster<CPU>> { impl SetClip for List<Raster<CPU>> {
fn set_clip(&mut self, clip: bool) { fn set_clip(&mut self, clip: bool) {
set_table_clip(self, clip); set_list_clip(self, clip);
} }
} }
impl SetClip for Table<Color> { impl SetClip for List<Color> {
fn set_clip(&mut self, clip: bool) { fn set_clip(&mut self, clip: bool) {
set_table_clip(self, clip); set_list_clip(self, clip);
} }
} }
impl SetClip for Table<GradientStops> { impl SetClip for List<GradientStops> {
fn set_clip(&mut self, clip: bool) { fn set_clip(&mut self, clip: bool) {
set_table_clip(self, clip); set_list_clip(self, clip);
} }
} }
@@ -166,17 +166,17 @@ fn blend_mode<T: SetBlendMode>(
_: impl Ctx, _: impl Ctx,
/// The layer stack that will be composited when rendering. /// The layer stack that will be composited when rendering.
#[implementations( #[implementations(
Table<Graphic>, List<Graphic>,
Table<Vector>, List<Vector>,
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
mut content: T, mut content: T,
/// The choice of equation that controls how brightness and color blends between overlapping pixels. /// The choice of equation that controls how brightness and color blends between overlapping pixels.
blend_mode: BlendMode, blend_mode: BlendMode,
) -> T { ) -> T {
// TODO: Find a way to make this apply once to the table's parent (i.e. its item in its parent table or Item<T>) rather than applying to each item in its own table, which produces the undesired result // TODO: Find a way to make this apply once to the list's parent (i.e. its item in its parent List<T> or Item<T>) rather than applying to each item in its own list, which produces the undesired result
content.set_blend_mode(blend_mode); content.set_blend_mode(blend_mode);
content content
} }
@@ -189,11 +189,11 @@ fn opacity<T: MultiplyAlpha + MultiplyFill>(
_: impl Ctx, _: impl Ctx,
/// The layer stack that will be composited when rendering. /// The layer stack that will be composited when rendering.
#[implementations( #[implementations(
Table<Graphic>, List<Graphic>,
Table<Vector>, List<Vector>,
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
mut content: T, mut content: T,
/// Whether the *Opacity* property is enabled, multiplying the existing opacity by the chosen percentage. /// Whether the *Opacity* property is enabled, multiplying the existing opacity by the chosen percentage.
@@ -214,7 +214,7 @@ fn opacity<T: MultiplyAlpha + MultiplyFill>(
#[default(100.)] #[default(100.)]
fill: Percentage, fill: Percentage,
) -> T { ) -> T {
// TODO: Find a way to make this apply once to the table's parent (i.e. its item in its parent table or Item<T>) rather than applying to each item in its own table, which produces the undesired result // TODO: Find a way to make this apply once to the list's parent (i.e. its item in its parent List<T> or Item<T>) rather than applying to each item in its own list, which produces the undesired result
if has_opacity { if has_opacity {
content.multiply_alpha(opacity / 100.); content.multiply_alpha(opacity / 100.);
} }
@@ -230,17 +230,17 @@ fn clipping_mask<T: SetClip>(
_: impl Ctx, _: impl Ctx,
/// The layer stack that will be composited when rendering. /// The layer stack that will be composited when rendering.
#[implementations( #[implementations(
Table<Graphic>, List<Graphic>,
Table<Vector>, List<Vector>,
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
mut content: T, mut content: T,
/// Whether the content inherits the alpha of the content beneath it. /// Whether the content inherits the alpha of the content beneath it.
clip: bool, clip: bool,
) -> T { ) -> T {
// TODO: Find a way to make this apply once to the table's parent (i.e. its item in its parent table or Item<T>) rather than applying to each item in its own table, which produces the undesired result // TODO: Find a way to make this apply once to the list's parent (i.e. its item in its parent List<T> or Item<T>) rather than applying to each item in its own list, which produces the undesired result
content.set_clip(clip); content.set_clip(clip);
content content
} }
+20 -20
View File
@@ -4,9 +4,9 @@ use core_types::blending::BlendMode;
use core_types::bounds::{BoundingBox, RenderBoundingBox}; use core_types::bounds::{BoundingBox, RenderBoundingBox};
use core_types::color::{Alpha, Color, Pixel, Sample}; use core_types::color::{Alpha, Color, Pixel, Sample};
use core_types::generic::FnNode; use core_types::generic::FnNode;
use core_types::list::{Item, List};
use core_types::math::bbox::{AxisAlignedBbox, Bbox}; use core_types::math::bbox::{AxisAlignedBbox, Bbox};
use core_types::registry::FutureWrapperNode; use core_types::registry::FutureWrapperNode;
use core_types::table::{Item, Table};
use core_types::transform::Transform; use core_types::transform::Transform;
use core_types::uuid::NodeId; use core_types::uuid::NodeId;
use core_types::value::ClonedNode; use core_types::value::ClonedNode;
@@ -83,7 +83,7 @@ fn brush_stamp_generator(#[unit(" px")] diameter: f64, color: Color, hardness: f
/// Used to efficiently paint brush strokes. Applies the same texture repeatedly at different positions with proper blending and boundary handling. /// Used to efficiently paint brush strokes. Applies the same texture repeatedly at different positions with proper blending and boundary handling.
#[node_macro::node(category(""), skip_impl)] #[node_macro::node(category(""), skip_impl)]
fn blit<BlendFn>(mut target: Table<Raster<CPU>>, texture: Raster<CPU>, positions: Vec<DVec2>, blend_mode: BlendFn) -> Table<Raster<CPU>> fn blit<BlendFn>(mut target: List<Raster<CPU>>, texture: Raster<CPU>, positions: Vec<DVec2>, blend_mode: BlendFn) -> List<Raster<CPU>>
where where
BlendFn: for<'any_input> Node<'any_input, (Color, Color), Output = Color>, BlendFn: for<'any_input> Node<'any_input, (Color, Color), Output = Color>,
{ {
@@ -137,7 +137,7 @@ where
pub async fn create_brush_texture(brush_style: &BrushStyle) -> Raster<CPU> { pub async fn create_brush_texture(brush_style: &BrushStyle) -> Raster<CPU> {
let stamp = brush_stamp_generator(brush_style.diameter, brush_style.color, brush_style.hardness, brush_style.flow); let stamp = brush_stamp_generator(brush_style.diameter, brush_style.color, brush_style.hardness, brush_style.flow);
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.)); let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.));
let blank_texture = empty_image((), transform, Table::new_from_element(Color::TRANSPARENT)).into_iter().next().unwrap_or_default(); let blank_texture = empty_image((), transform, List::new_from_element(Color::TRANSPARENT)).into_iter().next().unwrap_or_default();
let image = blend_stamp_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.)); let image = blend_stamp_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.));
image.into_element() image.into_element()
@@ -191,20 +191,20 @@ pub fn blend_with_mode(background: Item<Raster<CPU>>, foreground: Item<Raster<CP
async fn brush( async fn brush(
_: impl Ctx, _: impl Ctx,
/// Optional raster content that may be drawn onto. /// Optional raster content that may be drawn onto.
mut background: Table<Raster<CPU>>, mut background: List<Raster<CPU>>,
/// The list of brush stroke paths drawn by the Brush tool, with each including both its coordinates and styles. /// The list of brush stroke paths drawn by the Brush tool, with each including both its coordinates and styles.
trace: Table<BrushStroke>, trace: List<BrushStroke>,
/// Internal cache data used to accelerate rendering of the brush content. /// Internal cache data used to accelerate rendering of the brush content.
#[data] #[data]
cache: BrushCache, cache: BrushCache,
) -> Table<Raster<CPU>> { ) -> List<Raster<CPU>> {
if background.is_empty() { if background.is_empty() {
background.push(Item::default()); background.push(Item::default());
} }
// TODO: Find a way to handle more than one item // TODO: Find a way to handle more than one item
let table_row = background.clone_item(0).expect("Expected the one item we just pushed"); let list_item = background.clone_item(0).expect("Expected the one item we just pushed");
let bounds = Table::new_from_item(table_row.clone()).bounding_box(DAffine2::IDENTITY, false); let bounds = List::new_from_item(list_item.clone()).bounding_box(DAffine2::IDENTITY, false);
let [start, end] = if let RenderBoundingBox::Rectangle(rect) = bounds { rect } else { [DVec2::ZERO, DVec2::ZERO] }; let [start, end] = if let RenderBoundingBox::Rectangle(rect) = bounds { rect } else { [DVec2::ZERO, DVec2::ZERO] };
let background_bbox = AxisAlignedBbox { start, end }; let background_bbox = AxisAlignedBbox { start, end };
let stroke_bbox = trace.iter_element_values().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO); let stroke_bbox = trace.iter_element_values().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
@@ -221,11 +221,11 @@ async fn brush(
.cloned() .cloned()
.collect(); .collect();
let mut brush_plan = cache.compute_brush_plan(table_row, &draw_strokes); let mut brush_plan = cache.compute_brush_plan(list_item, &draw_strokes);
// TODO: Find a way to handle more than one item // TODO: Find a way to handle more than one item
let Some(mut actual_image) = extend_image_to_bounds((), Table::new_from_item(brush_plan.background), background_bounds).into_iter().next() else { let Some(mut actual_image) = extend_image_to_bounds((), List::new_from_item(brush_plan.background), background_bounds).into_iter().next() else {
return Table::new(); return List::new();
}; };
let final_stroke_idx = brush_plan.strokes.len().saturating_sub(1); let final_stroke_idx = brush_plan.strokes.len().saturating_sub(1);
@@ -263,15 +263,15 @@ async fn brush(
); );
let blit_target = if idx == 0 { let blit_target = if idx == 0 {
let target = core::mem::take(&mut brush_plan.first_stroke_texture); let target = core::mem::take(&mut brush_plan.first_stroke_texture);
extend_image_to_bounds((), Table::new_from_item(target), stroke_to_layer) extend_image_to_bounds((), List::new_from_item(target), stroke_to_layer)
} else { } else {
empty_image((), stroke_to_layer, Table::new_from_element(Color::TRANSPARENT)) empty_image((), stroke_to_layer, List::new_from_element(Color::TRANSPARENT))
// EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(()) // EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(())
}; };
let table = blit_node.eval(blit_target).await; let list = blit_node.eval(blit_target).await;
assert_eq!(table.len(), 1); assert_eq!(list.len(), 1);
table.into_iter().next().unwrap_or_default() list.into_iter().next().unwrap_or_default()
}; };
// Cache image before doing final blend, and store final stroke texture. // Cache image before doing final blend, and store final stroke texture.
@@ -311,7 +311,7 @@ async fn brush(
FutureWrapperNode::new(ClonedNode::new(positions)), FutureWrapperNode::new(ClonedNode::new(positions)),
FutureWrapperNode::new(ClonedNode::new(blend_params)), FutureWrapperNode::new(ClonedNode::new(blend_params)),
); );
erase_restore_mask = blit_node.eval(Table::new_from_item(erase_restore_mask)).await.into_iter().next().unwrap_or_default(); erase_restore_mask = blit_node.eval(List::new_from_item(erase_restore_mask)).await.into_iter().next().unwrap_or_default();
} }
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::MultiplyAlpha, 1.)); let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::MultiplyAlpha, 1.));
@@ -323,7 +323,7 @@ async fn brush(
let opacity: f64 = actual_image.attribute_cloned_or(ATTR_OPACITY, 1.); let opacity: f64 = actual_image.attribute_cloned_or(ATTR_OPACITY, 1.);
let fill: f64 = actual_image.attribute_cloned_or(ATTR_OPACITY_FILL, 1.); let fill: f64 = actual_image.attribute_cloned_or(ATTR_OPACITY_FILL, 1.);
let clip: bool = actual_image.attribute_cloned_or_default(ATTR_CLIPPING_MASK); let clip: bool = actual_image.attribute_cloned_or_default(ATTR_CLIPPING_MASK);
let layer: Table<NodeId> = actual_image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH); let layer: List<NodeId> = actual_image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH);
*background.element_mut(0).unwrap() = actual_image.into_element(); *background.element_mut(0).unwrap() = actual_image.into_element();
background.set_attribute(ATTR_TRANSFORM, 0, transform); background.set_attribute(ATTR_TRANSFORM, 0, transform);
@@ -421,8 +421,8 @@ mod test {
let image = brush( let image = brush(
(), (),
&BrushCache::default(), &BrushCache::default(),
Table::new_from_element(Raster::new_cpu(Image::<Color>::default())), List::new_from_element(Raster::new_cpu(Image::<Color>::default())),
Table::new_from_element(BrushStroke { List::new_from_element(BrushStroke {
trace: vec![crate::brush_stroke::BrushInputSample { position: DVec2::ZERO }], trace: vec![crate::brush_stroke::BrushInputSample { position: DVec2::ZERO }],
style: BrushStyle { style: BrushStyle {
color: Color::BLACK, color: Color::BLACK,
+1 -1
View File
@@ -2,7 +2,7 @@ use crate::brush_stroke::BrushStroke;
use crate::brush_stroke::BrushStyle; use crate::brush_stroke::BrushStyle;
use core_types::ATTR_TRANSFORM; use core_types::ATTR_TRANSFORM;
use core_types::graphene_hash::CacheHashWrapper; use core_types::graphene_hash::CacheHashWrapper;
use core_types::table::Item; use core_types::list::Item;
use raster_types::CPU; use raster_types::CPU;
use raster_types::Raster; use raster_types::Raster;
use std::collections::HashMap; use std::collections::HashMap;
+2 -2
View File
@@ -19,12 +19,12 @@ pub mod migrations {
#[serde(untagged)] #[serde(untagged)]
enum BrushStrokesFormat { enum BrushStrokesFormat {
Strokes(Vec<BrushStroke>), Strokes(Vec<BrushStroke>),
Table(LegacyTable), List(LegacyTable),
} }
Ok(match BrushStrokesFormat::deserialize(deserializer)? { Ok(match BrushStrokesFormat::deserialize(deserializer)? {
BrushStrokesFormat::Strokes(strokes) => strokes, BrushStrokesFormat::Strokes(strokes) => strokes,
BrushStrokesFormat::Table(table) => table.element, BrushStrokesFormat::List(list) => list.element,
}) })
} }
} }
+19 -19
View File
@@ -1,4 +1,4 @@
use core_types::table::Table; use core_types::list::List;
use core_types::transform::Footprint; use core_types::transform::Footprint;
use core_types::{CacheHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime, OwnedContextImpl}; use core_types::{CacheHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime, OwnedContextImpl};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
@@ -73,15 +73,15 @@ async fn quantize_real_time<T>(
Context -> DAffine2, Context -> DAffine2,
Context -> Footprint, Context -> Footprint,
Context -> DVec2, Context -> DVec2,
Context -> Table<Vector>, Context -> List<Vector>,
Context -> Table<Graphic>, Context -> List<Graphic>,
Context -> Table<Raster<CPU>>, Context -> List<Raster<CPU>>,
Context -> Table<Raster<GPU>>, Context -> List<Raster<GPU>>,
Context -> Table<Color>, Context -> List<Color>,
Context -> Table<Artboard>, Context -> List<Artboard>,
Context -> Table<GradientStops>, Context -> List<GradientStops>,
Context -> Table<String>, Context -> List<String>,
Context -> Table<f64>, Context -> List<f64>,
Context -> (), Context -> (),
)] )]
value: impl Node<'n, Context<'static>, Output = T>, value: impl Node<'n, Context<'static>, Output = T>,
@@ -113,15 +113,15 @@ async fn quantize_animation_time<T>(
Context -> DAffine2, Context -> DAffine2,
Context -> Footprint, Context -> Footprint,
Context -> DVec2, Context -> DVec2,
Context -> Table<Vector>, Context -> List<Vector>,
Context -> Table<Graphic>, Context -> List<Graphic>,
Context -> Table<Raster<CPU>>, Context -> List<Raster<CPU>>,
Context -> Table<Raster<GPU>>, Context -> List<Raster<GPU>>,
Context -> Table<Color>, Context -> List<Color>,
Context -> Table<Artboard>, Context -> List<Artboard>,
Context -> Table<GradientStops>, Context -> List<GradientStops>,
Context -> Table<String>, Context -> List<String>,
Context -> Table<f64>, Context -> List<f64>,
Context -> (), Context -> (),
)] )]
value: impl Node<'n, Context<'static>, Output = T>, value: impl Node<'n, Context<'static>, Output = T>,
+6 -6
View File
@@ -1,4 +1,4 @@
use core_types::table::Table; use core_types::list::List;
use core_types::{Color, ExtractVarArgs}; use core_types::{Color, ExtractVarArgs};
use core_types::{Ctx, ExtractIndex, ExtractPosition}; use core_types::{Ctx, ExtractIndex, ExtractPosition};
use glam::DVec2; use glam::DVec2;
@@ -7,7 +7,7 @@ use graphic_types::{Graphic, Vector};
use raster_types::{CPU, Raster}; use raster_types::{CPU, Raster};
#[node_macro::node(category("Context"), path(graphene_core::vector))] #[node_macro::node(category("Context"), path(graphene_core::vector))]
fn read_graphic(ctx: impl Ctx + ExtractVarArgs) -> Table<Graphic> { fn read_graphic(ctx: impl Ctx + ExtractVarArgs) -> List<Graphic> {
let Ok(var_arg) = ctx.vararg(0) else { return Default::default() }; let Ok(var_arg) = ctx.vararg(0) else { return Default::default() };
let var_arg = var_arg as &dyn std::any::Any; let var_arg = var_arg as &dyn std::any::Any;
@@ -15,7 +15,7 @@ fn read_graphic(ctx: impl Ctx + ExtractVarArgs) -> Table<Graphic> {
} }
#[node_macro::node(category("Context"), path(graphene_core::vector))] #[node_macro::node(category("Context"), path(graphene_core::vector))]
fn read_vector(ctx: impl Ctx + ExtractVarArgs) -> Table<Vector> { fn read_vector(ctx: impl Ctx + ExtractVarArgs) -> List<Vector> {
let Ok(var_arg) = ctx.vararg(0) else { return Default::default() }; let Ok(var_arg) = ctx.vararg(0) else { return Default::default() };
let var_arg = var_arg as &dyn std::any::Any; let var_arg = var_arg as &dyn std::any::Any;
@@ -23,7 +23,7 @@ fn read_vector(ctx: impl Ctx + ExtractVarArgs) -> Table<Vector> {
} }
#[node_macro::node(category("Context"), path(graphene_core::vector))] #[node_macro::node(category("Context"), path(graphene_core::vector))]
fn read_raster(ctx: impl Ctx + ExtractVarArgs) -> Table<Raster<CPU>> { fn read_raster(ctx: impl Ctx + ExtractVarArgs) -> List<Raster<CPU>> {
let Ok(var_arg) = ctx.vararg(0) else { return Default::default() }; let Ok(var_arg) = ctx.vararg(0) else { return Default::default() };
let var_arg = var_arg as &dyn std::any::Any; let var_arg = var_arg as &dyn std::any::Any;
@@ -31,7 +31,7 @@ fn read_raster(ctx: impl Ctx + ExtractVarArgs) -> Table<Raster<CPU>> {
} }
#[node_macro::node(category("Context"), path(graphene_core::vector))] #[node_macro::node(category("Context"), path(graphene_core::vector))]
fn read_color(ctx: impl Ctx + ExtractVarArgs) -> Table<Color> { fn read_color(ctx: impl Ctx + ExtractVarArgs) -> List<Color> {
let Ok(var_arg) = ctx.vararg(0) else { return Default::default() }; let Ok(var_arg) = ctx.vararg(0) else { return Default::default() };
let var_arg = var_arg as &dyn std::any::Any; let var_arg = var_arg as &dyn std::any::Any;
@@ -39,7 +39,7 @@ fn read_color(ctx: impl Ctx + ExtractVarArgs) -> Table<Color> {
} }
#[node_macro::node(category("Context"), path(graphene_core::vector))] #[node_macro::node(category("Context"), path(graphene_core::vector))]
fn read_gradient(ctx: impl Ctx + ExtractVarArgs) -> Table<GradientStops> { fn read_gradient(ctx: impl Ctx + ExtractVarArgs) -> List<GradientStops> {
let Ok(var_arg) = ctx.vararg(0) else { return Default::default() }; let Ok(var_arg) = ctx.vararg(0) else { return Default::default() };
let var_arg = var_arg as &dyn std::any::Any; let var_arg = var_arg as &dyn std::any::Any;
@@ -1,6 +1,6 @@
use core::f64; use core::f64;
use core_types::context::{CloneVarArgs, Context, ContextFeatures, Ctx, ExtractAll}; use core_types::context::{CloneVarArgs, Context, ContextFeatures, Ctx, ExtractAll};
use core_types::table::{AttributeDyn, AttributeValueDyn, Table, TableDyn}; use core_types::list::{AttributeDyn, AttributeValueDyn, List, ListDyn};
use core_types::transform::Footprint; use core_types::transform::Footprint;
use core_types::uuid::NodeId; use core_types::uuid::NodeId;
use core_types::{Color, OwnedContextImpl}; use core_types::{Color, OwnedContextImpl};
@@ -26,20 +26,20 @@ async fn context_modification<T>(
Context -> DAffine2, Context -> DAffine2,
Context -> Footprint, Context -> Footprint,
Context -> DVec2, Context -> DVec2,
Context -> Table<String>, Context -> List<String>,
Context -> Table<NodeId>, Context -> List<NodeId>,
Context -> Table<f64>, Context -> List<f64>,
Context -> Table<u8>, Context -> List<u8>,
Context -> Table<Vector>, Context -> List<Vector>,
Context -> Table<Graphic>, Context -> List<Graphic>,
Context -> Table<Raster<CPU>>, Context -> List<Raster<CPU>>,
Context -> Table<Raster<GPU>>, Context -> List<Raster<GPU>>,
Context -> Table<Color>, Context -> List<Color>,
Context -> Table<Artboard>, Context -> List<Artboard>,
Context -> Table<GradientStops>, Context -> List<GradientStops>,
Context -> AttributeDyn, Context -> AttributeDyn,
Context -> AttributeValueDyn, Context -> AttributeValueDyn,
Context -> TableDyn, Context -> ListDyn,
)] )]
value: impl Node<Context<'static>, Output = T>, value: impl Node<Context<'static>, Output = T>,
/// The parts of the context to keep when evaluating the input value. All other parts are nullified. /// The parts of the context to keep when evaluating the input value. All other parts are nullified.
+2 -2
View File
@@ -1,5 +1,5 @@
use core_types::Ctx; use core_types::Ctx;
use core_types::table::Table; use core_types::list::List;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use raster_types::{CPU, Raster}; use raster_types::{CPU, Raster};
@@ -31,6 +31,6 @@ fn unwrap_option<T: Default>(_: impl Ctx, #[implementations(Option<f64>, Option<
/// Meant for debugging purposes, not general use. Clones the input value. /// Meant for debugging purposes, not general use. Clones the input value.
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
fn clone<'i, T: Clone + 'i>(_: impl Ctx, #[implementations(&Table<Raster<CPU>>)] value: &'i T) -> T { fn clone<'i, T: Clone + 'i>(_: impl Ctx, #[implementations(&List<Raster<CPU>>)] value: &'i T) -> T {
value.clone() value.clone()
} }
+14 -14
View File
@@ -1,24 +1,24 @@
use core_types::table::{Item, Table}; use core_types::list::{Item, List};
use core_types::transform::TransformMut; use core_types::transform::TransformMut;
use core_types::{ATTR_BACKGROUND, ATTR_CLIP, ATTR_DIMENSIONS, ATTR_LOCATION, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl}; use core_types::{ATTR_BACKGROUND, ATTR_CLIP, ATTR_DIMENSIONS, ATTR_LOCATION, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graphic_types::graphic::{Graphic, IntoGraphicTable}; use graphic_types::graphic::{Graphic, IntoGraphicList};
use graphic_types::{Artboard, Vector}; use graphic_types::{Artboard, Vector};
use raster_types::{CPU, GPU, Raster}; use raster_types::{CPU, GPU, Raster};
use vector_types::GradientStops; use vector_types::GradientStops;
/// Constructs a single-row `Table<Artboard>` with the given content and metadata stored as row attributes. /// Constructs a single-row `List<Artboard>` with the given content and metadata stored as row attributes.
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
pub async fn create_artboard<T: IntoGraphicTable + 'n>( pub async fn create_artboard<T: IntoGraphicList + 'n>(
ctx: impl ExtractAll + CloneVarArgs + Ctx, ctx: impl ExtractAll + CloneVarArgs + Ctx,
/// Graphics to include within the artboard. /// Graphics to include within the artboard.
#[implementations( #[implementations(
Context -> Table<Graphic>, Context -> List<Graphic>,
Context -> Table<Vector>, Context -> List<Vector>,
Context -> Table<Raster<CPU>>, Context -> List<Raster<CPU>>,
Context -> Table<Raster<GPU>>, Context -> List<Raster<GPU>>,
Context -> Table<Color>, Context -> List<Color>,
Context -> Table<GradientStops>, Context -> List<GradientStops>,
Context -> DAffine2, Context -> DAffine2,
)] )]
content: impl Node<Context<'static>, Output = T>, content: impl Node<Context<'static>, Output = T>,
@@ -27,18 +27,18 @@ pub async fn create_artboard<T: IntoGraphicTable + 'n>(
/// Width and height of the artboard within the document. /// Width and height of the artboard within the document.
dimensions: DVec2, dimensions: DVec2,
/// Color of the artboard background. /// Color of the artboard background.
background: Table<Color>, background: List<Color>,
/// Whether to cut off the contained content that extends outside the artboard, or keep it visible. /// Whether to cut off the contained content that extends outside the artboard, or keep it visible.
#[default(true)] #[default(true)]
clip: bool, clip: bool,
) -> Table<Artboard> { ) -> List<Artboard> {
let footprint = ctx.try_footprint().copied(); let footprint = ctx.try_footprint().copied();
let mut new_ctx = OwnedContextImpl::from(ctx); let mut new_ctx = OwnedContextImpl::from(ctx);
if let Some(mut footprint) = footprint { if let Some(mut footprint) = footprint {
footprint.translate(location); footprint.translate(location);
new_ctx = new_ctx.with_footprint(footprint); new_ctx = new_ctx.with_footprint(footprint);
} }
let content = content.eval(new_ctx.into_context()).await.into_graphic_table(); let content = content.eval(new_ctx.into_context()).await.into_graphic_list();
// Normalize so `location` is the top-left corner and `dimensions` are positive (allowing negative input // Normalize so `location` is the top-left corner and `dimensions` are positive (allowing negative input
// dimensions to represent dragging from the opposite corner). Compute the corner using the raw signed // dimensions to represent dragging from the opposite corner). Compute the corner using the raw signed
@@ -49,7 +49,7 @@ pub async fn create_artboard<T: IntoGraphicTable + 'n>(
let background = background.element(0).copied().unwrap_or(Color::WHITE); let background = background.element(0).copied().unwrap_or(Color::WHITE);
// Name is not stored here, it's resolved live from the parent layer's display name // Name is not stored here, it's resolved live from the parent layer's display name
Table::new_from_item( List::new_from_item(
Item::new_from_element(Artboard::new(content)) Item::new_from_element(Artboard::new(content))
.with_attribute(ATTR_LOCATION, normalized_location) .with_attribute(ATTR_LOCATION, normalized_location)
.with_attribute(ATTR_DIMENSIONS, normalized_dimensions) .with_attribute(ATTR_DIMENSIONS, normalized_dimensions)
+221 -221
View File
@@ -1,10 +1,10 @@
use core_types::bounds::{BoundingBox, RenderBoundingBox}; use core_types::bounds::{BoundingBox, RenderBoundingBox};
use core_types::list::{AttributeDyn, AttributeValueDyn, Item, List, ListDyn};
use core_types::registry::types::{Angle, SignedInteger}; use core_types::registry::types::{Angle, SignedInteger};
use core_types::table::{AttributeDyn, AttributeValueDyn, Item, Table, TableDyn};
use core_types::uuid::NodeId; use core_types::uuid::NodeId;
use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM, AnyHash, BlendMode, CacheHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl}; use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM, AnyHash, BlendMode, CacheHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graphic_types::graphic::{Graphic, IntoGraphicTable}; use graphic_types::graphic::{Graphic, IntoGraphicList};
use graphic_types::{Artboard, Vector}; use graphic_types::{Artboard, Vector};
use raster_types::{CPU, GPU, Raster}; use raster_types::{CPU, GPU, Raster};
use vector_types::gradient::{GradientSpreadMethod, GradientType}; use vector_types::gradient::{GradientSpreadMethod, GradientType};
@@ -17,17 +17,17 @@ pub fn index_elements<T: graphic_types::graphic::AtIndex + Clone + Default>(
_: impl Ctx, _: impl Ctx,
/// The list of data. /// The list of data.
#[implementations( #[implementations(
Table<Artboard>, List<Artboard>,
Table<Graphic>, List<Graphic>,
Table<Vector>, List<Vector>,
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Raster<GPU>>, List<Raster<GPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
Table<String>, List<String>,
Table<f64>, List<f64>,
Table<u8>, List<u8>,
Table<NodeId>, List<NodeId>,
)] )]
list: T, list: T,
/// The index of the item to retrieve, starting from 0 for the first item. Negative indices count backwards from the end of the list, starting from -1 for the last item. /// The index of the item to retrieve, starting from 0 for the first item. Negative indices count backwards from the end of the list, starting from -1 for the last item.
@@ -48,14 +48,14 @@ pub fn omit_element<T: graphic_types::graphic::OmitIndex + Clone + Default>(
_: impl Ctx, _: impl Ctx,
/// The list of data. /// The list of data.
#[implementations( #[implementations(
Table<String>, List<String>,
Table<Artboard>, List<Artboard>,
Table<Graphic>, List<Graphic>,
Table<Vector>, List<Vector>,
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Raster<GPU>>, List<Raster<GPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
list: T, list: T,
/// The index of the item to remove, starting from 0 for the first item. Negative indices count backwards from the end of the list, starting from -1 for the last item. /// The index of the item to remove, starting from 0 for the first item. Negative indices count backwards from the end of the list, starting from -1 for the last item.
@@ -70,30 +70,30 @@ pub fn omit_element<T: graphic_types::graphic::OmitIndex + Clone + Default>(
} }
} }
/// Returns the bare element (without the item's attributes) at the specified index in a `Table`. /// Returns the bare element (without the item's attributes) at the specified index in a `List`.
/// Use this when downstream nodes want just the inner value rather than a `Table` containing a single item. /// Use this when downstream nodes want just the inner value rather than a `List` containing a single item.
/// If no value exists at that index, the element type's default is returned. /// If no value exists at that index, the element type's default is returned.
#[node_macro::node(category("General"))] #[node_macro::node(category("General"))]
pub fn extract_element<T: Clone + Default + Send + Sync + 'static>( pub fn extract_element<T: Clone + Default + Send + Sync + 'static>(
_: impl Ctx, _: impl Ctx,
/// The `Table` of data to extract from. /// The `List` of data to extract from.
#[implementations( #[implementations(
Table<String>, List<String>,
Table<f64>, List<f64>,
Table<u8>, List<u8>,
Table<NodeId>, List<NodeId>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
Table<Vector>, List<Vector>,
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Graphic>, List<Graphic>,
Table<Artboard>, List<Artboard>,
)] )]
table: Table<T>, list: List<T>,
/// The index of the item to retrieve, starting from 0 for the first item. Negative indices count backwards from the end of the list, starting from -1 for the last item. /// The index of the item to retrieve, starting from 0 for the first item. Negative indices count backwards from the end of the list, starting from -1 for the last item.
index: SignedInteger, index: SignedInteger,
) -> T { ) -> T {
let len = table.len(); let len = list.len();
let index = index as i32; let index = index as i32;
let resolved = if index < 0 { let resolved = if index < 0 {
let from_end = index.unsigned_abs() as usize; let from_end = index.unsigned_abs() as usize;
@@ -104,37 +104,37 @@ pub fn extract_element<T: Clone + Default + Send + Sync + 'static>(
} else { } else {
index as usize index as usize
}; };
table.element(resolved).cloned().unwrap_or_default() list.element(resolved).cloned().unwrap_or_default()
} }
#[node_macro::node(category("General"))] #[node_macro::node(category("General"))]
async fn map<Item: AnyHash + Send + Sync + CacheHash>( async fn map<Item: AnyHash + Send + Sync + CacheHash>(
ctx: impl Ctx + CloneVarArgs + ExtractAll, ctx: impl Ctx + CloneVarArgs + ExtractAll,
#[implementations( #[implementations(
Table<Graphic>, List<Graphic>,
Table<Vector>, List<Vector>,
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
content: Table<Item>, content: List<Item>,
#[implementations( #[implementations(
Context -> Table<Graphic>, Context -> List<Graphic>,
Context -> Table<Vector>, Context -> List<Vector>,
Context -> Table<Raster<CPU>>, Context -> List<Raster<CPU>>,
Context -> Table<Color>, Context -> List<Color>,
Context -> Table<GradientStops>, Context -> List<GradientStops>,
)] )]
mapped: impl Node<Context<'static>, Output = Table<Item>>, mapped: impl Node<Context<'static>, Output = List<Item>>,
) -> Table<Item> { ) -> List<Item> {
let mut rows = Table::new(); let mut rows = List::new();
for (i, row) in content.into_iter().enumerate() { for (i, row) in content.into_iter().enumerate() {
let owned_ctx = OwnedContextImpl::from(ctx.clone()); let owned_ctx = OwnedContextImpl::from(ctx.clone());
let owned_ctx = owned_ctx.with_vararg(Box::new(Table::new_from_item(row))).with_index(i); let owned_ctx = owned_ctx.with_vararg(Box::new(List::new_from_item(row))).with_index(i);
let table = mapped.eval(owned_ctx.into_context()).await; let list = mapped.eval(owned_ctx.into_context()).await;
rows.extend(table); rows.extend(list);
} }
rows rows
@@ -144,20 +144,20 @@ async fn map<Item: AnyHash + Send + Sync + CacheHash>(
async fn mirror<T: 'n + Send + Clone>( async fn mirror<T: 'n + Send + Clone>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Graphic>, List<Graphic>,
Table<Vector>, List<Vector>,
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
content: Table<T>, content: List<T>,
#[default(ReferencePoint::Center)] relative_to_bounds: ReferencePoint, #[default(ReferencePoint::Center)] relative_to_bounds: ReferencePoint,
#[unit(" px")] offset: f64, #[unit(" px")] offset: f64,
#[range((-90., 90.))] angle: Angle, #[range((-90., 90.))] angle: Angle,
#[default(true)] keep_original: bool, #[default(true)] keep_original: bool,
) -> Table<T> ) -> List<T>
where where
Table<T>: BoundingBox, List<T>: BoundingBox,
{ {
// Normalize the direction vector // Normalize the direction vector
let normal = DVec2::from_angle(angle.to_radians()); let normal = DVec2::from_angle(angle.to_radians());
@@ -186,12 +186,12 @@ where
reflection * DAffine2::from_translation(DVec2::from_angle(angle.to_radians()) * DVec2::splat(-offset)) reflection * DAffine2::from_translation(DVec2::from_angle(angle.to_radians()) * DVec2::splat(-offset))
}; };
let mut result_table = Table::new(); let mut result_list = List::new();
// Add original items depending on the keep_original flag // Add original items depending on the keep_original flag
if keep_original { if keep_original {
for item in content.clone().into_iter() { for item in content.clone().into_iter() {
result_table.push(item); result_list.push(item);
} }
} }
@@ -199,10 +199,10 @@ where
for mut row in content.into_iter() { for mut row in content.into_iter() {
let current_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); let current_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
row.set_attribute(ATTR_TRANSFORM, reflected_transform * current_transform); row.set_attribute(ATTR_TRANSFORM, reflected_transform * current_transform);
result_table.push(row); result_list.push(row);
} }
result_table result_list
} }
/// Returns the path identifying the subgraph (network) that contains this proto node — i.e. the input `node_path` /// Returns the path identifying the subgraph (network) that contains this proto node — i.e. the input `node_path`
@@ -212,13 +212,13 @@ where
/// editor tools (e.g. selection, click target routing) trace data back to its owning layer regardless of whether /// editor tools (e.g. selection, click target routing) trace data back to its owning layer regardless of whether
/// the layer is at the root document network or nested inside a custom subgraph. /// the layer is at the root document network or nested inside a custom subgraph.
#[node_macro::node(name("Path of Subgraph"), category(""))] #[node_macro::node(name("Path of Subgraph"), category(""))]
pub fn path_of_subgraph(_: impl Ctx, node_path: Table<NodeId>) -> Table<NodeId> { pub fn path_of_subgraph(_: impl Ctx, node_path: List<NodeId>) -> List<NodeId> {
let len = node_path.len(); let len = node_path.len();
node_path.into_iter().take(len.saturating_sub(1)).collect() node_path.into_iter().take(len.saturating_sub(1)).collect()
} }
/// Sets a named attribute on the input `Table`, computing one value per item via the value-producing input. That input /// Sets a named attribute on the input `List`, computing one value per item via the value-producing input. That input
/// is evaluated once per item, with the item's index and the item itself (as a `Table` containing only that item, /// is evaluated once per item, with the item's index and the item itself (as a `List` containing only that item,
/// passed as a vararg) provided via context, so the upstream pipeline can return a different value per item that may /// passed as a vararg) provided via context, so the upstream pipeline can return a different value per item that may
/// be derived from the item's own data. If the attribute already exists, its values are replaced; if not, it's added. /// be derived from the item's own data. If the attribute already exists, its values are replaced; if not, it's added.
/// The value is type-erased into an `AttributeValueDyn` by an auto-inserted convert node, so this node only /// The value is type-erased into an `AttributeValueDyn` by an auto-inserted convert node, so this node only
@@ -226,66 +226,66 @@ pub fn path_of_subgraph(_: impl Ctx, node_path: Table<NodeId>) -> Table<NodeId>
#[node_macro::node(category("Attributes: Write"))] #[node_macro::node(category("Attributes: Write"))]
async fn write_attribute<T: AnyHash + Clone + Send + Sync + CacheHash>( async fn write_attribute<T: AnyHash + Clone + Send + Sync + CacheHash>(
ctx: impl ExtractAll + CloneVarArgs + Ctx, ctx: impl ExtractAll + CloneVarArgs + Ctx,
/// The `Table` to set the named attribute on (one value per item). /// The `List` to set the named attribute on (one value per item).
#[implementations( #[implementations(
Table<Artboard>, List<Artboard>,
Table<Graphic>, List<Graphic>,
Table<Vector>, List<Vector>,
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
Table<f64>, List<f64>,
Table<bool>, List<bool>,
Table<String>, List<String>,
Table<DAffine2>, List<DAffine2>,
Table<BlendMode>, List<BlendMode>,
Table<GradientType>, List<GradientType>,
Table<GradientSpreadMethod>, List<GradientSpreadMethod>,
)] )]
mut content: Table<T>, mut content: List<T>,
/// The attribute name (key) to write or replace. /// The attribute name (key) to write or replace.
name: String, name: String,
/// The node that produces the attribute value for each item. Called once per item with the item's index in context. /// The node that produces the attribute value for each item. Called once per item with the item's index in context.
#[implementations(Context -> AttributeValueDyn)] #[implementations(Context -> AttributeValueDyn)]
value: impl Node<'n, Context<'static>, Output = AttributeValueDyn>, value: impl Node<'n, Context<'static>, Output = AttributeValueDyn>,
) -> Table<T> { ) -> List<T> {
for index in 0..content.len() { for index in 0..content.len() {
let row = content.clone_item(index).expect("index is within bounds"); let row = content.clone_item(index).expect("index is within bounds");
let owned_ctx = OwnedContextImpl::from(ctx.clone()).with_vararg(Box::new(Table::new_from_item(row))).with_index(index); let owned_ctx = OwnedContextImpl::from(ctx.clone()).with_vararg(Box::new(List::new_from_item(row))).with_index(index);
let v = value.eval(owned_ctx.into_context()).await; let v = value.eval(owned_ctx.into_context()).await;
content.set_attribute_value_dyn(&name, index, v); content.set_attribute_value_dyn(&name, index, v);
} }
content content
} }
/// Sets a named attribute on the primary table, with each value taken from the corresponding item's element in the source table (paired by index, wrapping if the source has fewer items). /// Sets a named attribute on the primary list, with each value taken from the corresponding item's element in the source list (paired by index, wrapping if the source has fewer items).
/// The source is type-erased into an `AttributeDyn` by an auto-inserted convert node, so this node only monomorphizes over `T` instead of the cartesian product `(T, U)`. /// The source is type-erased into an `AttributeDyn` by an auto-inserted convert node, so this node only monomorphizes over `T` instead of the cartesian product `(T, U)`.
#[node_macro::node(category("Attributes: Write"))] #[node_macro::node(category("Attributes: Write"))]
fn attach_attribute<T: AnyHash + Clone + Send + Sync + CacheHash>( fn attach_attribute<T: AnyHash + Clone + Send + Sync + CacheHash>(
_: impl Ctx, _: impl Ctx,
/// The `Table` to attach the new attribute to. /// The `List` to attach the new attribute to.
#[implementations( #[implementations(
Table<Artboard>, List<Artboard>,
Table<Graphic>, List<Graphic>,
Table<Vector>, List<Vector>,
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
Table<f64>, List<f64>,
Table<bool>, List<bool>,
Table<String>, List<String>,
Table<DAffine2>, List<DAffine2>,
Table<BlendMode>, List<BlendMode>,
Table<GradientType>, List<GradientType>,
Table<GradientSpreadMethod>, List<GradientSpreadMethod>,
)] )]
mut content: Table<T>, mut content: List<T>,
/// The source values to attach. Any `Table<U>` wired here is type-erased via an auto-inserted convert. /// The source values to attach. Any `List<U>` wired here is type-erased via an auto-inserted convert.
#[expose] #[expose]
source: AttributeDyn, source: AttributeDyn,
/// The name to assign to the new destination attribute. /// The name to assign to the new destination attribute.
name: String, name: String,
) -> Table<T> { ) -> List<T> {
if source.is_empty() { if source.is_empty() {
return content; return content;
} }
@@ -293,15 +293,15 @@ fn attach_attribute<T: AnyHash + Clone + Send + Sync + CacheHash>(
content content
} }
/// Reads a named `Vector` attribute from the input table, outputting each value as an element of a new `Table<Vector>`. /// Reads a named `Vector` attribute from the input list, outputting each value as an element of a new `List<Vector>`.
#[node_macro::node(category("Attributes: Read"))] #[node_macro::node(category("Attributes: Read"))]
fn read_attribute_vector( fn read_attribute_vector(
_: impl Ctx, _: impl Ctx,
content: TableDyn, content: ListDyn,
/// The attribute name (key) to read. /// The attribute name (key) to read.
name: String, name: String,
) -> Table<Vector> { ) -> List<Vector> {
let mut result = Table::with_capacity(content.len()); let mut result = List::with_capacity(content.len());
for index in 0..content.len() { for index in 0..content.len() {
let Some(value) = content.attribute::<Vector>(&name, index) else { continue }; let Some(value) = content.attribute::<Vector>(&name, index) else { continue };
result.push(Item::new_from_element(value.clone())); result.push(Item::new_from_element(value.clone()));
@@ -309,15 +309,15 @@ fn read_attribute_vector(
result result
} }
/// Reads a named numeric attribute (`f64`, `u64`, or `u32`) from the input table, outputting each value as an element of a new `Table<f64>`. Integer values are converted to `f64`. /// Reads a named numeric attribute (`f64`, `u64`, or `u32`) from the input list, outputting each value as an element of a new `List<f64>`. Integer values are converted to `f64`.
#[node_macro::node(category("Attributes: Read"))] #[node_macro::node(category("Attributes: Read"))]
fn read_attribute_number( fn read_attribute_number(
_: impl Ctx, _: impl Ctx,
content: TableDyn, content: ListDyn,
/// The attribute name (key) to read. /// The attribute name (key) to read.
name: String, name: String,
) -> Table<f64> { ) -> List<f64> {
let mut result = Table::with_capacity(content.len()); let mut result = List::with_capacity(content.len());
for index in 0..content.len() { for index in 0..content.len() {
let value = content let value = content
.attribute::<f64>(&name, index) .attribute::<f64>(&name, index)
@@ -330,15 +330,15 @@ fn read_attribute_number(
result result
} }
/// Reads a named `bool` attribute from the input table, outputting each value as an element of a new `Table<bool>`. /// Reads a named `bool` attribute from the input list, outputting each value as an element of a new `List<bool>`.
#[node_macro::node(category("Attributes: Read"))] #[node_macro::node(category("Attributes: Read"))]
fn read_attribute_bool( fn read_attribute_bool(
_: impl Ctx, _: impl Ctx,
content: TableDyn, content: ListDyn,
/// The attribute name (key) to read. /// The attribute name (key) to read.
name: String, name: String,
) -> Table<bool> { ) -> List<bool> {
let mut result = Table::with_capacity(content.len()); let mut result = List::with_capacity(content.len());
for index in 0..content.len() { for index in 0..content.len() {
let Some(value) = content.attribute::<bool>(&name, index) else { continue }; let Some(value) = content.attribute::<bool>(&name, index) else { continue };
result.push(Item::new_from_element(*value)); result.push(Item::new_from_element(*value));
@@ -346,15 +346,15 @@ fn read_attribute_bool(
result result
} }
/// Reads a named `String` attribute from the input table, outputting each value as an element of a new `Table<String>`. /// Reads a named `String` attribute from the input list, outputting each value as an element of a new `List<String>`.
#[node_macro::node(category("Attributes: Read"))] #[node_macro::node(category("Attributes: Read"))]
fn read_attribute_string( fn read_attribute_string(
_: impl Ctx, _: impl Ctx,
content: TableDyn, content: ListDyn,
/// The attribute name (key) to read. /// The attribute name (key) to read.
name: String, name: String,
) -> Table<String> { ) -> List<String> {
let mut result = Table::with_capacity(content.len()); let mut result = List::with_capacity(content.len());
for index in 0..content.len() { for index in 0..content.len() {
let Some(value) = content.attribute::<String>(&name, index) else { continue }; let Some(value) = content.attribute::<String>(&name, index) else { continue };
result.push(Item::new_from_element(value.clone())); result.push(Item::new_from_element(value.clone()));
@@ -362,15 +362,15 @@ fn read_attribute_string(
result result
} }
/// Reads a named `DAffine2` transform attribute from the input table, outputting each value as an element of a new `Table<DAffine2>`. /// Reads a named `DAffine2` transform attribute from the input list, outputting each value as an element of a new `List<DAffine2>`.
#[node_macro::node(category("Attributes: Read"))] #[node_macro::node(category("Attributes: Read"))]
fn read_attribute_transform( fn read_attribute_transform(
_: impl Ctx, _: impl Ctx,
content: TableDyn, content: ListDyn,
/// The attribute name (key) to read. /// The attribute name (key) to read.
name: String, name: String,
) -> Table<DAffine2> { ) -> List<DAffine2> {
let mut result = Table::with_capacity(content.len()); let mut result = List::with_capacity(content.len());
for index in 0..content.len() { for index in 0..content.len() {
let Some(value) = content.attribute::<DAffine2>(&name, index) else { continue }; let Some(value) = content.attribute::<DAffine2>(&name, index) else { continue };
result.push(Item::new_from_element(*value)); result.push(Item::new_from_element(*value));
@@ -378,15 +378,15 @@ fn read_attribute_transform(
result result
} }
/// Reads a named `Color` attribute from the input table, outputting each value as an element of a new `Table<Color>`. /// Reads a named `Color` attribute from the input list, outputting each value as an element of a new `List<Color>`.
#[node_macro::node(category("Attributes: Read"))] #[node_macro::node(category("Attributes: Read"))]
fn read_attribute_color( fn read_attribute_color(
_: impl Ctx, _: impl Ctx,
content: TableDyn, content: ListDyn,
/// The attribute name (key) to read. /// The attribute name (key) to read.
name: String, name: String,
) -> Table<Color> { ) -> List<Color> {
let mut result = Table::with_capacity(content.len()); let mut result = List::with_capacity(content.len());
for index in 0..content.len() { for index in 0..content.len() {
let Some(value) = content.attribute::<Color>(&name, index) else { continue }; let Some(value) = content.attribute::<Color>(&name, index) else { continue };
result.push(Item::new_from_element(*value)); result.push(Item::new_from_element(*value));
@@ -394,15 +394,15 @@ fn read_attribute_color(
result result
} }
/// Reads a named `BlendMode` attribute from the input table, outputting each value as an element of a new `Table<BlendMode>`. /// Reads a named `BlendMode` attribute from the input list, outputting each value as an element of a new `List<BlendMode>`.
#[node_macro::node(category("Attributes: Read"))] #[node_macro::node(category("Attributes: Read"))]
fn read_attribute_blend_mode( fn read_attribute_blend_mode(
_: impl Ctx, _: impl Ctx,
content: TableDyn, content: ListDyn,
/// The attribute name (key) to read. /// The attribute name (key) to read.
name: String, name: String,
) -> Table<BlendMode> { ) -> List<BlendMode> {
let mut result = Table::with_capacity(content.len()); let mut result = List::with_capacity(content.len());
for index in 0..content.len() { for index in 0..content.len() {
let Some(value) = content.attribute::<BlendMode>(&name, index) else { continue }; let Some(value) = content.attribute::<BlendMode>(&name, index) else { continue };
result.push(Item::new_from_element(*value)); result.push(Item::new_from_element(*value));
@@ -410,15 +410,15 @@ fn read_attribute_blend_mode(
result result
} }
/// Reads a named `GradientType` attribute from the input table, outputting each value as an element of a new `Table<GradientType>`. /// Reads a named `GradientType` attribute from the input list, outputting each value as an element of a new `List<GradientType>`.
#[node_macro::node(category("Attributes: Read"))] #[node_macro::node(category("Attributes: Read"))]
fn read_attribute_gradient_type( fn read_attribute_gradient_type(
_: impl Ctx, _: impl Ctx,
content: TableDyn, content: ListDyn,
/// The attribute name (key) to read. /// The attribute name (key) to read.
name: String, name: String,
) -> Table<GradientType> { ) -> List<GradientType> {
let mut result = Table::with_capacity(content.len()); let mut result = List::with_capacity(content.len());
for index in 0..content.len() { for index in 0..content.len() {
let Some(value) = content.attribute::<GradientType>(&name, index) else { continue }; let Some(value) = content.attribute::<GradientType>(&name, index) else { continue };
result.push(Item::new_from_element(*value)); result.push(Item::new_from_element(*value));
@@ -426,15 +426,15 @@ fn read_attribute_gradient_type(
result result
} }
/// Reads a named `GradientSpreadMethod` attribute from the input table, outputting each value as an element of a new `Table<GradientSpreadMethod>`. /// Reads a named `GradientSpreadMethod` attribute from the input list, outputting each value as an element of a new `List<GradientSpreadMethod>`.
#[node_macro::node(category("Attributes: Read"))] #[node_macro::node(category("Attributes: Read"))]
fn read_attribute_spread_method( fn read_attribute_spread_method(
_: impl Ctx, _: impl Ctx,
content: TableDyn, content: ListDyn,
/// The attribute name (key) to read. /// The attribute name (key) to read.
name: String, name: String,
) -> Table<GradientSpreadMethod> { ) -> List<GradientSpreadMethod> {
let mut result = Table::with_capacity(content.len()); let mut result = List::with_capacity(content.len());
for index in 0..content.len() { for index in 0..content.len() {
let Some(value) = content.attribute::<GradientSpreadMethod>(&name, index) else { continue }; let Some(value) = content.attribute::<GradientSpreadMethod>(&name, index) else { continue };
result.push(Item::new_from_element(*value)); result.push(Item::new_from_element(*value));
@@ -442,15 +442,15 @@ fn read_attribute_spread_method(
result result
} }
/// Reads a named `GradientStops` attribute from the input table, outputting each value as an element of a new `Table<GradientStops>`. /// Reads a named `GradientStops` attribute from the input list, outputting each value as an element of a new `List<GradientStops>`.
#[node_macro::node(category("Attributes: Read"))] #[node_macro::node(category("Attributes: Read"))]
fn read_attribute_gradient_stops( fn read_attribute_gradient_stops(
_: impl Ctx, _: impl Ctx,
content: TableDyn, content: ListDyn,
/// The attribute name (key) to read. /// The attribute name (key) to read.
name: String, name: String,
) -> Table<GradientStops> { ) -> List<GradientStops> {
let mut result = Table::with_capacity(content.len()); let mut result = List::with_capacity(content.len());
for index in 0..content.len() { for index in 0..content.len() {
let Some(value) = content.attribute::<GradientStops>(&name, index) else { continue }; let Some(value) = content.attribute::<GradientStops>(&name, index) else { continue };
result.push(Item::new_from_element(value.clone())); result.push(Item::new_from_element(value.clone()));
@@ -458,15 +458,15 @@ fn read_attribute_gradient_stops(
result result
} }
/// Reads a named `Artboard` attribute from the input table, outputting each value as an element of a new `Table<Artboard>`. /// Reads a named `Artboard` attribute from the input list, outputting each value as an element of a new `List<Artboard>`.
#[node_macro::node(category("Attributes: Read"))] #[node_macro::node(category("Attributes: Read"))]
fn read_attribute_artboard( fn read_attribute_artboard(
_: impl Ctx, _: impl Ctx,
content: TableDyn, content: ListDyn,
/// The attribute name (key) to read. /// The attribute name (key) to read.
name: String, name: String,
) -> Table<Artboard> { ) -> List<Artboard> {
let mut result = Table::with_capacity(content.len()); let mut result = List::with_capacity(content.len());
for index in 0..content.len() { for index in 0..content.len() {
let Some(value) = content.attribute::<Artboard>(&name, index) else { continue }; let Some(value) = content.attribute::<Artboard>(&name, index) else { continue };
result.push(Item::new_from_element(value.clone())); result.push(Item::new_from_element(value.clone()));
@@ -474,15 +474,15 @@ fn read_attribute_artboard(
result result
} }
/// Reads a named `Raster<CPU>` attribute from the input table, outputting each value as an element of a new `Table<Raster<CPU>>`. /// Reads a named `Raster<CPU>` attribute from the input list, outputting each value as an element of a new `List<Raster<CPU>>`.
#[node_macro::node(category("Attributes: Read"))] #[node_macro::node(category("Attributes: Read"))]
fn read_attribute_raster( fn read_attribute_raster(
_: impl Ctx, _: impl Ctx,
content: TableDyn, content: ListDyn,
/// The attribute name (key) to read. /// The attribute name (key) to read.
name: String, name: String,
) -> Table<Raster<CPU>> { ) -> List<Raster<CPU>> {
let mut result = Table::with_capacity(content.len()); let mut result = List::with_capacity(content.len());
for index in 0..content.len() { for index in 0..content.len() {
let Some(value) = content.attribute::<Raster<CPU>>(&name, index) else { continue }; let Some(value) = content.attribute::<Raster<CPU>>(&name, index) else { continue };
result.push(Item::new_from_element(value.clone())); result.push(Item::new_from_element(value.clone()));
@@ -490,18 +490,18 @@ fn read_attribute_raster(
result result
} }
/// Joins two `Table`s of the same type, extending the base `Table` with the items from the new `Table`. /// Joins two `List`s of the same type, extending the base `List` with the items from the new `List`.
#[node_macro::node(category("General"))] #[node_macro::node(category("General"))]
pub async fn extend<T: 'n + Send + Clone>( pub async fn extend<T: 'n + Send + Clone>(
_: impl Ctx, _: impl Ctx,
/// The `Table` whose items will appear at the start of the extended `Table`. /// The `List` whose items will appear at the start of the extended `List`.
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)] #[implementations(List<Artboard>, List<Graphic>, List<Vector>, List<Raster<CPU>>, List<Raster<GPU>>, List<Color>, List<GradientStops>)]
base: Table<T>, base: List<T>,
/// The `Table` whose items will appear at the end of the extended `Table`. /// The `List` whose items will appear at the end of the extended `List`.
#[expose] #[expose]
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)] #[implementations(List<Artboard>, List<Graphic>, List<Vector>, List<Raster<CPU>>, List<Raster<GPU>>, List<Color>, List<GradientStops>)]
new: Table<T>, new: List<T>,
) -> Table<T> { ) -> List<T> {
let mut base = base; let mut base = base;
base.extend(new); base.extend(new);
@@ -514,12 +514,12 @@ pub async fn extend<T: 'n + Send + Clone>(
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
pub async fn legacy_layer_extend<T: 'n + Send + Clone>( pub async fn legacy_layer_extend<T: 'n + Send + Clone>(
_: impl Ctx, _: impl Ctx,
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)] base: Table<T>, #[implementations(List<Artboard>, List<Graphic>, List<Vector>, List<Raster<CPU>>, List<Raster<GPU>>, List<Color>, List<GradientStops>)] base: List<T>,
#[expose] #[expose]
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)] #[implementations(List<Artboard>, List<Graphic>, List<Vector>, List<Raster<CPU>>, List<Raster<GPU>>, List<Color>, List<GradientStops>)]
new: Table<T>, new: List<T>,
nested_node_path: Table<NodeId>, nested_node_path: List<NodeId>,
) -> Table<T> { ) -> List<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 layer = { let layer = {
@@ -542,46 +542,46 @@ pub async fn legacy_layer_extend<T: 'n + Send + Clone>(
pub async fn wrap_graphic<T: Into<Graphic> + 'n>( pub async fn wrap_graphic<T: Into<Graphic> + 'n>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Graphic>, List<Graphic>,
Table<Vector>, List<Vector>,
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Raster<GPU>>, List<Raster<GPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
DAffine2, DAffine2,
)] )]
content: T, content: T,
) -> Table<Graphic> { ) -> List<Graphic> {
Table::new_from_element(content.into()) List::new_from_element(content.into())
} }
/// Converts a `Table` of graphical content into a `Table<Graphic>` by placing it into an element of a new wrapper `Table<Graphic>`. /// Converts a `List` of graphical content into a `List<Graphic>` by placing it into an element of a new wrapper `List<Graphic>`.
/// If it is already a `Table<Graphic>`, it is not wrapped again. Use the 'Wrap Graphic' node if wrapping is always desired. /// If it is already a `List<Graphic>`, it is not wrapped again. Use the 'Wrap Graphic' node if wrapping is always desired.
#[node_macro::node(category("General"))] #[node_macro::node(category("General"))]
pub async fn to_graphic<T: IntoGraphicTable + 'n>( pub async fn to_graphic<T: IntoGraphicList + 'n>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Graphic>, List<Graphic>,
Table<Vector>, List<Vector>,
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Raster<GPU>>, List<Raster<GPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
content: T, content: T,
) -> Table<Graphic> { ) -> List<Graphic> {
content.into_graphic_table() content.into_graphic_list()
} }
/// Removes a level of nesting from a `Table<Graphic>`, or all nesting if "Fully Flatten" is enabled. /// Removes a level of nesting from a `List<Graphic>`, or all nesting if "Fully Flatten" is enabled.
#[node_macro::node(category("General"))] #[node_macro::node(category("General"))]
pub async fn flatten_graphic(_: impl Ctx, content: Table<Graphic>, fully_flatten: bool) -> Table<Graphic> { pub async fn flatten_graphic(_: impl Ctx, content: List<Graphic>, fully_flatten: bool) -> List<Graphic> {
// TODO: Avoid mutable reference, instead return a new Table<Graphic>? // TODO: Avoid mutable reference, instead return a new List<Graphic>?
fn flatten_table(output_graphic_table: &mut Table<Graphic>, current_graphic_table: Table<Graphic>, fully_flatten: bool, recursion_depth: usize) { fn flatten_list(output_graphic_list: &mut List<Graphic>, current_graphic_list: List<Graphic>, fully_flatten: bool, recursion_depth: usize) {
for index in 0..current_graphic_table.len() { for index in 0..current_graphic_list.len() {
let Some(current_element) = current_graphic_table.element(index) else { continue }; let Some(current_element) = current_graphic_list.element(index) else { continue };
let current_element = current_element.clone(); let current_element = current_element.clone();
let current_transform: DAffine2 = current_graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index); let current_transform: DAffine2 = current_graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let recurse = fully_flatten || recursion_depth == 0; let recurse = fully_flatten || recursion_depth == 0;
@@ -593,82 +593,82 @@ pub async fn flatten_graphic(_: impl Ctx, content: Table<Graphic>, fully_flatten
*graphic_transform = current_transform * *graphic_transform; *graphic_transform = current_transform * *graphic_transform;
} }
flatten_table(output_graphic_table, current_element, fully_flatten, recursion_depth + 1); flatten_list(output_graphic_list, current_element, fully_flatten, recursion_depth + 1);
} }
// Push any leaf elements we encounter: either `Graphic::Graphic(...)` values beyond the recursion depth, or non-`Graphic::Graphic` variants (e.g. `Graphic::Vector`, `Graphic::Raster*`, `Graphic::Color`, `Graphic::Gradient`) // Push any leaf elements we encounter: either `Graphic::Graphic(...)` values beyond the recursion depth, or non-`Graphic::Graphic` variants (e.g. `Graphic::Vector`, `Graphic::Raster*`, `Graphic::Color`, `Graphic::Gradient`)
_ => { _ => {
let attributes = current_graphic_table.clone_item_attributes(index); let attributes = current_graphic_list.clone_item_attributes(index);
output_graphic_table.push(Item::from_parts(current_element, attributes)); output_graphic_list.push(Item::from_parts(current_element, attributes));
} }
} }
} }
} }
let mut output = Table::new(); let mut output = List::new();
flatten_table(&mut output, content, fully_flatten, 0); flatten_list(&mut output, content, fully_flatten, 0);
output output
} }
/// Converts a `Table<Graphic>` into a `Table<Vector>` by deeply flattening any vector content it contains, and discarding any non-vector content. /// Converts a `List<Graphic>` into a `List<Vector>` by deeply flattening any vector content it contains, and discarding any non-vector content.
#[node_macro::node(category("Vector"))] #[node_macro::node(category("Vector"))]
pub async fn flatten_vector<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Vector>)] content: T) -> Table<Vector> { pub async fn flatten_vector<T: IntoGraphicList + 'n + Send + Clone>(_: impl Ctx, #[implementations(List<Graphic>, List<Vector>)] content: T) -> List<Vector> {
let graphic_table = content.into_graphic_table(); let graphic_list = content.into_graphic_list();
let mut output: Table<Vector> = graphic_table.clone().into_flattened_table(); let mut output: List<Vector> = graphic_list.clone().into_flattened_list();
// TODO: Replace this snapshot hack with per-layer metadata driven by each layer's Monitor node. // TODO: Replace this snapshot hack with per-layer metadata driven by each layer's Monitor node.
// TODO: Flattening here erases the upstream `Table<Graphic>` hierarchy that editor metadata collection walks // TODO: Flattening here erases the upstream `List<Graphic>` hierarchy that editor metadata collection walks
// TODO: to populate `upstream_footprints` / `local_transforms` / `click_targets` per child layer. As a workaround // TODO: to populate `upstream_footprints` / `local_transforms` / `click_targets` per child layer. As a workaround
// TODO: we stash the pre-flattened table on the output so `Table<Vector>::collect_metadata` can recurse into it, // TODO: we stash the pre-flattened list on the output so `List<Vector>::collect_metadata` can recurse into it,
// TODO: which conflates render output with editor metadata and forces the pre-compensation dance below. // TODO: which conflates render output with editor metadata and forces the pre-compensation dance below.
// TODO: The cleaner fix is to drive each layer's metadata from its own Monitor's captured `(Context, Table<Graphic>)`, // TODO: The cleaner fix is to drive each layer's metadata from its own Monitor's captured `(Context, List<Graphic>)`,
// TODO: at which point this attribute (and the equivalents in Boolean Operation, Solidify Stroke, Flatten Path, // TODO: at which point this attribute (and the equivalents in Boolean Operation, Solidify Stroke, Flatten Path,
// TODO: Morph, Rasterize) become unnecessary. // TODO: Morph, Rasterize) become unnecessary.
if !output.is_empty() { if !output.is_empty() {
// Item 0 carries a composed transform inherited from the flattened input, but the merged_layers // Item 0 carries a composed transform inherited from the flattened input, but the merged_layers
// already holds the original transforms; pre-compensate by item 0's inverse so the renderer's // already holds the original transforms; pre-compensate by item 0's inverse so the renderer's
// `upstream_footprint *= item_0_transform` recursion cancels out and leaves the originals intact. // `upstream_footprint *= item_0_transform` recursion cancels out and leaves the originals intact.
let mut graphic_table = graphic_table; let mut graphic_list = graphic_list;
let item_0_transform: DAffine2 = output.attribute_cloned_or_default(ATTR_TRANSFORM, 0); let item_0_transform: DAffine2 = output.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
if item_0_transform.matrix2.determinant().abs() > f64::EPSILON { if item_0_transform.matrix2.determinant().abs() > f64::EPSILON {
let inverse = item_0_transform.inverse(); let inverse = item_0_transform.inverse();
for transform in graphic_table.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) { for transform in graphic_list.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
*transform = inverse * *transform; *transform = inverse * *transform;
} }
} }
output.set_attribute(ATTR_EDITOR_MERGED_LAYERS, 0, graphic_table); output.set_attribute(ATTR_EDITOR_MERGED_LAYERS, 0, graphic_list);
} }
output output
} }
/// Converts a `Table<Graphic>` into a `Table<Raster>` by deeply flattening any raster content it contains, and discarding any non-raster content. /// Converts a `List<Graphic>` into a `List<Raster>` by deeply flattening any raster content it contains, and discarding any non-raster content.
#[node_macro::node(category("Raster"))] #[node_macro::node(category("Raster"))]
pub async fn flatten_raster<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Raster<CPU>>)] content: T) -> Table<Raster<CPU>> { pub async fn flatten_raster<T: IntoGraphicList + 'n + Send + Clone>(_: impl Ctx, #[implementations(List<Graphic>, List<Raster<CPU>>)] content: T) -> List<Raster<CPU>> {
content.into_flattened_table() content.into_flattened_list()
} }
/// Converts a `Table<Graphic>` into a `Table<Color>` by deeply flattening any color content it contains, and discarding any non-color content. /// Converts a `List<Graphic>` into a `List<Color>` by deeply flattening any color content it contains, and discarding any non-color content.
#[node_macro::node(category("General"))] #[node_macro::node(category("General"))]
pub async fn flatten_color<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Color>)] content: T) -> Table<Color> { pub async fn flatten_color<T: IntoGraphicList + 'n + Send + Clone>(_: impl Ctx, #[implementations(List<Graphic>, List<Color>)] content: T) -> List<Color> {
content.into_flattened_table() content.into_flattened_list()
} }
/// Converts a `Table<Graphic>` into a `Table<GradientStops>` by deeply flattening any gradient content it contains, and discarding any non-gradient content. /// Converts a `List<Graphic>` into a `List<GradientStops>` by deeply flattening any gradient content it contains, and discarding any non-gradient content.
#[node_macro::node(category("General"))] #[node_macro::node(category("General"))]
pub async fn flatten_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<GradientStops>)] content: T) -> Table<GradientStops> { pub async fn flatten_gradient<T: IntoGraphicList + 'n + Send + Clone>(_: impl Ctx, #[implementations(List<Graphic>, List<GradientStops>)] content: T) -> List<GradientStops> {
content.into_flattened_table() content.into_flattened_list()
} }
/// Constructs a gradient from a `Table<Color>`, where the colors are evenly distributed as gradient stops across the range from 0 to 1. /// Constructs a gradient from a `List<Color>`, where the colors are evenly distributed as gradient stops across the range from 0 to 1.
#[node_macro::node(category("Color"))] #[node_macro::node(category("Color"))]
fn colors_to_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Color>)] colors: T) -> Table<GradientStops> { fn colors_to_gradient<T: IntoGraphicList + 'n + Send + Clone>(_: impl Ctx, #[implementations(List<Graphic>, List<Color>)] colors: T) -> List<GradientStops> {
let colors = colors.into_flattened_table::<Color>(); let colors = colors.into_flattened_list::<Color>();
let total_colors = colors.len(); let total_colors = colors.len();
if total_colors == 0 { if total_colors == 0 {
return Table::new_from_element(GradientStops::new(vec![ return List::new_from_element(GradientStops::new(vec![
GradientStop { GradientStop {
position: 0., position: 0.,
midpoint: 0.5, midpoint: 0.5,
@@ -683,7 +683,7 @@ fn colors_to_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[im
} }
if let (1, Some(&single_color)) = (total_colors, colors.element(0)) { if let (1, Some(&single_color)) = (total_colors, colors.element(0)) {
return Table::new_from_element(GradientStops::new(vec![ return List::new_from_element(GradientStops::new(vec![
GradientStop { GradientStop {
position: 0., position: 0.,
midpoint: 0.5, midpoint: 0.5,
@@ -702,5 +702,5 @@ fn colors_to_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[im
midpoint: 0.5, midpoint: 0.5,
color: row.into_element(), color: row.into_element(),
}); });
Table::new_from_element(GradientStops::new(colors)) List::new_from_element(GradientStops::new(colors))
} }
@@ -2,9 +2,9 @@
use base64::Engine; use base64::Engine;
#[cfg(target_family = "wasm")] #[cfg(target_family = "wasm")]
use canvas_utils::{Canvas, CanvasHandle}; use canvas_utils::{Canvas, CanvasHandle};
use core_types::list::{Item, List};
#[cfg(target_family = "wasm")] #[cfg(target_family = "wasm")]
use core_types::math::bbox::Bbox; use core_types::math::bbox::Bbox;
use core_types::table::{Item, Table};
#[cfg(target_family = "wasm")] #[cfg(target_family = "wasm")]
use core_types::transform::Footprint; use core_types::transform::Footprint;
#[cfg(target_family = "wasm")] #[cfg(target_family = "wasm")]
@@ -18,7 +18,7 @@ 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; use graphic_types::IntoGraphicList;
#[cfg(target_family = "wasm")] #[cfg(target_family = "wasm")]
use graphic_types::Vector; use graphic_types::Vector;
use graphic_types::raster_types::Image; use graphic_types::raster_types::Image;
@@ -85,7 +85,7 @@ async fn post_request(
#[name("URL")] #[name("URL")]
url: String, url: String,
/// The binary data to include in the body of the POST request. /// The binary data to include in the body of the POST request.
body: Table<u8>, body: List<u8>,
/// Makes the request run in the background without waiting on a response. This is useful for triggering webhooks without blocking the continued execution of the graph. /// Makes the request run in the background without waiting on a response. This is useful for triggering webhooks without blocking the continued execution of the graph.
discard_result: bool, discard_result: bool,
#[widget(ParsedWidgetOverride::Custom = "text_area")] headers: String, #[widget(ParsedWidgetOverride::Custom = "text_area")] headers: String,
@@ -115,14 +115,14 @@ async fn post_request(
/// Converts a text string to raw binary data. Useful for transmission over HTTP or writing to files. /// Converts a text string to raw binary data. Useful for transmission over HTTP or writing to files.
#[node_macro::node(category("Web Request"), name("String to Bytes"))] #[node_macro::node(category("Web Request"), name("String to Bytes"))]
fn string_to_bytes(_: impl Ctx, string: String) -> Table<u8> { fn string_to_bytes(_: impl Ctx, string: String) -> List<u8> {
string.into_bytes().into_iter().map(Item::new_from_element).collect() string.into_bytes().into_iter().map(Item::new_from_element).collect()
} }
/// Converts extracted raw RGBA pixel data from an input image. Each pixel becomes 4 sequential bytes. Useful for transmission over HTTP or writing to files. /// Converts extracted raw RGBA pixel data from an input image. Each pixel becomes 4 sequential bytes. Useful for transmission over HTTP or writing to files.
#[node_macro::node(category("Web Request"), name("Image to Bytes"))] #[node_macro::node(category("Web Request"), name("Image to Bytes"))]
fn image_to_bytes(_: impl Ctx, image: Table<Raster<CPU>>) -> Table<u8> { fn image_to_bytes(_: impl Ctx, image: List<Raster<CPU>>) -> List<u8> {
let Some(image) = image.element(0) else { return Table::new() }; let Some(image) = image.element(0) else { return List::new() };
image.data.iter().flat_map(|color| color.to_rgba8_srgb()).map(Item::new_from_element).collect() image.data.iter().flat_map(|color| color.to_rgba8_srgb()).map(Item::new_from_element).collect()
} }
@@ -146,9 +146,9 @@ async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")]
/// ///
/// Works with standard image format (PNG, JPEG, WebP, etc.). Automatically converts the color space to linear sRGB for accurate compositing. /// Works with standard image format (PNG, JPEG, WebP, etc.). Automatically converts the color space to linear sRGB for accurate compositing.
#[node_macro::node(category("Web Request"))] #[node_macro::node(category("Web Request"))]
fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> Table<Raster<CPU>> { fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> List<Raster<CPU>> {
let Some(image) = image::load_from_memory(data.as_ref()).ok() else { let Some(image) = image::load_from_memory(data.as_ref()).ok() else {
return Table::new(); return List::new();
}; };
let image = image.to_rgba32f(); let image = image.to_rgba32f();
let image = Image { let image = Image {
@@ -161,7 +161,7 @@ fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> Table<Raster<CPU>> {
..Default::default() ..Default::default()
}; };
Table::new_from_element(Raster::new_cpu(image)) List::new_from_element(Raster::new_cpu(image))
} }
#[cfg(target_family = "wasm")] #[cfg(target_family = "wasm")]
@@ -176,29 +176,29 @@ async fn create_canvas(_: impl Ctx) -> CanvasHandle {
async fn rasterize<T: WasmNotSend + Clone + 'n>( async fn rasterize<T: WasmNotSend + Clone + 'n>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Vector>, List<Vector>,
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Graphic>, List<Graphic>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
mut data: Table<T>, mut data: List<T>,
footprint: Footprint, footprint: Footprint,
mut canvas: CanvasHandle, mut canvas: CanvasHandle,
) -> Table<Raster<CPU>> ) -> List<Raster<CPU>>
where where
Table<T>: Render + Clone + graphic_types::IntoGraphicTable, List<T>: Render + Clone + graphic_types::IntoGraphicList,
{ {
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
if footprint.transform.matrix2.determinant() == 0. { if footprint.transform.matrix2.determinant() == 0. {
log::trace!("Invalid footprint received for rasterization"); log::trace!("Invalid footprint received for rasterization");
return Table::new(); return List::new();
} }
// Snapshot the input as a Table<Graphic> so the renderer can recurse into the original child layers // Snapshot the input as a List<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). // 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 upstream_graphic_list = data.clone().into_graphic_list();
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();
@@ -235,9 +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_item( List::new_from_item(
Item::new_from_element(Raster::new_cpu(image)) Item::new_from_element(Raster::new_cpu(image))
.with_attribute(ATTR_TRANSFORM, footprint.transform) .with_attribute(ATTR_TRANSFORM, footprint.transform)
.with_attribute(ATTR_EDITOR_MERGED_LAYERS, upstream_graphic_table), .with_attribute(ATTR_EDITOR_MERGED_LAYERS, upstream_graphic_list),
) )
} }
+7 -7
View File
@@ -1,4 +1,4 @@
use core_types::table::Table; use core_types::list::List;
use core_types::transform::{Footprint, Transform}; use core_types::transform::{Footprint, Transform};
use core_types::uuid::generate_uuid; use core_types::uuid::generate_uuid;
use core_types::{CloneVarArgs, ExtractAll, ExtractVarArgs}; use core_types::{CloneVarArgs, ExtractAll, ExtractVarArgs};
@@ -33,12 +33,12 @@ pub struct RenderIntermediate {
async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send + Sync>( async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send + Sync>(
ctx: impl Ctx + ExtractVarArgs + ExtractAll + CloneVarArgs, ctx: impl Ctx + ExtractVarArgs + ExtractAll + CloneVarArgs,
#[implementations( #[implementations(
Context -> Table<Artboard>, Context -> List<Artboard>,
Context -> Table<Graphic>, Context -> List<Graphic>,
Context -> Table<Vector>, Context -> List<Vector>,
Context -> Table<Raster<CPU>>, Context -> List<Raster<CPU>>,
Context -> Table<Color>, Context -> List<Color>,
Context -> Table<GradientStops>, Context -> List<GradientStops>,
)] )]
data: impl Node<Context<'static>, Output = T>, data: impl Node<Context<'static>, Output = T>,
) -> RenderIntermediate { ) -> RenderIntermediate {
+3 -2
View File
@@ -1,4 +1,5 @@
use core_types::{Ctx, table::Table}; use core_types::Ctx;
use core_types::list::List;
use graph_craft::application_io::PlatformEditorApi; use graph_craft::application_io::PlatformEditorApi;
use graphic_types::Vector; use graphic_types::Vector;
pub use text_nodes::*; pub use text_nodes::*;
@@ -61,7 +62,7 @@ fn text<'i: 'n>(
align: TextAlign, align: TextAlign,
/// Whether to split every letterform into its own vector item. Otherwise, a single vector compound path is produced. /// Whether to split every letterform into its own vector item. Otherwise, a single vector compound path is produced.
separate_glyphs: bool, separate_glyphs: bool,
) -> Table<Vector> { ) -> List<Vector> {
let typesetting = TypesettingConfig { let typesetting = TypesettingConfig {
font_size: size, font_size: size,
line_height_ratio: line_height, line_height_ratio: line_height,
+33 -33
View File
@@ -1,6 +1,6 @@
use core_types::Context; use core_types::Context;
use core_types::list::List;
use core_types::registry::types::{Fraction, Percentage, PixelSize}; use core_types::registry::types::{Fraction, Percentage, PixelSize};
use core_types::table::Table;
use core_types::transform::Footprint; use core_types::transform::Footprint;
use core_types::{Color, Ctx, num_traits}; use core_types::{Color, Ctx, num_traits};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
@@ -753,13 +753,13 @@ async fn switch<T, C: Send + 'n + Clone>(
Context -> u64, Context -> u64,
Context -> DVec2, Context -> DVec2,
Context -> DAffine2, Context -> DAffine2,
Context -> Table<Artboard>, Context -> List<Artboard>,
Context -> Table<Graphic>, Context -> List<Graphic>,
Context -> Table<Vector>, Context -> List<Vector>,
Context -> Table<Raster<CPU>>, Context -> List<Raster<CPU>>,
Context -> Table<Raster<GPU>>, Context -> List<Raster<GPU>>,
Context -> Table<Color>, Context -> List<Color>,
Context -> Table<GradientStops>, Context -> List<GradientStops>,
)] )]
if_true: impl Node<C, Output = T>, if_true: impl Node<C, Output = T>,
#[expose] #[expose]
@@ -772,13 +772,13 @@ async fn switch<T, C: Send + 'n + Clone>(
Context -> u64, Context -> u64,
Context -> DVec2, Context -> DVec2,
Context -> DAffine2, Context -> DAffine2,
Context -> Table<Artboard>, Context -> List<Artboard>,
Context -> Table<Graphic>, Context -> List<Graphic>,
Context -> Table<Vector>, Context -> List<Vector>,
Context -> Table<Raster<CPU>>, Context -> List<Raster<CPU>>,
Context -> Table<Raster<GPU>>, Context -> List<Raster<GPU>>,
Context -> Table<Color>, Context -> List<Color>,
Context -> Table<GradientStops>, Context -> List<GradientStops>,
)] )]
if_false: impl Node<C, Output = T>, if_false: impl Node<C, Output = T>,
) -> T { ) -> T {
@@ -811,70 +811,70 @@ fn vec2_value(_: impl Ctx, _primary: (), x: f64, y: f64) -> DVec2 {
/// Constructs a color value which may be set to any color, or no color. /// Constructs a color value which may be set to any color, or no color.
#[node_macro::node(category("Value"))] #[node_macro::node(category("Value"))]
fn color_value(_: impl Ctx, _primary: (), #[default(Color::BLACK)] color: Table<Color>) -> Table<Color> { fn color_value(_: impl Ctx, _primary: (), #[default(Color::BLACK)] color: List<Color>) -> List<Color> {
color color
} }
/// Constructs a color value from red, green, blue, and alpha components given as numbers from 0 to 1. /// Constructs a color value from red, green, blue, and alpha components given as numbers from 0 to 1.
#[node_macro::node(category("Color"), name("RGBA to Color"))] #[node_macro::node(category("Color"), name("RGBA to Color"))]
fn rgba_to_color(_: impl Ctx, _primary: (), red: Fraction, green: Fraction, blue: Fraction, #[default(1.)] alpha: Fraction) -> Table<Color> { fn rgba_to_color(_: impl Ctx, _primary: (), red: Fraction, green: Fraction, blue: Fraction, #[default(1.)] alpha: Fraction) -> List<Color> {
let red = (red as f32).clamp(0., 1.); let red = (red as f32).clamp(0., 1.);
let green = (green as f32).clamp(0., 1.); let green = (green as f32).clamp(0., 1.);
let blue = (blue as f32).clamp(0., 1.); let blue = (blue as f32).clamp(0., 1.);
let alpha = (alpha as f32).clamp(0., 1.); let alpha = (alpha as f32).clamp(0., 1.);
Table::new_from_element(Color::from_rgbaf32_unchecked(red, green, blue, alpha)) List::new_from_element(Color::from_rgbaf32_unchecked(red, green, blue, alpha))
} }
/// Constructs a color value from hue, saturation, value, and alpha components given as numbers from 0 to 1. /// Constructs a color value from hue, saturation, value, and alpha components given as numbers from 0 to 1.
#[node_macro::node(category("Color"), name("HSVA to Color"))] #[node_macro::node(category("Color"), name("HSVA to Color"))]
fn hsva_to_color(_: impl Ctx, _primary: (), hue: Fraction, #[default(1.)] saturation: Fraction, #[default(1.)] value: Fraction, #[default(1.)] alpha: Fraction) -> Table<Color> { fn hsva_to_color(_: impl Ctx, _primary: (), hue: Fraction, #[default(1.)] saturation: Fraction, #[default(1.)] value: Fraction, #[default(1.)] alpha: Fraction) -> List<Color> {
let hue = (hue as f32) - (hue as f32).floor(); let hue = (hue as f32) - (hue as f32).floor();
let saturation = (saturation as f32).clamp(0., 1.); let saturation = (saturation as f32).clamp(0., 1.);
let value = (value as f32).clamp(0., 1.); let value = (value as f32).clamp(0., 1.);
let alpha = (alpha as f32).clamp(0., 1.); let alpha = (alpha as f32).clamp(0., 1.);
Table::new_from_element(Color::from_hsva(hue, saturation, value, alpha)) List::new_from_element(Color::from_hsva(hue, saturation, value, alpha))
} }
/// Constructs a color value from hue, saturation, lightness, and alpha components given as numbers from 0 to 1. /// Constructs a color value from hue, saturation, lightness, and alpha components given as numbers from 0 to 1.
#[node_macro::node(category("Color"), name("HSLA to Color"))] #[node_macro::node(category("Color"), name("HSLA to Color"))]
fn hsla_to_color(_: impl Ctx, _primary: (), hue: Fraction, #[default(1.)] saturation: Fraction, #[default(0.5)] lightness: Fraction, #[default(1.)] alpha: Fraction) -> Table<Color> { fn hsla_to_color(_: impl Ctx, _primary: (), hue: Fraction, #[default(1.)] saturation: Fraction, #[default(0.5)] lightness: Fraction, #[default(1.)] alpha: Fraction) -> List<Color> {
let hue = (hue as f32) - (hue as f32).floor(); let hue = (hue as f32) - (hue as f32).floor();
let saturation = (saturation as f32).clamp(0., 1.); let saturation = (saturation as f32).clamp(0., 1.);
let lightness = (lightness as f32).clamp(0., 1.); let lightness = (lightness as f32).clamp(0., 1.);
let alpha = (alpha as f32).clamp(0., 1.); let alpha = (alpha as f32).clamp(0., 1.);
Table::new_from_element(Color::from_hsla(hue, saturation, lightness, alpha)) List::new_from_element(Color::from_hsla(hue, saturation, lightness, alpha))
} }
/// Constructs a color value from an sRGB color code string, such as `#RRGGBB` or `#RRGGBBAA`. Invalid hex code strings produce no color. /// Constructs a color value from an sRGB color code string, such as `#RRGGBB` or `#RRGGBBAA`. Invalid hex code strings produce no color.
#[node_macro::node(category("Color"), name("Hex to Color"))] #[node_macro::node(category("Color"), name("Hex to Color"))]
fn hex_to_color(_: impl Ctx, hex_code: String) -> Table<Color> { fn hex_to_color(_: impl Ctx, hex_code: String) -> List<Color> {
match Color::from_hex_str(&hex_code) { match Color::from_hex_str(&hex_code) {
Some(c) => Table::new_from_element(c), Some(c) => List::new_from_element(c),
None => Table::new(), None => List::new(),
} }
} }
/// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors. /// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors.
#[node_macro::node(category("Value"))] #[node_macro::node(category("Value"))]
fn gradient_value(_: impl Ctx, _primary: (), gradient: Table<GradientStops>) -> Table<GradientStops> { fn gradient_value(_: impl Ctx, _primary: (), gradient: List<GradientStops>) -> List<GradientStops> {
gradient gradient
} }
/// Sets the type (linear or radial) of each gradient in the input table. /// Sets the type (linear or radial) of each gradient in the input list.
#[node_macro::node(category("Color"))] #[node_macro::node(category("Color"))]
fn gradient_type(_: impl Ctx, mut gradient: Table<GradientStops>, gradient_type: vector_types::GradientType) -> Table<GradientStops> { fn gradient_type(_: impl Ctx, mut gradient: List<GradientStops>, gradient_type: vector_types::GradientType) -> List<GradientStops> {
for value in gradient.iter_attribute_values_mut_or_default::<vector_types::GradientType>(core_types::ATTR_GRADIENT_TYPE) { for value in gradient.iter_attribute_values_mut_or_default::<vector_types::GradientType>(core_types::ATTR_GRADIENT_TYPE) {
*value = gradient_type; *value = gradient_type;
} }
gradient gradient
} }
/// Sets how each gradient in the input table extends past its endpoints: Pad, Reflect, or Repeat. /// Sets how each gradient in the input list extends past its endpoints: Pad, Reflect, or Repeat.
#[node_macro::node(category("Color"))] #[node_macro::node(category("Color"))]
fn spread_method(_: impl Ctx, mut gradient: Table<GradientStops>, spread_method: vector_types::GradientSpreadMethod) -> Table<GradientStops> { fn spread_method(_: impl Ctx, mut gradient: List<GradientStops>, spread_method: vector_types::GradientSpreadMethod) -> List<GradientStops> {
for value in gradient.iter_attribute_values_mut_or_default::<vector_types::GradientSpreadMethod>(core_types::ATTR_SPREAD_METHOD) { for value in gradient.iter_attribute_values_mut_or_default::<vector_types::GradientSpreadMethod>(core_types::ATTR_SPREAD_METHOD) {
*value = spread_method; *value = spread_method;
} }
@@ -883,12 +883,12 @@ fn spread_method(_: impl Ctx, mut gradient: Table<GradientStops>, spread_method:
/// Gets the color at the specified position along the gradient, given a position from 0 (left) to 1 (right). /// Gets the color at the specified position along the gradient, given a position from 0 (left) to 1 (right).
#[node_macro::node(category("Color"))] #[node_macro::node(category("Color"))]
fn sample_gradient(_: impl Ctx, _primary: (), gradient: Table<GradientStops>, position: Fraction) -> Table<Color> { fn sample_gradient(_: impl Ctx, _primary: (), gradient: List<GradientStops>, position: Fraction) -> List<Color> {
let Some(gradient) = gradient.element(0) else { return Table::new() }; let Some(gradient) = gradient.element(0) else { return List::new() };
let position = position.clamp(0., 1.); let position = position.clamp(0., 1.);
let color = gradient.evaluate(position); let color = gradient.evaluate(position);
Table::new_from_element(color) List::new_from_element(color)
} }
/// Constructs a footprint value which may be set to any transformation of a unit square describing a render area, and a render resolution at least 1x1 integer pixels. /// Constructs a footprint value which may be set to any transformation of a unit square describing a render area, and a render resolution at least 1x1 integer pixels.
+35 -35
View File
@@ -1,4 +1,4 @@
use core_types::table::{Item, Table}; use core_types::list::{Item, List};
use core_types::uuid::NodeId; use core_types::uuid::NodeId;
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, BlendMode, Color, Ctx}; use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, BlendMode, Color, Ctx};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
@@ -14,15 +14,15 @@ use vector_types::kurbo::{Affine, BezPath, CubicBez, Line, ParamCurve, PathSeg,
pub use vector_types::vector::misc::BooleanOperation; pub use vector_types::vector::misc::BooleanOperation;
// TODO: Fix boolean ops to work by removing .transform() and .one_instance_*() calls, // TODO: Fix boolean ops to work by removing .transform() and .one_instance_*() calls,
// TODO: since before we used a Vec of single-item `Table`s and now we use a single `Table` // TODO: since before we used a Vec of single-item `List`s and now we use a single `List`
// TODO: with multiple items while still assuming a single item for the boolean operations. // TODO: with multiple items while still assuming a single item for the boolean operations.
/// Combines the geometric forms of one or more closed paths into a new vector path that results from cutting or joining the paths by the chosen method. /// Combines the geometric forms of one or more closed paths into a new vector path that results from cutting or joining the paths by the chosen method.
#[node_macro::node(category("Vector: Modifier"), memoize)] #[node_macro::node(category("Vector: Modifier"), memoize)]
async fn boolean_operation<I: graphic_types::IntoGraphicTable + 'n + Send + Clone>( async fn boolean_operation<I: graphic_types::IntoGraphicList + 'n + Send + Clone>(
_: impl Ctx, _: impl Ctx,
/// The `Table` of vector paths to perform the boolean operation on. Nested `Table`s are automatically flattened. /// The `List` of vector paths to perform the boolean operation on. Nested `List`s are automatically flattened.
#[implementations(Table<Graphic>, Table<Vector>)] #[implementations(List<Graphic>, List<Vector>)]
content: I, content: I,
/// Which boolean operation to perform on the paths. /// Which boolean operation to perform on the paths.
/// ///
@@ -31,32 +31,32 @@ async fn boolean_operation<I: graphic_types::IntoGraphicTable + 'n + Send + Clon
/// Intersection cuts away all but the overlapping areas shared by every path. /// Intersection cuts away all but the overlapping areas shared by every path.
/// Difference cuts away the overlapping areas shared by every path, leaving only the non-overlapping areas. /// Difference cuts away the overlapping areas shared by every path, leaving only the non-overlapping areas.
operation: BooleanOperation, operation: BooleanOperation,
) -> Table<Vector> { ) -> List<Vector> {
let content = content.into_graphic_table(); let content = content.into_graphic_list();
// The first index is the bottom of the stack // The first index is the bottom of the stack
let flattened = flatten_vector(&content); let flattened = flatten_vector(&content);
let mut result_vector_table = boolean_operation_on_vector_table(&flattened, operation); let mut result_vector_list = boolean_operation_on_vector_list(&flattened, operation);
// Replace the transformation matrix with a mutation of the vector points themselves // Replace the transformation matrix with a mutation of the vector points themselves
if result_vector_table.element_mut(0).is_some() { if result_vector_list.element_mut(0).is_some() {
let transform: DAffine2 = result_vector_table.attribute_cloned_or_default(ATTR_TRANSFORM, 0); let transform: DAffine2 = result_vector_list.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
result_vector_table.set_attribute(ATTR_TRANSFORM, 0, DAffine2::IDENTITY); result_vector_list.set_attribute(ATTR_TRANSFORM, 0, DAffine2::IDENTITY);
let result_vector = result_vector_table.element_mut(0).unwrap(); let result_vector = result_vector_list.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);
// Snapshot the input layers as the `editor:merged_layers` attribute so the renderer can recurse into them // Snapshot the input layers as the `editor:merged_layers` attribute so the renderer can recurse into them
// for editor click-target preservation. // for editor click-target preservation.
result_vector_table.set_attribute(ATTR_EDITOR_MERGED_LAYERS, 0, content.clone()); result_vector_list.set_attribute(ATTR_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(ATTR_TRANSFORM, 0); let merge_transform: DAffine2 = result_vector_list.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
result_vector_table.element_mut(0).unwrap().merge_by_distance_spatial(merge_transform, 0.0001); result_vector_list.element_mut(0).unwrap().merge_by_distance_spatial(merge_transform, 0.0001);
} }
result_vector_table result_vector_list
} }
#[derive(Clone, Debug, Default, PartialEq, Eq)] #[derive(Clone, Debug, Default, PartialEq, Eq)]
@@ -113,9 +113,9 @@ impl WindingNumber {
} }
} }
fn boolean_operation_on_vector_table(vector: &Table<Vector>, boolean_operation: BooleanOperation) -> Table<Vector> { fn boolean_operation_on_vector_list(vector: &List<Vector>, boolean_operation: BooleanOperation) -> List<Vector> {
const EPSILON: f64 = 1e-5; const EPSILON: f64 = 1e-5;
let mut table = Table::new(); let mut list = List::new();
let mut paths = Vec::new(); let mut paths = Vec::new();
let copy_from_index = if matches!(boolean_operation, BooleanOperation::SubtractFront) { let copy_from_index = if matches!(boolean_operation, BooleanOperation::SubtractFront) {
@@ -146,8 +146,8 @@ fn boolean_operation_on_vector_table(vector: &Table<Vector>, boolean_operation:
Ok(top) => top, Ok(top) => top,
Err(e) => { Err(e) => {
log::error!("Boolean operation failed while building topology: {e}"); log::error!("Boolean operation failed while building topology: {e}");
table.push(row); list.push(row);
return table; return list;
} }
}; };
let contours = top.contours(|winding| winding.is_inside(boolean_operation)); let contours = top.contours(|winding| winding.is_inside(boolean_operation));
@@ -158,18 +158,18 @@ fn boolean_operation_on_vector_table(vector: &Table<Vector>, boolean_operation:
row.element_mut().append_subpath(subpath.reverse(), false); row.element_mut().append_subpath(subpath.reverse(), false);
} }
table.push(row); list.push(row);
table list
} }
fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> { fn flatten_vector(graphic_list: &List<Graphic>) -> List<Vector> {
(0..graphic_table.len()) (0..graphic_list.len())
.flat_map(|index| { .flat_map(|index| {
let graphic = graphic_table.element(index).unwrap(); let graphic = graphic_list.element(index).unwrap();
match graphic.clone() { match graphic.clone() {
Graphic::Vector(vector) => { Graphic::Vector(vector) => {
// Apply the parent graphic's transform to each element of the `Table<Vector>` // Apply the parent graphic's transform to each element of the `List<Vector>`
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index); let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
vector vector
.into_iter() .into_iter()
.map(|mut sub_vector| { .map(|mut sub_vector| {
@@ -180,7 +180,7 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
Graphic::RasterCPU(image) => { Graphic::RasterCPU(image) => {
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index); let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let make_item = |transform, layer, blend_mode: BlendMode, opacity: f64, fill: f64, clip: bool| { let make_item = |transform, layer, blend_mode: BlendMode, opacity: f64, fill: f64, clip: bool| {
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);
@@ -202,7 +202,7 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
(0..image.len()) (0..image.len())
.map(|i| { .map(|i| {
let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i); let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i);
let layer: Table<NodeId> = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i); let layer: List<NodeId> = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i);
let blend_mode: BlendMode = image.attribute_cloned_or_default(ATTR_BLEND_MODE, i); let blend_mode: BlendMode = image.attribute_cloned_or_default(ATTR_BLEND_MODE, i);
let opacity: f64 = image.attribute_cloned_or(ATTR_OPACITY, i, 1.); let opacity: f64 = image.attribute_cloned_or(ATTR_OPACITY, i, 1.);
let fill: f64 = image.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.); let fill: f64 = image.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.);
@@ -212,7 +212,7 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
Graphic::RasterGPU(image) => { Graphic::RasterGPU(image) => {
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index); let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let make_item = |transform, layer, blend_mode: BlendMode, opacity: f64, fill: f64, clip: bool| { let make_item = |transform, layer, blend_mode: BlendMode, opacity: f64, fill: f64, clip: bool| {
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);
@@ -234,7 +234,7 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
(0..image.len()) (0..image.len())
.map(|i| { .map(|i| {
let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i); let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i);
let layer: Table<NodeId> = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i); let layer: List<NodeId> = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i);
let blend_mode: BlendMode = image.attribute_cloned_or_default(ATTR_BLEND_MODE, i); let blend_mode: BlendMode = image.attribute_cloned_or_default(ATTR_BLEND_MODE, i);
let opacity: f64 = image.attribute_cloned_or(ATTR_OPACITY, i, 1.); let opacity: f64 = image.attribute_cloned_or(ATTR_OPACITY, i, 1.);
let fill: f64 = image.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.); let fill: f64 = image.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.);
@@ -244,15 +244,15 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
Graphic::Graphic(mut graphic) => { Graphic::Graphic(mut graphic) => {
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index); let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
// Apply the parent graphic's transform to each element of the inner `Table` // Apply the parent graphic's transform to each element of the inner `List`
for transform in graphic.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) { for transform in graphic.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
*transform = parent_transform * *transform; *transform = parent_transform * *transform;
} }
// Recursively flatten the inner `Table` into the output `Table<Vector>` // Recursively flatten the inner `List` into the output `List<Vector>`
let flattened = flatten_vector(&graphic); let flattened = flatten_vector(&graphic);
let unioned = boolean_operation_on_vector_table(&flattened, BooleanOperation::Union); let unioned = boolean_operation_on_vector_list(&flattened, BooleanOperation::Union);
unioned.into_iter().collect::<Vec<_>>() unioned.into_iter().collect::<Vec<_>>()
} }
+4 -4
View File
@@ -12,11 +12,11 @@ impl Adjust<Color> for Color {
#[cfg(feature = "std")] #[cfg(feature = "std")]
mod adjust_std { mod adjust_std {
use super::*; use super::*;
use core_types::table::Table; use core_types::list::List;
use raster_types::{CPU, Raster}; use raster_types::{CPU, Raster};
use vector_types::GradientStops; use vector_types::GradientStops;
impl Adjust<Color> for Table<Raster<CPU>> { impl Adjust<Color> for List<Raster<CPU>> {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) { fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
for element in self.iter_element_values_mut() { for element in self.iter_element_values_mut() {
for color in element.data_mut().data.iter_mut() { for color in element.data_mut().data.iter_mut() {
@@ -25,14 +25,14 @@ mod adjust_std {
} }
} }
} }
impl Adjust<Color> for Table<Color> { impl Adjust<Color> for List<Color> {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) { fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
for element in self.iter_element_values_mut() { for element in self.iter_element_values_mut() {
*element = map_fn(element); *element = map_fn(element);
} }
} }
} }
impl Adjust<Color> for Table<GradientStops> { impl Adjust<Color> for List<GradientStops> {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) { fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
for element in self.iter_element_values_mut() { for element in self.iter_element_values_mut() {
element.adjust(&map_fn); element.adjust(&map_fn);
+50 -50
View File
@@ -4,7 +4,7 @@ use crate::adjust::Adjust;
use crate::cubic_spline::CubicSplines; use crate::cubic_spline::CubicSplines;
use core::fmt::Debug; use core::fmt::Debug;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use core_types::table::Table; use core_types::list::List;
use glam::{Vec3, Vec4}; use glam::{Vec3, Vec4};
use no_std_types::color::Color; use no_std_types::color::Color;
use no_std_types::context::Ctx; use no_std_types::context::Ctx;
@@ -53,9 +53,9 @@ pub enum LuminanceCalculation {
fn luminance<T: Adjust<Color>>( fn luminance<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,
@@ -78,9 +78,9 @@ fn luminance<T: Adjust<Color>>(
fn gamma_correction<T: Adjust<Color>>( fn gamma_correction<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,
@@ -99,9 +99,9 @@ fn gamma_correction<T: Adjust<Color>>(
fn extract_channel<T: Adjust<Color>>( fn extract_channel<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,
@@ -123,9 +123,9 @@ fn extract_channel<T: Adjust<Color>>(
fn make_opaque<T: Adjust<Color>>( fn make_opaque<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,
@@ -145,9 +145,9 @@ fn make_opaque<T: Adjust<Color>>(
fn brightness_contrast_classic<T: Adjust<Color>>( fn brightness_contrast_classic<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,
@@ -176,9 +176,9 @@ fn brightness_contrast_classic<T: Adjust<Color>>(
fn brightness_contrast<T: Adjust<Color>>( fn brightness_contrast<T: Adjust<Color>>(
_ctx: impl Ctx, _ctx: impl Ctx,
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,
@@ -257,9 +257,9 @@ fn brightness_contrast<T: Adjust<Color>>(
fn levels<T: Adjust<Color>>( fn levels<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
#[gpu_image] #[gpu_image]
mut image: T, mut image: T,
@@ -321,14 +321,14 @@ fn levels<T: Adjust<Color>>(
// Algorithm from: // Algorithm from:
// https://stackoverflow.com/a/55233732/775283 // https://stackoverflow.com/a/55233732/775283
// Works the same for gamma and linear color // Works the same for gamma and linear color
// TODO: Currently the un-Table-wrapped `tint` Color is causing a type error. Put this back in the "Raster: Adjustment" category once that's fixed. // TODO: Currently the un-List-wrapped `tint` Color is causing a type error. Put this back in the "Raster: Adjustment" category once that's fixed.
#[node_macro::node(name("Black & White"), category(""), properties("black_and_white_properties"), shader_node(PerPixelAdjust))] #[node_macro::node(name("Black & White"), category(""), properties("black_and_white_properties"), shader_node(PerPixelAdjust))]
fn black_and_white<T: Adjust<Color>>( fn black_and_white<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
#[gpu_image] #[gpu_image]
mut image: T, mut image: T,
@@ -399,9 +399,9 @@ fn black_and_white<T: Adjust<Color>>(
fn hue_saturation<T: Adjust<Color>>( fn hue_saturation<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,
@@ -434,9 +434,9 @@ fn hue_saturation<T: Adjust<Color>>(
fn invert<T: Adjust<Color>>( fn invert<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,
@@ -457,9 +457,9 @@ fn invert<T: Adjust<Color>>(
fn threshold<T: Adjust<Color>>( fn threshold<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
#[gpu_image] #[gpu_image]
mut image: T, mut image: T,
@@ -503,9 +503,9 @@ fn threshold<T: Adjust<Color>>(
fn vibrance<T: Adjust<Color>>( fn vibrance<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
#[gpu_image] #[gpu_image]
mut image: T, mut image: T,
@@ -682,9 +682,9 @@ pub enum DomainWarpType {
fn channel_mixer<T: Adjust<Color>>( fn channel_mixer<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
#[gpu_image] #[gpu_image]
mut image: T, mut image: T,
@@ -816,9 +816,9 @@ pub enum SelectiveColorChoice {
fn selective_color<T: Adjust<Color>>( fn selective_color<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
#[gpu_image] #[gpu_image]
mut image: T, mut image: T,
@@ -962,9 +962,9 @@ fn selective_color<T: Adjust<Color>>(
fn posterize<T: Adjust<Color>>( fn posterize<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,
@@ -996,9 +996,9 @@ fn posterize<T: Adjust<Color>>(
fn exposure<T: Adjust<Color>>( fn exposure<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
#[gpu_image] #[gpu_image]
mut input: T, mut input: T,
+31 -31
View File
@@ -1,6 +1,6 @@
use crate::adjust::Adjust; use crate::adjust::Adjust;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use core_types::table::Table; use core_types::list::List;
use no_std_types::Ctx; use no_std_types::Ctx;
use no_std_types::blending::BlendMode; use no_std_types::blending::BlendMode;
use no_std_types::color::{Color, Pixel}; use no_std_types::color::{Color, Pixel};
@@ -23,54 +23,54 @@ impl Blend<Color> for Color {
mod blend_std { mod blend_std {
use super::*; use super::*;
use core::cmp::Ordering; use core::cmp::Ordering;
use core_types::table::Table; use core_types::list::List;
use raster_types::Image; use raster_types::Image;
use raster_types::Raster; use raster_types::Raster;
impl Blend<Color> for Table<Raster<CPU>> { impl Blend<Color> for List<Raster<CPU>> {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self { fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let mut result_table = self.clone(); let mut result_list = self.clone();
let pair_count = result_table.len().min(under.len()); let pair_count = result_list.len().min(under.len());
for index in 0..pair_count { for index in 0..pair_count {
let Some(over) = result_table.element(index) else { break }; let Some(over) = result_list.element(index) else { break };
let Some(under_element) = under.element(index) else { break }; let Some(under_element) = under.element(index) else { break };
let data = over.data.iter().zip(under_element.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect(); let data = over.data.iter().zip(under_element.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
let (width, height) = (over.width, over.height); let (width, height) = (over.width, over.height);
*result_table.element_mut(index).unwrap() = Raster::new_cpu(Image { *result_list.element_mut(index).unwrap() = Raster::new_cpu(Image {
data, data,
width, width,
height, height,
base64_string: None, base64_string: None,
}); });
} }
result_table result_list
} }
} }
impl Blend<Color> for Table<Color> { impl Blend<Color> for List<Color> {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self { fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let mut result_table = self.clone(); let mut result_list = self.clone();
let pair_count = result_table.len().min(under.len()); let pair_count = result_list.len().min(under.len());
for index in 0..pair_count { for index in 0..pair_count {
let Some(over) = result_table.element(index) else { break }; let Some(over) = result_list.element(index) else { break };
let Some(under_element) = under.element(index) else { break }; let Some(under_element) = under.element(index) else { break };
let new_val = blend_fn(*over, *under_element); let new_val = blend_fn(*over, *under_element);
*result_table.element_mut(index).unwrap() = new_val; *result_list.element_mut(index).unwrap() = new_val;
} }
result_table result_list
} }
} }
impl Blend<Color> for Table<GradientStops> { impl Blend<Color> for List<GradientStops> {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self { fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let mut result_table = self.clone(); let mut result_list = self.clone();
let pair_count = result_table.len().min(under.len()); let pair_count = result_list.len().min(under.len());
for index in 0..pair_count { for index in 0..pair_count {
let Some(over) = result_table.element(index) else { break }; let Some(over) = result_list.element(index) else { break };
let Some(under_element) = under.element(index) else { break }; let Some(under_element) = under.element(index) else { break };
let new_val = over.blend(under_element, &blend_fn); let new_val = over.blend(under_element, &blend_fn);
*result_table.element_mut(index).unwrap() = new_val; *result_list.element_mut(index).unwrap() = new_val;
} }
result_table result_list
} }
} }
impl Blend<Color> for GradientStops { impl Blend<Color> for GradientStops {
@@ -145,17 +145,17 @@ pub fn apply_blend_mode(foreground: Color, background: Color, blend_mode: BlendM
fn mix<T: Blend<Color> + Send>( fn mix<T: Blend<Color> + Send>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
#[gpu_image] #[gpu_image]
over: T, over: T,
#[expose] #[expose]
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
#[gpu_image] #[gpu_image]
under: T, under: T,
@@ -169,9 +169,9 @@ fn mix<T: Blend<Color> + Send>(
fn color_overlay<T: Adjust<Color>>( fn color_overlay<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
#[gpu_image] #[gpu_image]
mut image: T, mut image: T,
@@ -197,7 +197,7 @@ fn color_overlay<T: Adjust<Color>>(
mod test { mod test {
use core_types::blending::BlendMode; use core_types::blending::BlendMode;
use core_types::color::Color; use core_types::color::Color;
use core_types::table::Table; use core_types::list::List;
use raster_types::Image; use raster_types::Image;
use raster_types::Raster; use raster_types::Raster;
@@ -212,7 +212,7 @@ mod test {
// 100% of the output should come from the multiplied value // 100% of the output should come from the multiplied value
let opacity = 100.; let opacity = 100.;
let result = super::color_overlay((), Table::new_from_element(Raster::new_cpu(image.clone())), overlay_color, BlendMode::Multiply, opacity); let result = super::color_overlay((), List::new_from_element(Raster::new_cpu(image.clone())), overlay_color, BlendMode::Multiply, opacity);
let result = result.element(0).unwrap().clone(); let result = result.element(0).unwrap().clone();
// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0) // The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0)
+2 -2
View File
@@ -1,6 +1,6 @@
use core_types::context::Ctx; use core_types::context::Ctx;
use core_types::list::List;
use core_types::registry::types::Percentage; use core_types::registry::types::Percentage;
use core_types::table::Table;
use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage}; use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage};
use ndarray::{Array2, ArrayBase, Dim, OwnedRepr}; use ndarray::{Array2, ArrayBase, Dim, OwnedRepr};
use raster_types::Image; use raster_types::Image;
@@ -8,7 +8,7 @@ use raster_types::{CPU, Raster};
use std::cmp::{max, min}; use std::cmp::{max, min};
#[node_macro::node(category("Raster: Filter"))] #[node_macro::node(category("Raster: Filter"))]
async fn dehaze(_: impl Ctx, image_frame: Table<Raster<CPU>>, strength: Percentage) -> Table<Raster<CPU>> { async fn dehaze(_: impl Ctx, image_frame: List<Raster<CPU>>, strength: Percentage) -> List<Raster<CPU>> {
image_frame image_frame
.into_iter() .into_iter()
.map(|mut row| { .map(|mut row| {
+5 -5
View File
@@ -1,7 +1,7 @@
use core_types::color::Color; use core_types::color::Color;
use core_types::context::Ctx; use core_types::context::Ctx;
use core_types::list::List;
use core_types::registry::types::PixelLength; use core_types::registry::types::PixelLength;
use core_types::table::Table;
use raster_types::Image; use raster_types::Image;
use raster_types::{Bitmap, BitmapMut}; use raster_types::{Bitmap, BitmapMut};
use raster_types::{CPU, Raster}; use raster_types::{CPU, Raster};
@@ -11,7 +11,7 @@ use raster_types::{CPU, Raster};
async fn blur( async fn blur(
_: impl Ctx, _: impl Ctx,
/// The image to be blurred. /// The image to be blurred.
image_frame: Table<Raster<CPU>>, image_frame: List<Raster<CPU>>,
/// The radius of the blur kernel. /// The radius of the blur kernel.
#[range((0., 100.))] #[range((0., 100.))]
#[hard_min(0.)] #[hard_min(0.)]
@@ -20,7 +20,7 @@ async fn blur(
box_blur: bool, box_blur: bool,
/// Opt to incorrectly apply the filter with color calculations in gamma space for compatibility with the results from other software. /// Opt to incorrectly apply the filter with color calculations in gamma space for compatibility with the results from other software.
gamma: bool, gamma: bool,
) -> Table<Raster<CPU>> { ) -> List<Raster<CPU>> {
image_frame image_frame
.into_iter() .into_iter()
.map(|mut row| { .map(|mut row| {
@@ -47,12 +47,12 @@ async fn blur(
async fn median_filter( async fn median_filter(
_: impl Ctx, _: impl Ctx,
/// The image to be filtered. /// The image to be filtered.
image_frame: Table<Raster<CPU>>, image_frame: List<Raster<CPU>>,
/// The radius of the filter kernel. Larger values remove more noise but may blur fine details. /// The radius of the filter kernel. Larger values remove more noise but may blur fine details.
#[range((0., 50.))] #[range((0., 50.))]
#[hard_min(0.)] #[hard_min(0.)]
radius: PixelLength, radius: PixelLength,
) -> Table<Raster<CPU>> { ) -> List<Raster<CPU>> {
image_frame image_frame
.into_iter() .into_iter()
.map(|mut row| { .map(|mut row| {
+5 -5
View File
@@ -1,7 +1,7 @@
//! Not immediately shader compatible due to needing [`GradientStops`] as a param, which needs [`Vec`] //! Not immediately shader compatible due to needing [`GradientStops`] as a param, which needs [`Vec`]
use crate::adjust::Adjust; use crate::adjust::Adjust;
use core_types::table::Table; use core_types::list::List;
use core_types::{Color, Ctx}; use core_types::{Color, Ctx};
use raster_types::{CPU, Raster}; use raster_types::{CPU, Raster};
use vector_types::GradientStops; use vector_types::GradientStops;
@@ -13,12 +13,12 @@ use vector_types::GradientStops;
async fn gradient_map<T: Adjust<Color>>( async fn gradient_map<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
mut image: T, mut image: T,
gradient: Table<GradientStops>, gradient: List<GradientStops>,
reverse: bool, reverse: bool,
) -> T { ) -> T {
let Some(gradient) = gradient.element(0) else { return image }; let Some(gradient) = gradient.element(0) else { return image };
@@ -1,16 +1,16 @@
use core_types::color::Color; use core_types::color::Color;
use core_types::context::Ctx; use core_types::context::Ctx;
use core_types::table::{Item, Table}; use core_types::list::{Item, List};
use raster_types::{CPU, Raster}; use raster_types::{CPU, Raster};
#[node_macro::node(category("Color"))] #[node_macro::node(category("Color"))]
async fn image_color_palette( async fn image_color_palette(
_: impl Ctx, _: impl Ctx,
image: Table<Raster<CPU>>, image: List<Raster<CPU>>,
#[default(4)] #[default(4)]
#[hard_min(1)] #[hard_min(1)]
count: u32, count: u32,
) -> Table<Color> { ) -> List<Color> {
const GRID: f32 = 3.; const GRID: f32 = 3.;
let bins = GRID * GRID * GRID; let bins = GRID * GRID * GRID;
@@ -71,7 +71,7 @@ mod test {
fn test_image_color_palette() { fn test_image_color_palette() {
let result = image_color_palette( let result = image_color_palette(
(), (),
Table::new_from_element(Raster::new_cpu(Image { List::new_from_element(Raster::new_cpu(Image {
width: 100, width: 100,
height: 100, height: 100,
data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000], data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000],
@@ -79,6 +79,6 @@ mod test {
})), })),
1, 1,
); );
assert_eq!(futures::executor::block_on(result), Table::new_from_element(Color::from_rgbaf32(0., 0., 0., 1.).unwrap())); assert_eq!(futures::executor::block_on(result), List::new_from_element(Color::from_rgbaf32(0., 0., 0., 1.).unwrap()));
} }
} }
+26 -26
View File
@@ -3,8 +3,8 @@ use core_types::ATTR_TRANSFORM;
use core_types::color::Color; use core_types::color::Color;
use core_types::color::{Alpha, AlphaMut, Channel, LinearChannel, Luminance, RGBMut}; use core_types::color::{Alpha, AlphaMut, Channel, LinearChannel, Luminance, RGBMut};
use core_types::context::{Ctx, ExtractFootprint}; use core_types::context::{Ctx, ExtractFootprint};
use core_types::list::{Item, List};
use core_types::math::bbox::Bbox; use core_types::math::bbox::Bbox;
use core_types::table::{Item, Table};
use core_types::transform::Transform; use core_types::transform::Transform;
use dyn_any::DynAny; use dyn_any::DynAny;
use fastnoise_lite; use fastnoise_lite;
@@ -30,7 +30,7 @@ impl From<std::io::Error> for Error {
} }
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Table<Raster<CPU>>) -> Table<Raster<CPU>> { pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: List<Raster<CPU>>) -> List<Raster<CPU>> {
image_frame image_frame
.into_iter() .into_iter()
.filter_map(|row| { .filter_map(|row| {
@@ -97,11 +97,11 @@ pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Tabl
pub fn combine_channels( pub fn combine_channels(
_: impl Ctx, _: impl Ctx,
_primary: (), _primary: (),
#[expose] red: Table<Raster<CPU>>, #[expose] red: List<Raster<CPU>>,
#[expose] green: Table<Raster<CPU>>, #[expose] green: List<Raster<CPU>>,
#[expose] blue: Table<Raster<CPU>>, #[expose] blue: List<Raster<CPU>>,
#[expose] alpha: Table<Raster<CPU>>, #[expose] alpha: List<Raster<CPU>>,
) -> Table<Raster<CPU>> { ) -> List<Raster<CPU>> {
let max_len = red.len().max(green.len()).max(blue.len()).max(alpha.len()); let max_len = red.len().max(green.len()).max(blue.len()).max(alpha.len());
let red = red.into_iter().map(Some).chain(std::iter::repeat(None)).take(max_len); let red = red.into_iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
let green = green.into_iter().map(Some).chain(std::iter::repeat(None)).take(max_len); let green = green.into_iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
@@ -178,11 +178,11 @@ pub fn combine_channels(
pub fn mask( pub fn mask(
_: impl Ctx, _: impl Ctx,
/// The image to be masked. /// The image to be masked.
image: Table<Raster<CPU>>, image: List<Raster<CPU>>,
/// The stencil to be used for masking. /// The stencil to be used for masking.
#[expose] #[expose]
stencil: Table<Raster<CPU>>, stencil: List<Raster<CPU>>,
) -> Table<Raster<CPU>> { ) -> List<Raster<CPU>> {
// TODO: Figure out what it means to support multiple stencil items? // TODO: Figure out what it means to support multiple stencil items?
let Some(stencil) = stencil.into_iter().next() else { let Some(stencil) = stencil.into_iter().next() else {
// No stencil provided so we return the original image // No stencil provided so we return the original image
@@ -226,7 +226,7 @@ pub fn mask(
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
pub fn extend_image_to_bounds(_: impl Ctx, image: Table<Raster<CPU>>, bounds: DAffine2) -> Table<Raster<CPU>> { pub fn extend_image_to_bounds(_: impl Ctx, image: List<Raster<CPU>>, bounds: DAffine2) -> List<Raster<CPU>> {
image image
.into_iter() .into_iter()
.map(|mut row| { .map(|mut row| {
@@ -240,7 +240,7 @@ pub fn extend_image_to_bounds(_: impl Ctx, image: Table<Raster<CPU>>, bounds: DA
let image_data = &row.element().data; let image_data = &row.element().data;
let (image_width, image_height) = (row.element().width, row.element().height); let (image_width, image_height) = (row.element().width, row.element().height);
if image_width == 0 || image_height == 0 { if image_width == 0 || image_height == 0 {
return empty_image((), bounds, Table::new_from_element(Color::TRANSPARENT)).into_iter().next().unwrap(); return empty_image((), bounds, List::new_from_element(Color::TRANSPARENT)).into_iter().next().unwrap();
} }
let orig_image_scale = DVec2::new(image_width as f64, image_height as f64); let orig_image_scale = DVec2::new(image_width as f64, image_height as f64);
@@ -274,23 +274,23 @@ pub fn extend_image_to_bounds(_: impl Ctx, image: Table<Raster<CPU>>, bounds: DA
} }
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Table<Color>) -> Table<Raster<CPU>> { pub fn empty_image(_: impl Ctx, transform: DAffine2, color: List<Color>) -> List<Raster<CPU>> {
let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32; let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32;
let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32; let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32;
let color = color.element(0).copied().unwrap_or(Color::WHITE); let color = color.element(0).copied().unwrap_or(Color::WHITE);
let image = Image::new(width, height, color); let image = Image::new(width, height, color);
let mut result_table = Table::new_from_element(Raster::new_cpu(image)); let mut result_list = List::new_from_element(Raster::new_cpu(image));
result_table.set_attribute(ATTR_TRANSFORM, 0, transform); result_list.set_attribute(ATTR_TRANSFORM, 0, transform);
// Callers of empty_image can safely unwrap on returned `Table` // Callers of empty_image can safely unwrap on returned `List`
result_table result_list
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
pub fn image(_: impl Ctx, _primary: (), image: Image<Color>) -> Table<Raster<CPU>> { pub fn image(_: impl Ctx, _primary: (), image: Image<Color>) -> List<Raster<CPU>> {
Table::new_from_element(Raster::new_cpu(image)) List::new_from_element(Raster::new_cpu(image))
} }
/// Generates customizable procedural noise patterns. /// Generates customizable procedural noise patterns.
@@ -328,7 +328,7 @@ pub fn noise_pattern(
#[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_jitter")] #[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_jitter")]
#[default(1.)] #[default(1.)]
cellular_jitter: f64, cellular_jitter: f64,
) -> Table<Raster<CPU>> { ) -> List<Raster<CPU>> {
let footprint = ctx.footprint(); let footprint = ctx.footprint();
let viewport_bounds = footprint.viewport_bounds_in_local_space(); let viewport_bounds = footprint.viewport_bounds_in_local_space();
@@ -346,7 +346,7 @@ pub fn noise_pattern(
// If the image would not be visible, return an empty image // If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. { if size.x <= 0. || size.y <= 0. {
return Table::new(); return List::new();
} }
let transform = DAffine2::from_translation(offset) * DAffine2::from_scale(size); let transform = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
@@ -392,7 +392,7 @@ pub fn noise_pattern(
} }
} }
return Table::new_from_item(Item::new_from_element(Raster::new_cpu(image)).with_attribute(ATTR_TRANSFORM, transform)); return List::new_from_item(Item::new_from_element(Raster::new_cpu(image)).with_attribute(ATTR_TRANSFORM, transform));
} }
}; };
noise.set_noise_type(Some(noise_type)); noise.set_noise_type(Some(noise_type));
@@ -450,11 +450,11 @@ pub fn noise_pattern(
} }
} }
Table::new_from_item(Item::new_from_element(Raster::new_cpu(image)).with_attribute(ATTR_TRANSFORM, transform)) List::new_from_item(Item::new_from_element(Raster::new_cpu(image)).with_attribute(ATTR_TRANSFORM, transform))
} }
#[node_macro::node(category("Raster: Pattern"))] #[node_macro::node(category("Raster: Pattern"))]
pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> Table<Raster<CPU>> { pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> List<Raster<CPU>> {
let footprint = ctx.footprint(); let footprint = ctx.footprint();
let viewport_bounds = footprint.viewport_bounds_in_local_space(); let viewport_bounds = footprint.viewport_bounds_in_local_space();
@@ -466,7 +466,7 @@ pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> Table<Raster<CPU>> {
// If the image would not be visible, return an empty image // If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. { if size.x <= 0. || size.y <= 0. {
return Table::new(); return List::new();
} }
let scale = footprint.scale(); let scale = footprint.scale();
@@ -488,7 +488,7 @@ pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> Table<Raster<CPU>> {
} }
} }
Table::new_from_item( List::new_from_item(
Item::new_from_element(Raster::new_cpu(Image { Item::new_from_element(Raster::new_cpu(Image {
width, width,
height, height,
+51 -51
View File
@@ -1,7 +1,7 @@
use crate::gcore::Context; use crate::gcore::Context;
use core::f64::consts::TAU; use core::f64::consts::TAU;
use core_types::list::List;
use core_types::registry::types::{Angle, PixelSize}; use core_types::registry::types::{Angle, PixelSize};
use core_types::table::Table;
use core_types::{ATTR_TRANSFORM, CloneVarArgs, Color, Ctx, ExtractAll, InjectVarArgs, OwnedContextImpl}; use core_types::{ATTR_TRANSFORM, CloneVarArgs, Color, Ctx, ExtractAll, InjectVarArgs, OwnedContextImpl};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graphic_types::{Graphic, Vector}; use graphic_types::{Graphic, Vector};
@@ -12,23 +12,23 @@ use vector_types::GradientStops;
async fn repeat<T: Into<Graphic> + Default + Send + Clone + 'static>( async fn repeat<T: Into<Graphic> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Ctx, ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[implementations( #[implementations(
Context -> Table<Graphic>, Context -> List<Graphic>,
Context -> Table<Vector>, Context -> List<Vector>,
Context -> Table<Raster<CPU>>, Context -> List<Raster<CPU>>,
Context -> Table<Color>, Context -> List<Color>,
Context -> Table<GradientStops>, Context -> List<GradientStops>,
)] )]
content: impl Node<'n, Context<'static>, Output = Table<T>>, content: impl Node<'n, Context<'static>, Output = List<T>>,
#[default(1)] #[default(1)]
#[hard_min(1)] #[hard_min(1)]
count: u32, count: u32,
reverse: bool, reverse: bool,
) -> Table<T> { ) -> List<T> {
// Someday this node can have the option to generate infinitely instead of a fixed count (basically `std::iter::repeat`). // Someday this node can have the option to generate infinitely instead of a fixed count (basically `std::iter::repeat`).
let count = count.max(1) as usize; let count = count.max(1) as usize;
let mut result_table = Table::new(); let mut result_list = List::new();
for index in 0..count { for index in 0..count {
let index = if reverse { count - index - 1 } else { index }; let index = if reverse { count - index - 1 } else { index };
@@ -37,24 +37,24 @@ async fn repeat<T: Into<Graphic> + Default + Send + Clone + 'static>(
let generated_content = content.eval(new_ctx.into_context()).await; let generated_content = content.eval(new_ctx.into_context()).await;
for generated_row in generated_content.into_iter() { for generated_row in generated_content.into_iter() {
result_table.push(generated_row); result_list.push(generated_row);
} }
} }
result_table result_list
} }
#[node_macro::node(category("Repeat"))] #[node_macro::node(category("Repeat"))]
pub async fn repeat_array<T: Into<Graphic> + Default + Send + Clone + 'static>( pub async fn repeat_array<T: Into<Graphic> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Ctx, ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[implementations( #[implementations(
Context -> Table<Graphic>, Context -> List<Graphic>,
Context -> Table<Vector>, Context -> List<Vector>,
Context -> Table<Raster<CPU>>, Context -> List<Raster<CPU>>,
Context -> Table<Color>, Context -> List<Color>,
Context -> Table<GradientStops>, Context -> List<GradientStops>,
)] )]
content: impl Node<'n, Context<'static>, Output = Table<T>>, content: impl Node<'n, Context<'static>, Output = List<T>>,
#[default(100., 100.)] #[default(100., 100.)]
// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed. // TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.
direction: PixelSize, direction: PixelSize,
@@ -62,12 +62,12 @@ pub async fn repeat_array<T: Into<Graphic> + Default + Send + Clone + 'static>(
#[default(5)] #[default(5)]
#[hard_min(1)] #[hard_min(1)]
count: u32, count: u32,
) -> Table<T> { ) -> List<T> {
let angle = angle.to_radians(); let angle = angle.to_radians();
let count = count.max(1); let count = count.max(1);
let total = (count - 1) as f64; let total = (count - 1) as f64;
let mut result_table = Table::new(); let mut result_list = List::new();
for index in 0..count { for index in 0..count {
let angle = index as f64 * angle / total; let angle = index as f64 * angle / total;
@@ -85,24 +85,24 @@ pub async fn repeat_array<T: Into<Graphic> + Default + Send + Clone + 'static>(
let local_matrix = DAffine2::from_mat2(local_transform.matrix2); let local_matrix = DAffine2::from_mat2(local_transform.matrix2);
*row.attribute_mut_or_insert_default(ATTR_TRANSFORM) = local_translation * transform * local_matrix; *row.attribute_mut_or_insert_default(ATTR_TRANSFORM) = local_translation * transform * local_matrix;
result_table.push(row); result_list.push(row);
} }
} }
result_table result_list
} }
#[node_macro::node(category("Repeat"))] #[node_macro::node(category("Repeat"))]
async fn repeat_radial<T: Into<Graphic> + Default + Send + Clone + 'static>( async fn repeat_radial<T: Into<Graphic> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Ctx, ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[implementations( #[implementations(
Context -> Table<Graphic>, Context -> List<Graphic>,
Context -> Table<Vector>, Context -> List<Vector>,
Context -> Table<Raster<CPU>>, Context -> List<Raster<CPU>>,
Context -> Table<Color>, Context -> List<Color>,
Context -> Table<GradientStops>, Context -> List<GradientStops>,
)] )]
content: impl Node<'n, Context<'static>, Output = Table<T>>, content: impl Node<'n, Context<'static>, Output = List<T>>,
start_angle: Angle, start_angle: Angle,
#[unit(" px")] #[unit(" px")]
#[default(5)] #[default(5)]
@@ -110,10 +110,10 @@ async fn repeat_radial<T: Into<Graphic> + Default + Send + Clone + 'static>(
#[default(5)] #[default(5)]
#[hard_min(1)] #[hard_min(1)]
count: u32, count: u32,
) -> Table<T> { ) -> List<T> {
let count = count.max(1); let count = count.max(1);
let mut result_table = Table::new(); let mut result_list = List::new();
for index in 0..count { for index in 0..count {
let angle = DAffine2::from_angle((TAU / count as f64) * index as f64 + start_angle.to_radians()); let angle = DAffine2::from_angle((TAU / count as f64) * index as f64 + start_angle.to_radians());
@@ -131,28 +131,28 @@ async fn repeat_radial<T: Into<Graphic> + Default + Send + Clone + 'static>(
let local_matrix = DAffine2::from_mat2(local_transform.matrix2); let local_matrix = DAffine2::from_mat2(local_transform.matrix2);
*row.attribute_mut_or_insert_default(ATTR_TRANSFORM) = local_translation * transform * local_matrix; *row.attribute_mut_or_insert_default(ATTR_TRANSFORM) = local_translation * transform * local_matrix;
result_table.push(row); result_list.push(row);
} }
} }
result_table result_list
} }
#[node_macro::node(category("Repeat"), name("Repeat on Points"))] #[node_macro::node(category("Repeat"), name("Repeat on Points"))]
async fn repeat_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>( async fn repeat_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx + InjectVarArgs, ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx + InjectVarArgs,
points: Table<Vector>, points: List<Vector>,
#[implementations( #[implementations(
Context -> Table<Graphic>, Context -> List<Graphic>,
Context -> Table<Vector>, Context -> List<Vector>,
Context -> Table<Raster<CPU>>, Context -> List<Raster<CPU>>,
Context -> Table<Color>, Context -> List<Color>,
Context -> Table<GradientStops>, Context -> List<GradientStops>,
)] )]
content: impl Node<'n, Context<'static>, Output = Table<T>>, content: impl Node<'n, Context<'static>, Output = List<T>>,
reverse: bool, reverse: bool,
) -> Table<T> { ) -> List<T> {
let mut result_table = Table::new(); let mut result_list = List::new();
for points_index in 0..points.len() { for points_index in 0..points.len() {
let Some(points_element) = points.element(points_index) else { continue }; let Some(points_element) = points.element(points_index) else { continue };
@@ -166,7 +166,7 @@ async fn repeat_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
for mut generated_row in generated_content.into_iter() { for mut generated_row in generated_content.into_iter() {
generated_row.attribute_mut_or_insert_default::<DAffine2>(ATTR_TRANSFORM).translation = transformed_point; generated_row.attribute_mut_or_insert_default::<DAffine2>(ATTR_TRANSFORM).translation = transformed_point;
result_table.push(generated_row); result_list.push(generated_row);
} }
}; };
@@ -182,7 +182,7 @@ async fn repeat_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
} }
} }
result_table result_list
} }
#[cfg(test)] #[cfg(test)]
@@ -202,8 +202,8 @@ mod test {
use vector_nodes::generator_nodes::RectangleNode; use vector_nodes::generator_nodes::RectangleNode;
use vector_types::subpath::Subpath; use vector_types::subpath::Subpath;
fn vector_node_from_bezpath(bezpath: BezPath) -> Table<Vector> { fn vector_node_from_bezpath(bezpath: BezPath) -> List<Vector> {
Table::new_from_element(Vector::from_bezpath(bezpath)) List::new_from_element(Vector::from_bezpath(bezpath))
} }
#[derive(Clone)] #[derive(Clone)]
@@ -230,7 +230,7 @@ mod test {
); );
let positions = [DVec2::new(40., 20.), DVec2::ONE, DVec2::new(-42., 9.), DVec2::new(10., 345.)]; let positions = [DVec2::new(40., 20.), DVec2::ONE, DVec2::new(-42., 9.), DVec2::new(10., 345.)];
let points = Table::new_from_element(Vector::from_subpath(Subpath::from_anchors(positions, false))); let points = List::new_from_element(Vector::from_subpath(Subpath::from_anchors(positions, false)));
let generated = super::repeat_on_points(context, points, &rect, false).await; let generated = super::repeat_on_points(context, points, &rect, false).await;
assert_eq!(generated.len(), positions.len()); assert_eq!(generated.len(), positions.len());
for (position, index) in positions.into_iter().zip(0..generated.len()) { for (position, index) in positions.into_iter().zip(0..generated.len()) {
@@ -257,8 +257,8 @@ mod test {
count, count,
) )
.await; .await;
let vector_table = vector_nodes::flatten_path(Footprint::default(), repeated).await; let vector_list = vector_nodes::flatten_path(Footprint::default(), repeated).await;
let vector = vector_table.element(0).unwrap(); let vector = vector_list.element(0).unwrap();
assert_eq!(vector.region_manipulator_groups().count(), 3); assert_eq!(vector.region_manipulator_groups().count(), 3);
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() { for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
assert!((manipulator_groups[0].anchor - direction * index as f64 / (count - 1) as f64).length() < 1e-5); assert!((manipulator_groups[0].anchor - direction * index as f64 / (count - 1) as f64).length() < 1e-5);
@@ -278,8 +278,8 @@ mod test {
count, count,
) )
.await; .await;
let vector_table = vector_nodes::flatten_path(Footprint::default(), repeated).await; let vector_list = vector_nodes::flatten_path(Footprint::default(), repeated).await;
let vector = vector_table.element(0).unwrap(); let vector = vector_list.element(0).unwrap();
assert_eq!(vector.region_manipulator_groups().count(), 8); assert_eq!(vector.region_manipulator_groups().count(), 8);
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() { for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
assert!((manipulator_groups[0].anchor - direction * index as f64 / (count - 1) as f64).length() < 1e-5); assert!((manipulator_groups[0].anchor - direction * index as f64 / (count - 1) as f64).length() < 1e-5);
@@ -290,8 +290,8 @@ mod test {
async fn repeat_radial() { async fn repeat_radial() {
let context = OwnedContextImpl::default().into_context(); let context = OwnedContextImpl::default().into_context();
let repeated = super::repeat_radial(context, &FutureWrapperNode(vector_node_from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY))), 45., 4., 8).await; let repeated = super::repeat_radial(context, &FutureWrapperNode(vector_node_from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY))), 45., 4., 8).await;
let vector_table = vector_nodes::flatten_path(Footprint::default(), repeated).await; let vector_list = vector_nodes::flatten_path(Footprint::default(), repeated).await;
let vector = vector_table.element(0).unwrap(); let vector = vector_list.element(0).unwrap();
assert_eq!(vector.region_manipulator_groups().count(), 8); assert_eq!(vector.region_manipulator_groups().count(), 8);
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() { for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
+4 -4
View File
@@ -1,4 +1,4 @@
use core_types::table::{Item, Table}; use core_types::list::{Item, List};
use core_types::{ATTR_TYPE, Ctx}; use core_types::{ATTR_TYPE, Ctx};
use serde_json::Value; use serde_json::Value;
@@ -240,10 +240,10 @@ fn query_json_all(
/// Strips the surrounding double quotes from string values, returning the raw text. Other types are never wrapped in quotes. /// Strips the surrounding double quotes from string values, returning the raw text. Other types are never wrapped in quotes.
#[default(true)] #[default(true)]
unquote_strings: bool, unquote_strings: bool,
) -> Table<String> { ) -> List<String> {
let cleaned = strip_trailing_commas(&json); let cleaned = strip_trailing_commas(&json);
let Ok(value): Result<Value, _> = serde_json::from_str(&cleaned) else { return Table::new() }; let Ok(value): Result<Value, _> = serde_json::from_str(&cleaned) else { return List::new() };
let Some(segments) = parse_json_path(path.trim()) else { return Table::new() }; let Some(segments) = parse_json_path(path.trim()) else { return List::new() };
let mut results = Vec::new(); let mut results = Vec::new();
resolve_all(&value, &segments, !unquote_strings, &mut results); resolve_all(&value, &segments, !unquote_strings, &mut results);
+6 -6
View File
@@ -7,8 +7,8 @@ mod to_path;
use convert_case::{Boundary, Converter, pattern}; use convert_case::{Boundary, Converter, pattern};
use core_types::graphene_hash::CacheHash; use core_types::graphene_hash::CacheHash;
use core_types::list::{Item, List};
use core_types::registry::types::{SignedInteger, TextArea}; use core_types::registry::types::{SignedInteger, TextArea};
use core_types::table::{Item, Table};
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractVarArgs, OwnedContextImpl}; use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractVarArgs, OwnedContextImpl};
use dyn_any::DynAny; use dyn_any::DynAny;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
@@ -737,7 +737,7 @@ fn string_split(
/// "\n" (newline), "\r" (carriage return), "\t" (tab), "\0" (null), and "\\" (backslash). /// "\n" (newline), "\r" (carriage return), "\t" (tab), "\0" (null), and "\\" (backslash).
#[default(true)] #[default(true)]
delimiter_escaping: bool, delimiter_escaping: bool,
) -> Table<String> { ) -> List<String> {
let delimiter = if delimiter_escaping { unescape_string(delimiter) } else { delimiter }; let delimiter = if delimiter_escaping { unescape_string(delimiter) } else { delimiter };
string.split(&delimiter).map(str::to_string).map(Item::new_from_element).collect() string.split(&delimiter).map(str::to_string).map(Item::new_from_element).collect()
@@ -750,7 +750,7 @@ fn string_split(
fn string_join( fn string_join(
_: impl Ctx, _: impl Ctx,
/// The list of strings to join together. /// The list of strings to join together.
strings: Table<String>, strings: List<String>,
/// The text placed between each pair of strings. /// The text placed between each pair of strings.
#[default(", ")] #[default(", ")]
separator: String, separator: String,
@@ -768,12 +768,12 @@ fn string_join(
#[node_macro::node(category("Text"))] #[node_macro::node(category("Text"))]
async fn map_string( async fn map_string(
ctx: impl Ctx + CloneVarArgs + ExtractAll, ctx: impl Ctx + CloneVarArgs + ExtractAll,
strings: Table<String>, strings: List<String>,
#[expose] #[expose]
#[implementations(Context -> String)] #[implementations(Context -> String)]
mapped: impl Node<Context<'static>, Output = String>, mapped: impl Node<Context<'static>, Output = String>,
) -> Table<String> { ) -> List<String> {
let mut result = Table::new(); let mut result = List::new();
for (i, row) in strings.into_iter().enumerate() { for (i, row) in strings.into_iter().enumerate() {
let string = row.into_element(); let string = row.into_element();
+18 -18
View File
@@ -1,4 +1,4 @@
use core_types::table::{Item, Table}; use core_types::list::{Item, List};
use core_types::{ATTR_EDITOR_CLICK_TARGET, ATTR_EDITOR_TEXT_FRAME, ATTR_TRANSFORM}; use core_types::{ATTR_EDITOR_CLICK_TARGET, ATTR_EDITOR_TEXT_FRAME, ATTR_TRANSFORM};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use parley::GlyphRun; use parley::GlyphRun;
@@ -14,7 +14,7 @@ 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>, pub vector_list: List<Vector>,
/// Per-glyph AABBs collected in single-item mode, published as `ATTR_EDITOR_CLICK_TARGET` in `finalize()`. /// Per-glyph AABBs collected in single-item mode, published as `ATTR_EDITOR_CLICK_TARGET` in `finalize()`.
merged_click_target_bboxes: Vec<[DVec2; 2]>, merged_click_target_bboxes: Vec<[DVec2; 2]>,
/// Per-glyph baselines, parallel to `merged_click_target_bboxes`. Groups glyphs by line for the widening pass. /// Per-glyph baselines, parallel to `merged_click_target_bboxes`. Groups glyphs by line for the widening pass.
@@ -35,7 +35,7 @@ impl PathBuilder {
Self { Self {
current_subpath: Subpath::new(Vec::new(), false), current_subpath: Subpath::new(Vec::new(), false),
glyph_subpaths: Vec::new(), glyph_subpaths: Vec::new(),
vector_table: if per_glyph_items { Table::new() } else { Table::new_from_element(Vector::default()) }, vector_list: if per_glyph_items { List::new() } else { List::new_from_element(Vector::default()) },
merged_click_target_bboxes: Vec::new(), merged_click_target_bboxes: Vec::new(),
merged_click_target_baselines: Vec::new(), merged_click_target_baselines: Vec::new(),
per_glyph_bboxes: Vec::new(), per_glyph_bboxes: Vec::new(),
@@ -87,14 +87,14 @@ impl PathBuilder {
let item = Item::new_from_element(Vector::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false)) let item = Item::new_from_element(Vector::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false))
.with_attribute(ATTR_TRANSFORM, DAffine2::from_translation(glyph_offset)) .with_attribute(ATTR_TRANSFORM, DAffine2::from_translation(glyph_offset))
.with_attribute(ATTR_EDITOR_TEXT_FRAME, frame_in_item_local); .with_attribute(ATTR_EDITOR_TEXT_FRAME, frame_in_item_local);
self.vector_table.push(item); self.vector_list.push(item);
// Defer click target creation to `finalize()` where adjacent AABBs get widened // Defer click target creation to `finalize()` where adjacent AABBs get widened
self.per_glyph_bboxes.push(glyph_bbox); self.per_glyph_bboxes.push(glyph_bbox);
} 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 `Table<Vector>` item // Unwrapping here is ok because `self.vector_list` is initialized with a single `List<Vector>` item
self.vector_table.element_mut(0).unwrap().append_subpath(subpath, false); self.vector_list.element_mut(0).unwrap().append_subpath(subpath, false);
} }
if let Some(bbox) = glyph_bbox { if let Some(bbox) = glyph_bbox {
self.merged_click_target_bboxes.push(bbox); self.merged_click_target_bboxes.push(bbox);
@@ -163,16 +163,16 @@ impl PathBuilder {
} }
} }
pub fn finalize(mut self) -> Table<Vector> { pub fn finalize(mut self) -> List<Vector> {
// Empty table = all glyphs clipped by height. Create a placeholder with the same item-0 // Empty list = all glyphs clipped by height. Create a placeholder with the same item-0
// transform a populated table would have so `local_transforms` stays stable mid-drag. // transform a populated list would have so `local_transforms` stays stable mid-drag.
// TODO: Remove this hack and move the attribute up to the parent return value when <https://github.com/GraphiteEditor/Graphite/issues/3779> is done. // TODO: Remove this hack and move the attribute up to the parent return value when <https://github.com/GraphiteEditor/Graphite/issues/3779> is done.
if self.vector_table.is_empty() { if self.vector_list.is_empty() {
let frame_in_item_local = DAffine2::from_scale_angle_translation(self.text_frame_size, 0., -self.first_glyph_offset); let frame_in_item_local = DAffine2::from_scale_angle_translation(self.text_frame_size, 0., -self.first_glyph_offset);
let item = Item::new_from_element(Vector::default()) let item = Item::new_from_element(Vector::default())
.with_attribute(ATTR_TRANSFORM, DAffine2::from_translation(self.first_glyph_offset)) .with_attribute(ATTR_TRANSFORM, DAffine2::from_translation(self.first_glyph_offset))
.with_attribute(ATTR_EDITOR_TEXT_FRAME, frame_in_item_local); .with_attribute(ATTR_EDITOR_TEXT_FRAME, frame_in_item_local);
self.vector_table.push(item); self.vector_list.push(item);
} }
// Widen per-glyph AABBs to close horizontal gaps, then publish as click targets // Widen per-glyph AABBs to close horizontal gaps, then publish as click targets
@@ -184,7 +184,7 @@ impl PathBuilder {
.enumerate() .enumerate()
.filter_map(|(index, bbox)| { .filter_map(|(index, bbox)| {
let bbox = (*bbox)?; let bbox = (*bbox)?;
let offset = self.vector_table.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, index).translation; let offset = self.vector_list.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, index).translation;
Some((index, offset, [bbox[0] + offset, bbox[1] + offset])) Some((index, offset, [bbox[0] + offset, bbox[1] + offset]))
}) })
.collect(); .collect();
@@ -197,7 +197,7 @@ impl PathBuilder {
for (entry, widened) in entries.iter().zip(layer_bboxes.iter()) { for (entry, widened) in entries.iter().zip(layer_bboxes.iter()) {
let glyph_local = [widened[0] - entry.1, widened[1] - entry.1]; let glyph_local = [widened[0] - entry.1, widened[1] - entry.1];
let rect = Subpath::new_rectangle(glyph_local[0], glyph_local[1]); let rect = Subpath::new_rectangle(glyph_local[0], glyph_local[1]);
self.vector_table.set_attribute(ATTR_EDITOR_CLICK_TARGET, entry.0, Vector::from_subpaths([rect], false)); self.vector_list.set_attribute(ATTR_EDITOR_CLICK_TARGET, entry.0, Vector::from_subpaths([rect], false));
} }
} }
@@ -207,18 +207,18 @@ impl PathBuilder {
widen_horizontal_gaps(&mut bboxes, &self.merged_click_target_baselines); widen_horizontal_gaps(&mut bboxes, &self.merged_click_target_baselines);
let widened_subpaths: Vec<_> = bboxes.iter().map(|[min, max]| Subpath::new_rectangle(*min, *max)).collect(); let widened_subpaths: Vec<_> = bboxes.iter().map(|[min, max]| Subpath::new_rectangle(*min, *max)).collect();
self.vector_table.set_attribute(ATTR_EDITOR_CLICK_TARGET, 0, Vector::from_subpaths(widened_subpaths, false)); self.vector_list.set_attribute(ATTR_EDITOR_CLICK_TARGET, 0, Vector::from_subpaths(widened_subpaths, false));
} }
// Fill in text frame for items that don't have one yet (single-item mode, where item 0 = identity) // Fill in text frame for items that don't have one yet (single-item mode, where item 0 = identity)
let frame = DAffine2::from_scale(self.text_frame_size); let frame = DAffine2::from_scale(self.text_frame_size);
for index in 0..self.vector_table.len() { for index in 0..self.vector_list.len() {
if self.vector_table.attribute::<DAffine2>(ATTR_EDITOR_TEXT_FRAME, index).is_none() { if self.vector_list.attribute::<DAffine2>(ATTR_EDITOR_TEXT_FRAME, index).is_none() {
self.vector_table.set_attribute(ATTR_EDITOR_TEXT_FRAME, index, frame); self.vector_list.set_attribute(ATTR_EDITOR_TEXT_FRAME, index, frame);
} }
} }
self.vector_table self.vector_list
} }
} }
+12 -12
View File
@@ -1,5 +1,5 @@
use core_types::list::{Item, List};
use core_types::registry::types::SignedInteger; use core_types::registry::types::SignedInteger;
use core_types::table::{Item, Table};
use core_types::{ATTR_END, ATTR_NAME, ATTR_START, Ctx}; use core_types::{ATTR_END, ATTR_NAME, ATTR_START, Ctx};
/// Checks whether the string contains a match for the given regular expression pattern. Optionally restricts the match to only the start and/or end of the string. /// Checks whether the string contains a match for the given regular expression pattern. Optionally restricts the match to only the start and/or end of the string.
@@ -96,9 +96,9 @@ fn regex_find(
case_insensitive: bool, case_insensitive: bool,
/// Make `^` and `$` match the start and end of each line, not just the whole string. /// Make `^` and `$` match the start and end of each line, not just the whole string.
multiline: bool, multiline: bool,
) -> Table<String> { ) -> List<String> {
if pattern.is_empty() { if pattern.is_empty() {
return Table::new(); return List::new();
} }
let flags = match (case_insensitive, multiline) { let flags = match (case_insensitive, multiline) {
@@ -111,7 +111,7 @@ fn regex_find(
let Ok(regex) = fancy_regex::Regex::new(&full_pattern) else { let Ok(regex) = fancy_regex::Regex::new(&full_pattern) else {
log::error!("Invalid regex pattern: {pattern}"); log::error!("Invalid regex pattern: {pattern}");
return Table::new(); return List::new();
}; };
// Capture group names indexed positionally; index 0 (the whole match) is always None. // Capture group names indexed positionally; index 0 (the whole match) is always None.
@@ -124,7 +124,7 @@ fn regex_find(
let resolved_index = if match_index < 0 { let resolved_index = if match_index < 0 {
let from_end = (-match_index) as usize; let from_end = (-match_index) as usize;
if from_end > matches.len() { if from_end > matches.len() {
return Table::new(); return List::new();
} }
matches.len() - from_end matches.len() - from_end
} else { } else {
@@ -132,7 +132,7 @@ fn regex_find(
}; };
let Some(captures) = matches.get(resolved_index) else { let Some(captures) = matches.get(resolved_index) else {
return Table::new(); return List::new();
}; };
// Index 0 is the whole match, 1+ are capture groups // Index 0 is the whole match, 1+ are capture groups
@@ -165,9 +165,9 @@ fn regex_find_all(
case_insensitive: bool, case_insensitive: bool,
/// Make `^` and `$` match the start and end of each line, not just the whole string. /// Make `^` and `$` match the start and end of each line, not just the whole string.
multiline: bool, multiline: bool,
) -> Table<String> { ) -> List<String> {
if pattern.is_empty() { if pattern.is_empty() {
return Table::new(); return List::new();
} }
let flags = match (case_insensitive, multiline) { let flags = match (case_insensitive, multiline) {
@@ -180,7 +180,7 @@ fn regex_find_all(
let Ok(regex) = fancy_regex::Regex::new(&full_pattern) else { let Ok(regex) = fancy_regex::Regex::new(&full_pattern) else {
log::error!("Invalid regex pattern: {pattern}"); log::error!("Invalid regex pattern: {pattern}");
return Table::new(); return List::new();
}; };
regex regex
@@ -208,9 +208,9 @@ fn regex_split(
case_insensitive: bool, case_insensitive: bool,
/// Make `^` and `$` match the start and end of each line, not just the whole string. /// Make `^` and `$` match the start and end of each line, not just the whole string.
multiline: bool, multiline: bool,
) -> Table<String> { ) -> List<String> {
if pattern.is_empty() { if pattern.is_empty() {
return Table::new_from_element(string); return List::new_from_element(string);
} }
let flags = match (case_insensitive, multiline) { let flags = match (case_insensitive, multiline) {
@@ -223,7 +223,7 @@ fn regex_split(
let Ok(regex) = fancy_regex::Regex::new(&full_pattern) else { let Ok(regex) = fancy_regex::Regex::new(&full_pattern) else {
log::error!("Invalid regex pattern: {pattern}"); log::error!("Invalid regex pattern: {pattern}");
return Table::new_from_element(string); return List::new_from_element(string);
}; };
regex.split(&string).filter_map(|s| s.ok()).map(|s| s.to_string()).map(Item::new_from_element).collect() regex.split(&string).filter_map(|s| s.ok()).map(|s| s.to_string()).map(Item::new_from_element).collect()
+3 -3
View File
@@ -1,6 +1,6 @@
use super::{Font, FontCache, TypesettingConfig}; use super::{Font, FontCache, TypesettingConfig};
use core::cell::RefCell; use core::cell::RefCell;
use core_types::table::Table; use core_types::list::List;
use glam::DVec2; use glam::DVec2;
use parley::fontique::{Blob, FamilyId, FontInfo}; use parley::fontique::{Blob, FamilyId, FontInfo};
use parley::{AlignmentOptions, FontContext, Layout, LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty}; use parley::{AlignmentOptions, FontContext, Layout, LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty};
@@ -87,9 +87,9 @@ 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(&mut self, text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, per_glyph_items: bool) -> Table<Vector> { pub fn to_path(&mut self, text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, per_glyph_items: bool) -> List<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 List::new_from_element(Vector::default());
}; };
let text_frame_size = DVec2::new( let text_frame_size = DVec2::new(
+2 -2
View File
@@ -1,12 +1,12 @@
use super::text_context::TextContext; use super::text_context::TextContext;
use super::{Font, FontCache, TypesettingConfig}; use super::{Font, FontCache, TypesettingConfig};
use core_types::table::Table; use core_types::list::List;
use glam::DVec2; use glam::DVec2;
use parley::fontique::Blob; use parley::fontique::Blob;
use std::sync::Arc; use std::sync::Arc;
use vector_types::Vector; use vector_types::Vector;
pub fn to_path(text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, per_glyph_items: bool) -> Table<Vector> { pub fn to_path(text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, per_glyph_items: bool) -> List<Vector> {
TextContext::with_thread_local(|ctx| ctx.to_path(text, font, font_cache, typesetting, per_glyph_items)) TextContext::with_thread_local(|ctx| ctx.to_path(text, font, font_cache, typesetting, per_glyph_items))
} }
@@ -1,6 +1,6 @@
use core::f64; use core::f64;
use core_types::color::Color; use core_types::color::Color;
use core_types::table::{Table, TableDyn}; use core_types::list::{List, ListDyn};
use core_types::transform::{ApplyTransform, ScaleType, Transform}; use core_types::transform::{ApplyTransform, ScaleType, Transform};
use core_types::{ATTR_TRANSFORM, CloneVarArgs, Context, Ctx, ExtractAll, InjectFootprint, ModifyFootprint, OwnedContextImpl}; use core_types::{ATTR_TRANSFORM, CloneVarArgs, Context, Ctx, ExtractAll, InjectFootprint, ModifyFootprint, OwnedContextImpl};
use glam::{DAffine2, DMat2, DVec2}; use glam::{DAffine2, DMat2, DVec2};
@@ -16,12 +16,12 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
#[implementations( #[implementations(
Context -> DAffine2, Context -> DAffine2,
Context -> DVec2, Context -> DVec2,
Context -> Table<Graphic>, Context -> List<Graphic>,
Context -> Table<Vector>, Context -> List<Vector>,
Context -> Table<Raster<CPU>>, Context -> List<Raster<CPU>>,
Context -> Table<Raster<GPU>>, Context -> List<Raster<GPU>>,
Context -> Table<Color>, Context -> List<Color>,
Context -> Table<GradientStops>, Context -> List<GradientStops>,
)] )]
content: impl Node<Context<'static>, Output = T>, content: impl Node<Context<'static>, Output = T>,
#[widget(ParsedWidgetOverride::Custom = "transform_translation")] translation: DVec2, #[widget(ParsedWidgetOverride::Custom = "transform_translation")] translation: DVec2,
@@ -56,18 +56,18 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
fn reset_transform<T>( fn reset_transform<T>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Graphic>, List<Graphic>,
Table<Vector>, List<Vector>,
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Raster<GPU>>, List<Raster<GPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
mut content: Table<T>, mut content: List<T>,
#[default(true)] reset_translation: bool, #[default(true)] reset_translation: bool,
reset_rotation: bool, reset_rotation: bool,
reset_scale: bool, reset_scale: bool,
) -> Table<T> { ) -> List<T> {
for row_transform in content.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) { for row_transform in content.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
if reset_translation { if reset_translation {
row_transform.translation = DVec2::ZERO; row_transform.translation = DVec2::ZERO;
@@ -89,21 +89,21 @@ fn reset_transform<T>(
content content
} }
/// Overwrites the transform of each item in the input `Table` with the specified transform. /// Overwrites the transform of each item in the input `List` with the specified transform.
#[node_macro::node(category("Math: Transform"))] #[node_macro::node(category("Math: Transform"))]
fn replace_transform<T>( fn replace_transform<T>(
_: impl Ctx + InjectFootprint, _: impl Ctx + InjectFootprint,
#[implementations( #[implementations(
Table<Graphic>, List<Graphic>,
Table<Vector>, List<Vector>,
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Raster<GPU>>, List<Raster<GPU>>,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
)] )]
mut content: Table<T>, mut content: List<T>,
transform: DAffine2, transform: DAffine2,
) -> Table<T> { ) -> List<T> {
for row_transform in content.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) { for row_transform in content.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
*row_transform = transform.transform(); *row_transform = transform.transform();
} }
@@ -111,9 +111,9 @@ fn replace_transform<T>(
} }
// TODO: Figure out how this node should behave once #2982 is implemented. // TODO: Figure out how this node should behave once #2982 is implemented.
/// Obtains the transform of the first item in the input `Table`, if present. /// Obtains the transform of the first item in the input `List`, if present.
#[node_macro::node(category("Math: Transform"), path(core_types::vector))] #[node_macro::node(category("Math: Transform"), path(core_types::vector))]
async fn extract_transform(_: impl Ctx, content: TableDyn) -> DAffine2 { async fn extract_transform(_: impl Ctx, content: ListDyn) -> DAffine2 {
content.attribute::<DAffine2>(ATTR_TRANSFORM, 0).copied().unwrap_or_default() content.attribute::<DAffine2>(ATTR_TRANSFORM, 0).copied().unwrap_or_default()
} }
+30 -30
View File
@@ -1,5 +1,5 @@
use core_types::list::List;
use core_types::registry::types::{Angle, PixelLength, PixelSize}; use core_types::registry::types::{Angle, PixelLength, PixelSize};
use core_types::table::Table;
use core_types::{CacheHash, Ctx}; use core_types::{CacheHash, Ctx};
use dyn_any::DynAny; use dyn_any::DynAny;
use glam::DVec2; use glam::DVec2;
@@ -10,16 +10,16 @@ use vector_types::vector::misc::{HandleId, SpiralType};
use vector_types::vector::{PointId, SegmentId, StrokeId}; use vector_types::vector::{PointId, SegmentId, StrokeId};
trait CornerRadius { trait CornerRadius {
fn generate(self, size: DVec2, clamped: bool) -> Table<Vector>; fn generate(self, size: DVec2, clamped: bool) -> List<Vector>;
} }
impl CornerRadius for f64 { impl CornerRadius for f64 {
fn generate(self, size: DVec2, clamped: bool) -> Table<Vector> { fn generate(self, size: DVec2, clamped: bool) -> List<Vector> {
let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self }; let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self };
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_rounded_rectangle(size / -2., size / 2., [clamped_radius; 4]))) List::new_from_element(Vector::from_subpath(subpath::Subpath::new_rounded_rectangle(size / -2., size / 2., [clamped_radius; 4])))
} }
} }
impl CornerRadius for Table<f64> { impl CornerRadius for List<f64> {
fn generate(self, size: DVec2, clamped: bool) -> Table<Vector> { fn generate(self, size: DVec2, clamped: bool) -> List<Vector> {
// Expand to four corners using the CSS `border-radius` shorthand rules. // Expand to four corners using the CSS `border-radius` shorthand rules.
// - `[a]` → `[a, a, a, a]` // - `[a]` → `[a, a, a, a]`
// - `[a, b]` → `[a, b, a, b]` // - `[a, b]` → `[a, b, a, b]`
@@ -50,7 +50,7 @@ impl CornerRadius for Table<f64> {
} else { } else {
radii radii
}; };
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_rounded_rectangle(size / -2., size / 2., clamped_radius))) List::new_from_element(Vector::from_subpath(subpath::Subpath::new_rounded_rectangle(size / -2., size / 2., clamped_radius)))
} }
} }
@@ -62,9 +62,9 @@ fn circle(
#[unit(" px")] #[unit(" px")]
#[default(50.)] #[default(50.)]
radius: f64, radius: f64,
) -> Table<Vector> { ) -> List<Vector> {
let radius = radius.abs(); let radius = radius.abs();
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius)))) List::new_from_element(Vector::from_subpath(subpath::Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
} }
/// Generates an arc shape forming a portion of a circle which may be open, closed, or a pie slice. /// Generates an arc shape forming a portion of a circle which may be open, closed, or a pie slice.
@@ -80,8 +80,8 @@ fn arc(
#[range((0., 360.))] #[range((0., 360.))]
sweep_angle: Angle, sweep_angle: Angle,
arc_type: ArcType, arc_type: ArcType,
) -> Table<Vector> { ) -> List<Vector> {
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_arc( List::new_from_element(Vector::from_subpath(subpath::Subpath::new_arc(
radius, radius,
start_angle / 360. * std::f64::consts::TAU, start_angle / 360. * std::f64::consts::TAU,
sweep_angle / 360. * std::f64::consts::TAU, sweep_angle / 360. * std::f64::consts::TAU,
@@ -104,8 +104,8 @@ fn spiral(
#[default(0.)] inner_radius: f64, #[default(0.)] inner_radius: f64,
#[default(25)] outer_radius: f64, #[default(25)] outer_radius: f64,
#[default(90.)] angular_resolution: f64, #[default(90.)] angular_resolution: f64,
) -> Table<Vector> { ) -> List<Vector> {
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_spiral( List::new_from_element(Vector::from_subpath(subpath::Subpath::new_spiral(
inner_radius, inner_radius,
outer_radius, outer_radius,
turns, turns,
@@ -126,7 +126,7 @@ fn ellipse(
#[unit(" px")] #[unit(" px")]
#[default(25)] #[default(25)]
radius_y: f64, radius_y: f64,
) -> Table<Vector> { ) -> List<Vector> {
let radius = DVec2::new(radius_x, radius_y); let radius = DVec2::new(radius_x, radius_y);
let corner1 = -radius; let corner1 = -radius;
let corner2 = radius; let corner2 = radius;
@@ -140,7 +140,7 @@ fn ellipse(
.push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]); .push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]);
} }
Table::new_from_element(ellipse) List::new_from_element(ellipse)
} }
/// Generates a rectangle shape with the chosen width and height. It may also have rounded corners if desired. /// Generates a rectangle shape with the chosen width and height. It may also have rounded corners if desired.
@@ -155,9 +155,9 @@ fn rectangle<T: CornerRadius>(
#[default(100)] #[default(100)]
height: f64, height: f64,
_individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability _individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability
#[implementations(f64, Table<f64>)] corner_radius: T, #[implementations(f64, List<f64>)] corner_radius: T,
#[default(true)] clamped: bool, #[default(true)] clamped: bool,
) -> Table<Vector> { ) -> List<Vector> {
corner_radius.generate(DVec2::new(width, height), clamped) corner_radius.generate(DVec2::new(width, height), clamped)
} }
@@ -173,10 +173,10 @@ fn regular_polygon<T: AsU64>(
#[unit(" px")] #[unit(" px")]
#[default(50)] #[default(50)]
radius: f64, radius: f64,
) -> Table<Vector> { ) -> List<Vector> {
let points = sides.as_u64(); let points = sides.as_u64();
let radius: f64 = radius * 2.; let radius: f64 = radius * 2.;
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius))) List::new_from_element(Vector::from_subpath(subpath::Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
} }
/// Generates an n-pointed star shape with inner and outer points at chosen radii from the center. /// Generates an n-pointed star shape with inner and outer points at chosen radii from the center.
@@ -194,12 +194,12 @@ fn star<T: AsU64>(
#[unit(" px")] #[unit(" px")]
#[default(25)] #[default(25)]
radius_2: f64, radius_2: f64,
) -> Table<Vector> { ) -> List<Vector> {
let points = sides.as_u64(); let points = sides.as_u64();
let diameter: f64 = radius_1 * 2.; let diameter: f64 = radius_1 * 2.;
let inner_diameter = radius_2 * 2.; let inner_diameter = radius_2 * 2.;
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter))) List::new_from_element(Vector::from_subpath(subpath::Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
} }
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] #[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
@@ -233,7 +233,7 @@ fn qr_code(
size: f64, size: f64,
error_correction: QRCodeErrorCorrectionLevel, error_correction: QRCodeErrorCorrectionLevel,
#[default(false)] individual_squares: bool, #[default(false)] individual_squares: bool,
) -> Table<Vector> { ) -> List<Vector> {
let ecc = match error_correction { let ecc = match error_correction {
QRCodeErrorCorrectionLevel::Low => qrcodegen::QrCodeEcc::Low, QRCodeErrorCorrectionLevel::Low => qrcodegen::QrCodeEcc::Low,
QRCodeErrorCorrectionLevel::Medium => qrcodegen::QrCodeEcc::Medium, QRCodeErrorCorrectionLevel::Medium => qrcodegen::QrCodeEcc::Medium,
@@ -241,7 +241,7 @@ fn qr_code(
QRCodeErrorCorrectionLevel::High => qrcodegen::QrCodeEcc::High, QRCodeErrorCorrectionLevel::High => qrcodegen::QrCodeEcc::High,
}; };
let Ok(qr_code) = qrcodegen::QrCode::encode_text(&text, ecc) else { return Table::default() }; let Ok(qr_code) = qrcodegen::QrCode::encode_text(&text, ecc) else { return List::default() };
let mut vector = match individual_squares { let mut vector = match individual_squares {
true => { true => {
@@ -270,7 +270,7 @@ fn qr_code(
vector.transform(glam::DAffine2::from_scale(DVec2::splat(size.max(1.) / qr_code.size() as f64))); vector.transform(glam::DAffine2::from_scale(DVec2::splat(size.max(1.) / qr_code.size() as f64)));
} }
Table::new_from_element(vector) List::new_from_element(vector)
} }
/// Generates an arrow from the origin to the chosen coordinate. /// Generates an arrow from the origin to the chosen coordinate.
@@ -282,13 +282,13 @@ fn arrow(
#[default(10)] shaft_width: PixelLength, #[default(10)] shaft_width: PixelLength,
#[default(30)] head_width: PixelLength, #[default(30)] head_width: PixelLength,
#[default(20)] head_length: PixelLength, #[default(20)] head_length: PixelLength,
) -> Table<Vector> { ) -> List<Vector> {
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_arrow(DVec2::ZERO, arrow_to, shaft_width, head_width, head_length))) List::new_from_element(Vector::from_subpath(subpath::Subpath::new_arrow(DVec2::ZERO, arrow_to, shaft_width, head_width, head_length)))
} }
#[node_macro::node(category("Vector: Shape"))] #[node_macro::node(category("Vector: Shape"))]
fn line(_: impl Ctx, _primary: (), #[default(100., 100.)] line_to: PixelSize) -> Table<Vector> { fn line(_: impl Ctx, _primary: (), #[default(100., 100.)] line_to: PixelSize) -> List<Vector> {
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_line(DVec2::ZERO, line_to))) List::new_from_element(Vector::from_subpath(subpath::Subpath::new_line(DVec2::ZERO, line_to)))
} }
trait GridSpacing { trait GridSpacing {
@@ -319,7 +319,7 @@ fn grid<T: GridSpacing>(
#[default(10)] columns: u32, #[default(10)] columns: u32,
#[default(10)] rows: u32, #[default(10)] rows: u32,
#[default(30., 30.)] angles: DVec2, #[default(30., 30.)] angles: DVec2,
) -> Table<Vector> { ) -> List<Vector> {
let (x_spacing, y_spacing) = spacing.as_dvec2().into(); let (x_spacing, y_spacing) = spacing.as_dvec2().into();
let (angle_a, angle_b) = angles.into(); let (angle_a, angle_b) = angles.into();
@@ -401,7 +401,7 @@ fn grid<T: GridSpacing>(
} }
} }
Table::new_from_element(vector) List::new_from_element(vector)
} }
#[cfg(test)] #[cfg(test)]
@@ -1,4 +1,4 @@
use core_types::table::Table; use core_types::list::List;
use core_types::uuid::NodeId; use core_types::uuid::NodeId;
use core_types::{ATTR_EDITOR_CLICK_TARGET, ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, Ctx}; use core_types::{ATTR_EDITOR_CLICK_TARGET, ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, Ctx};
use glam::DAffine2; use glam::DAffine2;
@@ -7,8 +7,8 @@ use vector_types::vector::VectorModification;
/// Applies a differential modification to a vector path, associating changes made by the Pen and Path tools to indices of edited points and segments. /// Applies a differential modification to a vector path, associating changes made by the Pen and Path tools to indices of edited points and segments.
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Box<VectorModification>, node_path: Table<NodeId>) -> Table<Vector> { async fn path_modify(_ctx: impl Ctx, mut vector: List<Vector>, modification: Box<VectorModification>, node_path: List<NodeId>) -> List<Vector> {
use core_types::table::Item; use core_types::list::Item;
if vector.is_empty() { if vector.is_empty() {
vector.push(Item::default()); vector.push(Item::default());
@@ -20,11 +20,11 @@ async fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Bo
// Set the path to the encapsulating subgraph (drop our own trailing entry from `node_path`), // Set the path to the encapsulating subgraph (drop our own trailing entry from `node_path`),
// matching the `path_of_subgraph` proto so editor tools can route data back to the parent layer. // matching the `path_of_subgraph` proto so editor tools can route data back to the parent layer.
let subgraph_path: Table<NodeId> = { let subgraph_path: List<NodeId> = {
let len = node_path.len(); let len = node_path.len();
node_path.into_iter().take(len.saturating_sub(1)).collect() node_path.into_iter().take(len.saturating_sub(1)).collect()
}; };
let existing: Table<NodeId> = vector.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0); let existing: List<NodeId> = vector.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0);
vector.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, if existing.is_empty() { subgraph_path } else { existing }); vector.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, if existing.is_empty() { subgraph_path } else { existing });
if vector.len() > 1 { if vector.len() > 1 {
@@ -35,7 +35,7 @@ async fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Bo
/// Applies the vector path's local transformation to its geometry and resets the transform to the identity. /// Applies the vector path's local transformation to its geometry and resets the transform to the identity.
#[node_macro::node(category("Vector"))] #[node_macro::node(category("Vector"))]
async fn apply_transform(_ctx: impl Ctx, mut vector: Table<Vector>) -> Table<Vector> { async fn apply_transform(_ctx: impl Ctx, mut vector: List<Vector>) -> List<Vector> {
let (elements, transforms) = vector.element_and_attribute_slices_mut::<DAffine2>(ATTR_TRANSFORM); let (elements, transforms) = vector.element_and_attribute_slices_mut::<DAffine2>(ATTR_TRANSFORM);
for (element, transform) in elements.iter_mut().zip(transforms.iter_mut()) { for (element, transform) in elements.iter_mut().zip(transforms.iter_mut()) {
for (_, point) in element.point_domain.positions_mut() { for (_, point) in element.point_domain.positions_mut() {
+142 -142
View File
@@ -3,8 +3,8 @@ use core::f64::consts::{PI, TAU};
use core::hash::{Hash, Hasher}; use core::hash::{Hash, Hasher};
use core_types::blending::BlendMode; use core_types::blending::BlendMode;
use core_types::bounds::{BoundingBox, RenderBoundingBox}; use core_types::bounds::{BoundingBox, RenderBoundingBox};
use core_types::list::{Item, List, ListDyn};
use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLength, Progression, SeedValue}; use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLength, Progression, SeedValue};
use core_types::table::{Item, Table, TableDyn};
use core_types::transform::{Footprint, Transform}; use core_types::transform::{Footprint, Transform};
use core_types::uuid::NodeId; use core_types::uuid::NodeId;
use core_types::{ use core_types::{
@@ -14,7 +14,7 @@ use core_types::{
use glam::{DAffine2, DMat2, DVec2}; use glam::{DAffine2, DMat2, DVec2};
use graphic_types::Vector; use graphic_types::Vector;
use graphic_types::raster_types::{CPU, GPU, Raster}; use graphic_types::raster_types::{CPU, GPU, Raster};
use graphic_types::{Graphic, IntoGraphicTable}; use graphic_types::{Graphic, IntoGraphicList};
use kurbo::simplify::{SimplifyOptions, simplify_bezpath}; use kurbo::simplify::{SimplifyOptions, simplify_bezpath};
use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveArclen, PathEl, PathSeg, Shape}; use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveArclen, PathEl, PathSeg, Shape};
use rand::{Rng, SeedableRng}; use rand::{Rng, SeedableRng};
@@ -33,18 +33,18 @@ use vector_types::vector::style::{Fill, Gradient, GradientStops, PaintOrder, Str
use vector_types::vector::{FillId, PointId, RegionId, SegmentDomain, SegmentId, StrokeId, VectorExt}; use vector_types::vector::{FillId, PointId, RegionId, SegmentDomain, SegmentId, StrokeId, VectorExt};
/// Implemented for types that contain vector items reachable via mutable access. /// Implemented for types that contain vector items reachable via mutable access.
/// Used for the fill and stroke nodes so they can apply to either `Table<Graphic>` or `Table<Vector>`. /// Used for the fill and stroke nodes so they can apply to either `List<Graphic>` or `List<Vector>`.
trait VectorTableIterMut { trait VectorListIterMut {
fn for_each_vector_mut(&mut self, f: impl FnMut(&mut Vector, DAffine2)); fn for_each_vector_mut(&mut self, f: impl FnMut(&mut Vector, DAffine2));
fn vector_count(&self) -> usize; fn vector_count(&self) -> usize;
} }
impl VectorTableIterMut for Table<Graphic> { impl VectorListIterMut for List<Graphic> {
fn for_each_vector_mut(&mut self, mut f: impl FnMut(&mut Vector, DAffine2)) { fn for_each_vector_mut(&mut self, mut f: impl FnMut(&mut Vector, DAffine2)) {
for graphic in self.iter_element_values_mut() { for graphic in self.iter_element_values_mut() {
let Some(vector_table) = graphic.as_vector_mut() else { continue }; let Some(vector_list) = graphic.as_vector_mut() else { continue };
let (elements, transforms) = vector_table.element_and_attribute_slices_mut::<DAffine2>(ATTR_TRANSFORM); let (elements, transforms) = vector_list.element_and_attribute_slices_mut::<DAffine2>(ATTR_TRANSFORM);
for (vector, transform) in elements.iter_mut().zip(transforms.iter()) { for (vector, transform) in elements.iter_mut().zip(transforms.iter()) {
f(vector, *transform); f(vector, *transform);
} }
@@ -52,11 +52,11 @@ impl VectorTableIterMut for Table<Graphic> {
} }
fn vector_count(&self) -> usize { fn vector_count(&self) -> usize {
self.iter_element_values().filter_map(|element| element.as_vector()).map(|table| table.len()).sum() self.iter_element_values().filter_map(|element| element.as_vector()).map(|list| list.len()).sum()
} }
} }
impl VectorTableIterMut for Table<Vector> { impl VectorListIterMut for List<Vector> {
fn for_each_vector_mut(&mut self, mut f: impl FnMut(&mut Vector, DAffine2)) { fn for_each_vector_mut(&mut self, mut f: impl FnMut(&mut Vector, DAffine2)) {
let (elements, transforms) = self.element_and_attribute_slices_mut::<DAffine2>(ATTR_TRANSFORM); let (elements, transforms) = self.element_and_attribute_slices_mut::<DAffine2>(ATTR_TRANSFORM);
for (vector, transform) in elements.iter_mut().zip(transforms.iter()) { for (vector, transform) in elements.iter_mut().zip(transforms.iter()) {
@@ -74,7 +74,7 @@ impl VectorTableIterMut for Table<Vector> {
async fn assign_colors<T>( async fn assign_colors<T>(
_: impl Ctx, _: impl Ctx,
/// The content with vector paths to apply the fill and/or stroke style to. /// The content with vector paths to apply the fill and/or stroke style to.
#[implementations(Table<Graphic>, Table<Vector>)] #[implementations(List<Graphic>, List<Vector>)]
#[widget(ParsedWidgetOverride::Hidden)] #[widget(ParsedWidgetOverride::Hidden)]
mut content: T, mut content: T,
/// Whether to style the fill. /// Whether to style the fill.
@@ -84,7 +84,7 @@ async fn assign_colors<T>(
stroke: bool, stroke: bool,
/// The range of colors to select from. /// The range of colors to select from.
#[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")] #[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")]
gradient: Table<GradientStops>, gradient: List<GradientStops>,
/// Whether to reverse the gradient. /// Whether to reverse the gradient.
reverse: bool, reverse: bool,
/// Whether to randomize the color selection for each element from throughout the gradient. /// Whether to randomize the color selection for each element from throughout the gradient.
@@ -98,7 +98,7 @@ async fn assign_colors<T>(
repeat_every: u32, repeat_every: u32,
) -> T ) -> T
where where
T: VectorTableIterMut + 'n + Send, T: VectorListIterMut + 'n + Send,
{ {
let Some(row) = gradient.into_iter().next() else { return content }; let Some(row) = gradient.into_iter().next() else { return content };
@@ -136,34 +136,34 @@ where
/// Applies a fill style to the vector content, giving an appearance to the area within the interior of the geometry. /// Applies a fill style to the vector content, giving an appearance to the area within the interior of the geometry.
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))] #[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))]
async fn fill<F: Into<Fill> + 'n + Send, V: VectorTableIterMut + 'n + Send>( async fn fill<F: Into<Fill> + 'n + Send, V: VectorListIterMut + 'n + Send>(
_: impl Ctx, _: impl Ctx,
/// The content with vector paths to apply the fill style to. /// The content with vector paths to apply the fill style to.
#[implementations( #[implementations(
Table<Vector>, List<Vector>,
Table<Vector>, List<Vector>,
Table<Vector>, List<Vector>,
Table<Vector>, List<Vector>,
Table<Graphic>, List<Graphic>,
Table<Graphic>, List<Graphic>,
Table<Graphic>, List<Graphic>,
Table<Graphic>, List<Graphic>,
)] )]
mut content: V, mut content: V,
/// The fill to paint the path with. /// The fill to paint the path with.
#[default(Color::BLACK)] #[default(Color::BLACK)]
#[implementations( #[implementations(
Fill, Fill,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
Gradient, Gradient,
Fill, Fill,
Table<Color>, List<Color>,
Table<GradientStops>, List<GradientStops>,
Gradient, Gradient,
)] )]
fill: F, fill: F,
_backup_color: Table<Color>, _backup_color: List<Color>,
_backup_gradient: Gradient, _backup_gradient: Gradient,
) -> V { ) -> V {
let fill: Fill = fill.into(); let fill: Fill = fill.into();
@@ -182,7 +182,7 @@ impl IntoF64Vec for f64 {
vec![self] vec![self]
} }
} }
impl IntoF64Vec for Table<f64> { impl IntoF64Vec for List<f64> {
fn into_vec(self) -> Vec<f64> { fn into_vec(self) -> Vec<f64> {
self.into_iter().map(|row| row.into_element()).collect() self.into_iter().map(|row| row.into_element()).collect()
} }
@@ -198,11 +198,11 @@ impl IntoF64Vec for String {
async fn stroke<V, L: IntoF64Vec>( async fn stroke<V, L: IntoF64Vec>(
_: impl Ctx, _: impl Ctx,
/// The content with vector paths to apply the stroke style to. /// The content with vector paths to apply the stroke style to.
#[implementations(Table<Vector>, Table<Vector>, Table<Vector>, Table<Graphic>, Table<Graphic>, Table<Graphic>)] #[implementations(List<Vector>, List<Vector>, List<Vector>, List<Graphic>, List<Graphic>, List<Graphic>)]
mut content: Table<V>, mut content: List<V>,
/// The stroke color. /// The stroke color.
#[default(Color::BLACK)] #[default(Color::BLACK)]
color: Table<Color>, color: List<Color>,
/// The stroke thickness. /// The stroke thickness.
#[unit(" px")] #[unit(" px")]
#[default(2.)] #[default(2.)]
@@ -220,14 +220,14 @@ async fn stroke<V, L: IntoF64Vec>(
/// The order to paint the stroke on top of the fill, or the fill on top of the stroke. /// The order to paint the stroke on top of the fill, or the fill on top of the stroke.
paint_order: PaintOrder, paint_order: PaintOrder,
/// The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed. /// The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.
#[implementations(Table<f64>, f64, String, Table<f64>, f64, String)] #[implementations(List<f64>, f64, String, List<f64>, f64, String)]
dash_lengths: L, dash_lengths: L,
/// The phase offset distance from the starting point of the dash pattern. /// The phase offset distance from the starting point of the dash pattern.
#[unit(" px")] #[unit(" px")]
dash_offset: f64, dash_offset: f64,
) -> Table<V> ) -> List<V>
where where
Table<V>: VectorTableIterMut + 'n + Send, List<V>: VectorListIterMut + 'n + Send,
{ {
let dash_lengths = dash_lengths.into_vec().into_iter().map(|length| length.max(0.)).collect(); let dash_lengths = dash_lengths.into_vec().into_iter().map(|length| length.max(0.)).collect();
@@ -256,11 +256,11 @@ where
#[node_macro::node(name("Copy to Points"), category("Repeat"), path(core_types::vector))] #[node_macro::node(name("Copy to Points"), category("Repeat"), path(core_types::vector))]
async fn copy_to_points<I: 'n + Send + Clone>( async fn copy_to_points<I: 'n + Send + Clone>(
_: impl Ctx, _: impl Ctx,
points: Table<Vector>, points: List<Vector>,
/// Artwork to be copied and placed at each point. /// Artwork to be copied and placed at each point.
#[expose] #[expose]
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>, Table<GradientStops>)] #[implementations(List<Graphic>, List<Vector>, List<Raster<CPU>>, List<Color>, List<GradientStops>)]
content: Table<I>, content: List<I>,
/// Minimum range of randomized sizes given to each placed copy. /// Minimum range of randomized sizes given to each placed copy.
#[default(1)] #[default(1)]
#[range((0., 2.))] #[range((0., 2.))]
@@ -281,8 +281,8 @@ async fn copy_to_points<I: 'n + Send + Clone>(
random_rotation: Angle, random_rotation: Angle,
/// Seed to determine unique variations on all the randomized copy angles. /// Seed to determine unique variations on all the randomized copy angles.
random_rotation_seed: SeedValue, random_rotation_seed: SeedValue,
) -> Table<I> { ) -> List<I> {
let mut result_table = Table::new(); let mut result_list = List::new();
let random_scale_difference = random_scale_max - random_scale_min; let random_scale_difference = random_scale_max - random_scale_min;
@@ -325,18 +325,18 @@ async fn copy_to_points<I: 'n + Send + Clone>(
let row_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); let row_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
row.set_attribute(ATTR_TRANSFORM, transform * row_transform); row.set_attribute(ATTR_TRANSFORM, transform * row_transform);
result_table.push(row); result_list.push(row);
} }
} }
} }
result_table result_list
} }
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))] #[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
async fn round_corners( async fn round_corners(
_: impl Ctx, _: impl Ctx,
source: Table<Vector>, source: List<Vector>,
#[hard_min(0.)] #[hard_min(0.)]
#[default(10.)] #[default(10.)]
radius: PixelLength, radius: PixelLength,
@@ -351,7 +351,7 @@ async fn round_corners(
#[hard_max(180.)] #[hard_max(180.)]
#[default(5.)] #[default(5.)]
min_angle_threshold: Angle, min_angle_threshold: Angle,
) -> Table<Vector> { ) -> List<Vector> {
(0..source.len()) (0..source.len())
.map(|index| { .map(|index| {
let source_transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM, index); let source_transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM, index);
@@ -450,12 +450,12 @@ async fn round_corners(
#[node_macro::node(name("Merge by Distance"), category("Vector: Modifier"), path(core_types::vector))] #[node_macro::node(name("Merge by Distance"), category("Vector: Modifier"), path(core_types::vector))]
pub fn merge_by_distance( pub fn merge_by_distance(
_: impl Ctx, _: impl Ctx,
content: Table<Vector>, content: List<Vector>,
#[default(0.1)] #[default(0.1)]
#[hard_min(0.0001)] #[hard_min(0.0001)]
distance: PixelLength, distance: PixelLength,
algorithm: MergeByDistanceAlgorithm, algorithm: MergeByDistanceAlgorithm,
) -> Table<Vector> { ) -> List<Vector> {
match algorithm { match algorithm {
MergeByDistanceAlgorithm::Spatial => content MergeByDistanceAlgorithm::Spatial => content
.into_iter() .into_iter()
@@ -673,7 +673,7 @@ pub mod extrude_algorithms {
} }
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))] #[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
async fn extrude(_: impl Ctx, mut source: Table<Vector>, direction: DVec2, joining_algorithm: ExtrudeJoiningAlgorithm) -> Table<Vector> { async fn extrude(_: impl Ctx, mut source: List<Vector>, direction: DVec2, joining_algorithm: ExtrudeJoiningAlgorithm) -> List<Vector> {
for vector in source.iter_element_values_mut() { for vector in source.iter_element_values_mut() {
extrude_algorithms::extrude(vector, direction, joining_algorithm); extrude_algorithms::extrude(vector, direction, joining_algorithm);
} }
@@ -681,7 +681,7 @@ async fn extrude(_: impl Ctx, mut source: Table<Vector>, direction: DVec2, joini
} }
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))] #[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
async fn box_warp(_: impl Ctx, content: Table<Vector>, #[expose] rectangle: Table<Vector>) -> Table<Vector> { async fn box_warp(_: impl Ctx, content: List<Vector>, #[expose] rectangle: List<Vector>) -> List<Vector> {
let Some(target) = rectangle.element(0).cloned() else { return content }; let Some(target) = rectangle.element(0).cloned() else { return content };
let target_transform: DAffine2 = rectangle.attribute_cloned_or_default(ATTR_TRANSFORM, 0); let target_transform: DAffine2 = rectangle.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
@@ -746,7 +746,7 @@ async fn box_warp(_: impl Ctx, content: Table<Vector>, #[expose] rectangle: Tabl
result.style.set_stroke_transform(DAffine2::IDENTITY); result.style.set_stroke_transform(DAffine2::IDENTITY);
// Add this to the `Table` and reset the transform since we've applied it directly to the points // Add this to the `List` and reset the transform since we've applied it directly to the points
*row.element_mut() = result; *row.element_mut() = result;
row.set_attribute(ATTR_TRANSFORM, DAffine2::IDENTITY); row.set_attribute(ATTR_TRANSFORM, DAffine2::IDENTITY);
row row
@@ -769,12 +769,12 @@ fn bilinear_interpolate(t: DVec2, quad: &[DVec2; 4]) -> DVec2 {
async fn pack_strips<T: 'n + Send + Clone>( async fn pack_strips<T: 'n + Send + Clone>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Table<Graphic>, List<Graphic>,
Table<Vector>, List<Vector>,
Table<Raster<CPU>>, List<Raster<CPU>>,
Table<Raster<GPU>>, List<Raster<GPU>>,
)] )]
elements: Table<T>, elements: List<T>,
#[default(0.)] #[default(0.)]
#[unit(" px")] #[unit(" px")]
separation: f64, separation: f64,
@@ -782,10 +782,10 @@ async fn pack_strips<T: 'n + Send + Clone>(
#[unit(" px")] #[unit(" px")]
strip_max_length: f64, strip_max_length: f64,
strip_direction: RowsOrColumns, strip_direction: RowsOrColumns,
) -> Table<T> ) -> List<T>
where where
Graphic: From<Table<T>>, Graphic: From<List<T>>,
Table<T>: BoundingBox, List<T>: BoundingBox,
{ {
// Packs shapes using bounds with Best-Fit Decreasing Height (BFDH) algorithm: // Packs shapes using bounds with Best-Fit Decreasing Height (BFDH) algorithm:
// - Sort shapes by cross-axis size (tallest first for rows, widest first for columns) // - Sort shapes by cross-axis size (tallest first for rows, widest first for columns)
@@ -802,8 +802,8 @@ where
let mut items: Vec<(f64, f64, DVec2, Item<T>)> = elements let mut items: Vec<(f64, f64, DVec2, Item<T>)> = elements
.into_iter() .into_iter()
.map(|row| { .map(|row| {
// Single-item `Table` to query its bounding box // Single-item `List` to query its bounding box
let single = Table::new_from_item(row.clone()); let single = List::new_from_item(row.clone());
let (w, h, top_left) = match single.bounding_box(DAffine2::IDENTITY, false) { let (w, h, top_left) = match single.bounding_box(DAffine2::IDENTITY, false) {
RenderBoundingBox::Rectangle([min, max]) => { RenderBoundingBox::Rectangle([min, max]) => {
let size = max - min; let size = max - min;
@@ -822,7 +822,7 @@ where
// Sort by cross-axis size, largest first // Sort by cross-axis size, largest first
items.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(Ordering::Equal)); items.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(Ordering::Equal));
let mut result = Table::new(); let mut result = List::new();
let mut strips: Vec<Strip> = Vec::new(); let mut strips: Vec<Strip> = Vec::new();
// This looks n^2 but it is just n*k where k is the number of strips, which is generally much smaller than n // This looks n^2 but it is just n*k where k is the number of strips, which is generally much smaller than n
@@ -889,7 +889,7 @@ where
#[node_macro::node(category("Vector: Modifier"), name("Auto-Tangents"), path(core_types::vector))] #[node_macro::node(category("Vector: Modifier"), name("Auto-Tangents"), path(core_types::vector))]
async fn auto_tangents( async fn auto_tangents(
_: impl Ctx, _: impl Ctx,
source: Table<Vector>, source: List<Vector>,
/// The amount of spread for the auto-tangents, from 0 (sharp corner) to 1 (full spread). /// The amount of spread for the auto-tangents, from 0 (sharp corner) to 1 (full spread).
#[default(0.5)] #[default(0.5)]
// TODO: Make this a soft range to allow any value to be typed in outside the slider range of 0 to 1 // TODO: Make this a soft range to allow any value to be typed in outside the slider range of 0 to 1
@@ -898,7 +898,7 @@ async fn auto_tangents(
/// If active, existing non-zero handles won't be affected. /// If active, existing non-zero handles won't be affected.
#[default(true)] #[default(true)]
preserve_existing: bool, preserve_existing: bool,
) -> Table<Vector> { ) -> List<Vector> {
(0..source.len()) (0..source.len())
.map(|index| { .map(|index| {
let transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM, index); let transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM, index);
@@ -1041,7 +1041,7 @@ async fn auto_tangents(
} }
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))] #[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
async fn bounding_box(_: impl Ctx, content: Table<Vector>) -> Table<Vector> { async fn bounding_box(_: impl Ctx, content: List<Vector>) -> List<Vector> {
content content
.into_iter() .into_iter()
.map(|mut row| { .map(|mut row| {
@@ -1066,7 +1066,7 @@ async fn bounding_box(_: impl Ctx, content: Table<Vector>) -> Table<Vector> {
} }
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))] #[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
async fn dimensions(_: impl Ctx, content: Table<Vector>) -> DVec2 { async fn dimensions(_: impl Ctx, content: List<Vector>) -> DVec2 {
(0..content.len()) (0..content.len())
.filter_map(|index| content.element(index).unwrap().bounding_box_with_transform(content.attribute_cloned_or_default(ATTR_TRANSFORM, index))) .filter_map(|index| content.element(index).unwrap().bounding_box_with_transform(content.attribute_cloned_or_default(ATTR_TRANSFORM, index)))
.reduce(|[acc_top_left, acc_bottom_right], [top_left, bottom_right]| [acc_top_left.min(top_left), acc_bottom_right.max(bottom_right)]) .reduce(|[acc_top_left, acc_bottom_right], [top_left, bottom_right]| [acc_top_left.min(top_left), acc_bottom_right.max(bottom_right)])
@@ -1079,16 +1079,16 @@ async fn dimensions(_: impl Ctx, content: Table<Vector>) -> DVec2 {
/// ///
/// This is useful in conjunction with nodes that repeat it, followed by the "Points to Polyline" node to string together a path of the points. /// This is useful in conjunction with nodes that repeat it, followed by the "Points to Polyline" node to string together a path of the points.
#[node_macro::node(category("Vector"), name("Vec2 to Point"), path(core_types::vector))] #[node_macro::node(category("Vector"), name("Vec2 to Point"), path(core_types::vector))]
async fn vec2_to_point(_: impl Ctx, vec2: DVec2) -> Table<Vector> { async fn vec2_to_point(_: impl Ctx, vec2: DVec2) -> List<Vector> {
let mut point_domain = PointDomain::new(); let mut point_domain = PointDomain::new();
point_domain.push(PointId::generate(), vec2); point_domain.push(PointId::generate(), vec2);
Table::new_from_item(Item::new_from_element(Vector { point_domain, ..Default::default() })) List::new_from_item(Item::new_from_element(Vector { point_domain, ..Default::default() }))
} }
/// Creates a polyline from a series of vector points, replacing any existing segments and regions that may already exist. /// Creates a polyline from a series of vector points, replacing any existing segments and regions that may already exist.
#[node_macro::node(category("Vector"), name("Points to Polyline"), path(core_types::vector))] #[node_macro::node(category("Vector"), name("Points to Polyline"), path(core_types::vector))]
async fn points_to_polyline(_: impl Ctx, mut points: Table<Vector>, #[default(true)] closed: bool) -> Table<Vector> { async fn points_to_polyline(_: impl Ctx, mut points: List<Vector>, #[default(true)] closed: bool) -> List<Vector> {
for vector in points.iter_element_values_mut() { for vector in points.iter_element_values_mut() {
let mut segment_domain = SegmentDomain::new(); let mut segment_domain = SegmentDomain::new();
let mut next_id = SegmentId::ZERO; let mut next_id = SegmentId::ZERO;
@@ -1116,7 +1116,7 @@ async fn points_to_polyline(_: impl Ctx, mut points: Table<Vector>, #[default(tr
} }
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector), properties("offset_path_properties"))] #[node_macro::node(category("Vector: Modifier"), path(core_types::vector), properties("offset_path_properties"))]
async fn offset_path(_: impl Ctx, content: Table<Vector>, distance: f64, join: StrokeJoin, #[default(4.)] miter_limit: f64) -> Table<Vector> { async fn offset_path(_: impl Ctx, content: List<Vector>, distance: f64, join: StrokeJoin, #[default(4.)] miter_limit: f64) -> List<Vector> {
content content
.into_iter() .into_iter()
.map(|mut row| { .map(|mut row| {
@@ -1160,13 +1160,13 @@ async fn offset_path(_: impl Ctx, content: Table<Vector>, distance: f64, join: S
} }
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))] #[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
async fn solidify_stroke<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Vector>)] content: T) -> Table<Vector> { async fn solidify_stroke<T: IntoGraphicList + 'n + Send + Clone>(_: impl Ctx, #[implementations(List<Graphic>, List<Vector>)] content: T) -> List<Vector> {
// TODO: Make this node support stroke align, which it currently ignores // TODO: Make this node support stroke align, which it currently ignores
let graphic_table = content.into_graphic_table(); let graphic_list = content.into_graphic_list();
let flattened: Table<Vector> = graphic_table.clone().into_flattened_table(); let flattened: List<Vector> = graphic_list.clone().into_flattened_list();
let mut output: Table<Vector> = flattened let mut output: List<Vector> = flattened
.into_iter() .into_iter()
.flat_map(|row| { .flat_map(|row| {
let (mut vector, attributes) = row.into_parts(); let (mut vector, attributes) = row.into_parts();
@@ -1227,7 +1227,7 @@ async fn solidify_stroke<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #
let stroke_row = Item::from_parts(solidified_stroke, attributes); let stroke_row = Item::from_parts(solidified_stroke, attributes);
// Ordering based on the paint order. The first item in the `Table` is rendered below the second. // Ordering based on the paint order. The first item in the `List` is rendered below the second.
match paint_order { match paint_order {
PaintOrder::StrokeAbove => fill_row.into_iter().chain(std::iter::once(stroke_row)).collect::<Vec<_>>(), PaintOrder::StrokeAbove => fill_row.into_iter().chain(std::iter::once(stroke_row)).collect::<Vec<_>>(),
PaintOrder::StrokeBelow => std::iter::once(stroke_row).chain(fill_row).collect::<Vec<_>>(), PaintOrder::StrokeBelow => std::iter::once(stroke_row).chain(fill_row).collect::<Vec<_>>(),
@@ -1241,23 +1241,23 @@ async fn solidify_stroke<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #
// Row 0 carries a composed transform inherited from the flattened input, but the merged_layers // Row 0 carries a composed transform inherited from the flattened input, but the merged_layers
// already holds the original transforms; pre-compensate by row 0's inverse so the renderer's // already holds the original transforms; pre-compensate by row 0's inverse so the renderer's
// `upstream_footprint *= row_0_transform` recursion cancels out and leaves the originals intact. // `upstream_footprint *= row_0_transform` recursion cancels out and leaves the originals intact.
let mut graphic_table = graphic_table; let mut graphic_list = graphic_list;
let row_0_transform: DAffine2 = output.attribute_cloned_or_default(ATTR_TRANSFORM, 0); let row_0_transform: DAffine2 = output.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
if row_0_transform.matrix2.determinant().abs() > f64::EPSILON { if row_0_transform.matrix2.determinant().abs() > f64::EPSILON {
let inverse = row_0_transform.inverse(); let inverse = row_0_transform.inverse();
for transform in graphic_table.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) { for transform in graphic_list.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
*transform = inverse * *transform; *transform = inverse * *transform;
} }
} }
output.set_attribute(ATTR_EDITOR_MERGED_LAYERS, 0, graphic_table); output.set_attribute(ATTR_EDITOR_MERGED_LAYERS, 0, graphic_list);
} }
output output
} }
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))] #[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
async fn separate_subpaths(_: impl Ctx, content: Table<Vector>) -> Table<Vector> { async fn separate_subpaths(_: impl Ctx, content: List<Vector>) -> List<Vector> {
content content
.into_iter() .into_iter()
.flat_map(|row| { .flat_map(|row| {
@@ -1283,7 +1283,7 @@ async fn separate_subpaths(_: impl Ctx, content: Table<Vector>) -> Table<Vector>
async fn path_is_closed( async fn path_is_closed(
_: impl Ctx, _: impl Ctx,
/// The vector content whose subpaths are inspected. /// The vector content whose subpaths are inspected.
content: Table<Vector>, content: List<Vector>,
/// The index of the subpath to check, counting across subpaths in all vector elements. /// The index of the subpath to check, counting across subpaths in all vector elements.
index: f64, index: f64,
) -> bool { ) -> bool {
@@ -1295,7 +1295,7 @@ async fn path_is_closed(
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn map_points(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: Table<Vector>, mapped: impl Node<Context<'static>, Output = DVec2>) -> Table<Vector> { async fn map_points(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: List<Vector>, mapped: impl Node<Context<'static>, Output = DVec2>) -> List<Vector> {
let mut content = content; let mut content = content;
let mut index = 0; let mut index = 0;
@@ -1313,18 +1313,18 @@ async fn map_points(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: Table<Ve
// TODO: Rename to "Combine Paths" and make this happen per-element instead of flattening every element into a single path. The migration for this should then become a Flatten Vector -> Combine Paths pair of nodes. // TODO: Rename to "Combine Paths" and make this happen per-element instead of flattening every element into a single path. The migration for this should then become a Flatten Vector -> Combine Paths pair of nodes.
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
pub async fn flatten_path<T: IntoGraphicTable + 'n + Send>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Vector>)] content: T) -> Table<Vector> { pub async fn flatten_path<T: IntoGraphicList + 'n + Send>(_: impl Ctx, #[implementations(List<Graphic>, List<Vector>)] content: T) -> List<Vector> {
let graphic_table = content.into_graphic_table(); let graphic_list = content.into_graphic_list();
let flattened = graphic_table.clone().into_flattened_table::<Vector>(); let flattened = graphic_list.clone().into_flattened_list::<Vector>();
// Create a `Table` with one empty `Vector` element, then get a mutable reference to it which we append flattened subpaths to // Create a `List` with one empty `Vector` element, then get a mutable reference to it which we append flattened subpaths to
let mut output_table = Table::new_from_element(Vector::default()); let mut output_list = List::new_from_element(Vector::default());
let output = output_table.element_mut(0).unwrap(); let output = output_list.element_mut(0).unwrap();
// 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 layer_path: Table<NodeId> = flattened.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index); let layer_path: List<NodeId> = flattened.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index);
let node_id = layer_path.iter_element_values().next_back().map(|node_id| node_id.0).unwrap_or_default(); let node_id = layer_path.iter_element_values().next_back().map(|node_id| node_id.0).unwrap_or_default();
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
@@ -1338,26 +1338,26 @@ pub async fn flatten_path<T: IntoGraphicTable + 'n + Send>(_: impl Ctx, #[implem
output.style = element.style.clone(); output.style = element.style.clone();
} }
// Preserve a reference to the original upstream `Table<Graphic>` so the renderer can recurse into it // Preserve a reference to the original upstream `List<Graphic>` 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_table.set_attribute(ATTR_EDITOR_MERGED_LAYERS, 0, graphic_table); output_list.set_attribute(ATTR_EDITOR_MERGED_LAYERS, 0, graphic_list);
// Adopt the last input item's layer so the editor can also bucket clicks under a contributing child layer // Adopt the last input item'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 layer_path: Table<NodeId> = flattened.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, primary); let layer_path: List<NodeId> = flattened.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, primary);
output_table.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer_path); output_list.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer_path);
} }
output_table output_list
} }
/// Convert vector geometry into a polyline composed of evenly spaced points. /// Convert vector geometry into a polyline composed of evenly spaced points.
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector), properties("sample_polyline_properties"), memoize)] #[node_macro::node(category("Vector: Modifier"), path(core_types::vector), properties("sample_polyline_properties"), memoize)]
async fn sample_polyline( async fn sample_polyline(
_: impl Ctx, _: impl Ctx,
content: Table<Vector>, content: List<Vector>,
spacing: PointSpacingType, spacing: PointSpacingType,
#[default(100.)] #[default(100.)]
#[hard_min(0.)] #[hard_min(0.)]
@@ -1373,7 +1373,7 @@ async fn sample_polyline(
#[unit(" px")] #[unit(" px")]
stop_offset: f64, stop_offset: f64,
adaptive_spacing: bool, adaptive_spacing: bool,
) -> Table<Vector> { ) -> List<Vector> {
let pathseg_perimeter = |segment: PathSeg| { let pathseg_perimeter = |segment: PathSeg| {
if is_linear(segment) { if is_linear(segment) {
Line::new(segment.start(), segment.end()).perimeter(DEFAULT_ACCURACY) Line::new(segment.start(), segment.end()).perimeter(DEFAULT_ACCURACY)
@@ -1444,12 +1444,12 @@ async fn sample_polyline(
async fn simplify( async fn simplify(
_: impl Ctx, _: impl Ctx,
/// The vector paths to simplify. /// The vector paths to simplify.
content: Table<Vector>, content: List<Vector>,
/// The maximum distance the simplified path may deviate from the original. /// The maximum distance the simplified path may deviate from the original.
#[default(5.)] #[default(5.)]
#[unit(" px")] #[unit(" px")]
tolerance: Length, tolerance: Length,
) -> Table<Vector> { ) -> List<Vector> {
if tolerance <= 0. { if tolerance <= 0. {
return content; return content;
} }
@@ -1488,12 +1488,12 @@ async fn simplify(
async fn decimate( async fn decimate(
_: impl Ctx, _: impl Ctx,
/// The vector paths to decimate. /// The vector paths to decimate.
content: Table<Vector>, content: List<Vector>,
/// The maximum distance a point can deviate from the simplified path before it is kept. /// The maximum distance a point can deviate from the simplified path before it is kept.
#[default(5.)] #[default(5.)]
#[unit(" px")] #[unit(" px")]
tolerance: Length, tolerance: Length,
) -> Table<Vector> { ) -> List<Vector> {
// Tolerance of 0 means no simplification is possible, so return immediately // Tolerance of 0 means no simplification is possible, so return immediately
if tolerance <= 0. { if tolerance <= 0. {
return content; return content;
@@ -1616,14 +1616,14 @@ async fn decimate(
async fn cut_path( async fn cut_path(
_: impl Ctx, _: impl Ctx,
/// The path to insert a cut into. /// The path to insert a cut into.
mut content: Table<Vector>, mut content: List<Vector>,
/// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on. /// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on.
progression: Progression, progression: Progression,
/// Swap the direction of the path. /// Swap the direction of the path.
reverse: bool, reverse: bool,
/// Traverse the path using each segment's Bézier curve parameterization instead of the Euclidean distance. Faster to compute but doesn't respect actual distances. /// Traverse the path using each segment's Bézier curve parameterization instead of the Euclidean distance. Faster to compute but doesn't respect actual distances.
parameterized_distance: bool, parameterized_distance: bool,
) -> Table<Vector> { ) -> List<Vector> {
let euclidian = !parameterized_distance; let euclidian = !parameterized_distance;
let bezpaths = content let bezpaths = content
@@ -1664,7 +1664,7 @@ async fn cut_path(
/// Cuts path segments into separate disconnected pieces where each is a distinct subpath. /// Cuts path segments into separate disconnected pieces where each is a distinct subpath.
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))] #[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
async fn cut_segments(_: impl Ctx, mut content: Table<Vector>) -> Table<Vector> { async fn cut_segments(_: impl Ctx, mut content: List<Vector>) -> List<Vector> {
// Iterate through every segment and make a copy of each of its endpoints, then reassign each segment's endpoints to its own unique point copy // Iterate through every segment and make a copy of each of its endpoints, then reassign each segment's endpoints to its own unique point copy
for vector in content.iter_element_values_mut() { for vector in content.iter_element_values_mut() {
let points_count = vector.point_domain.ids().len(); let points_count = vector.point_domain.ids().len();
@@ -1726,7 +1726,7 @@ async fn cut_segments(_: impl Ctx, mut content: Table<Vector>) -> Table<Vector>
async fn position_on_path( async fn position_on_path(
_: impl Ctx, _: impl Ctx,
/// The path to traverse. /// The path to traverse.
content: Table<Vector>, content: List<Vector>,
/// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on. /// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on.
progression: Progression, progression: Progression,
/// Swap the direction of the path. /// Swap the direction of the path.
@@ -1764,7 +1764,7 @@ async fn position_on_path(
async fn tangent_on_path( async fn tangent_on_path(
_: impl Ctx, _: impl Ctx,
/// The path to traverse. /// The path to traverse.
content: Table<Vector>, content: List<Vector>,
/// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on. /// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on.
progression: Progression, progression: Progression,
/// Swap the direction of the path. /// Swap the direction of the path.
@@ -1811,14 +1811,14 @@ async fn tangent_on_path(
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector), memoize)] #[node_macro::node(category("Vector: Modifier"), path(core_types::vector), memoize)]
async fn scatter_points( async fn scatter_points(
_: impl Ctx, _: impl Ctx,
content: Table<Vector>, content: List<Vector>,
#[unit(" px")] #[unit(" px")]
#[default(10.)] #[default(10.)]
#[hard_min(0.01)] #[hard_min(0.01)]
#[range((1., 100.))] #[range((1., 100.))]
separation: f64, separation: f64,
seed: SeedValue, seed: SeedValue,
) -> Table<Vector> { ) -> List<Vector> {
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
content content
@@ -1858,7 +1858,7 @@ async fn scatter_points(
} }
#[node_macro::node(name("Spline"), category("Vector: Modifier"), path(core_types::vector))] #[node_macro::node(name("Spline"), category("Vector: Modifier"), path(core_types::vector))]
async fn spline(_: impl Ctx, content: Table<Vector>) -> Table<Vector> { async fn spline(_: impl Ctx, content: List<Vector>) -> List<Vector> {
content content
.into_iter() .into_iter()
.filter_map(|mut row| { .filter_map(|mut row| {
@@ -1961,7 +1961,7 @@ fn apply_point_deltas(element: &mut Vector, deltas: &[DVec2], transform: DAffine
async fn jitter_points( async fn jitter_points(
_: impl Ctx, _: impl Ctx,
/// The vector geometry with points to be jittered. /// The vector geometry with points to be jittered.
content: Table<Vector>, content: List<Vector>,
/// The maximum extent of the random distance each point can be offset. /// The maximum extent of the random distance each point can be offset.
#[default(5.)] #[default(5.)]
#[unit(" px")] #[unit(" px")]
@@ -1971,7 +1971,7 @@ async fn jitter_points(
/// Whether to offset anchor points along their normal direction (perpendicular to the path) or in a random direction. Free-floating and branching points have no normal direction, so they receive a random-angled offset regardless of this setting. /// Whether to offset anchor points along their normal direction (perpendicular to the path) or in a random direction. Free-floating and branching points have no normal direction, so they receive a random-angled offset regardless of this setting.
#[default(true)] #[default(true)]
along_normals: bool, along_normals: bool,
) -> Table<Vector> { ) -> List<Vector> {
content content
.into_iter() .into_iter()
.map(|mut row| { .map(|mut row| {
@@ -2011,12 +2011,12 @@ async fn jitter_points(
async fn offset_points( async fn offset_points(
_: impl Ctx, _: impl Ctx,
/// The vector geometry with points to be offset. /// The vector geometry with points to be offset.
content: Table<Vector>, content: List<Vector>,
/// The distance to offset each anchor point along its normal. Positive values move outward, negative values move inward. /// The distance to offset each anchor point along its normal. Positive values move outward, negative values move inward.
#[default(10.)] #[default(10.)]
#[unit(" px")] #[unit(" px")]
distance: f64, distance: f64,
) -> Table<Vector> { ) -> List<Vector> {
content content
.into_iter() .into_iter()
.map(|mut row| { .map(|mut row| {
@@ -2045,10 +2045,10 @@ async fn offset_points(
/// ///
/// *Progression* morphs through all objects. Interpolation is linear unless *Path* geometry is provided to control the trajectory between key objects. The **Origins to Polyline** node may be used to create a path with anchor points corresponding to each object. Other nodes can modify its path segments. /// *Progression* morphs through all objects. Interpolation is linear unless *Path* geometry is provided to control the trajectory between key objects. The **Origins to Polyline** node may be used to create a path with anchor points corresponding to each object. Other nodes can modify its path segments.
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))] #[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
async fn morph<I: IntoGraphicTable + 'n + Send + Clone>( async fn morph<I: IntoGraphicList + 'n + Send + Clone>(
_: impl Ctx, _: impl Ctx,
/// The vector objects to interpolate between. Mixed graphic content is deeply flattened to keep only vector elements. /// The vector objects to interpolate between. Mixed graphic content is deeply flattened to keep only vector elements.
#[implementations(Table<Graphic>, Table<Vector>)] #[implementations(List<Graphic>, List<Vector>)]
content: I, content: I,
/// The fractional part `[0, 1)` traverses the morph uniformly along the path. If the control path has multiple subpaths, each added integer selects the next subpath. /// The fractional part `[0, 1)` traverses the morph uniformly along the path. If the control path has multiple subpaths, each added integer selects the next subpath.
progression: Progression, progression: Progression,
@@ -2059,8 +2059,8 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
/// "Objects" morphs through each group element at an equal rate. "Distances" keeps constant speed with time between objects proportional to their distances. "Angles" keeps constant rotational speed. "Sizes" keeps constant shrink/growth speed. "Slants" keeps constant shearing angle speed. /// "Objects" morphs through each group element at an equal rate. "Distances" keeps constant speed with time between objects proportional to their distances. "Angles" keeps constant rotational speed. "Sizes" keeps constant shrink/growth speed. "Slants" keeps constant shearing angle speed.
distribution: InterpolationDistribution, distribution: InterpolationDistribution,
/// An optional control path whose anchor points correspond to each object. Curved segments between points will shape the morph trajectory instead of traveling straight. If there is a break between path segments, the separate subpaths are selected by index from the integer part of the progression value. For example, `[1, 2)` morphs along the segments of the second subpath, and so on. /// An optional control path whose anchor points correspond to each object. Curved segments between points will shape the morph trajectory instead of traveling straight. If there is a break between path segments, the separate subpaths are selected by index from the integer part of the progression value. For example, `[1, 2)` morphs along the segments of the second subpath, and so on.
path: Table<Vector>, path: List<Vector>,
) -> Table<Vector> { ) -> List<Vector> {
/// Promotes a segment's handle pair to cubic-equivalent Bézier control points. /// Promotes a segment's handle pair to cubic-equivalent Bézier control points.
/// For linear segments (both None), handles are placed at their respective anchors (zero-length) /// For linear segments (both None), handles are placed at their respective anchors (zero-length)
/// so that interpolation against another zero-length cubic doesn't introduce unwanted curvature. /// so that interpolation against another zero-length cubic doesn't introduce unwanted curvature.
@@ -2158,11 +2158,11 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
} }
} }
// Preserve original `Table<Graphic>` as upstream data so this group layer's nested layers can be edited by the tools. // Preserve original `List<Graphic>` as upstream data so this group layer's nested layers can be edited by the tools.
let mut graphic_table_content = content.clone().into_graphic_table(); let mut graphic_list_content = content.clone().into_graphic_list();
// If the input isn't a Table<Vector>, we convert it into one by flattening any Table<Graphic> content. // If the input isn't a List<Vector>, we convert it into one by flattening any List<Graphic> content.
let content = content.into_flattened_table::<Vector>(); let content = content.into_flattened_list::<Vector>();
// Not enough elements to interpolate between, so we return the input as-is // Not enough elements to interpolate between, so we return the input as-is
if content.len() <= 1 { if content.len() <= 1 {
@@ -2398,7 +2398,7 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
// in which case we skip pre-compensation to avoid propagating NaN through merged_layers 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>(ATTR_TRANSFORM) { for transform in graphic_list_content.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
*transform = lerped_inverse * *transform; *transform = lerped_inverse * *transform;
} }
} }
@@ -2411,9 +2411,9 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
let mut attributes = content.clone_item_attributes(endpoint_index); let mut attributes = content.clone_item_attributes(endpoint_index);
attributes.insert(ATTR_TRANSFORM, lerped_transform); attributes.insert(ATTR_TRANSFORM, lerped_transform);
attributes.insert(ATTR_EDITOR_MERGED_LAYERS, graphic_table_content); attributes.insert(ATTR_EDITOR_MERGED_LAYERS, graphic_list_content);
return Table::new_from_item(Item::from_parts(endpoint_element.clone(), attributes)); return List::new_from_item(Item::from_parts(endpoint_element.clone(), attributes));
} }
let mut vector = Vector { let mut vector = Vector {
@@ -2567,9 +2567,9 @@ 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 layer_path: Table<NodeId> = content.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, primary_index); let layer_path: List<NodeId> = content.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, primary_index);
Table::new_from_item( List::new_from_item(
Item::new_from_element(vector) Item::new_from_element(vector)
.with_attribute(ATTR_TRANSFORM, lerped_transform) .with_attribute(ATTR_TRANSFORM, lerped_transform)
.with_attribute(ATTR_BLEND_MODE, lerped_blend_mode) .with_attribute(ATTR_BLEND_MODE, lerped_blend_mode)
@@ -2577,7 +2577,7 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
.with_attribute(ATTR_OPACITY_FILL, lerped_fill) .with_attribute(ATTR_OPACITY_FILL, lerped_fill)
.with_attribute(ATTR_CLIPPING_MASK, lerped_clip) .with_attribute(ATTR_CLIPPING_MASK, lerped_clip)
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer_path) .with_attribute(ATTR_EDITOR_LAYER_PATH, layer_path)
.with_attribute(ATTR_EDITOR_MERGED_LAYERS, graphic_table_content), .with_attribute(ATTR_EDITOR_MERGED_LAYERS, graphic_list_content),
) )
} }
@@ -2852,7 +2852,7 @@ fn bevel_algorithm(mut vector: Vector, transform: DAffine2, distance: f64) -> Ve
} }
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))] #[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
fn bevel(_: impl Ctx, source: Table<Vector>, #[default(10.)] distance: Length) -> Table<Vector> { fn bevel(_: impl Ctx, source: List<Vector>, #[default(10.)] distance: Length) -> List<Vector> {
source source
.into_iter() .into_iter()
.map(|row| { .map(|row| {
@@ -2865,7 +2865,7 @@ fn bevel(_: impl Ctx, source: Table<Vector>, #[default(10.)] distance: Length) -
} }
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))] #[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
fn close_path(_: impl Ctx, source: Table<Vector>) -> Table<Vector> { fn close_path(_: impl Ctx, source: List<Vector>) -> List<Vector> {
source source
.into_iter() .into_iter()
.map(|mut row| { .map(|mut row| {
@@ -2876,7 +2876,7 @@ fn close_path(_: impl Ctx, source: Table<Vector>) -> Table<Vector> {
} }
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))] #[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
fn point_inside(_: impl Ctx, source: Table<Vector>, point: DVec2) -> bool { fn point_inside(_: impl Ctx, source: List<Vector>, point: DVec2) -> bool {
source.into_iter().any(|row| { source.into_iter().any(|row| {
let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
row.element().check_point_inside_shape(transform, point) row.element().check_point_inside_shape(transform, point)
@@ -2886,22 +2886,22 @@ fn point_inside(_: impl Ctx, source: Table<Vector>, point: DVec2) -> bool {
// TODO: Return u32, u64, or usize instead of f64 after #1621 is resolved and has allowed us to implement automatic type conversion in the node graph for nodes with generic type inputs. // TODO: Return u32, u64, or usize instead of f64 after #1621 is resolved and has allowed us to implement automatic type conversion in the node graph for nodes with generic type inputs.
// TODO: (Currently automatic type conversion only works for concrete types, via the Graphene preprocessor and not the full Graphene type system.) // TODO: (Currently automatic type conversion only works for concrete types, via the Graphene preprocessor and not the full Graphene type system.)
#[node_macro::node(category("General"), path(graphene_core::vector))] #[node_macro::node(category("General"), path(graphene_core::vector))]
async fn count_elements(_: impl Ctx, content: TableDyn) -> f64 { async fn count_elements(_: impl Ctx, content: ListDyn) -> f64 {
content.len() as f64 content.len() as f64
} }
#[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))] #[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))]
async fn count_points(_: impl Ctx, content: Table<Vector>) -> f64 { async fn count_points(_: impl Ctx, content: List<Vector>) -> f64 {
content.iter_element_values().map(|vector| vector.point_domain.positions().len() as f64).sum() content.iter_element_values().map(|vector| vector.point_domain.positions().len() as f64).sum()
} }
/// Retrieves the vec2 position (in local space) of the anchor point at the specified index in a `Table` of vector elements. /// Retrieves the vec2 position (in local space) of the anchor point at the specified index in a `List` of vector elements.
/// If no value exists at that index, the position (0, 0) is returned. /// If no value exists at that index, the position (0, 0) is returned.
#[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))] #[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))]
async fn index_points( async fn index_points(
_: impl Ctx, _: impl Ctx,
/// The vector element or elements containing the anchor points to be retrieved. /// The vector element or elements containing the anchor points to be retrieved.
content: Table<Vector>, content: List<Vector>,
/// The index of the points to retrieve, starting from 0 for the first point. Negative indices count backwards from the end, starting from -1 for the last item. /// The index of the points to retrieve, starting from 0 for the first point. Negative indices count backwards from the end, starting from -1 for the last item.
index: f64, index: f64,
) -> DVec2 { ) -> DVec2 {
@@ -2932,7 +2932,7 @@ async fn index_points(
} }
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))] #[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
async fn path_length(_: impl Ctx, source: Table<Vector>) -> f64 { async fn path_length(_: impl Ctx, source: List<Vector>) -> f64 {
(0..source.len()) (0..source.len())
.map(|index| { .map(|index| {
let transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM, index); let transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM, index);
@@ -2951,7 +2951,7 @@ async fn path_length(_: impl Ctx, source: Table<Vector>) -> f64 {
} }
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))] #[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: impl Node<Context<'static>, Output = Table<Vector>>) -> f64 { async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: impl Node<Context<'static>, Output = List<Vector>>) -> f64 {
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context(); let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
let vector = content.eval(new_ctx).await; let vector = content.eval(new_ctx).await;
@@ -2965,7 +2965,7 @@ async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: impl Node<Cont
} }
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))] #[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: impl Node<Context<'static>, Output = Table<Vector>>, centroid_type: CentroidType) -> DVec2 { async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: impl Node<Context<'static>, Output = List<Vector>>, centroid_type: CentroidType) -> DVec2 {
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context(); let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
let vector = content.eval(new_ctx).await; let vector = content.eval(new_ctx).await;
@@ -3042,8 +3042,8 @@ mod test {
} }
} }
fn vector_node_from_bezpath(bezpath: BezPath) -> Table<Vector> { fn vector_node_from_bezpath(bezpath: BezPath) -> List<Vector> {
Table::new_from_element(Vector::from_bezpath(bezpath)) List::new_from_element(Vector::from_bezpath(bezpath))
} }
fn create_vector_item(bezpath: BezPath, transform: DAffine2) -> Item<Vector> { fn create_vector_item(bezpath: BezPath, transform: DAffine2) -> Item<Vector> {
@@ -3070,7 +3070,7 @@ mod test {
// Test a rectangular path with non-zero rotation // Test a rectangular path with non-zero rotation
let square = Vector::from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY)); let square = Vector::from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY));
let mut square = Table::new_from_element(square); let mut square = List::new_from_element(square);
square.with_attribute_mut_or_default(ATTR_TRANSFORM, 0, |t: &mut DAffine2| *t *= DAffine2::from_angle(std::f64::consts::FRAC_PI_4)); square.with_attribute_mut_or_default(ATTR_TRANSFORM, 0, |t: &mut DAffine2| *t *= DAffine2::from_angle(std::f64::consts::FRAC_PI_4));
let bounding_box = BoundingBoxNode { content: FutureWrapperNode(square) }.eval(Footprint::default()).await; let bounding_box = BoundingBoxNode { content: FutureWrapperNode(square) }.eval(Footprint::default()).await;
let bounding_box = bounding_box.element(0).unwrap(); let bounding_box = bounding_box.element(0).unwrap();
@@ -3156,9 +3156,9 @@ mod test {
let bezpath = Rect::new(100., 100., 201., 201.).to_path(DEFAULT_ACCURACY); let bezpath = Rect::new(100., 100., 201., 201.).to_path(DEFAULT_ACCURACY);
let transform = DAffine2::from_scale(DVec2::new(2., 2.)); let transform = DAffine2::from_scale(DVec2::new(2., 2.));
let row = create_vector_item(bezpath, transform); let row = create_vector_item(bezpath, transform);
let table = (0..5).map(|_| row.clone()).collect::<Table<Vector>>(); let list = (0..5).map(|_| row.clone()).collect::<List<Vector>>();
let length = super::path_length(Footprint::default(), table).await; let length = super::path_length(Footprint::default(), list).await;
// 101 (each rectangle edge length) * 4 (rectangle perimeter) * 2 (scale) * 5 (number of rows) // 101 (each rectangle edge length) * 4 (rectangle perimeter) * 2 (scale) * 5 (number of rows)
assert_eq!(length, 101. * 4. * 2. * 5.); assert_eq!(length, 101. * 4. * 2. * 5.);
@@ -3177,7 +3177,7 @@ mod test {
*second_rectangle.attribute_mut_or_insert_default::<DAffine2>(ATTR_TRANSFORM) *= DAffine2::from_translation((-100., -100.).into()); *second_rectangle.attribute_mut_or_insert_default::<DAffine2>(ATTR_TRANSFORM) *= DAffine2::from_translation((-100., -100.).into());
rectangles.push(second_rectangle); rectangles.push(second_rectangle);
let morphed = super::morph(Footprint::default(), rectangles, 0.5, false, InterpolationDistribution::default(), Table::default()).await; let morphed = super::morph(Footprint::default(), rectangles, 0.5, false, InterpolationDistribution::default(), List::default()).await;
let morphed_element = morphed.element(0).unwrap(); let morphed_element = morphed.element(0).unwrap();
// Geometry stays in local space (original rectangle coordinates) // Geometry stays in local space (original rectangle coordinates)
assert_eq!( assert_eq!(
@@ -3259,11 +3259,11 @@ mod test {
source.push(curve.as_path_el()); source.push(curve.as_path_el());
let vector = Vector::from_bezpath(source); let vector = Vector::from_bezpath(source);
let mut vector_table = Table::new_from_element(vector.clone()); let mut vector_list = List::new_from_element(vector.clone());
vector_table.set_attribute(ATTR_TRANSFORM, 0, DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.))); vector_list.set_attribute(ATTR_TRANSFORM, 0, DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.)));
let beveled = super::bevel((), Table::new_from_element(vector), 2_f64.sqrt() * 10.); let beveled = super::bevel((), List::new_from_element(vector), 2_f64.sqrt() * 10.);
let beveled = beveled.element(0).unwrap(); let beveled = beveled.element(0).unwrap();
assert_eq!(beveled.point_domain.positions().len(), 4); assert_eq!(beveled.point_domain.positions().len(), 4);
@@ -3310,8 +3310,8 @@ mod test {
let subpath = BezPath::from_path_segments([line, point, curve].into_iter()); let subpath = BezPath::from_path_segments([line, point, curve].into_iter());
let beveled_table = super::bevel(Footprint::default(), vector_node_from_bezpath(subpath), 5.); let beveled_list = super::bevel(Footprint::default(), vector_node_from_bezpath(subpath), 5.);
let beveled = beveled_table.element(0).unwrap(); let beveled = beveled_list.element(0).unwrap();
assert_eq!(beveled.point_domain.positions().len(), 6); assert_eq!(beveled.point_domain.positions().len(), 6);
assert_eq!(beveled.segment_domain.ids().len(), 5); assert_eq!(beveled.segment_domain.ids().len(), 5);
@@ -189,7 +189,7 @@ It is now becoming time to delve into the next phase of making the node graph mo
- Lambdas: treating a node as a piece of data given to another node, so it can be run in a loop with varying parameters in each iteration. - Lambdas: treating a node as a piece of data given to another node, so it can be run in a loop with varying parameters in each iteration.
- Instances: generalizing graphical data, transforms, and groups so that every layer is one or multiple instances, each with a unique transform. This will finally fix the long-lived limitation of layers lacking a proper pivot point. - Instances: generalizing graphical data, transforms, and groups so that every layer is one or multiple instances, each with a unique transform. This will finally fix the long-lived limitation of layers lacking a proper pivot point.
- Tables: representing lists of data like vector points and segments in a spreadsheet. Formalizing the tabular data representation lets the node engine benefit from ECS-like performance gains by optimizing CPU cache utilization. - Lists: representing lists of data like vector points and segments in a spreadsheet. Formalizing the tabular data representation lets the node engine benefit from ECS-like performance gains by optimizing CPU cache utilization.
- Attributes: encoding properties (of points, of segments, of instances, of appearance styles, etc.) in columns on the tabular data. This will unlock Graphite to become as powerful as Blender geometry nodes which works based on the same design principle. - Attributes: encoding properties (of points, of segments, of instances, of appearance styles, etc.) in columns on the tabular data. This will unlock Graphite to become as powerful as Blender geometry nodes which works based on the same design principle.
### Raster graphics editing ### Raster graphics editing
+2 -2
View File
@@ -120,7 +120,7 @@ Marrying vector and raster under one roof enables both art forms to complement e
</div> </div>
<div class="feature-icon complete" title="Development Complete"> <div class="feature-icon complete" title="Development Complete">
<img class="atlas" style="--atlas-index: 63" src="https://static.graphite.art/icons/icon-atlas-roadmap__5.png" alt="" /> <img class="atlas" style="--atlas-index: 63" src="https://static.graphite.art/icons/icon-atlas-roadmap__5.png" alt="" />
<span>Table-based graphical data format</span> <span>List-based graphical data format</span>
</div> </div>
<div class="feature-icon complete" title="Development Complete"> <div class="feature-icon complete" title="Development Complete">
<img class="atlas" style="--atlas-index: 67" src="https://static.graphite.art/icons/icon-atlas-roadmap__5.png" alt="" /> <img class="atlas" style="--atlas-index: 67" src="https://static.graphite.art/icons/icon-atlas-roadmap__5.png" alt="" />
@@ -148,7 +148,7 @@ Marrying vector and raster under one roof enables both art forms to complement e
</div> </div>
<div class="feature-icon ongoing" title="Development Ongoing"> <div class="feature-icon ongoing" title="Development Ongoing">
<img class="atlas" style="--atlas-index: 9" src="https://static.graphite.art/icons/icon-atlas-roadmap__5.png" alt="" /> <img class="atlas" style="--atlas-index: 9" src="https://static.graphite.art/icons/icon-atlas-roadmap__5.png" alt="" />
<span>Custom attributes for table data</span> <span>Custom attributes for list data</span>
</div> </div>
<div class="feature-icon ongoing" title="Development Ongoing"> <div class="feature-icon ongoing" title="Development Ongoing">
<img class="atlas" style="--atlas-index: 17" src="https://static.graphite.art/icons/icon-atlas-roadmap__5.png" alt="" /> <img class="atlas" style="--atlas-index: 17" src="https://static.graphite.art/icons/icon-atlas-roadmap__5.png" alt="" />