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

View File

@@ -220,7 +220,7 @@ pub enum DocumentNodeMetadata {
impl DocumentNodeMetadata {
pub fn ty(&self) -> Type {
match self {
DocumentNodeMetadata::DocumentNodePath => concrete!(core_types::table::Table<NodeId>),
DocumentNodeMetadata::DocumentNodePath => concrete!(core_types::list::List<NodeId>),
}
}
}

View File

@@ -2,7 +2,7 @@ use super::DocumentNode;
use crate::application_io::PlatformEditorApi;
use crate::proto::{Any as DAny, FutureAny};
use brush_nodes::brush_stroke::BrushStroke;
use core_types::table::Table;
use core_types::list::List;
use core_types::transform::Footprint;
use core_types::uuid::NodeId;
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).
macro_rules! for_each_type_default {
($action:ident) => {
$action!(Table<Graphic>);
$action!(Table<Artboard>);
$action!(Table<Raster<CPU>>);
$action!(Table<Vector>);
$action!(Table<String>);
$action!(List<Graphic>);
$action!(List<Artboard>);
$action!(List<Raster<CPU>>);
$action!(List<Vector>);
$action!(List<String>);
$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.
/// Example: `TaggedValue::TypeDefault(descriptor!(String))` stores the type `String` but no specific string value.
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(alias = "F64Table", alias = "VecF64", alias = "VecF32", alias = "F64Array4")]
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(alias = "ColorTable", alias = "OptionalColor", alias = "ColorNotInTable")]
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`.)
#[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")]
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(alias = "BrushStrokeTable")]
BrushStrokes(Vec<BrushStroke>),
@@ -78,7 +78,7 @@ macro_rules! tagged_value {
// =======================
#[serde(skip)]
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)]
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))`.
@@ -142,17 +142,17 @@ macro_rules! tagged_value {
Self::from_type_or_none(&Type::Concrete(td)).to_dynany()
}
Self::F64Array(values) => {
let table: Table<f64> = values.into_iter().map(core_types::table::Item::new_from_element).collect();
Box::new(table)
let list: List<f64> = values.into_iter().map(core_types::list::Item::new_from_element).collect();
Box::new(list)
}
Self::Color(color) => {
let table: Table<Color> = color.into_iter().map(core_types::table::Item::new_from_element).collect();
Box::new(table)
let list: List<Color> = color.into_iter().map(core_types::list::Item::new_from_element).collect();
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) => {
let table: Table<BrushStroke> = strokes.into_iter().map(core_types::table::Item::new_from_element).collect();
Box::new(table)
let list: List<BrushStroke> = strokes.into_iter().map(core_types::list::Item::new_from_element).collect();
Box::new(list)
}
// =======================
// AUTO-GENERATED VARIANTS
@@ -163,8 +163,8 @@ macro_rules! tagged_value {
// =======================
Self::RenderOutput(x) => Box::new(x),
Self::NodeIdPath(path) => {
let table: Table<NodeId> = path.into_iter().map(core_types::table::Item::new_from_element).collect();
Box::new(table)
let list: List<NodeId> = path.into_iter().map(core_types::list::Item::new_from_element).collect();
Box::new(list)
}
Self::DocumentNode(node) => Box::new(node),
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::F64Array(values) => {
let table: Table<f64> = values.into_iter().map(core_types::table::Item::new_from_element).collect();
Arc::new(table)
let list: List<f64> = values.into_iter().map(core_types::list::Item::new_from_element).collect();
Arc::new(list)
}
Self::Color(color) => {
let table: Table<Color> = color.into_iter().map(core_types::table::Item::new_from_element).collect();
Arc::new(table)
let list: List<Color> = color.into_iter().map(core_types::list::Item::new_from_element).collect();
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) => {
let table: Table<BrushStroke> = strokes.into_iter().map(core_types::table::Item::new_from_element).collect();
Arc::new(table)
let list: List<BrushStroke> = strokes.into_iter().map(core_types::list::Item::new_from_element).collect();
Arc::new(list)
}
// =======================
// AUTO-GENERATED VARIANTS
@@ -212,8 +212,8 @@ macro_rules! tagged_value {
// =======================
Self::RenderOutput(x) => Arc::new(x),
Self::NodeIdPath(path) => {
let table: Table<NodeId> = path.into_iter().map(core_types::table::Item::new_from_element).collect();
Arc::new(table)
let list: List<NodeId> = path.into_iter().map(core_types::list::Item::new_from_element).collect();
Arc::new(list)
}
Self::DocumentNode(node) => Arc::new(node),
Self::ContextFeatures(features) => Arc::new(features),
@@ -229,10 +229,10 @@ macro_rules! tagged_value {
// ===============
Self::None => concrete!(()),
Self::TypeDefault(td) => Type::Concrete(td.clone()),
Self::F64Array(_) => concrete!(Table<f64>),
Self::Color(_) => concrete!(Table<Color>),
Self::Gradient(_) => concrete!(Table<GradientStops>),
Self::BrushStrokes(_) => concrete!(Table<BrushStroke>),
Self::F64Array(_) => concrete!(List<f64>),
Self::Color(_) => concrete!(List<Color>),
Self::Gradient(_) => concrete!(List<GradientStops>),
Self::BrushStrokes(_) => concrete!(List<BrushStroke>),
// =======================
// AUTO-GENERATED VARIANTS
// =======================
@@ -241,7 +241,7 @@ macro_rules! tagged_value {
// NON-SERIALIZED VARIANTS
// =======================
Self::RenderOutput(_) => concrete!(RenderOutput),
Self::NodeIdPath(_) => concrete!(Table<NodeId>),
Self::NodeIdPath(_) => concrete!(List<NodeId>),
Self::DocumentNode(_) => concrete!(DocumentNode),
Self::ContextFeatures(_) => concrete!(ContextFeatures),
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
// 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) }
// Table-wrapped types need a single-item default with the element's default, not an empty table
if name == std::any::type_name::<Table<Color>>() { return Some(TaggedValue::Color(Some(Color::default()))) }
if name == std::any::type_name::<Table<GradientStops>>() { return Some(TaggedValue::Gradient(GradientStops::default())) }
// List-wrapped types need a single-item default with the element's default, not an empty list
if name == std::any::type_name::<List<Color>>() { return Some(TaggedValue::Color(Some(Color::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::<Table<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<f64>>() { return Some(TaggedValue::F64Array(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.
macro_rules! check {
($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::<DVec2>() => to_dvec2(string).map(TaggedValue::DVec2)?,
() 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::<Table<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<Color>>() => to_color(string).map(|color| TaggedValue::Color(Some(color)))?,
() 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::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?,
_ => 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:
///
/// - `BrushCache` → `TaggedValue::None` (purely runtime cache; no payload to preserve)
/// - `Graphic` (or alias `GraphicGroup`/`Group`) → `TaggedValue::TypeDefault(descriptor!(Table<Graphic>))`
/// - `Artboard` (or alias `ArtboardGroup`) → `TaggedValue::TypeDefault(descriptor!(Table<Artboard>))`
/// - `Graphic` (or alias `GraphicGroup`/`Group`) → `TaggedValue::TypeDefault(descriptor!(List<Graphic>))`
/// - `Artboard` (or alias `ArtboardGroup`) → `TaggedValue::TypeDefault(descriptor!(List<Artboard>))`
/// - `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>>)`
/// - empty → `TaggedValue::TypeDefault(descriptor!(Table<Raster<CPU>>))`
/// - empty → `TaggedValue::TypeDefault(descriptor!(List<Raster<CPU>>))`
/// - `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)
/// - 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`.
// 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() {
"BrushCache" => return Ok(MemoHash::new(TaggedValue::None)),
"Graphic" | "GraphicGroup" | "Group" => return Ok(MemoHash::new(TaggedValue::TypeDefault(descriptor!(Table<Graphic>)))),
"Artboard" | "ArtboardGroup" => return Ok(MemoHash::new(TaggedValue::TypeDefault(descriptor!(Table<Artboard>)))),
"Graphic" | "GraphicGroup" | "Group" => return Ok(MemoHash::new(TaggedValue::TypeDefault(descriptor!(List<Graphic>)))),
"Artboard" | "ArtboardGroup" => return Ok(MemoHash::new(TaggedValue::TypeDefault(descriptor!(List<Artboard>)))),
"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());
if let Some(image_value) = first_element {
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::TypeDefault(descriptor!(Table<Raster<CPU>>))));
return Ok(MemoHash::new(TaggedValue::TypeDefault(descriptor!(List<Raster<CPU>>))));
}
"Vector" | "VectorData" => {
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));
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>`.
// Disambiguate by payload shape: a Gradient struct has `start`/`end` keys; a `GradientStops` has none of those (it has `position`/`midpoint`/`color`).

View File

@@ -8,6 +8,7 @@ use graphene_std::any::DynAnyNode;
use graphene_std::application_io::ImageTexture;
use graphene_std::brush::brush_stroke::BrushStroke;
use graphene_std::gradient::GradientStops;
use graphene_std::list::{AttributeDyn, AttributeValueDyn, List, ListDyn};
#[cfg(target_family = "wasm")]
use graphene_std::platform_application_io::canvas_utils::CanvasHandle;
#[cfg(feature = "gpu")]
@@ -16,7 +17,6 @@ use graphene_std::raster::color::Color;
use graphene_std::raster::*;
use graphene_std::raster::{CPU, Raster};
use graphene_std::render_node::RenderIntermediate;
use graphene_std::table::{AttributeDyn, AttributeValueDyn, Table, TableDyn};
use graphene_std::transform::Footprint;
use graphene_std::uuid::NodeId;
use graphene_std::vector::Vector;
@@ -32,47 +32,47 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
// ==========
// INTO NODES
// ==========
into_node!(from: Table<Graphic>, to: Table<Graphic>),
into_node!(from: Table<Vector>, to: Table<Vector>),
into_node!(from: Table<Raster<CPU>>, to: Table<Raster<CPU>>),
into_node!(from: List<Graphic>, to: List<Graphic>),
into_node!(from: List<Vector>, to: List<Vector>),
into_node!(from: List<Raster<CPU>>, to: List<Raster<CPU>>),
#[cfg(feature = "gpu")]
into_node!(from: Table<Raster<GPU>>, to: Table<Raster<GPU>>),
convert_node!(from: Table<Vector>, to: Table<Graphic>),
convert_node!(from: Table<Raster<CPU>>, to: Table<Graphic>),
into_node!(from: List<Raster<GPU>>, to: List<Raster<GPU>>),
convert_node!(from: List<Vector>, to: List<Graphic>),
convert_node!(from: List<Raster<CPU>>, to: List<Graphic>),
#[cfg(feature = "gpu")]
convert_node!(from: Table<Raster<GPU>>, to: Table<Graphic>),
// Type-erased attribute column conversions for the `Attach Attribute` node, so it monomorphizes only over the destination table type.
convert_node!(from: Table<Artboard>, to: AttributeDyn),
convert_node!(from: Table<Graphic>, to: AttributeDyn),
convert_node!(from: Table<Vector>, to: AttributeDyn),
convert_node!(from: Table<Raster<CPU>>, to: AttributeDyn),
convert_node!(from: Table<Color>, to: AttributeDyn),
convert_node!(from: Table<GradientStops>, to: AttributeDyn),
convert_node!(from: Table<f64>, to: AttributeDyn),
convert_node!(from: Table<bool>, to: AttributeDyn),
convert_node!(from: Table<String>, to: AttributeDyn),
convert_node!(from: Table<DAffine2>, to: AttributeDyn),
convert_node!(from: Table<BlendMode>, to: AttributeDyn),
convert_node!(from: Table<graphene_std::vector::style::GradientType>, to: AttributeDyn),
convert_node!(from: Table<graphene_std::vector::style::GradientSpreadMethod>, to: AttributeDyn),
convert_node!(from: Table<Artboard>, to: TableDyn),
convert_node!(from: Table<Graphic>, to: TableDyn),
convert_node!(from: Table<Vector>, to: TableDyn),
convert_node!(from: Table<Raster<CPU>>, to: TableDyn),
convert_node!(from: List<Raster<GPU>>, to: List<Graphic>),
// Type-erased attribute conversions for the `Attach Attribute` node, so it monomorphizes only over the destination `List` type.
convert_node!(from: List<Artboard>, to: AttributeDyn),
convert_node!(from: List<Graphic>, to: AttributeDyn),
convert_node!(from: List<Vector>, to: AttributeDyn),
convert_node!(from: List<Raster<CPU>>, to: AttributeDyn),
convert_node!(from: List<Color>, to: AttributeDyn),
convert_node!(from: List<GradientStops>, to: AttributeDyn),
convert_node!(from: List<f64>, to: AttributeDyn),
convert_node!(from: List<bool>, to: AttributeDyn),
convert_node!(from: List<String>, to: AttributeDyn),
convert_node!(from: List<DAffine2>, to: AttributeDyn),
convert_node!(from: List<BlendMode>, to: AttributeDyn),
convert_node!(from: List<graphene_std::vector::style::GradientType>, to: AttributeDyn),
convert_node!(from: List<graphene_std::vector::style::GradientSpreadMethod>, to: AttributeDyn),
convert_node!(from: List<Artboard>, to: ListDyn),
convert_node!(from: List<Graphic>, to: ListDyn),
convert_node!(from: List<Vector>, to: ListDyn),
convert_node!(from: List<Raster<CPU>>, to: ListDyn),
#[cfg(feature = "gpu")]
convert_node!(from: Table<Raster<GPU>>, to: TableDyn),
convert_node!(from: Table<Color>, to: TableDyn),
convert_node!(from: Table<GradientStops>, to: TableDyn),
convert_node!(from: Table<f64>, to: TableDyn),
convert_node!(from: Table<bool>, to: TableDyn),
convert_node!(from: Table<String>, to: TableDyn),
convert_node!(from: Table<u8>, to: TableDyn),
convert_node!(from: Table<NodeId>, to: TableDyn),
convert_node!(from: Table<DAffine2>, to: TableDyn),
convert_node!(from: Table<BlendMode>, to: TableDyn),
convert_node!(from: Table<graphene_std::vector::style::GradientType>, to: TableDyn),
convert_node!(from: Table<graphene_std::vector::style::GradientSpreadMethod>, to: TableDyn),
// Type-erased attribute value conversions for the `Write Attribute` node, so it monomorphizes only over the destination table type.
convert_node!(from: List<Raster<GPU>>, to: ListDyn),
convert_node!(from: List<Color>, to: ListDyn),
convert_node!(from: List<GradientStops>, to: ListDyn),
convert_node!(from: List<f64>, to: ListDyn),
convert_node!(from: List<bool>, to: ListDyn),
convert_node!(from: List<String>, to: ListDyn),
convert_node!(from: List<u8>, to: ListDyn),
convert_node!(from: List<NodeId>, to: ListDyn),
convert_node!(from: List<DAffine2>, to: ListDyn),
convert_node!(from: List<BlendMode>, to: ListDyn),
convert_node!(from: List<graphene_std::vector::style::GradientType>, to: ListDyn),
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 `List` type.
convert_node!(from: f64, to: AttributeValueDyn),
convert_node!(from: u32, 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: graphene_std::vector::style::GradientType, to: AttributeValueDyn),
convert_node!(from: graphene_std::vector::style::GradientSpreadMethod, to: AttributeValueDyn),
convert_node!(from: Table<String>, to: AttributeValueDyn),
convert_node!(from: Table<NodeId>, to: AttributeValueDyn),
convert_node!(from: Table<Color>, to: AttributeValueDyn),
convert_node!(from: Table<GradientStops>, to: AttributeValueDyn),
convert_node!(from: Table<Graphic>, to: AttributeValueDyn),
// into_node!(from: Table<Raster<CPU>>, to: Table<Raster<SRGBA8>>),
convert_node!(from: List<String>, to: AttributeValueDyn),
convert_node!(from: List<NodeId>, to: AttributeValueDyn),
convert_node!(from: List<Color>, to: AttributeValueDyn),
convert_node!(from: List<GradientStops>, to: AttributeValueDyn),
convert_node!(from: List<Graphic>, to: AttributeValueDyn),
// into_node!(from: List<Raster<CPU>>, to: List<Raster<SRGBA8>>),
#[cfg(feature = "gpu")]
into_node!(from: &PlatformEditorApi, to: &WgpuExecutor),
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: DAffine2, to: String),
#[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")]
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")]
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")]
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
// =============
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 => Table<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 => Table<Raster<CPU>>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<Artboard>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<Graphic>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<Vector>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<Raster<CPU>>]),
#[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 => Table<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<Raster<GPU>>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<Color>]),
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 => String]),
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::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 => Table<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 => Table<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 => Table<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 => Table<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 => Table<graphene_std::vector::style::GradientSpreadMethod>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<String>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<NodeId>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<f64>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<u8>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<bool>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<DAffine2>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => List<BlendMode>]),
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 => 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 => 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 => 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 => graphene_std::transform::Footprint]),
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 => 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 => 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")]
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 => 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 => Table<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 => Table<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<Artboard>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<Graphic>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<Vector>]),
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 => 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 => Table<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 => Table<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 => Table<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 => Table<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 => Table<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<GradientStops>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<String>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<NodeId>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<f64>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<u8>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<bool>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<DAffine2>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<BlendMode>]),
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 => 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 => 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")]
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => CanvasHandle]),
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 => &PlatformEditorApi]),
#[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<Color>]),
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::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 => 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 => graphene_std::ContextFeatures]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::Footprint]),

View File

@@ -15,7 +15,7 @@ pub trait BoundingBox {
/// 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.
/// 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.
/// 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.

View File

@@ -4,13 +4,13 @@ pub mod bounds;
pub mod consts;
pub mod context;
pub mod generic;
pub mod list;
pub mod math;
pub mod memo;
pub mod misc;
pub mod ops;
pub mod registry;
pub mod render_complexity;
pub mod table;
pub mod transform;
pub mod uuid;
pub mod value;
@@ -23,6 +23,10 @@ pub use ctor;
pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync};
pub use graphene_hash;
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 no_std_types::AsU32;
pub use no_std_types::blending;
@@ -33,10 +37,6 @@ pub use num_traits;
use std::any::TypeId;
use std::future::Future;
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")]
pub use tsify;
pub use types::Cow;

View File

@@ -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).
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.
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
/// the original child layers after their content has been collapsed.
pub const ATTR_EDITOR_MERGED_LAYERS: &str = "editor:merged_layers";
@@ -147,7 +147,7 @@ impl Clone for Box<dyn AnyAttributeValue> {
// 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 {
/// Clones this attribute into a new boxed trait object.
fn clone_box(&self) -> Box<dyn AnyAttribute>;
@@ -224,7 +224,7 @@ impl Clone for Box<dyn AnyAttribute> {
// 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>);
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.
/// 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`).
pub struct AttributeDyn(pub Box<dyn AnyAttribute>);
@@ -439,26 +439,26 @@ unsafe impl StaticType for AttributeValueDyn {
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.
/// Lets a node accept any `Table<U>` source via the auto-inserted `Convert<TableDyn, ()>` 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).
/// 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 `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 `List`).
#[derive(Default)]
pub struct TableDyn {
pub struct ListDyn {
attributes: Vec<(String, Box<dyn AnyAttribute>)>,
len: usize,
}
impl TableDyn {
/// Number of items in the underlying table.
impl ListDyn {
/// Number of items in the underlying `List`.
pub fn len(&self) -> usize {
self.len
}
/// Whether the underlying table has zero items.
/// Whether the underlying `List` has zero items.
pub fn is_empty(&self) -> bool {
self.len == 0
}
@@ -471,16 +471,16 @@ impl TableDyn {
}
}
impl<T> From<Table<T>> for TableDyn {
fn from(table: Table<T>) -> Self {
impl<T> From<List<T>> for ListDyn {
fn from(list: List<T>) -> Self {
Self {
attributes: table.attributes.attributes,
len: table.attributes.len,
attributes: list.attributes.attributes,
len: list.attributes.len,
}
}
}
impl Clone for TableDyn {
impl Clone for ListDyn {
fn clone(&self) -> Self {
Self {
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 {
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 {
self.len == other.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) {
self.len.cache_hash(state);
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;
}
@@ -631,7 +631,7 @@ impl ItemAttributeValues {
/// The storage data structure for attributes.
///
/// 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.
#[derive(Clone, Default)]
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 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
/// index through element/attribute accessor methods, or consumed as owned [`Item`]s via iteration.
#[derive(Clone, Debug)]
pub struct Table<T> {
pub struct List<T> {
element: Vec<T>,
attributes: Attributes,
}
impl<T> Table<T> {
/// Creates an empty table with no items.
impl<T> List<T> {
/// Creates an empty list with no items.
pub fn new() -> Self {
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 {
Self {
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 {
Self {
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 {
let mut attributes = Attributes::new();
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>) {
self.element.push(item.element);
self.attributes.push_item(item.attributes);
}
/// Appends all items from another table into this one.
pub fn extend(&mut self, table: Table<T>) {
self.element.extend(table.element);
self.attributes.extend(table.attributes);
/// Appends all items from another list into this one.
pub fn extend(&mut self, list: List<T>) {
self.element.extend(list.element);
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 {
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 {
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> {
self.attributes.keys()
}
@@ -991,7 +991,7 @@ impl<T> Table<T> {
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) {
let key = key.into();
self.attributes.attributes.retain(|(k, _)| k != &key);
@@ -999,7 +999,7 @@ impl<T> Table<T> {
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.
pub fn set_attribute_value_dyn(&mut self, key: impl Into<String>, index: usize, value: AttributeValueDyn) {
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.
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
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 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 {
let attributes = self.attributes.into_item_vec();
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 {
Self {
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) {
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 {
// Attributes participate in equality so the `a == b` ⇒ `hash(a) == hash(b)` contract holds with `cache_hash`
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.
fn apply_transform(&mut self, modification: &DAffine2) {
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> {
type Static = Table<T::Static>;
unsafe impl<T: StaticTypeSized> StaticType for List<T> {
type Static = List<T::Static>;
}
impl<T> FromIterator<Item<T>> for Table<T> {
/// Collects an iterator of [`Item`]s into a [`Table`], pre-allocating based on the iterator's size hint.
impl<T> FromIterator<Item<T>> for List<T> {
/// 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 {
let iter = iter.into_iter();
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 {
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.
///
/// 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)]
pub struct Item<T> {
element: T,
@@ -1317,9 +1317,9 @@ impl<T> Item<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.
pub struct ItemIter<T> {
element: std::vec::IntoIter<T>,

View File

@@ -77,12 +77,12 @@ pub fn migrate_to_optional_color<'de, D: serde::Deserializer<'de>>(deserializer:
#[cfg_attr(feature = "serde", serde(untagged))]
enum ColorFormat {
OptionalColor(Option<Color>),
Table(LegacyTable<Color>),
List(LegacyTable<Color>),
}
Ok(match ColorFormat::deserialize(deserializer)? {
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))]
enum F64ArrayFormat {
Array(Vec<f64>),
Table(LegacyTable<f64>),
List(LegacyTable<f64>),
}
Ok(match F64ArrayFormat::deserialize(deserializer)? {
F64ArrayFormat::Array(values) => values,
F64ArrayFormat::Table(table) => table.element,
F64ArrayFormat::List(list) => list.element,
})
}

View File

@@ -1,5 +1,5 @@
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 glam::DVec2;
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;
}
impl<U, T: TableConvert<U> + Send> Convert<Table<U>, ()> for Table<T> {
async fn convert(self, _: Footprint, _: ()) -> Table<U> {
let table: Table<U> = self
impl<U, T: ListConvert<U> + Send> Convert<List<U>, ()> for List<T> {
async fn convert(self, _: Footprint, _: ()) -> List<U> {
let list: List<U> = self
.into_iter()
.map(|row| {
let (element, attributes) = row.into_parts();
Item::from_parts(element.convert_row(), attributes)
})
.collect();
table
list
}
}
/// Wraps each row's element into a type-erased column. Lets nodes that accept a source attribute
/// from any `Table<U>` express their signature as `AttributeColumnDyn` and avoid monomorphizing
/// Wraps each row's element into a type-erased attribute. Lets nodes that accept a source attribute
/// 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.
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 {
let values: Vec<T> = self.into_iter().map(|row| row.into_element()).collect();
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
/// (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.
impl<T: Clone + Send + Sync + Default + std::fmt::Debug + PartialEq + CacheHash + 'static> Convert<AttributeValueDyn, ()> for T {
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
/// only need attribute access (such as the `read_attribute_*` family) take a single `TableDyn` input
/// instead of monomorphizing over every possible carrier table type.
impl<T: Send> Convert<TableDyn, ()> for Table<T> {
async fn convert(self, _: Footprint, _: ()) -> TableDyn {
/// 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 `ListDyn` input
/// instead of monomorphizing over every possible carrier list type.
impl<T: Send> Convert<ListDyn, ()> for List<T> {
async fn convert(self, _: Footprint, _: ()) -> ListDyn {
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.
macro_rules! impl_convert {

View File

@@ -1,6 +1,6 @@
// Raster types moved to raster-types crate
use crate::Color;
use crate::table::Table;
use crate::list::List;
pub trait RenderComplexity {
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 {
self.iter_element_values().map(|element| element.render_complexity()).fold(0, usize::saturating_add)
}

View File

@@ -1,44 +1,44 @@
use crate::graphic::Graphic;
use core_types::bounds::{BoundingBox, RenderBoundingBox};
use core_types::graphene_hash::CacheHash;
use core_types::list::List;
use core_types::render_complexity::RenderComplexity;
use core_types::table::Table;
use dyn_any::DynAny;
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
/// enclosing `Table<Artboard>`, not as fields here. This keeps `Artboard` a pure type-system boundary
/// that prevents arbitrary `Table<Table<...<Graphic>>>` nesting.
/// enclosing `List<Artboard>`, not as fields here. This keeps `Artboard` a pure type-system boundary
/// that prevents arbitrary `List<List<...<Graphic>>>` nesting.
#[derive(Clone, Debug, Default, CacheHash, PartialEq, DynAny)]
pub struct Artboard(Table<Graphic>);
pub struct Artboard(List<Graphic>);
impl Artboard {
pub fn new(content: Table<Graphic>) -> Self {
pub fn new(content: List<Graphic>) -> Self {
Self(content)
}
pub fn as_graphic_table(&self) -> &Table<Graphic> {
pub fn as_graphic_list(&self) -> &List<Graphic> {
&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
}
pub fn into_graphic_table(self) -> Table<Graphic> {
pub fn into_graphic_list(self) -> List<Graphic> {
self.0
}
}
impl From<Table<Graphic>> for Artboard {
fn from(content: Table<Graphic>) -> Self {
impl From<List<Graphic>> for Artboard {
fn from(content: List<Graphic>) -> Self {
Self(content)
}
}
impl From<Artboard> for Table<Graphic> {
impl From<Artboard> for List<Graphic> {
fn from(artboard: Artboard) -> Self {
artboard.0
}

View File

@@ -1,8 +1,8 @@
use core_types::bounds::{BoundingBox, RenderBoundingBox};
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::table::Table;
use core_types::uuid::NodeId;
use core_types::{ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Color};
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.
#[derive(Clone, Debug, CacheHash, PartialEq, DynAny)]
pub enum Graphic {
Graphic(Table<Graphic>),
Vector(Table<Vector>),
RasterCPU(Table<Raster<CPU>>),
RasterGPU(Table<Raster<GPU>>),
Color(Table<Color>),
Gradient(Table<GradientStops>),
Graphic(List<Graphic>),
Vector(List<Vector>),
RasterCPU(List<Raster<CPU>>),
RasterGPU(List<Raster<GPU>>),
Color(List<Color>),
Gradient(List<GradientStops>),
}
impl Default for Graphic {
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
impl From<Table<Graphic>> for Graphic {
fn from(graphic: Table<Graphic>) -> Self {
impl From<List<Graphic>> for Graphic {
fn from(graphic: List<Graphic>) -> Self {
Graphic::Graphic(graphic)
}
}
@@ -62,113 +40,113 @@ impl From<Table<Graphic>> for Graphic {
// Vector
impl From<Vector> for Graphic {
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 {
fn from(vector: Table<Vector>) -> Self {
impl From<List<Vector>> for Graphic {
fn from(vector: List<Vector>) -> Self {
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>
impl From<Raster<CPU>> for Graphic {
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 {
fn from(raster: Table<Raster<CPU>>) -> Self {
impl From<List<Raster<CPU>>> for Graphic {
fn from(raster: List<Raster<CPU>>) -> Self {
Graphic::RasterCPU(raster)
}
}
// Note: Table conversions handled by blanket impl in gcore
// Note: List conversions handled by blanket impl in gcore
// Raster<GPU>
impl From<Raster<GPU>> for Graphic {
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 {
fn from(raster: Table<Raster<GPU>>) -> Self {
impl From<List<Raster<GPU>>> for Graphic {
fn from(raster: List<Raster<GPU>>) -> Self {
Graphic::RasterGPU(raster)
}
}
// Note: Table conversions handled by blanket impl in gcore
// Note: List conversions handled by blanket impl in gcore
// Color
impl From<Color> for Graphic {
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 {
fn from(color: Table<Color>) -> Self {
impl From<List<Color>> for Graphic {
fn from(color: List<Color>) -> Self {
Graphic::Color(color)
}
}
// Note: Table conversions handled by blanket impl in gcore
// Note: Table<Color> -> Option<Color> is in gcore (Color is defined there)
// Note: List conversions handled by blanket impl in gcore
// Note: List<Color> -> Option<Color> is in gcore (Color is defined there)
// GradientStops
impl From<GradientStops> for Graphic {
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 {
fn from(gradient: Table<GradientStops>) -> Self {
impl From<List<GradientStops>> for Graphic {
fn from(gradient: List<GradientStops>) -> Self {
Graphic::Gradient(gradient)
}
}
/// Deeply flattens a `Table<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.
fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) -> Table<T> {
fn flatten_recursive<T>(output: &mut Table<T>, current_graphic_table: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) {
for current_graphic_row in current_graphic_table.into_iter() {
let layer_path: Table<NodeId> = current_graphic_row.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH);
/// 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-`List`s composes transforms and opacity.
fn flatten_graphic_list<T>(content: List<Graphic>, extract_variant: fn(Graphic) -> Option<List<T>>) -> List<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_list.into_iter() {
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_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.);
match current_graphic_row.into_element() {
// Compose the parent's transform, opacity, and fill onto each child row
Graphic::Graphic(mut sub_table) => {
// Identity default means a missing column still composes correctly
for v in sub_table.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
Graphic::Graphic(mut sub_list) => {
// Identity default means a missing attribute still composes correctly
for v in sub_list.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
*v = current_transform * *v;
}
// f64 defaults to 0, but opacity/fill default to 1, so missing columns must be set rather than multiplied
if let Some(values) = sub_table.iter_attribute_values_mut::<f64>(ATTR_OPACITY) {
// f64 defaults to 0, but opacity/fill default to 1, so missing attributes must be set rather than multiplied
if let Some(values) = sub_list.iter_attribute_values_mut::<f64>(ATTR_OPACITY) {
for v in values {
*v *= current_opacity;
}
} 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;
}
}
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 {
*v *= current_fill;
}
} 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;
}
}
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
other => {
if let Some(typed_table) = extract_variant(other) {
for mut item in typed_table.into_iter() {
if let Some(typed_list) = extract_variant(other) {
for mut item in typed_list.into_iter() {
let row_transform: DAffine2 = item.attribute_cloned_or_default(ATTR_TRANSFORM);
let row_opacity: f64 = item.attribute_cloned_or(ATTR_OPACITY, 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);
output
}
/// 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 {
fn try_from_graphic(graphic: Graphic) -> Option<Table<Self>>;
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>>;
}
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 }
}
}
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 }
}
}
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 }
}
}
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 }
}
}
// Local trait to convert types to Table<Graphic> (avoids orphan rule issues)
pub trait IntoGraphicTable {
fn into_graphic_table(self) -> Table<Graphic>;
// Local trait to convert types to List<Graphic> (avoids orphan rule issues)
pub trait IntoGraphicList {
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>`.
fn into_flattened_table<T: TryFromGraphic>(self) -> 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_list<T: TryFromGraphic>(self) -> List<T>
where
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> {
fn into_graphic_table(self) -> Table<Graphic> {
impl IntoGraphicList for List<Graphic> {
fn into_graphic_list(self) -> List<Graphic> {
self
}
}
impl IntoGraphicTable for Table<Vector> {
fn into_graphic_table(self) -> Table<Graphic> {
impl IntoGraphicList for List<Vector> {
fn into_graphic_list(self) -> List<Graphic> {
// 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
let layer_path: Table<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0);
let mut graphic_table = Table::new_from_element(Graphic::Vector(self));
// `flatten_graphic_list` doesn't overwrite the inner Vector's stamp with an empty value
let layer_path: List<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0);
let mut graphic_list = List::new_from_element(Graphic::Vector(self));
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>> {
fn into_graphic_table(self) -> Table<Graphic> {
Table::new_from_element(Graphic::RasterCPU(self))
impl IntoGraphicList for List<Raster<CPU>> {
fn into_graphic_list(self) -> List<Graphic> {
List::new_from_element(Graphic::RasterCPU(self))
}
}
impl IntoGraphicTable for Table<Raster<GPU>> {
fn into_graphic_table(self) -> Table<Graphic> {
Table::new_from_element(Graphic::RasterGPU(self))
impl IntoGraphicList for List<Raster<GPU>> {
fn into_graphic_list(self) -> List<Graphic> {
List::new_from_element(Graphic::RasterGPU(self))
}
}
impl IntoGraphicTable for Table<Color> {
fn into_graphic_table(self) -> Table<Graphic> {
Table::new_from_element(Graphic::Color(self))
impl IntoGraphicList for List<Color> {
fn into_graphic_list(self) -> List<Graphic> {
List::new_from_element(Graphic::Color(self))
}
}
impl IntoGraphicTable for Table<GradientStops> {
fn into_graphic_table(self) -> Table<Graphic> {
Table::new_from_element(Graphic::Gradient(self))
impl IntoGraphicList for List<GradientStops> {
fn into_graphic_list(self) -> List<Graphic> {
List::new_from_element(Graphic::Gradient(self))
}
}
impl IntoGraphicTable for DAffine2 {
fn into_graphic_table(self) -> Table<Graphic> {
Table::new_from_element(Graphic::default())
impl IntoGraphicList for DAffine2 {
fn into_graphic_list(self) -> List<Graphic> {
List::new_from_element(Graphic::default())
}
}
@@ -289,45 +267,45 @@ impl From<DAffine2> for Graphic {
Graphic::default()
}
}
// Note: Table conversions handled by blanket impl in gcore
// Note: List conversions handled by blanket impl in gcore
impl Graphic {
pub fn as_graphic(&self) -> Option<&Table<Graphic>> {
pub fn as_graphic(&self) -> Option<&List<Graphic>> {
match self {
Graphic::Graphic(graphic) => Some(graphic),
_ => 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 {
Graphic::Graphic(graphic) => Some(graphic),
_ => None,
}
}
pub fn as_vector(&self) -> Option<&Table<Vector>> {
pub fn as_vector(&self) -> Option<&List<Vector>> {
match self {
Graphic::Vector(vector) => Some(vector),
_ => 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 {
Graphic::Vector(vector) => Some(vector),
_ => None,
}
}
pub fn as_raster(&self) -> Option<&Table<Raster<CPU>>> {
pub fn as_raster(&self) -> Option<&List<Raster<CPU>>> {
match self {
Graphic::RasterCPU(raster) => Some(raster),
_ => 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 {
Graphic::RasterCPU(raster) => Some(raster),
_ => None,
@@ -335,17 +313,17 @@ impl Graphic {
}
pub fn had_clip_enabled(&self) -> bool {
fn all_clipped<T>(table: &Table<T>) -> bool {
table.iter_attribute_values_or_default::<bool>(ATTR_CLIPPING_MASK).all(|clip| clip)
fn all_clipped<T>(list: &List<T>) -> bool {
list.iter_attribute_values_or_default::<bool>(ATTR_CLIPPING_MASK).all(|clip| clip)
}
match self {
Graphic::Vector(table) => all_clipped(table),
Graphic::Graphic(table) => all_clipped(table),
Graphic::RasterCPU(table) => all_clipped(table),
Graphic::RasterGPU(table) => all_clipped(table),
Graphic::Color(table) => all_clipped(table),
Graphic::Gradient(table) => all_clipped(table),
Graphic::Vector(list) => all_clipped(list),
Graphic::Graphic(list) => all_clipped(list),
Graphic::RasterCPU(list) => all_clipped(list),
Graphic::RasterGPU(list) => all_clipped(list),
Graphic::Color(list) => all_clipped(list),
Graphic::Gradient(list) => all_clipped(list),
}
}
@@ -364,12 +342,12 @@ impl Graphic {
impl BoundingBox for Graphic {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
match self {
Graphic::Vector(table) => table.bounding_box(transform, include_stroke),
Graphic::RasterCPU(table) => table.bounding_box(transform, include_stroke),
Graphic::RasterGPU(table) => table.bounding_box(transform, include_stroke),
Graphic::Graphic(table) => table.bounding_box(transform, include_stroke),
Graphic::Color(table) => table.bounding_box(transform, include_stroke),
Graphic::Gradient(table) => table.bounding_box(transform, include_stroke),
Graphic::Vector(list) => list.bounding_box(transform, include_stroke),
Graphic::RasterCPU(list) => list.bounding_box(transform, include_stroke),
Graphic::RasterGPU(list) => list.bounding_box(transform, include_stroke),
Graphic::Graphic(list) => list.bounding_box(transform, include_stroke),
Graphic::Color(list) => list.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 {
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 {
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 {
Graphic::RasterGPU(Table::new_from_element(self))
Graphic::RasterGPU(List::new_from_element(self))
}
}
impl RenderComplexity for Graphic {
fn render_complexity(&self) -> usize {
match self {
Self::Graphic(table) => table.render_complexity(),
Self::Vector(table) => table.render_complexity(),
Self::RasterCPU(table) => table.render_complexity(),
Self::RasterGPU(table) => table.render_complexity(),
Self::Color(table) => table.render_complexity(),
Self::Gradient(table) => table.render_complexity(),
Self::Graphic(list) => list.render_complexity(),
Self::Vector(list) => list.render_complexity(),
Self::RasterCPU(list) => list.render_complexity(),
Self::RasterGPU(list) => list.render_complexity(),
Self::Color(list) => list.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() }
}
}
impl<T: Clone> AtIndex for Table<T> {
type Output = Table<T>;
impl<T: Clone> AtIndex for List<T> {
type Output = List<T>;
fn at_index(&self, index: usize) -> Option<Self::Output> {
self.clone_item(index).map(|row| {
let mut result_table = Self::default();
result_table.push(row);
result_table
let mut result_list = Self::default();
result_list.push(row);
result_list
})
}
@@ -464,7 +442,7 @@ impl<T: Clone> OmitIndex for Vec<T> {
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 {
let mut result = Self::default();
for i in 0..self.len() {

View File

@@ -8,7 +8,7 @@ pub use vector_types;
// Re-export commonly used types at the crate root
pub use artboard::Artboard;
pub use graphic::{Graphic, IntoGraphicTable, TryFromGraphic, Vector};
pub use graphic::{Graphic, IntoGraphicList, TryFromGraphic, Vector};
pub mod migrations {
use vector_types::vector::{PathStyle, PointDomain, RegionDomain, SegmentDomain, misc::HandleId};
@@ -16,11 +16,11 @@ pub mod migrations {
use crate::Vector;
// 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> {
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)]
struct OldVectorData {
style: PathStyle,
@@ -42,7 +42,7 @@ pub mod migrations {
enum VectorFormat {
Vector(Vector),
OldVectorData(OldVectorData),
Table(LegacyTable),
List(LegacyTable),
}
Ok(match VectorFormat::deserialize(deserializer)? {
@@ -54,7 +54,7 @@ pub mod migrations {
segment_domain: old.segment_domain,
region_domain: old.region_domain,
}),
VectorFormat::Table(table) => table.element.into_iter().next(),
VectorFormat::List(list) => list.element.into_iter().next(),
})
}
}

View File

@@ -5,9 +5,9 @@ use core_types::blending::BlendMode;
use core_types::bounds::BoundingBox;
use core_types::bounds::RenderBoundingBox;
use core_types::color::Color;
use core_types::list::{Item, List};
use core_types::math::quad::Quad;
use core_types::render_complexity::RenderComplexity;
use core_types::table::{Item, Table};
use core_types::transform::Footprint;
use core_types::uuid::{NodeId, generate_uuid};
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.
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>) {
self.add_upstream_click_targets(outlines);
}
@@ -423,23 +423,23 @@ pub trait Render: BoundingBox + RenderComplexity {
impl Render for Graphic {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
match self {
Graphic::Graphic(table) => table.render_svg(render, render_params),
Graphic::Vector(table) => table.render_svg(render, render_params),
Graphic::RasterCPU(table) => table.render_svg(render, render_params),
Graphic::Graphic(list) => list.render_svg(render, render_params),
Graphic::Vector(list) => list.render_svg(render, render_params),
Graphic::RasterCPU(list) => list.render_svg(render, render_params),
Graphic::RasterGPU(_) => (),
Graphic::Color(table) => table.render_svg(render, render_params),
Graphic::Gradient(table) => table.render_svg(render, render_params),
Graphic::Color(list) => list.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) {
match self {
Graphic::Graphic(table) => table.render_to_vello(scene, transform, context, render_params),
Graphic::Vector(table) => table.render_to_vello(scene, transform, context, render_params),
Graphic::RasterCPU(table) => table.render_to_vello(scene, transform, context, render_params),
Graphic::RasterGPU(table) => table.render_to_vello(scene, transform, context, render_params),
Graphic::Color(table) => table.render_to_vello(scene, transform, context, render_params),
Graphic::Gradient(table) => table.render_to_vello(scene, transform, context, render_params),
Graphic::Graphic(list) => list.render_to_vello(scene, transform, context, render_params),
Graphic::Vector(list) => list.render_to_vello(scene, transform, context, render_params),
Graphic::RasterCPU(list) => list.render_to_vello(scene, transform, context, render_params),
Graphic::RasterGPU(list) => list.render_to_vello(scene, transform, context, render_params),
Graphic::Color(list) => list.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(_) => {
metadata.upstream_footprints.insert(element_id, footprint);
}
Graphic::Vector(table) => {
Graphic::Vector(list) => {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than the first item
if !table.is_empty() {
let layer_path: Table<NodeId> = table.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0);
if !list.is_empty() {
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 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.local_transforms.insert(element_id, transform);
}
}
Graphic::RasterCPU(table) => {
Graphic::RasterCPU(list) => {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than the first item
if !table.is_empty() {
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
if !list.is_empty() {
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);
// TODO: Find a way to handle more than the first item
if !table.is_empty() {
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
if !list.is_empty() {
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);
// TODO: Find a way to handle more than the first item
if !table.is_empty() {
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
if !list.is_empty() {
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);
// TODO: Find a way to handle more than the first item
if !table.is_empty() {
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
if !list.is_empty() {
metadata.local_transforms.insert(element_id, list.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
}
}
}
}
match self {
Graphic::Graphic(table) => table.collect_metadata(metadata, footprint, element_id),
Graphic::Vector(table) => table.collect_metadata(metadata, footprint, element_id),
Graphic::RasterCPU(table) => table.collect_metadata(metadata, footprint, element_id),
Graphic::RasterGPU(table) => table.collect_metadata(metadata, footprint, element_id),
Graphic::Color(table) => table.collect_metadata(metadata, footprint, element_id),
Graphic::Gradient(table) => table.collect_metadata(metadata, footprint, element_id),
Graphic::Graphic(list) => list.collect_metadata(metadata, footprint, element_id),
Graphic::Vector(list) => list.collect_metadata(metadata, footprint, element_id),
Graphic::RasterCPU(list) => list.collect_metadata(metadata, footprint, element_id),
Graphic::RasterGPU(list) => list.collect_metadata(metadata, footprint, element_id),
Graphic::Color(list) => list.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>) {
match self {
Graphic::Graphic(table) => table.add_upstream_click_targets(click_targets),
Graphic::Vector(table) => table.add_upstream_click_targets(click_targets),
Graphic::RasterCPU(table) => table.add_upstream_click_targets(click_targets),
Graphic::RasterGPU(table) => table.add_upstream_click_targets(click_targets),
Graphic::Color(table) => table.add_upstream_click_targets(click_targets),
Graphic::Gradient(table) => table.add_upstream_click_targets(click_targets),
Graphic::Graphic(list) => list.add_upstream_click_targets(click_targets),
Graphic::Vector(list) => list.add_upstream_click_targets(click_targets),
Graphic::RasterCPU(list) => list.add_upstream_click_targets(click_targets),
Graphic::RasterGPU(list) => list.add_upstream_click_targets(click_targets),
Graphic::Color(list) => list.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>) {
match self {
Graphic::Graphic(table) => table.add_upstream_outline_targets(outlines),
Graphic::Vector(table) => table.add_upstream_outline_targets(outlines),
Graphic::RasterCPU(table) => table.add_upstream_outline_targets(outlines),
Graphic::RasterGPU(table) => table.add_upstream_outline_targets(outlines),
Graphic::Color(table) => table.add_upstream_outline_targets(outlines),
Graphic::Gradient(table) => table.add_upstream_outline_targets(outlines),
Graphic::Graphic(list) => list.add_upstream_outline_targets(outlines),
Graphic::Vector(list) => list.add_upstream_outline_targets(outlines),
Graphic::RasterCPU(list) => list.add_upstream_outline_targets(outlines),
Graphic::RasterGPU(list) => list.add_upstream_outline_targets(outlines),
Graphic::Color(list) => list.add_upstream_outline_targets(outlines),
Graphic::Gradient(list) => list.add_upstream_outline_targets(outlines),
}
}
fn contains_artboard(&self) -> bool {
match self {
Graphic::Graphic(table) => table.contains_artboard(),
Graphic::Vector(table) => table.contains_artboard(),
Graphic::RasterCPU(table) => table.contains_artboard(),
Graphic::RasterGPU(table) => table.contains_artboard(),
Graphic::Color(table) => table.contains_artboard(),
Graphic::Gradient(table) => table.contains_artboard(),
Graphic::Graphic(list) => list.contains_artboard(),
Graphic::Vector(list) => list.contains_artboard(),
Graphic::RasterCPU(list) => list.contains_artboard(),
Graphic::RasterGPU(list) => list.contains_artboard(),
Graphic::Color(list) => list.contains_artboard(),
Graphic::Gradient(list) => list.contains_artboard(),
}
}
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
match self {
Graphic::Graphic(table) => table.new_ids_from_hash(reference),
Graphic::Vector(table) => table.new_ids_from_hash(reference),
Graphic::Graphic(list) => list.new_ids_from_hash(reference),
Graphic::Vector(list) => list.new_ids_from_hash(reference),
Graphic::RasterCPU(_) => (),
Graphic::RasterGPU(_) => (),
Graphic::Color(_) => (),
@@ -551,19 +551,19 @@ impl Render for Graphic {
}
}
/// Reads the artboard metadata for the item at `index` from a `Table<Artboard>`.
fn read_artboard_attributes(table: &Table<Artboard>, index: usize) -> (DVec2, DVec2, Color, bool) {
let location: DVec2 = table.attribute_cloned_or_default(ATTR_LOCATION, index);
let dimensions: DVec2 = table.attribute_cloned_or_default(ATTR_DIMENSIONS, index);
let background: Color = table.attribute_cloned_or_default(ATTR_BACKGROUND, index);
let clip: bool = table.attribute_cloned_or_default(ATTR_CLIP, index);
/// Reads the artboard metadata for the item at `index` from a `List<Artboard>`.
fn read_artboard_attributes(list: &List<Artboard>, index: usize) -> (DVec2, DVec2, Color, bool) {
let location: DVec2 = list.attribute_cloned_or_default(ATTR_LOCATION, index);
let dimensions: DVec2 = list.attribute_cloned_or_default(ATTR_DIMENSIONS, index);
let background: Color = list.attribute_cloned_or_default(ATTR_BACKGROUND, index);
let clip: bool = list.attribute_cloned_or_default(ATTR_CLIP, index);
(location, dimensions, background, clip)
}
impl Render for Table<Artboard> {
impl Render for List<Artboard> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
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 x = location.x.min(location.x + dimensions.x);
@@ -621,7 +621,7 @@ impl Render for Table<Artboard> {
use vello::peniko;
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 [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>) {
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 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();
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) {
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>) {
for index in 0..self.len() {
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 element = self.element(index).unwrap();
@@ -908,14 +908,14 @@ impl Render for Table<Graphic> {
}
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()) {
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) {
for index in 0..self.len() {
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 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)
});
@@ -1312,7 +1312,7 @@ impl Render for Table<Vector> {
// 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.
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);
// 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 {
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);
do_stroke(scene, 2.);
@@ -1344,7 +1344,7 @@ impl Render for Table<Vector> {
do_fill(scene);
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);
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>) {
// 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]`.
let item_zero_transform: DAffine2 = if !self.is_empty() {
self.attribute_cloned_or_default(ATTR_TRANSFORM, 0)
@@ -1401,7 +1401,7 @@ impl Render for Table<Vector> {
for index in 0..self.len() {
let Some(source) = self.element(index) else { continue };
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();
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,
// Flatten Path, Morph, or any other destructive merge), recurse into that snapshot so the editor can
// surface the original child layers' click targets.
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>(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() {
let mut upstream_footprint = footprint;
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) {
for index in 0..self.len() {
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.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() {
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
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
// area, so the children are already in the coordinate space matching `footprint` here — we must NOT
// multiply in `transform` (which is the rasterization area, not a layer-stack transform).
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>(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() {
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()));
impl Render for Table<Raster<GPU>> {
impl Render for List<Raster<GPU>> {
fn render_svg(&self, _render: &mut SvgRender, _render_params: &RenderParams) {
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.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() {
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
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
// area, so the children are already in the coordinate space matching `footprint` here — we must NOT
// multiply in `transform` (which is the rasterization area, not a layer-stack transform).
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>(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() {
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 Vello, we create a layer with a placeholder transform which we
// 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) {
for (index, color) in self.iter_element_values().enumerate() {
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) {
// 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.
@@ -2017,7 +2017,7 @@ impl Render for Table<GradientStops> {
let mut layer = false;
if opacity < 1. || blend_mode_attr != BlendMode::default() {
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);
layer = true;
}

View File

@@ -539,12 +539,12 @@ pub fn migrate_to_gradient_stops<'de, D: serde::Deserializer<'de>>(deserializer:
#[cfg_attr(feature = "serde", serde(untagged))]
enum GradientStopsFormat {
Stops(GradientStops),
Table(LegacyTable),
List(LegacyTable),
}
Ok(match GradientStopsFormat::deserialize(deserializer)? {
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(),
})
}

View File

@@ -4,7 +4,7 @@ pub use crate::gradient::*;
use core_types::ATTR_OPACITY;
use core_types::Color;
use core_types::color::Alpha;
use core_types::table::Table;
use core_types::list::List;
use core_types::transform::Transform;
use dyn_any::DynAny;
use glam::DAffine2;
@@ -133,16 +133,16 @@ impl From<Option<Color>> for Fill {
}
}
impl From<Table<Color>> for Fill {
fn from(color: Table<Color>) -> Fill {
impl From<List<Color>> for Fill {
fn from(color: List<Color>) -> Fill {
let alpha: f64 = color.attribute_cloned_or(ATTR_OPACITY, 0, 1.);
let color = color.element(0).copied();
Fill::solid_or_none(color.map(|c| c.with_alpha(c.alpha() * alpha as f32)))
}
}
impl From<Table<GradientStops>> for Fill {
fn from(gradient: Table<GradientStops>) -> Fill {
impl From<List<GradientStops>> for Fill {
fn from(gradient: List<GradientStops>) -> Fill {
Fill::Gradient(Gradient {
stops: gradient.element(0).cloned().unwrap_or_default(),
..Default::default()

View File

@@ -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)]
mod tests {

View File

@@ -1,7 +1,7 @@
use crate::WgpuContext;
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::table::{Item, Table};
use futures::lock::Mutex;
use raster_types::{GPU, Raster};
use std::borrow::Cow;
@@ -33,7 +33,7 @@ impl PerPixelAdjustShaderRuntime {
}
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 pipeline = cache
.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());
let device = &context.device;
let name = self.name.as_str();
@@ -236,7 +236,7 @@ impl PerPixelAdjustGraphicsPipeline {
let attributes = textures.clone_item_attributes(index);
Item::from_parts(Raster::new(GPU { texture: tex_out }), attributes)
})
.collect::<Table<_>>();
.collect::<List<_>>();
context.queue.submit([cmd.finish()]);
out
}

View File

@@ -2,8 +2,8 @@ use crate::WgpuExecutor;
use core_types::Color;
use core_types::Ctx;
use core_types::color::SRGBA8;
use core_types::list::{Item, List};
use core_types::ops::Convert;
use core_types::table::{Item, Table};
use core_types::transform::Footprint;
use raster_types::Image;
use raster_types::{CPU, GPU, Raster};
@@ -137,19 +137,19 @@ impl RasterGpuToRasterCpuConverter {
}
}
/// Passthrough conversion for GPU `Table`s - no conversion needed
impl<'i> Convert<Table<Raster<GPU>>, &'i WgpuExecutor> for Table<Raster<GPU>> {
async fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> Table<Raster<GPU>> {
/// Passthrough conversion for GPU `List`s - no conversion needed
impl<'i> Convert<List<Raster<GPU>>, &'i WgpuExecutor> for List<Raster<GPU>> {
async fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> List<Raster<GPU>> {
self
}
}
/// Converts a `Table<Raster<CPU>>` to `Table<Raster<GPU>>` by uploading each image to a texture
impl<'i> Convert<Table<Raster<GPU>>, &'i WgpuExecutor> for Table<Raster<CPU>> {
async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Table<Raster<GPU>> {
/// Converts a `List<Raster<CPU>>` to `List<Raster<GPU>>` by uploading each image to a texture
impl<'i> Convert<List<Raster<GPU>>, &'i WgpuExecutor> for List<Raster<CPU>> {
async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> List<Raster<GPU>> {
let device = &executor.context.device;
let queue = &executor.context.queue;
let table = self
let list = self
.into_iter()
.map(|row| {
let (image, attributes) = row.into_parts();
@@ -160,7 +160,7 @@ impl<'i> Convert<Table<Raster<GPU>>, &'i WgpuExecutor> for Table<Raster<CPU>> {
.collect();
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
impl<'i> Convert<Table<Raster<CPU>>, &'i WgpuExecutor> for Table<Raster<CPU>> {
async fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> Table<Raster<CPU>> {
/// Passthrough conversion for CPU `List`s - no conversion needed
impl<'i> Convert<List<Raster<CPU>>, &'i WgpuExecutor> for List<Raster<CPU>> {
async fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> List<Raster<CPU>> {
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.
impl<'i> Convert<Table<Raster<CPU>>, &'i WgpuExecutor> for Table<Raster<GPU>> {
async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Table<Raster<CPU>> {
/// 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<List<Raster<CPU>>, &'i WgpuExecutor> for List<Raster<GPU>> {
async fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> List<Raster<CPU>> {
let device = &executor.context.device;
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.
///
/// 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(""))]
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,
#[implementations(Table<Raster<CPU>>, Table<Raster<GPU>>)] input: T,
#[implementations(List<Raster<CPU>>, List<Raster<GPU>>)] input: T,
executor: &'a WgpuExecutor,
) -> Table<Raster<GPU>> {
) -> List<Raster<GPU>> {
input.convert(Footprint::DEFAULT, executor).await
}

View File

@@ -1235,7 +1235,7 @@ mod tests {
fn test_node_with_implementations() {
let attr = quote!(category("Raster: Adjustment"));
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...
}
);
@@ -1261,11 +1261,11 @@ mod tests {
where_clause: None,
input: Input {
pat_ident: pat_ident("image"),
ty: parse_quote!(Table<Raster<P>>),
ty: parse_quote!(List<Raster<P>>),
implementations: Punctuated::new(),
context_features: vec![],
},
output_type: parse_quote!(Table<Raster<P>>),
output_type: parse_quote!(List<Raster<P>>),
is_async: false,
fields: vec![ParsedField {
pat_ident: pat_ident("shadows"),
@@ -1377,7 +1377,7 @@ mod tests {
fn test_async_node() {
let attr = quote!(category("IO"));
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...
}
);
@@ -1407,7 +1407,7 @@ mod tests {
implementations: Punctuated::new(),
context_features: vec![],
},
output_type: parse_quote!(Table<Raster<CPU>>),
output_type: parse_quote!(List<Raster<CPU>>),
is_async: true,
fields: vec![ParsedField {
pat_ident: pat_ident("path"),
@@ -1534,7 +1534,7 @@ mod tests {
fn test_invalid_implementation_syntax() {
let attr = quote!(category("Test"));
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...
}
);
@@ -1560,12 +1560,12 @@ mod tests {
#[implementations((), #tuples, Footprint)]
footprint: F,
#[implementations(
() -> Table<Raster<CPU>>,
() -> Table<Color>,
() -> Table<GradientStops>,
Footprint -> Table<Raster<CPU>>,
Footprint -> Table<Color>,
Footprint -> Table<GradientStops>,
() -> List<Raster<CPU>>,
() -> List<Color>,
() -> List<GradientStops>,
Footprint -> List<Raster<CPU>>,
Footprint -> List<Color>,
Footprint -> List<GradientStops>,
)]
image: impl Node<F, Output = T>,
) -> T {

View File

@@ -186,7 +186,7 @@ impl PerPixelAdjustCodegen<'_> {
let wgpu_executor = self.crate_ident.wgpu_executor()?;
// 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
.parsed
.fields

View File

@@ -1,5 +1,5 @@
use core_types::list::List;
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 graphic_types::Graphic;
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) {
if let Some(values) = table.iter_attribute_values_mut::<f64>(key) {
fn multiply_list_attribute<T>(list: &mut List<T>, key: &str, factor: f64) {
if let Some(values) = list.iter_attribute_values_mut::<f64>(key) {
for v in values {
*v *= factor;
}
} 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;
}
}
}
impl MultiplyAlpha for Table<Vector> {
impl MultiplyAlpha for List<Vector> {
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) {
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) {
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) {
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) {
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.))
}
}
impl MultiplyFill for Table<Vector> {
impl MultiplyFill for List<Vector> {
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) {
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) {
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) {
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) {
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_table_blend_mode<T>(table: &mut Table<T>, blend_mode: BlendMode) {
for v in table.iter_attribute_values_mut_or_default::<BlendMode>(ATTR_BLEND_MODE) {
fn set_list_blend_mode<T>(list: &mut List<T>, blend_mode: BlendMode) {
for v in list.iter_attribute_values_mut_or_default::<BlendMode>(ATTR_BLEND_MODE) {
*v = blend_mode;
}
}
impl SetBlendMode for Table<Vector> {
impl SetBlendMode for List<Vector> {
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) {
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) {
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) {
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) {
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_table_clip<T>(table: &mut Table<T>, clip: bool) {
for v in table.iter_attribute_values_mut_or_default::<bool>(ATTR_CLIPPING_MASK) {
fn set_list_clip<T>(list: &mut List<T>, clip: bool) {
for v in list.iter_attribute_values_mut_or_default::<bool>(ATTR_CLIPPING_MASK) {
*v = clip;
}
}
impl SetClip for Table<Vector> {
impl SetClip for List<Vector> {
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) {
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) {
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) {
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) {
set_table_clip(self, clip);
set_list_clip(self, clip);
}
}
@@ -166,17 +166,17 @@ fn blend_mode<T: SetBlendMode>(
_: impl Ctx,
/// The layer stack that will be composited when rendering.
#[implementations(
Table<Graphic>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Graphic>,
List<Vector>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
mut content: T,
/// The choice of equation that controls how brightness and color blends between overlapping pixels.
blend_mode: BlendMode,
) -> 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
}
@@ -189,11 +189,11 @@ fn opacity<T: MultiplyAlpha + MultiplyFill>(
_: impl Ctx,
/// The layer stack that will be composited when rendering.
#[implementations(
Table<Graphic>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Graphic>,
List<Vector>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
mut content: T,
/// 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.)]
fill: Percentage,
) -> 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 {
content.multiply_alpha(opacity / 100.);
}
@@ -230,17 +230,17 @@ fn clipping_mask<T: SetClip>(
_: impl Ctx,
/// The layer stack that will be composited when rendering.
#[implementations(
Table<Graphic>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Graphic>,
List<Vector>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
mut content: T,
/// Whether the content inherits the alpha of the content beneath it.
clip: bool,
) -> 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
}

View File

@@ -4,9 +4,9 @@ use core_types::blending::BlendMode;
use core_types::bounds::{BoundingBox, RenderBoundingBox};
use core_types::color::{Alpha, Color, Pixel, Sample};
use core_types::generic::FnNode;
use core_types::list::{Item, List};
use core_types::math::bbox::{AxisAlignedBbox, Bbox};
use core_types::registry::FutureWrapperNode;
use core_types::table::{Item, Table};
use core_types::transform::Transform;
use core_types::uuid::NodeId;
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.
#[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
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> {
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 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.));
image.into_element()
@@ -191,20 +191,20 @@ pub fn blend_with_mode(background: Item<Raster<CPU>>, foreground: Item<Raster<CP
async fn brush(
_: impl Ctx,
/// 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.
trace: Table<BrushStroke>,
trace: List<BrushStroke>,
/// Internal cache data used to accelerate rendering of the brush content.
#[data]
cache: BrushCache,
) -> Table<Raster<CPU>> {
) -> List<Raster<CPU>> {
if background.is_empty() {
background.push(Item::default());
}
// 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 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);
@@ -221,11 +221,11 @@ async fn brush(
.cloned()
.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
let Some(mut actual_image) = extend_image_to_bounds((), Table::new_from_item(brush_plan.background), background_bounds).into_iter().next() else {
return Table::new();
let Some(mut actual_image) = extend_image_to_bounds((), List::new_from_item(brush_plan.background), background_bounds).into_iter().next() else {
return List::new();
};
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 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 {
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(())
};
let table = blit_node.eval(blit_target).await;
assert_eq!(table.len(), 1);
table.into_iter().next().unwrap_or_default()
let list = blit_node.eval(blit_target).await;
assert_eq!(list.len(), 1);
list.into_iter().next().unwrap_or_default()
};
// 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(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.));
@@ -323,7 +323,7 @@ async fn brush(
let opacity: f64 = actual_image.attribute_cloned_or(ATTR_OPACITY, 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 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.set_attribute(ATTR_TRANSFORM, 0, transform);
@@ -421,8 +421,8 @@ mod test {
let image = brush(
(),
&BrushCache::default(),
Table::new_from_element(Raster::new_cpu(Image::<Color>::default())),
Table::new_from_element(BrushStroke {
List::new_from_element(Raster::new_cpu(Image::<Color>::default())),
List::new_from_element(BrushStroke {
trace: vec![crate::brush_stroke::BrushInputSample { position: DVec2::ZERO }],
style: BrushStyle {
color: Color::BLACK,

View File

@@ -2,7 +2,7 @@ use crate::brush_stroke::BrushStroke;
use crate::brush_stroke::BrushStyle;
use core_types::ATTR_TRANSFORM;
use core_types::graphene_hash::CacheHashWrapper;
use core_types::table::Item;
use core_types::list::Item;
use raster_types::CPU;
use raster_types::Raster;
use std::collections::HashMap;

View File

@@ -19,12 +19,12 @@ pub mod migrations {
#[serde(untagged)]
enum BrushStrokesFormat {
Strokes(Vec<BrushStroke>),
Table(LegacyTable),
List(LegacyTable),
}
Ok(match BrushStrokesFormat::deserialize(deserializer)? {
BrushStrokesFormat::Strokes(strokes) => strokes,
BrushStrokesFormat::Table(table) => table.element,
BrushStrokesFormat::List(list) => list.element,
})
}
}

View File

@@ -1,4 +1,4 @@
use core_types::table::Table;
use core_types::list::List;
use core_types::transform::Footprint;
use core_types::{CacheHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime, OwnedContextImpl};
use glam::{DAffine2, DVec2};
@@ -73,15 +73,15 @@ async fn quantize_real_time<T>(
Context -> DAffine2,
Context -> Footprint,
Context -> DVec2,
Context -> Table<Vector>,
Context -> Table<Graphic>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> Table<Color>,
Context -> Table<Artboard>,
Context -> Table<GradientStops>,
Context -> Table<String>,
Context -> Table<f64>,
Context -> List<Vector>,
Context -> List<Graphic>,
Context -> List<Raster<CPU>>,
Context -> List<Raster<GPU>>,
Context -> List<Color>,
Context -> List<Artboard>,
Context -> List<GradientStops>,
Context -> List<String>,
Context -> List<f64>,
Context -> (),
)]
value: impl Node<'n, Context<'static>, Output = T>,
@@ -113,15 +113,15 @@ async fn quantize_animation_time<T>(
Context -> DAffine2,
Context -> Footprint,
Context -> DVec2,
Context -> Table<Vector>,
Context -> Table<Graphic>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> Table<Color>,
Context -> Table<Artboard>,
Context -> Table<GradientStops>,
Context -> Table<String>,
Context -> Table<f64>,
Context -> List<Vector>,
Context -> List<Graphic>,
Context -> List<Raster<CPU>>,
Context -> List<Raster<GPU>>,
Context -> List<Color>,
Context -> List<Artboard>,
Context -> List<GradientStops>,
Context -> List<String>,
Context -> List<f64>,
Context -> (),
)]
value: impl Node<'n, Context<'static>, Output = T>,

View File

@@ -1,4 +1,4 @@
use core_types::table::Table;
use core_types::list::List;
use core_types::{Color, ExtractVarArgs};
use core_types::{Ctx, ExtractIndex, ExtractPosition};
use glam::DVec2;
@@ -7,7 +7,7 @@ use graphic_types::{Graphic, Vector};
use raster_types::{CPU, Raster};
#[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 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))]
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 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))]
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 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))]
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 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))]
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 var_arg = var_arg as &dyn std::any::Any;

View File

@@ -1,6 +1,6 @@
use core::f64;
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::uuid::NodeId;
use core_types::{Color, OwnedContextImpl};
@@ -26,20 +26,20 @@ async fn context_modification<T>(
Context -> DAffine2,
Context -> Footprint,
Context -> DVec2,
Context -> Table<String>,
Context -> Table<NodeId>,
Context -> Table<f64>,
Context -> Table<u8>,
Context -> Table<Vector>,
Context -> Table<Graphic>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> Table<Color>,
Context -> Table<Artboard>,
Context -> Table<GradientStops>,
Context -> List<String>,
Context -> List<NodeId>,
Context -> List<f64>,
Context -> List<u8>,
Context -> List<Vector>,
Context -> List<Graphic>,
Context -> List<Raster<CPU>>,
Context -> List<Raster<GPU>>,
Context -> List<Color>,
Context -> List<Artboard>,
Context -> List<GradientStops>,
Context -> AttributeDyn,
Context -> AttributeValueDyn,
Context -> TableDyn,
Context -> ListDyn,
)]
value: impl Node<Context<'static>, Output = T>,
/// The parts of the context to keep when evaluating the input value. All other parts are nullified.

View File

@@ -1,5 +1,5 @@
use core_types::Ctx;
use core_types::table::Table;
use core_types::list::List;
use glam::{DAffine2, DVec2};
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.
#[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()
}

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::{ATTR_BACKGROUND, ATTR_CLIP, ATTR_DIMENSIONS, ATTR_LOCATION, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
use glam::{DAffine2, DVec2};
use graphic_types::graphic::{Graphic, IntoGraphicTable};
use graphic_types::graphic::{Graphic, IntoGraphicList};
use graphic_types::{Artboard, Vector};
use raster_types::{CPU, GPU, Raster};
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(""))]
pub async fn create_artboard<T: IntoGraphicTable + 'n>(
pub async fn create_artboard<T: IntoGraphicList + 'n>(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
/// Graphics to include within the artboard.
#[implementations(
Context -> Table<Graphic>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> Table<Color>,
Context -> Table<GradientStops>,
Context -> List<Graphic>,
Context -> List<Vector>,
Context -> List<Raster<CPU>>,
Context -> List<Raster<GPU>>,
Context -> List<Color>,
Context -> List<GradientStops>,
Context -> DAffine2,
)]
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.
dimensions: DVec2,
/// 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.
#[default(true)]
clip: bool,
) -> Table<Artboard> {
) -> List<Artboard> {
let footprint = ctx.try_footprint().copied();
let mut new_ctx = OwnedContextImpl::from(ctx);
if let Some(mut footprint) = footprint {
footprint.translate(location);
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
// 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);
// 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))
.with_attribute(ATTR_LOCATION, normalized_location)
.with_attribute(ATTR_DIMENSIONS, normalized_dimensions)

View File

@@ -1,10 +1,10 @@
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::table::{AttributeDyn, AttributeValueDyn, Item, Table, TableDyn};
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 glam::{DAffine2, DVec2};
use graphic_types::graphic::{Graphic, IntoGraphicTable};
use graphic_types::graphic::{Graphic, IntoGraphicList};
use graphic_types::{Artboard, Vector};
use raster_types::{CPU, GPU, Raster};
use vector_types::gradient::{GradientSpreadMethod, GradientType};
@@ -17,17 +17,17 @@ pub fn index_elements<T: graphic_types::graphic::AtIndex + Clone + Default>(
_: impl Ctx,
/// The list of data.
#[implementations(
Table<Artboard>,
Table<Graphic>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Raster<GPU>>,
Table<Color>,
Table<GradientStops>,
Table<String>,
Table<f64>,
Table<u8>,
Table<NodeId>,
List<Artboard>,
List<Graphic>,
List<Vector>,
List<Raster<CPU>>,
List<Raster<GPU>>,
List<Color>,
List<GradientStops>,
List<String>,
List<f64>,
List<u8>,
List<NodeId>,
)]
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.
@@ -48,14 +48,14 @@ pub fn omit_element<T: graphic_types::graphic::OmitIndex + Clone + Default>(
_: impl Ctx,
/// The list of data.
#[implementations(
Table<String>,
Table<Artboard>,
Table<Graphic>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Raster<GPU>>,
Table<Color>,
Table<GradientStops>,
List<String>,
List<Artboard>,
List<Graphic>,
List<Vector>,
List<Raster<CPU>>,
List<Raster<GPU>>,
List<Color>,
List<GradientStops>,
)]
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.
@@ -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`.
/// Use this when downstream nodes want just the inner value rather than a `Table` containing a single item.
/// 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 `List` containing a single item.
/// If no value exists at that index, the element type's default is returned.
#[node_macro::node(category("General"))]
pub fn extract_element<T: Clone + Default + Send + Sync + 'static>(
_: impl Ctx,
/// The `Table` of data to extract from.
/// The `List` of data to extract from.
#[implementations(
Table<String>,
Table<f64>,
Table<u8>,
Table<NodeId>,
Table<Color>,
Table<GradientStops>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Graphic>,
Table<Artboard>,
List<String>,
List<f64>,
List<u8>,
List<NodeId>,
List<Color>,
List<GradientStops>,
List<Vector>,
List<Raster<CPU>>,
List<Graphic>,
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.
index: SignedInteger,
) -> T {
let len = table.len();
let len = list.len();
let index = index as i32;
let resolved = if index < 0 {
let from_end = index.unsigned_abs() as usize;
@@ -104,37 +104,37 @@ pub fn extract_element<T: Clone + Default + Send + Sync + 'static>(
} else {
index as usize
};
table.element(resolved).cloned().unwrap_or_default()
list.element(resolved).cloned().unwrap_or_default()
}
#[node_macro::node(category("General"))]
async fn map<Item: AnyHash + Send + Sync + CacheHash>(
ctx: impl Ctx + CloneVarArgs + ExtractAll,
#[implementations(
Table<Graphic>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Graphic>,
List<Vector>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
content: Table<Item>,
content: List<Item>,
#[implementations(
Context -> Table<Graphic>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Color>,
Context -> Table<GradientStops>,
Context -> List<Graphic>,
Context -> List<Vector>,
Context -> List<Raster<CPU>>,
Context -> List<Color>,
Context -> List<GradientStops>,
)]
mapped: impl Node<Context<'static>, Output = Table<Item>>,
) -> Table<Item> {
let mut rows = Table::new();
mapped: impl Node<Context<'static>, Output = List<Item>>,
) -> List<Item> {
let mut rows = List::new();
for (i, row) in content.into_iter().enumerate() {
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 table = mapped.eval(owned_ctx.into_context()).await;
let owned_ctx = owned_ctx.with_vararg(Box::new(List::new_from_item(row))).with_index(i);
let list = mapped.eval(owned_ctx.into_context()).await;
rows.extend(table);
rows.extend(list);
}
rows
@@ -144,20 +144,20 @@ async fn map<Item: AnyHash + Send + Sync + CacheHash>(
async fn mirror<T: 'n + Send + Clone>(
_: impl Ctx,
#[implementations(
Table<Graphic>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Graphic>,
List<Vector>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
content: Table<T>,
content: List<T>,
#[default(ReferencePoint::Center)] relative_to_bounds: ReferencePoint,
#[unit(" px")] offset: f64,
#[range((-90., 90.))] angle: Angle,
#[default(true)] keep_original: bool,
) -> Table<T>
) -> List<T>
where
Table<T>: BoundingBox,
List<T>: BoundingBox,
{
// Normalize the direction vector
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))
};
let mut result_table = Table::new();
let mut result_list = List::new();
// Add original items depending on the keep_original flag
if keep_original {
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() {
let current_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_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`
@@ -212,13 +212,13 @@ where
/// 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.
#[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();
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
/// is evaluated once per item, with the item's index and the item itself (as a `Table` containing only that item,
/// 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 `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
/// 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
@@ -226,66 +226,66 @@ pub fn path_of_subgraph(_: impl Ctx, node_path: Table<NodeId>) -> Table<NodeId>
#[node_macro::node(category("Attributes: Write"))]
async fn write_attribute<T: AnyHash + Clone + Send + Sync + CacheHash>(
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(
Table<Artboard>,
Table<Graphic>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
Table<f64>,
Table<bool>,
Table<String>,
Table<DAffine2>,
Table<BlendMode>,
Table<GradientType>,
Table<GradientSpreadMethod>,
List<Artboard>,
List<Graphic>,
List<Vector>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
List<f64>,
List<bool>,
List<String>,
List<DAffine2>,
List<BlendMode>,
List<GradientType>,
List<GradientSpreadMethod>,
)]
mut content: Table<T>,
mut content: List<T>,
/// The attribute name (key) to write or replace.
name: String,
/// The node that produces the attribute value for each item. Called once per item with the item's index in context.
#[implementations(Context -> AttributeValueDyn)]
value: impl Node<'n, Context<'static>, Output = AttributeValueDyn>,
) -> Table<T> {
) -> List<T> {
for index in 0..content.len() {
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;
content.set_attribute_value_dyn(&name, index, v);
}
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)`.
#[node_macro::node(category("Attributes: Write"))]
fn attach_attribute<T: AnyHash + Clone + Send + Sync + CacheHash>(
_: impl Ctx,
/// The `Table` to attach the new attribute to.
/// The `List` to attach the new attribute to.
#[implementations(
Table<Artboard>,
Table<Graphic>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
Table<f64>,
Table<bool>,
Table<String>,
Table<DAffine2>,
Table<BlendMode>,
Table<GradientType>,
Table<GradientSpreadMethod>,
List<Artboard>,
List<Graphic>,
List<Vector>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
List<f64>,
List<bool>,
List<String>,
List<DAffine2>,
List<BlendMode>,
List<GradientType>,
List<GradientSpreadMethod>,
)]
mut content: Table<T>,
/// The source values to attach. Any `Table<U>` wired here is type-erased via an auto-inserted convert.
mut content: List<T>,
/// The source values to attach. Any `List<U>` wired here is type-erased via an auto-inserted convert.
#[expose]
source: AttributeDyn,
/// The name to assign to the new destination attribute.
name: String,
) -> Table<T> {
) -> List<T> {
if source.is_empty() {
return content;
}
@@ -293,15 +293,15 @@ fn attach_attribute<T: AnyHash + Clone + Send + Sync + CacheHash>(
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"))]
fn read_attribute_vector(
_: impl Ctx,
content: TableDyn,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> Table<Vector> {
let mut result = Table::with_capacity(content.len());
) -> List<Vector> {
let mut result = List::with_capacity(content.len());
for index in 0..content.len() {
let Some(value) = content.attribute::<Vector>(&name, index) else { continue };
result.push(Item::new_from_element(value.clone()));
@@ -309,15 +309,15 @@ fn read_attribute_vector(
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"))]
fn read_attribute_number(
_: impl Ctx,
content: TableDyn,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> Table<f64> {
let mut result = Table::with_capacity(content.len());
) -> List<f64> {
let mut result = List::with_capacity(content.len());
for index in 0..content.len() {
let value = content
.attribute::<f64>(&name, index)
@@ -330,15 +330,15 @@ fn read_attribute_number(
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"))]
fn read_attribute_bool(
_: impl Ctx,
content: TableDyn,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> Table<bool> {
let mut result = Table::with_capacity(content.len());
) -> List<bool> {
let mut result = List::with_capacity(content.len());
for index in 0..content.len() {
let Some(value) = content.attribute::<bool>(&name, index) else { continue };
result.push(Item::new_from_element(*value));
@@ -346,15 +346,15 @@ fn read_attribute_bool(
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"))]
fn read_attribute_string(
_: impl Ctx,
content: TableDyn,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> Table<String> {
let mut result = Table::with_capacity(content.len());
) -> List<String> {
let mut result = List::with_capacity(content.len());
for index in 0..content.len() {
let Some(value) = content.attribute::<String>(&name, index) else { continue };
result.push(Item::new_from_element(value.clone()));
@@ -362,15 +362,15 @@ fn read_attribute_string(
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"))]
fn read_attribute_transform(
_: impl Ctx,
content: TableDyn,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> Table<DAffine2> {
let mut result = Table::with_capacity(content.len());
) -> List<DAffine2> {
let mut result = List::with_capacity(content.len());
for index in 0..content.len() {
let Some(value) = content.attribute::<DAffine2>(&name, index) else { continue };
result.push(Item::new_from_element(*value));
@@ -378,15 +378,15 @@ fn read_attribute_transform(
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"))]
fn read_attribute_color(
_: impl Ctx,
content: TableDyn,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> Table<Color> {
let mut result = Table::with_capacity(content.len());
) -> List<Color> {
let mut result = List::with_capacity(content.len());
for index in 0..content.len() {
let Some(value) = content.attribute::<Color>(&name, index) else { continue };
result.push(Item::new_from_element(*value));
@@ -394,15 +394,15 @@ fn read_attribute_color(
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"))]
fn read_attribute_blend_mode(
_: impl Ctx,
content: TableDyn,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> Table<BlendMode> {
let mut result = Table::with_capacity(content.len());
) -> List<BlendMode> {
let mut result = List::with_capacity(content.len());
for index in 0..content.len() {
let Some(value) = content.attribute::<BlendMode>(&name, index) else { continue };
result.push(Item::new_from_element(*value));
@@ -410,15 +410,15 @@ fn read_attribute_blend_mode(
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"))]
fn read_attribute_gradient_type(
_: impl Ctx,
content: TableDyn,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> Table<GradientType> {
let mut result = Table::with_capacity(content.len());
) -> List<GradientType> {
let mut result = List::with_capacity(content.len());
for index in 0..content.len() {
let Some(value) = content.attribute::<GradientType>(&name, index) else { continue };
result.push(Item::new_from_element(*value));
@@ -426,15 +426,15 @@ fn read_attribute_gradient_type(
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"))]
fn read_attribute_spread_method(
_: impl Ctx,
content: TableDyn,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> Table<GradientSpreadMethod> {
let mut result = Table::with_capacity(content.len());
) -> List<GradientSpreadMethod> {
let mut result = List::with_capacity(content.len());
for index in 0..content.len() {
let Some(value) = content.attribute::<GradientSpreadMethod>(&name, index) else { continue };
result.push(Item::new_from_element(*value));
@@ -442,15 +442,15 @@ fn read_attribute_spread_method(
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"))]
fn read_attribute_gradient_stops(
_: impl Ctx,
content: TableDyn,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> Table<GradientStops> {
let mut result = Table::with_capacity(content.len());
) -> List<GradientStops> {
let mut result = List::with_capacity(content.len());
for index in 0..content.len() {
let Some(value) = content.attribute::<GradientStops>(&name, index) else { continue };
result.push(Item::new_from_element(value.clone()));
@@ -458,15 +458,15 @@ fn read_attribute_gradient_stops(
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"))]
fn read_attribute_artboard(
_: impl Ctx,
content: TableDyn,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> Table<Artboard> {
let mut result = Table::with_capacity(content.len());
) -> List<Artboard> {
let mut result = List::with_capacity(content.len());
for index in 0..content.len() {
let Some(value) = content.attribute::<Artboard>(&name, index) else { continue };
result.push(Item::new_from_element(value.clone()));
@@ -474,15 +474,15 @@ fn read_attribute_artboard(
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"))]
fn read_attribute_raster(
_: impl Ctx,
content: TableDyn,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> Table<Raster<CPU>> {
let mut result = Table::with_capacity(content.len());
) -> List<Raster<CPU>> {
let mut result = List::with_capacity(content.len());
for index in 0..content.len() {
let Some(value) = content.attribute::<Raster<CPU>>(&name, index) else { continue };
result.push(Item::new_from_element(value.clone()));
@@ -490,18 +490,18 @@ fn read_attribute_raster(
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"))]
pub async fn extend<T: 'n + Send + Clone>(
_: impl Ctx,
/// The `Table` whose items will appear at the start of the extended `Table`.
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]
base: Table<T>,
/// The `Table` whose items will appear at the end of the extended `Table`.
/// The `List` whose items will appear at the start of the extended `List`.
#[implementations(List<Artboard>, List<Graphic>, List<Vector>, List<Raster<CPU>>, List<Raster<GPU>>, List<Color>, List<GradientStops>)]
base: List<T>,
/// The `List` whose items will appear at the end of the extended `List`.
#[expose]
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]
new: Table<T>,
) -> Table<T> {
#[implementations(List<Artboard>, List<Graphic>, List<Vector>, List<Raster<CPU>>, List<Raster<GPU>>, List<Color>, List<GradientStops>)]
new: List<T>,
) -> List<T> {
let mut base = base;
base.extend(new);
@@ -514,12 +514,12 @@ pub async fn extend<T: 'n + Send + Clone>(
#[node_macro::node(category(""))]
pub async fn legacy_layer_extend<T: 'n + Send + Clone>(
_: 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]
#[implementations(Table<Artboard>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>, Table<Color>, Table<GradientStops>)]
new: Table<T>,
nested_node_path: Table<NodeId>,
) -> Table<T> {
#[implementations(List<Artboard>, List<Graphic>, List<Vector>, List<Raster<CPU>>, List<Raster<GPU>>, List<Color>, List<GradientStops>)]
new: List<T>,
nested_node_path: List<NodeId>,
) -> List<T> {
// 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).
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>(
_: impl Ctx,
#[implementations(
Table<Graphic>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Raster<GPU>>,
Table<Color>,
Table<GradientStops>,
List<Graphic>,
List<Vector>,
List<Raster<CPU>>,
List<Raster<GPU>>,
List<Color>,
List<GradientStops>,
DAffine2,
)]
content: T,
) -> Table<Graphic> {
Table::new_from_element(content.into())
) -> List<Graphic> {
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>`.
/// If it is already a `Table<Graphic>`, it is not wrapped again. Use the 'Wrap Graphic' node if wrapping is always desired.
/// 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 `List<Graphic>`, it is not wrapped again. Use the 'Wrap Graphic' node if wrapping is always desired.
#[node_macro::node(category("General"))]
pub async fn to_graphic<T: IntoGraphicTable + 'n>(
pub async fn to_graphic<T: IntoGraphicList + 'n>(
_: impl Ctx,
#[implementations(
Table<Graphic>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Raster<GPU>>,
Table<Color>,
Table<GradientStops>,
List<Graphic>,
List<Vector>,
List<Raster<CPU>>,
List<Raster<GPU>>,
List<Color>,
List<GradientStops>,
)]
content: T,
) -> Table<Graphic> {
content.into_graphic_table()
) -> List<Graphic> {
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"))]
pub async fn flatten_graphic(_: impl Ctx, content: Table<Graphic>, fully_flatten: bool) -> Table<Graphic> {
// TODO: Avoid mutable reference, instead return a new Table<Graphic>?
fn flatten_table(output_graphic_table: &mut Table<Graphic>, current_graphic_table: Table<Graphic>, fully_flatten: bool, recursion_depth: usize) {
for index in 0..current_graphic_table.len() {
let Some(current_element) = current_graphic_table.element(index) else { continue };
pub async fn flatten_graphic(_: impl Ctx, content: List<Graphic>, fully_flatten: bool) -> List<Graphic> {
// TODO: Avoid mutable reference, instead return a new List<Graphic>?
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_list.len() {
let Some(current_element) = current_graphic_list.element(index) else { continue };
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;
@@ -593,82 +593,82 @@ pub async fn flatten_graphic(_: impl Ctx, content: Table<Graphic>, fully_flatten
*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`)
_ => {
let attributes = current_graphic_table.clone_item_attributes(index);
output_graphic_table.push(Item::from_parts(current_element, attributes));
let attributes = current_graphic_list.clone_item_attributes(index);
output_graphic_list.push(Item::from_parts(current_element, attributes));
}
}
}
}
let mut output = Table::new();
flatten_table(&mut output, content, fully_flatten, 0);
let mut output = List::new();
flatten_list(&mut output, content, fully_flatten, 0);
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"))]
pub async fn flatten_vector<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Vector>)] content: T) -> Table<Vector> {
let graphic_table = content.into_graphic_table();
let mut output: Table<Vector> = graphic_table.clone().into_flattened_table();
pub async fn flatten_vector<T: IntoGraphicList + 'n + Send + Clone>(_: impl Ctx, #[implementations(List<Graphic>, List<Vector>)] content: T) -> List<Vector> {
let graphic_list = content.into_graphic_list();
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: 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: 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: 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: Morph, Rasterize) become unnecessary.
if !output.is_empty() {
// 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
// `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);
if item_0_transform.matrix2.determinant().abs() > f64::EPSILON {
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;
}
}
output.set_attribute(ATTR_EDITOR_MERGED_LAYERS, 0, graphic_table);
output.set_attribute(ATTR_EDITOR_MERGED_LAYERS, 0, graphic_list);
}
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"))]
pub async fn flatten_raster<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Raster<CPU>>)] content: T) -> Table<Raster<CPU>> {
content.into_flattened_table()
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_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"))]
pub async fn flatten_color<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Color>)] content: T) -> Table<Color> {
content.into_flattened_table()
pub async fn flatten_color<T: IntoGraphicList + 'n + Send + Clone>(_: impl Ctx, #[implementations(List<Graphic>, List<Color>)] content: T) -> List<Color> {
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"))]
pub async fn flatten_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<GradientStops>)] content: T) -> Table<GradientStops> {
content.into_flattened_table()
pub async fn flatten_gradient<T: IntoGraphicList + 'n + Send + Clone>(_: impl Ctx, #[implementations(List<Graphic>, List<GradientStops>)] content: T) -> List<GradientStops> {
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"))]
fn colors_to_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Color>)] colors: T) -> Table<GradientStops> {
let colors = colors.into_flattened_table::<Color>();
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_list::<Color>();
let total_colors = colors.len();
if total_colors == 0 {
return Table::new_from_element(GradientStops::new(vec![
return List::new_from_element(GradientStops::new(vec![
GradientStop {
position: 0.,
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)) {
return Table::new_from_element(GradientStops::new(vec![
return List::new_from_element(GradientStops::new(vec![
GradientStop {
position: 0.,
midpoint: 0.5,
@@ -702,5 +702,5 @@ fn colors_to_gradient<T: IntoGraphicTable + 'n + Send + Clone>(_: impl Ctx, #[im
midpoint: 0.5,
color: row.into_element(),
});
Table::new_from_element(GradientStops::new(colors))
List::new_from_element(GradientStops::new(colors))
}

View File

@@ -2,9 +2,9 @@
use base64::Engine;
#[cfg(target_family = "wasm")]
use canvas_utils::{Canvas, CanvasHandle};
use core_types::list::{Item, List};
#[cfg(target_family = "wasm")]
use core_types::math::bbox::Bbox;
use core_types::table::{Item, Table};
#[cfg(target_family = "wasm")]
use core_types::transform::Footprint;
#[cfg(target_family = "wasm")]
@@ -18,7 +18,7 @@ pub use graphene_canvas_utils as canvas_utils;
#[cfg(target_family = "wasm")]
use graphic_types::Graphic;
#[cfg(target_family = "wasm")]
use graphic_types::IntoGraphicTable;
use graphic_types::IntoGraphicList;
#[cfg(target_family = "wasm")]
use graphic_types::Vector;
use graphic_types::raster_types::Image;
@@ -85,7 +85,7 @@ async fn post_request(
#[name("URL")]
url: String,
/// 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.
discard_result: bool,
#[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.
#[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()
}
/// 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"))]
fn image_to_bytes(_: impl Ctx, image: Table<Raster<CPU>>) -> Table<u8> {
let Some(image) = image.element(0) else { return Table::new() };
fn image_to_bytes(_: impl Ctx, image: List<Raster<CPU>>) -> List<u8> {
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()
}
@@ -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.
#[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 {
return Table::new();
return List::new();
};
let image = image.to_rgba32f();
let image = Image {
@@ -161,7 +161,7 @@ fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> Table<Raster<CPU>> {
..Default::default()
};
Table::new_from_element(Raster::new_cpu(image))
List::new_from_element(Raster::new_cpu(image))
}
#[cfg(target_family = "wasm")]
@@ -176,29 +176,29 @@ async fn create_canvas(_: impl Ctx) -> CanvasHandle {
async fn rasterize<T: WasmNotSend + Clone + 'n>(
_: impl Ctx,
#[implementations(
Table<Vector>,
Table<Raster<CPU>>,
Table<Graphic>,
Table<Color>,
Table<GradientStops>,
List<Vector>,
List<Raster<CPU>>,
List<Graphic>,
List<Color>,
List<GradientStops>,
)]
mut data: Table<T>,
mut data: List<T>,
footprint: Footprint,
mut canvas: CanvasHandle,
) -> Table<Raster<CPU>>
) -> List<Raster<CPU>>
where
Table<T>: Render + Clone + graphic_types::IntoGraphicTable,
List<T>: Render + Clone + graphic_types::IntoGraphicList,
{
use glam::{DAffine2, DVec2};
if footprint.transform.matrix2.determinant() == 0. {
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).
let upstream_graphic_table = data.clone().into_graphic_table();
let upstream_graphic_list = data.clone().into_graphic_list();
let mut render = SvgRender::new();
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 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))
.with_attribute(ATTR_TRANSFORM, footprint.transform)
.with_attribute(ATTR_EDITOR_MERGED_LAYERS, upstream_graphic_table),
.with_attribute(ATTR_EDITOR_MERGED_LAYERS, upstream_graphic_list),
)
}

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::uuid::generate_uuid;
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>(
ctx: impl Ctx + ExtractVarArgs + ExtractAll + CloneVarArgs,
#[implementations(
Context -> Table<Artboard>,
Context -> Table<Graphic>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Color>,
Context -> Table<GradientStops>,
Context -> List<Artboard>,
Context -> List<Graphic>,
Context -> List<Vector>,
Context -> List<Raster<CPU>>,
Context -> List<Color>,
Context -> List<GradientStops>,
)]
data: impl Node<Context<'static>, Output = T>,
) -> RenderIntermediate {

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 graphic_types::Vector;
pub use text_nodes::*;
@@ -61,7 +62,7 @@ fn text<'i: 'n>(
align: TextAlign,
/// Whether to split every letterform into its own vector item. Otherwise, a single vector compound path is produced.
separate_glyphs: bool,
) -> Table<Vector> {
) -> List<Vector> {
let typesetting = TypesettingConfig {
font_size: size,
line_height_ratio: line_height,

View File

@@ -1,6 +1,6 @@
use core_types::Context;
use core_types::list::List;
use core_types::registry::types::{Fraction, Percentage, PixelSize};
use core_types::table::Table;
use core_types::transform::Footprint;
use core_types::{Color, Ctx, num_traits};
use glam::{DAffine2, DVec2};
@@ -753,13 +753,13 @@ async fn switch<T, C: Send + 'n + Clone>(
Context -> u64,
Context -> DVec2,
Context -> DAffine2,
Context -> Table<Artboard>,
Context -> Table<Graphic>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> Table<Color>,
Context -> Table<GradientStops>,
Context -> List<Artboard>,
Context -> List<Graphic>,
Context -> List<Vector>,
Context -> List<Raster<CPU>>,
Context -> List<Raster<GPU>>,
Context -> List<Color>,
Context -> List<GradientStops>,
)]
if_true: impl Node<C, Output = T>,
#[expose]
@@ -772,13 +772,13 @@ async fn switch<T, C: Send + 'n + Clone>(
Context -> u64,
Context -> DVec2,
Context -> DAffine2,
Context -> Table<Artboard>,
Context -> Table<Graphic>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> Table<Color>,
Context -> Table<GradientStops>,
Context -> List<Artboard>,
Context -> List<Graphic>,
Context -> List<Vector>,
Context -> List<Raster<CPU>>,
Context -> List<Raster<GPU>>,
Context -> List<Color>,
Context -> List<GradientStops>,
)]
if_false: impl Node<C, Output = 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.
#[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
}
/// 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"))]
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 green = (green as f32).clamp(0., 1.);
let blue = (blue 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.
#[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 saturation = (saturation as f32).clamp(0., 1.);
let value = (value 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.
#[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 saturation = (saturation as f32).clamp(0., 1.);
let lightness = (lightness 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.
#[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) {
Some(c) => Table::new_from_element(c),
None => Table::new(),
Some(c) => List::new_from_element(c),
None => List::new(),
}
}
/// 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"))]
fn gradient_value(_: impl Ctx, _primary: (), gradient: Table<GradientStops>) -> Table<GradientStops> {
fn gradient_value(_: impl Ctx, _primary: (), gradient: List<GradientStops>) -> List<GradientStops> {
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"))]
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) {
*value = gradient_type;
}
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"))]
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) {
*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).
#[node_macro::node(category("Color"))]
fn sample_gradient(_: impl Ctx, _primary: (), gradient: Table<GradientStops>, position: Fraction) -> Table<Color> {
let Some(gradient) = gradient.element(0) else { return Table::new() };
fn sample_gradient(_: impl Ctx, _primary: (), gradient: List<GradientStops>, position: Fraction) -> List<Color> {
let Some(gradient) = gradient.element(0) else { return List::new() };
let position = position.clamp(0., 1.);
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.

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::{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};
@@ -14,15 +14,15 @@ use vector_types::kurbo::{Affine, BezPath, CubicBez, Line, ParamCurve, PathSeg,
pub use vector_types::vector::misc::BooleanOperation;
// 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.
/// 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)]
async fn boolean_operation<I: graphic_types::IntoGraphicTable + 'n + Send + Clone>(
async fn boolean_operation<I: graphic_types::IntoGraphicList + 'n + Send + Clone>(
_: impl Ctx,
/// The `Table` of vector paths to perform the boolean operation on. Nested `Table`s are automatically flattened.
#[implementations(Table<Graphic>, Table<Vector>)]
/// The `List` of vector paths to perform the boolean operation on. Nested `List`s are automatically flattened.
#[implementations(List<Graphic>, List<Vector>)]
content: I,
/// 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.
/// Difference cuts away the overlapping areas shared by every path, leaving only the non-overlapping areas.
operation: BooleanOperation,
) -> Table<Vector> {
let content = content.into_graphic_table();
) -> List<Vector> {
let content = content.into_graphic_list();
// The first index is the bottom of the stack
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
if result_vector_table.element_mut(0).is_some() {
let transform: DAffine2 = result_vector_table.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
result_vector_table.set_attribute(ATTR_TRANSFORM, 0, DAffine2::IDENTITY);
if result_vector_list.element_mut(0).is_some() {
let transform: DAffine2 = result_vector_list.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
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);
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
// 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
let merge_transform: DAffine2 = result_vector_table.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
result_vector_table.element_mut(0).unwrap().merge_by_distance_spatial(merge_transform, 0.0001);
let merge_transform: DAffine2 = result_vector_list.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
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)]
@@ -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;
let mut table = Table::new();
let mut list = List::new();
let mut paths = Vec::new();
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,
Err(e) => {
log::error!("Boolean operation failed while building topology: {e}");
table.push(row);
return table;
list.push(row);
return list;
}
};
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);
}
table.push(row);
table
list.push(row);
list
}
fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
(0..graphic_table.len())
fn flatten_vector(graphic_list: &List<Graphic>) -> List<Vector> {
(0..graphic_list.len())
.flat_map(|index| {
let graphic = graphic_table.element(index).unwrap();
let graphic = graphic_list.element(index).unwrap();
match graphic.clone() {
Graphic::Vector(vector) => {
// Apply the parent graphic's transform to each element of the `Table<Vector>`
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index);
// Apply the parent graphic's transform to each element of the `List<Vector>`
let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
vector
.into_iter()
.map(|mut sub_vector| {
@@ -180,7 +180,7 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
.collect::<Vec<_>>()
}
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 mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
subpath.apply_transform(transform);
@@ -202,7 +202,7 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
(0..image.len())
.map(|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 opacity: f64 = image.attribute_cloned_or(ATTR_OPACITY, 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<_>>()
}
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 mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
subpath.apply_transform(transform);
@@ -234,7 +234,7 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
(0..image.len())
.map(|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 opacity: f64 = image.attribute_cloned_or(ATTR_OPACITY, 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<_>>()
}
Graphic::Graphic(mut graphic) => {
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index);
// Apply the parent graphic's transform to each element of the inner `Table`
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 `List`
for transform in graphic.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_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 unioned = boolean_operation_on_vector_table(&flattened, BooleanOperation::Union);
let unioned = boolean_operation_on_vector_list(&flattened, BooleanOperation::Union);
unioned.into_iter().collect::<Vec<_>>()
}

View File

@@ -12,11 +12,11 @@ impl Adjust<Color> for Color {
#[cfg(feature = "std")]
mod adjust_std {
use super::*;
use core_types::table::Table;
use core_types::list::List;
use raster_types::{CPU, Raster};
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) {
for element in self.iter_element_values_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) {
for element in self.iter_element_values_mut() {
*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) {
for element in self.iter_element_values_mut() {
element.adjust(&map_fn);

View File

@@ -4,7 +4,7 @@ use crate::adjust::Adjust;
use crate::cubic_spline::CubicSplines;
use core::fmt::Debug;
#[cfg(feature = "std")]
use core_types::table::Table;
use core_types::list::List;
use glam::{Vec3, Vec4};
use no_std_types::color::Color;
use no_std_types::context::Ctx;
@@ -53,9 +53,9 @@ pub enum LuminanceCalculation {
fn luminance<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
#[gpu_image]
mut input: T,
@@ -78,9 +78,9 @@ fn luminance<T: Adjust<Color>>(
fn gamma_correction<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
#[gpu_image]
mut input: T,
@@ -99,9 +99,9 @@ fn gamma_correction<T: Adjust<Color>>(
fn extract_channel<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
#[gpu_image]
mut input: T,
@@ -123,9 +123,9 @@ fn extract_channel<T: Adjust<Color>>(
fn make_opaque<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
#[gpu_image]
mut input: T,
@@ -145,9 +145,9 @@ fn make_opaque<T: Adjust<Color>>(
fn brightness_contrast_classic<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
#[gpu_image]
mut input: T,
@@ -176,9 +176,9 @@ fn brightness_contrast_classic<T: Adjust<Color>>(
fn brightness_contrast<T: Adjust<Color>>(
_ctx: impl Ctx,
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
#[gpu_image]
mut input: T,
@@ -257,9 +257,9 @@ fn brightness_contrast<T: Adjust<Color>>(
fn levels<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
#[gpu_image]
mut image: T,
@@ -321,14 +321,14 @@ fn levels<T: Adjust<Color>>(
// Algorithm from:
// https://stackoverflow.com/a/55233732/775283
// 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))]
fn black_and_white<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
#[gpu_image]
mut image: T,
@@ -399,9 +399,9 @@ fn black_and_white<T: Adjust<Color>>(
fn hue_saturation<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
#[gpu_image]
mut input: T,
@@ -434,9 +434,9 @@ fn hue_saturation<T: Adjust<Color>>(
fn invert<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
#[gpu_image]
mut input: T,
@@ -457,9 +457,9 @@ fn invert<T: Adjust<Color>>(
fn threshold<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
#[gpu_image]
mut image: T,
@@ -503,9 +503,9 @@ fn threshold<T: Adjust<Color>>(
fn vibrance<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
#[gpu_image]
mut image: T,
@@ -682,9 +682,9 @@ pub enum DomainWarpType {
fn channel_mixer<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
#[gpu_image]
mut image: T,
@@ -816,9 +816,9 @@ pub enum SelectiveColorChoice {
fn selective_color<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
#[gpu_image]
mut image: T,
@@ -962,9 +962,9 @@ fn selective_color<T: Adjust<Color>>(
fn posterize<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
#[gpu_image]
mut input: T,
@@ -996,9 +996,9 @@ fn posterize<T: Adjust<Color>>(
fn exposure<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
#[gpu_image]
mut input: T,

View File

@@ -1,6 +1,6 @@
use crate::adjust::Adjust;
#[cfg(feature = "std")]
use core_types::table::Table;
use core_types::list::List;
use no_std_types::Ctx;
use no_std_types::blending::BlendMode;
use no_std_types::color::{Color, Pixel};
@@ -23,54 +23,54 @@ impl Blend<Color> for Color {
mod blend_std {
use super::*;
use core::cmp::Ordering;
use core_types::table::Table;
use core_types::list::List;
use raster_types::Image;
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 {
let mut result_table = self.clone();
let pair_count = result_table.len().min(under.len());
let mut result_list = self.clone();
let pair_count = result_list.len().min(under.len());
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 data = over.data.iter().zip(under_element.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
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,
width,
height,
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 {
let mut result_table = self.clone();
let pair_count = result_table.len().min(under.len());
let mut result_list = self.clone();
let pair_count = result_list.len().min(under.len());
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 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 {
let mut result_table = self.clone();
let pair_count = result_table.len().min(under.len());
let mut result_list = self.clone();
let pair_count = result_list.len().min(under.len());
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 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 {
@@ -145,17 +145,17 @@ pub fn apply_blend_mode(foreground: Color, background: Color, blend_mode: BlendM
fn mix<T: Blend<Color> + Send>(
_: impl Ctx,
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
#[gpu_image]
over: T,
#[expose]
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
#[gpu_image]
under: T,
@@ -169,9 +169,9 @@ fn mix<T: Blend<Color> + Send>(
fn color_overlay<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
#[gpu_image]
mut image: T,
@@ -197,7 +197,7 @@ fn color_overlay<T: Adjust<Color>>(
mod test {
use core_types::blending::BlendMode;
use core_types::color::Color;
use core_types::table::Table;
use core_types::list::List;
use raster_types::Image;
use raster_types::Raster;
@@ -212,7 +212,7 @@ mod test {
// 100% of the output should come from the multiplied value
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();
// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0)

View File

@@ -1,6 +1,6 @@
use core_types::context::Ctx;
use core_types::list::List;
use core_types::registry::types::Percentage;
use core_types::table::Table;
use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage};
use ndarray::{Array2, ArrayBase, Dim, OwnedRepr};
use raster_types::Image;
@@ -8,7 +8,7 @@ use raster_types::{CPU, Raster};
use std::cmp::{max, min};
#[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
.into_iter()
.map(|mut row| {

View File

@@ -1,7 +1,7 @@
use core_types::color::Color;
use core_types::context::Ctx;
use core_types::list::List;
use core_types::registry::types::PixelLength;
use core_types::table::Table;
use raster_types::Image;
use raster_types::{Bitmap, BitmapMut};
use raster_types::{CPU, Raster};
@@ -11,7 +11,7 @@ use raster_types::{CPU, Raster};
async fn blur(
_: impl Ctx,
/// The image to be blurred.
image_frame: Table<Raster<CPU>>,
image_frame: List<Raster<CPU>>,
/// The radius of the blur kernel.
#[range((0., 100.))]
#[hard_min(0.)]
@@ -20,7 +20,7 @@ async fn blur(
box_blur: bool,
/// Opt to incorrectly apply the filter with color calculations in gamma space for compatibility with the results from other software.
gamma: bool,
) -> Table<Raster<CPU>> {
) -> List<Raster<CPU>> {
image_frame
.into_iter()
.map(|mut row| {
@@ -47,12 +47,12 @@ async fn blur(
async fn median_filter(
_: impl Ctx,
/// 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.
#[range((0., 50.))]
#[hard_min(0.)]
radius: PixelLength,
) -> Table<Raster<CPU>> {
) -> List<Raster<CPU>> {
image_frame
.into_iter()
.map(|mut row| {

View File

@@ -1,7 +1,7 @@
//! Not immediately shader compatible due to needing [`GradientStops`] as a param, which needs [`Vec`]
use crate::adjust::Adjust;
use core_types::table::Table;
use core_types::list::List;
use core_types::{Color, Ctx};
use raster_types::{CPU, Raster};
use vector_types::GradientStops;
@@ -13,12 +13,12 @@ use vector_types::GradientStops;
async fn gradient_map<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
Table<Raster<CPU>>,
Table<Color>,
Table<GradientStops>,
List<Raster<CPU>>,
List<Color>,
List<GradientStops>,
)]
mut image: T,
gradient: Table<GradientStops>,
gradient: List<GradientStops>,
reverse: bool,
) -> T {
let Some(gradient) = gradient.element(0) else { return image };

View File

@@ -1,16 +1,16 @@
use core_types::color::Color;
use core_types::context::Ctx;
use core_types::table::{Item, Table};
use core_types::list::{Item, List};
use raster_types::{CPU, Raster};
#[node_macro::node(category("Color"))]
async fn image_color_palette(
_: impl Ctx,
image: Table<Raster<CPU>>,
image: List<Raster<CPU>>,
#[default(4)]
#[hard_min(1)]
count: u32,
) -> Table<Color> {
) -> List<Color> {
const GRID: f32 = 3.;
let bins = GRID * GRID * GRID;
@@ -71,7 +71,7 @@ mod test {
fn test_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,
height: 100,
data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000],
@@ -79,6 +79,6 @@ mod test {
})),
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()));
}
}

View File

@@ -3,8 +3,8 @@ use core_types::ATTR_TRANSFORM;
use core_types::color::Color;
use core_types::color::{Alpha, AlphaMut, Channel, LinearChannel, Luminance, RGBMut};
use core_types::context::{Ctx, ExtractFootprint};
use core_types::list::{Item, List};
use core_types::math::bbox::Bbox;
use core_types::table::{Item, Table};
use core_types::transform::Transform;
use dyn_any::DynAny;
use fastnoise_lite;
@@ -30,7 +30,7 @@ impl From<std::io::Error> for Error {
}
#[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
.into_iter()
.filter_map(|row| {
@@ -97,11 +97,11 @@ pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Tabl
pub fn combine_channels(
_: impl Ctx,
_primary: (),
#[expose] red: Table<Raster<CPU>>,
#[expose] green: Table<Raster<CPU>>,
#[expose] blue: Table<Raster<CPU>>,
#[expose] alpha: Table<Raster<CPU>>,
) -> Table<Raster<CPU>> {
#[expose] red: List<Raster<CPU>>,
#[expose] green: List<Raster<CPU>>,
#[expose] blue: List<Raster<CPU>>,
#[expose] alpha: List<Raster<CPU>>,
) -> List<Raster<CPU>> {
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 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(
_: impl Ctx,
/// The image to be masked.
image: Table<Raster<CPU>>,
image: List<Raster<CPU>>,
/// The stencil to be used for masking.
#[expose]
stencil: Table<Raster<CPU>>,
) -> Table<Raster<CPU>> {
stencil: List<Raster<CPU>>,
) -> List<Raster<CPU>> {
// TODO: Figure out what it means to support multiple stencil items?
let Some(stencil) = stencil.into_iter().next() else {
// No stencil provided so we return the original image
@@ -226,7 +226,7 @@ pub fn mask(
}
#[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
.into_iter()
.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_width, image_height) = (row.element().width, row.element().height);
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);
@@ -274,23 +274,23 @@ pub fn extend_image_to_bounds(_: impl Ctx, image: Table<Raster<CPU>>, bounds: DA
}
#[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 height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32;
let color = color.element(0).copied().unwrap_or(Color::WHITE);
let image = Image::new(width, height, color);
let mut result_table = Table::new_from_element(Raster::new_cpu(image));
result_table.set_attribute(ATTR_TRANSFORM, 0, transform);
let mut result_list = List::new_from_element(Raster::new_cpu(image));
result_list.set_attribute(ATTR_TRANSFORM, 0, transform);
// Callers of empty_image can safely unwrap on returned `Table`
result_table
// Callers of empty_image can safely unwrap on returned `List`
result_list
}
#[node_macro::node(category(""))]
pub fn image(_: impl Ctx, _primary: (), image: Image<Color>) -> Table<Raster<CPU>> {
Table::new_from_element(Raster::new_cpu(image))
pub fn image(_: impl Ctx, _primary: (), image: Image<Color>) -> List<Raster<CPU>> {
List::new_from_element(Raster::new_cpu(image))
}
/// Generates customizable procedural noise patterns.
@@ -328,7 +328,7 @@ pub fn noise_pattern(
#[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_jitter")]
#[default(1.)]
cellular_jitter: f64,
) -> Table<Raster<CPU>> {
) -> List<Raster<CPU>> {
let footprint = ctx.footprint();
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 size.x <= 0. || size.y <= 0. {
return Table::new();
return List::new();
}
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));
@@ -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"))]
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 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 size.x <= 0. || size.y <= 0. {
return Table::new();
return List::new();
}
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 {
width,
height,

View File

@@ -1,7 +1,7 @@
use crate::gcore::Context;
use core::f64::consts::TAU;
use core_types::list::List;
use core_types::registry::types::{Angle, PixelSize};
use core_types::table::Table;
use core_types::{ATTR_TRANSFORM, CloneVarArgs, Color, Ctx, ExtractAll, InjectVarArgs, OwnedContextImpl};
use glam::{DAffine2, DVec2};
use graphic_types::{Graphic, Vector};
@@ -12,23 +12,23 @@ use vector_types::GradientStops;
async fn repeat<T: Into<Graphic> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[implementations(
Context -> Table<Graphic>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Color>,
Context -> Table<GradientStops>,
Context -> List<Graphic>,
Context -> List<Vector>,
Context -> List<Raster<CPU>>,
Context -> List<Color>,
Context -> List<GradientStops>,
)]
content: impl Node<'n, Context<'static>, Output = Table<T>>,
content: impl Node<'n, Context<'static>, Output = List<T>>,
#[default(1)]
#[hard_min(1)]
count: u32,
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`).
let count = count.max(1) as usize;
let mut result_table = Table::new();
let mut result_list = List::new();
for index in 0..count {
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;
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"))]
pub async fn repeat_array<T: Into<Graphic> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[implementations(
Context -> Table<Graphic>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Color>,
Context -> Table<GradientStops>,
Context -> List<Graphic>,
Context -> List<Vector>,
Context -> List<Raster<CPU>>,
Context -> List<Color>,
Context -> List<GradientStops>,
)]
content: impl Node<'n, Context<'static>, Output = Table<T>>,
content: impl Node<'n, Context<'static>, Output = List<T>>,
#[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.
direction: PixelSize,
@@ -62,12 +62,12 @@ pub async fn repeat_array<T: Into<Graphic> + Default + Send + Clone + 'static>(
#[default(5)]
#[hard_min(1)]
count: u32,
) -> Table<T> {
) -> List<T> {
let angle = angle.to_radians();
let count = count.max(1);
let total = (count - 1) as f64;
let mut result_table = Table::new();
let mut result_list = List::new();
for index in 0..count {
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);
*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"))]
async fn repeat_radial<T: Into<Graphic> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[implementations(
Context -> Table<Graphic>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Color>,
Context -> Table<GradientStops>,
Context -> List<Graphic>,
Context -> List<Vector>,
Context -> List<Raster<CPU>>,
Context -> List<Color>,
Context -> List<GradientStops>,
)]
content: impl Node<'n, Context<'static>, Output = Table<T>>,
content: impl Node<'n, Context<'static>, Output = List<T>>,
start_angle: Angle,
#[unit(" px")]
#[default(5)]
@@ -110,10 +110,10 @@ async fn repeat_radial<T: Into<Graphic> + Default + Send + Clone + 'static>(
#[default(5)]
#[hard_min(1)]
count: u32,
) -> Table<T> {
) -> List<T> {
let count = count.max(1);
let mut result_table = Table::new();
let mut result_list = List::new();
for index in 0..count {
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);
*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"))]
async fn repeat_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx + InjectVarArgs,
points: Table<Vector>,
points: List<Vector>,
#[implementations(
Context -> Table<Graphic>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Color>,
Context -> Table<GradientStops>,
Context -> List<Graphic>,
Context -> List<Vector>,
Context -> List<Raster<CPU>>,
Context -> List<Color>,
Context -> List<GradientStops>,
)]
content: impl Node<'n, Context<'static>, Output = Table<T>>,
content: impl Node<'n, Context<'static>, Output = List<T>>,
reverse: bool,
) -> Table<T> {
let mut result_table = Table::new();
) -> List<T> {
let mut result_list = List::new();
for points_index in 0..points.len() {
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() {
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)]
@@ -202,8 +202,8 @@ mod test {
use vector_nodes::generator_nodes::RectangleNode;
use vector_types::subpath::Subpath;
fn vector_node_from_bezpath(bezpath: BezPath) -> Table<Vector> {
Table::new_from_element(Vector::from_bezpath(bezpath))
fn vector_node_from_bezpath(bezpath: BezPath) -> List<Vector> {
List::new_from_element(Vector::from_bezpath(bezpath))
}
#[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 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;
assert_eq!(generated.len(), positions.len());
for (position, index) in positions.into_iter().zip(0..generated.len()) {
@@ -257,8 +257,8 @@ mod test {
count,
)
.await;
let vector_table = vector_nodes::flatten_path(Footprint::default(), repeated).await;
let vector = vector_table.element(0).unwrap();
let vector_list = vector_nodes::flatten_path(Footprint::default(), repeated).await;
let vector = vector_list.element(0).unwrap();
assert_eq!(vector.region_manipulator_groups().count(), 3);
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);
@@ -278,8 +278,8 @@ mod test {
count,
)
.await;
let vector_table = vector_nodes::flatten_path(Footprint::default(), repeated).await;
let vector = vector_table.element(0).unwrap();
let vector_list = vector_nodes::flatten_path(Footprint::default(), repeated).await;
let vector = vector_list.element(0).unwrap();
assert_eq!(vector.region_manipulator_groups().count(), 8);
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);
@@ -290,8 +290,8 @@ mod test {
async fn repeat_radial() {
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 vector_table = vector_nodes::flatten_path(Footprint::default(), repeated).await;
let vector = vector_table.element(0).unwrap();
let vector_list = vector_nodes::flatten_path(Footprint::default(), repeated).await;
let vector = vector_list.element(0).unwrap();
assert_eq!(vector.region_manipulator_groups().count(), 8);
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {

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 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.
#[default(true)]
unquote_strings: bool,
) -> Table<String> {
) -> List<String> {
let cleaned = strip_trailing_commas(&json);
let Ok(value): Result<Value, _> = serde_json::from_str(&cleaned) else { return Table::new() };
let Some(segments) = parse_json_path(path.trim()) 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 List::new() };
let mut results = Vec::new();
resolve_all(&value, &segments, !unquote_strings, &mut results);

View File

@@ -7,8 +7,8 @@ mod to_path;
use convert_case::{Boundary, Converter, pattern};
use core_types::graphene_hash::CacheHash;
use core_types::list::{Item, List};
use core_types::registry::types::{SignedInteger, TextArea};
use core_types::table::{Item, Table};
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractVarArgs, OwnedContextImpl};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
@@ -737,7 +737,7 @@ fn string_split(
/// "\n" (newline), "\r" (carriage return), "\t" (tab), "\0" (null), and "\\" (backslash).
#[default(true)]
delimiter_escaping: bool,
) -> Table<String> {
) -> List<String> {
let delimiter = if delimiter_escaping { unescape_string(delimiter) } else { delimiter };
string.split(&delimiter).map(str::to_string).map(Item::new_from_element).collect()
@@ -750,7 +750,7 @@ fn string_split(
fn string_join(
_: impl Ctx,
/// The list of strings to join together.
strings: Table<String>,
strings: List<String>,
/// The text placed between each pair of strings.
#[default(", ")]
separator: String,
@@ -768,12 +768,12 @@ fn string_join(
#[node_macro::node(category("Text"))]
async fn map_string(
ctx: impl Ctx + CloneVarArgs + ExtractAll,
strings: Table<String>,
strings: List<String>,
#[expose]
#[implementations(Context -> String)]
mapped: impl Node<Context<'static>, Output = String>,
) -> Table<String> {
let mut result = Table::new();
) -> List<String> {
let mut result = List::new();
for (i, row) in strings.into_iter().enumerate() {
let string = row.into_element();

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 glam::{DAffine2, DVec2};
use parley::GlyphRun;
@@ -14,7 +14,7 @@ pub struct PathBuilder {
current_subpath: Subpath<PointId>,
origin: DVec2,
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()`.
merged_click_target_bboxes: Vec<[DVec2; 2]>,
/// Per-glyph baselines, parallel to `merged_click_target_bboxes`. Groups glyphs by line for the widening pass.
@@ -35,7 +35,7 @@ impl PathBuilder {
Self {
current_subpath: Subpath::new(Vec::new(), false),
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_baselines: 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))
.with_attribute(ATTR_TRANSFORM, DAffine2::from_translation(glyph_offset))
.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
self.per_glyph_bboxes.push(glyph_bbox);
} else {
for subpath in self.glyph_subpaths.drain(..) {
// Unwrapping here is ok because `self.vector_table` is initialized with a single `Table<Vector>` item
self.vector_table.element_mut(0).unwrap().append_subpath(subpath, false);
// Unwrapping here is ok because `self.vector_list` is initialized with a single `List<Vector>` item
self.vector_list.element_mut(0).unwrap().append_subpath(subpath, false);
}
if let Some(bbox) = glyph_bbox {
self.merged_click_target_bboxes.push(bbox);
@@ -163,16 +163,16 @@ impl PathBuilder {
}
}
pub fn finalize(mut self) -> Table<Vector> {
// Empty table = 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.
pub fn finalize(mut self) -> List<Vector> {
// Empty list = all glyphs clipped by height. Create a placeholder with the same item-0
// 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.
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 item = Item::new_from_element(Vector::default())
.with_attribute(ATTR_TRANSFORM, DAffine2::from_translation(self.first_glyph_offset))
.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
@@ -184,7 +184,7 @@ impl PathBuilder {
.enumerate()
.filter_map(|(index, 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]))
})
.collect();
@@ -197,7 +197,7 @@ impl PathBuilder {
for (entry, widened) in entries.iter().zip(layer_bboxes.iter()) {
let glyph_local = [widened[0] - entry.1, widened[1] - entry.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);
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)
let frame = DAffine2::from_scale(self.text_frame_size);
for index in 0..self.vector_table.len() {
if self.vector_table.attribute::<DAffine2>(ATTR_EDITOR_TEXT_FRAME, index).is_none() {
self.vector_table.set_attribute(ATTR_EDITOR_TEXT_FRAME, index, frame);
for index in 0..self.vector_list.len() {
if self.vector_list.attribute::<DAffine2>(ATTR_EDITOR_TEXT_FRAME, index).is_none() {
self.vector_list.set_attribute(ATTR_EDITOR_TEXT_FRAME, index, frame);
}
}
self.vector_table
self.vector_list
}
}

View File

@@ -1,5 +1,5 @@
use core_types::list::{Item, List};
use core_types::registry::types::SignedInteger;
use core_types::table::{Item, Table};
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.
@@ -96,9 +96,9 @@ fn regex_find(
case_insensitive: bool,
/// Make `^` and `$` match the start and end of each line, not just the whole string.
multiline: bool,
) -> Table<String> {
) -> List<String> {
if pattern.is_empty() {
return Table::new();
return List::new();
}
let flags = match (case_insensitive, multiline) {
@@ -111,7 +111,7 @@ fn regex_find(
let Ok(regex) = fancy_regex::Regex::new(&full_pattern) else {
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.
@@ -124,7 +124,7 @@ fn regex_find(
let resolved_index = if match_index < 0 {
let from_end = (-match_index) as usize;
if from_end > matches.len() {
return Table::new();
return List::new();
}
matches.len() - from_end
} else {
@@ -132,7 +132,7 @@ fn regex_find(
};
let Some(captures) = matches.get(resolved_index) else {
return Table::new();
return List::new();
};
// Index 0 is the whole match, 1+ are capture groups
@@ -165,9 +165,9 @@ fn regex_find_all(
case_insensitive: bool,
/// Make `^` and `$` match the start and end of each line, not just the whole string.
multiline: bool,
) -> Table<String> {
) -> List<String> {
if pattern.is_empty() {
return Table::new();
return List::new();
}
let flags = match (case_insensitive, multiline) {
@@ -180,7 +180,7 @@ fn regex_find_all(
let Ok(regex) = fancy_regex::Regex::new(&full_pattern) else {
log::error!("Invalid regex pattern: {pattern}");
return Table::new();
return List::new();
};
regex
@@ -208,9 +208,9 @@ fn regex_split(
case_insensitive: bool,
/// Make `^` and `$` match the start and end of each line, not just the whole string.
multiline: bool,
) -> Table<String> {
) -> List<String> {
if pattern.is_empty() {
return Table::new_from_element(string);
return List::new_from_element(string);
}
let flags = match (case_insensitive, multiline) {
@@ -223,7 +223,7 @@ fn regex_split(
let Ok(regex) = fancy_regex::Regex::new(&full_pattern) else {
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()

View File

@@ -1,6 +1,6 @@
use super::{Font, FontCache, TypesettingConfig};
use core::cell::RefCell;
use core_types::table::Table;
use core_types::list::List;
use glam::DVec2;
use parley::fontique::{Blob, FamilyId, FontInfo};
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
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 {
return Table::new_from_element(Vector::default());
return List::new_from_element(Vector::default());
};
let text_frame_size = DVec2::new(

View File

@@ -1,12 +1,12 @@
use super::text_context::TextContext;
use super::{Font, FontCache, TypesettingConfig};
use core_types::table::Table;
use core_types::list::List;
use glam::DVec2;
use parley::fontique::Blob;
use std::sync::Arc;
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))
}

View File

@@ -1,6 +1,6 @@
use core::f64;
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::{ATTR_TRANSFORM, CloneVarArgs, Context, Ctx, ExtractAll, InjectFootprint, ModifyFootprint, OwnedContextImpl};
use glam::{DAffine2, DMat2, DVec2};
@@ -16,12 +16,12 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
#[implementations(
Context -> DAffine2,
Context -> DVec2,
Context -> Table<Graphic>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> Table<Color>,
Context -> Table<GradientStops>,
Context -> List<Graphic>,
Context -> List<Vector>,
Context -> List<Raster<CPU>>,
Context -> List<Raster<GPU>>,
Context -> List<Color>,
Context -> List<GradientStops>,
)]
content: impl Node<Context<'static>, Output = T>,
#[widget(ParsedWidgetOverride::Custom = "transform_translation")] translation: DVec2,
@@ -56,18 +56,18 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
fn reset_transform<T>(
_: impl Ctx,
#[implementations(
Table<Graphic>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Raster<GPU>>,
Table<Color>,
Table<GradientStops>,
List<Graphic>,
List<Vector>,
List<Raster<CPU>>,
List<Raster<GPU>>,
List<Color>,
List<GradientStops>,
)]
mut content: Table<T>,
mut content: List<T>,
#[default(true)] reset_translation: bool,
reset_rotation: bool,
reset_scale: bool,
) -> Table<T> {
) -> List<T> {
for row_transform in content.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
if reset_translation {
row_transform.translation = DVec2::ZERO;
@@ -89,21 +89,21 @@ fn reset_transform<T>(
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"))]
fn replace_transform<T>(
_: impl Ctx + InjectFootprint,
#[implementations(
Table<Graphic>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Raster<GPU>>,
Table<Color>,
Table<GradientStops>,
List<Graphic>,
List<Vector>,
List<Raster<CPU>>,
List<Raster<GPU>>,
List<Color>,
List<GradientStops>,
)]
mut content: Table<T>,
mut content: List<T>,
transform: DAffine2,
) -> Table<T> {
) -> List<T> {
for row_transform in content.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_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.
/// 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))]
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()
}

View File

@@ -1,5 +1,5 @@
use core_types::list::List;
use core_types::registry::types::{Angle, PixelLength, PixelSize};
use core_types::table::Table;
use core_types::{CacheHash, Ctx};
use dyn_any::DynAny;
use glam::DVec2;
@@ -10,16 +10,16 @@ use vector_types::vector::misc::{HandleId, SpiralType};
use vector_types::vector::{PointId, SegmentId, StrokeId};
trait CornerRadius {
fn generate(self, size: DVec2, clamped: bool) -> Table<Vector>;
fn generate(self, size: DVec2, clamped: bool) -> List<Vector>;
}
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 };
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> {
fn generate(self, size: DVec2, clamped: bool) -> Table<Vector> {
impl CornerRadius for List<f64> {
fn generate(self, size: DVec2, clamped: bool) -> List<Vector> {
// Expand to four corners using the CSS `border-radius` shorthand rules.
// - `[a]` → `[a, a, a, a]`
// - `[a, b]` → `[a, b, a, b]`
@@ -50,7 +50,7 @@ impl CornerRadius for Table<f64> {
} else {
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")]
#[default(50.)]
radius: f64,
) -> Table<Vector> {
) -> List<Vector> {
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.
@@ -80,8 +80,8 @@ fn arc(
#[range((0., 360.))]
sweep_angle: Angle,
arc_type: ArcType,
) -> Table<Vector> {
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_arc(
) -> List<Vector> {
List::new_from_element(Vector::from_subpath(subpath::Subpath::new_arc(
radius,
start_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(25)] outer_radius: f64,
#[default(90.)] angular_resolution: f64,
) -> Table<Vector> {
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_spiral(
) -> List<Vector> {
List::new_from_element(Vector::from_subpath(subpath::Subpath::new_spiral(
inner_radius,
outer_radius,
turns,
@@ -126,7 +126,7 @@ fn ellipse(
#[unit(" px")]
#[default(25)]
radius_y: f64,
) -> Table<Vector> {
) -> List<Vector> {
let radius = DVec2::new(radius_x, radius_y);
let corner1 = -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])]);
}
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.
@@ -155,9 +155,9 @@ fn rectangle<T: CornerRadius>(
#[default(100)]
height: f64,
_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,
) -> Table<Vector> {
) -> List<Vector> {
corner_radius.generate(DVec2::new(width, height), clamped)
}
@@ -173,10 +173,10 @@ fn regular_polygon<T: AsU64>(
#[unit(" px")]
#[default(50)]
radius: f64,
) -> Table<Vector> {
) -> List<Vector> {
let points = sides.as_u64();
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.
@@ -194,12 +194,12 @@ fn star<T: AsU64>(
#[unit(" px")]
#[default(25)]
radius_2: f64,
) -> Table<Vector> {
) -> List<Vector> {
let points = sides.as_u64();
let diameter: f64 = radius_1 * 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))]
@@ -233,7 +233,7 @@ fn qr_code(
size: f64,
error_correction: QRCodeErrorCorrectionLevel,
#[default(false)] individual_squares: bool,
) -> Table<Vector> {
) -> List<Vector> {
let ecc = match error_correction {
QRCodeErrorCorrectionLevel::Low => qrcodegen::QrCodeEcc::Low,
QRCodeErrorCorrectionLevel::Medium => qrcodegen::QrCodeEcc::Medium,
@@ -241,7 +241,7 @@ fn qr_code(
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 {
true => {
@@ -270,7 +270,7 @@ fn qr_code(
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.
@@ -282,13 +282,13 @@ fn arrow(
#[default(10)] shaft_width: PixelLength,
#[default(30)] head_width: PixelLength,
#[default(20)] head_length: PixelLength,
) -> Table<Vector> {
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_arrow(DVec2::ZERO, arrow_to, shaft_width, head_width, head_length)))
) -> List<Vector> {
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"))]
fn line(_: impl Ctx, _primary: (), #[default(100., 100.)] line_to: PixelSize) -> Table<Vector> {
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_line(DVec2::ZERO, line_to)))
fn line(_: impl Ctx, _primary: (), #[default(100., 100.)] line_to: PixelSize) -> List<Vector> {
List::new_from_element(Vector::from_subpath(subpath::Subpath::new_line(DVec2::ZERO, line_to)))
}
trait GridSpacing {
@@ -319,7 +319,7 @@ fn grid<T: GridSpacing>(
#[default(10)] columns: u32,
#[default(10)] rows: u32,
#[default(30., 30.)] angles: DVec2,
) -> Table<Vector> {
) -> List<Vector> {
let (x_spacing, y_spacing) = spacing.as_dvec2().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)]

View File

@@ -1,4 +1,4 @@
use core_types::table::Table;
use core_types::list::List;
use core_types::uuid::NodeId;
use core_types::{ATTR_EDITOR_CLICK_TARGET, ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, Ctx};
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.
#[node_macro::node(category(""))]
async fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Box<VectorModification>, node_path: Table<NodeId>) -> Table<Vector> {
use core_types::table::Item;
async fn path_modify(_ctx: impl Ctx, mut vector: List<Vector>, modification: Box<VectorModification>, node_path: List<NodeId>) -> List<Vector> {
use core_types::list::Item;
if vector.is_empty() {
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`),
// 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();
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 });
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.
#[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);
for (element, transform) in elements.iter_mut().zip(transforms.iter_mut()) {
for (_, point) in element.point_domain.positions_mut() {

View File

@@ -3,8 +3,8 @@ use core::f64::consts::{PI, TAU};
use core::hash::{Hash, Hasher};
use core_types::blending::BlendMode;
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::table::{Item, Table, TableDyn};
use core_types::transform::{Footprint, Transform};
use core_types::uuid::NodeId;
use core_types::{
@@ -14,7 +14,7 @@ use core_types::{
use glam::{DAffine2, DMat2, DVec2};
use graphic_types::Vector;
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::{Affine, BezPath, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveArclen, PathEl, PathSeg, Shape};
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};
/// 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>`.
trait VectorTableIterMut {
/// Used for the fill and stroke nodes so they can apply to either `List<Graphic>` or `List<Vector>`.
trait VectorListIterMut {
fn for_each_vector_mut(&mut self, f: impl FnMut(&mut Vector, DAffine2));
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)) {
for graphic in self.iter_element_values_mut() {
let Some(vector_table) = graphic.as_vector_mut() else { continue };
let (elements, transforms) = vector_table.element_and_attribute_slices_mut::<DAffine2>(ATTR_TRANSFORM);
let Some(vector_list) = graphic.as_vector_mut() else { continue };
let (elements, transforms) = vector_list.element_and_attribute_slices_mut::<DAffine2>(ATTR_TRANSFORM);
for (vector, transform) in elements.iter_mut().zip(transforms.iter()) {
f(vector, *transform);
}
@@ -52,11 +52,11 @@ impl VectorTableIterMut for Table<Graphic> {
}
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)) {
let (elements, transforms) = self.element_and_attribute_slices_mut::<DAffine2>(ATTR_TRANSFORM);
for (vector, transform) in elements.iter_mut().zip(transforms.iter()) {
@@ -74,7 +74,7 @@ impl VectorTableIterMut for Table<Vector> {
async fn assign_colors<T>(
_: impl Ctx,
/// 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)]
mut content: T,
/// Whether to style the fill.
@@ -84,7 +84,7 @@ async fn assign_colors<T>(
stroke: bool,
/// The range of colors to select from.
#[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")]
gradient: Table<GradientStops>,
gradient: List<GradientStops>,
/// Whether to reverse the gradient.
reverse: bool,
/// 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,
) -> T
where
T: VectorTableIterMut + 'n + Send,
T: VectorListIterMut + 'n + Send,
{
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.
#[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,
/// The content with vector paths to apply the fill style to.
#[implementations(
Table<Vector>,
Table<Vector>,
Table<Vector>,
Table<Vector>,
Table<Graphic>,
Table<Graphic>,
Table<Graphic>,
Table<Graphic>,
List<Vector>,
List<Vector>,
List<Vector>,
List<Vector>,
List<Graphic>,
List<Graphic>,
List<Graphic>,
List<Graphic>,
)]
mut content: V,
/// The fill to paint the path with.
#[default(Color::BLACK)]
#[implementations(
Fill,
Table<Color>,
Table<GradientStops>,
List<Color>,
List<GradientStops>,
Gradient,
Fill,
Table<Color>,
Table<GradientStops>,
List<Color>,
List<GradientStops>,
Gradient,
)]
fill: F,
_backup_color: Table<Color>,
_backup_color: List<Color>,
_backup_gradient: Gradient,
) -> V {
let fill: Fill = fill.into();
@@ -182,7 +182,7 @@ impl IntoF64Vec for f64 {
vec![self]
}
}
impl IntoF64Vec for Table<f64> {
impl IntoF64Vec for List<f64> {
fn into_vec(self) -> Vec<f64> {
self.into_iter().map(|row| row.into_element()).collect()
}
@@ -198,11 +198,11 @@ impl IntoF64Vec for String {
async fn stroke<V, L: IntoF64Vec>(
_: impl Ctx,
/// The content with vector paths to apply the stroke style to.
#[implementations(Table<Vector>, Table<Vector>, Table<Vector>, Table<Graphic>, Table<Graphic>, Table<Graphic>)]
mut content: Table<V>,
#[implementations(List<Vector>, List<Vector>, List<Vector>, List<Graphic>, List<Graphic>, List<Graphic>)]
mut content: List<V>,
/// The stroke color.
#[default(Color::BLACK)]
color: Table<Color>,
color: List<Color>,
/// The stroke thickness.
#[unit(" px")]
#[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.
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.
#[implementations(Table<f64>, f64, String, Table<f64>, f64, String)]
#[implementations(List<f64>, f64, String, List<f64>, f64, String)]
dash_lengths: L,
/// The phase offset distance from the starting point of the dash pattern.
#[unit(" px")]
dash_offset: f64,
) -> Table<V>
) -> List<V>
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();
@@ -256,11 +256,11 @@ where
#[node_macro::node(name("Copy to Points"), category("Repeat"), path(core_types::vector))]
async fn copy_to_points<I: 'n + Send + Clone>(
_: impl Ctx,
points: Table<Vector>,
points: List<Vector>,
/// Artwork to be copied and placed at each point.
#[expose]
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>, Table<GradientStops>)]
content: Table<I>,
#[implementations(List<Graphic>, List<Vector>, List<Raster<CPU>>, List<Color>, List<GradientStops>)]
content: List<I>,
/// Minimum range of randomized sizes given to each placed copy.
#[default(1)]
#[range((0., 2.))]
@@ -281,8 +281,8 @@ async fn copy_to_points<I: 'n + Send + Clone>(
random_rotation: Angle,
/// Seed to determine unique variations on all the randomized copy angles.
random_rotation_seed: SeedValue,
) -> Table<I> {
let mut result_table = Table::new();
) -> List<I> {
let mut result_list = List::new();
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);
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))]
async fn round_corners(
_: impl Ctx,
source: Table<Vector>,
source: List<Vector>,
#[hard_min(0.)]
#[default(10.)]
radius: PixelLength,
@@ -351,7 +351,7 @@ async fn round_corners(
#[hard_max(180.)]
#[default(5.)]
min_angle_threshold: Angle,
) -> Table<Vector> {
) -> List<Vector> {
(0..source.len())
.map(|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))]
pub fn merge_by_distance(
_: impl Ctx,
content: Table<Vector>,
content: List<Vector>,
#[default(0.1)]
#[hard_min(0.0001)]
distance: PixelLength,
algorithm: MergeByDistanceAlgorithm,
) -> Table<Vector> {
) -> List<Vector> {
match algorithm {
MergeByDistanceAlgorithm::Spatial => content
.into_iter()
@@ -673,7 +673,7 @@ pub mod extrude_algorithms {
}
#[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() {
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))]
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 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);
// 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.set_attribute(ATTR_TRANSFORM, DAffine2::IDENTITY);
row
@@ -769,12 +769,12 @@ fn bilinear_interpolate(t: DVec2, quad: &[DVec2; 4]) -> DVec2 {
async fn pack_strips<T: 'n + Send + Clone>(
_: impl Ctx,
#[implementations(
Table<Graphic>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Raster<GPU>>,
List<Graphic>,
List<Vector>,
List<Raster<CPU>>,
List<Raster<GPU>>,
)]
elements: Table<T>,
elements: List<T>,
#[default(0.)]
#[unit(" px")]
separation: f64,
@@ -782,10 +782,10 @@ async fn pack_strips<T: 'n + Send + Clone>(
#[unit(" px")]
strip_max_length: f64,
strip_direction: RowsOrColumns,
) -> Table<T>
) -> List<T>
where
Graphic: From<Table<T>>,
Table<T>: BoundingBox,
Graphic: From<List<T>>,
List<T>: BoundingBox,
{
// 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)
@@ -802,8 +802,8 @@ where
let mut items: Vec<(f64, f64, DVec2, Item<T>)> = elements
.into_iter()
.map(|row| {
// Single-item `Table` to query its bounding box
let single = Table::new_from_item(row.clone());
// Single-item `List` to query its bounding box
let single = List::new_from_item(row.clone());
let (w, h, top_left) = match single.bounding_box(DAffine2::IDENTITY, false) {
RenderBoundingBox::Rectangle([min, max]) => {
let size = max - min;
@@ -822,7 +822,7 @@ where
// Sort by cross-axis size, largest first
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();
// 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))]
async fn auto_tangents(
_: impl Ctx,
source: Table<Vector>,
source: List<Vector>,
/// The amount of spread for the auto-tangents, from 0 (sharp corner) to 1 (full spread).
#[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
@@ -898,7 +898,7 @@ async fn auto_tangents(
/// If active, existing non-zero handles won't be affected.
#[default(true)]
preserve_existing: bool,
) -> Table<Vector> {
) -> List<Vector> {
(0..source.len())
.map(|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))]
async fn bounding_box(_: impl Ctx, content: Table<Vector>) -> Table<Vector> {
async fn bounding_box(_: impl Ctx, content: List<Vector>) -> List<Vector> {
content
.into_iter()
.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))]
async fn dimensions(_: impl Ctx, content: Table<Vector>) -> DVec2 {
async fn dimensions(_: impl Ctx, content: List<Vector>) -> DVec2 {
(0..content.len())
.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)])
@@ -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.
#[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();
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.
#[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() {
let mut segment_domain = SegmentDomain::new();
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"))]
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
.into_iter()
.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))]
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
let graphic_table = content.into_graphic_table();
let flattened: Table<Vector> = graphic_table.clone().into_flattened_table();
let graphic_list = content.into_graphic_list();
let flattened: List<Vector> = graphic_list.clone().into_flattened_list();
let mut output: Table<Vector> = flattened
let mut output: List<Vector> = flattened
.into_iter()
.flat_map(|row| {
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);
// 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 {
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<_>>(),
@@ -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
// 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.
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);
if row_0_transform.matrix2.determinant().abs() > f64::EPSILON {
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;
}
}
output.set_attribute(ATTR_EDITOR_MERGED_LAYERS, 0, graphic_table);
output.set_attribute(ATTR_EDITOR_MERGED_LAYERS, 0, graphic_list);
}
output
}
#[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
.into_iter()
.flat_map(|row| {
@@ -1283,7 +1283,7 @@ async fn separate_subpaths(_: impl Ctx, content: Table<Vector>) -> Table<Vector>
async fn path_is_closed(
_: impl Ctx,
/// 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.
index: f64,
) -> bool {
@@ -1295,7 +1295,7 @@ async fn path_is_closed(
}
#[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 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.
#[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> {
let graphic_table = content.into_graphic_table();
let flattened = graphic_table.clone().into_flattened_table::<Vector>();
pub async fn flatten_path<T: IntoGraphicList + 'n + Send>(_: impl Ctx, #[implementations(List<Graphic>, List<Vector>)] content: T) -> List<Vector> {
let graphic_list = content.into_graphic_list();
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
let mut output_table = Table::new_from_element(Vector::default());
let output = output_table.element_mut(0).unwrap();
// Create a `List` with one empty `Vector` element, then get a mutable reference to it which we append flattened subpaths to
let mut output_list = List::new_from_element(Vector::default());
let output = output_list.element_mut(0).unwrap();
// Concatenate every vector element's subpaths into the single output compound path
for index in 0..flattened.len() {
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 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();
}
// 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.
// 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
if !flattened.is_empty() {
let primary = flattened.len() - 1;
let layer_path: Table<NodeId> = flattened.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, primary);
output_table.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer_path);
let layer_path: List<NodeId> = flattened.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, primary);
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.
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector), properties("sample_polyline_properties"), memoize)]
async fn sample_polyline(
_: impl Ctx,
content: Table<Vector>,
content: List<Vector>,
spacing: PointSpacingType,
#[default(100.)]
#[hard_min(0.)]
@@ -1373,7 +1373,7 @@ async fn sample_polyline(
#[unit(" px")]
stop_offset: f64,
adaptive_spacing: bool,
) -> Table<Vector> {
) -> List<Vector> {
let pathseg_perimeter = |segment: PathSeg| {
if is_linear(segment) {
Line::new(segment.start(), segment.end()).perimeter(DEFAULT_ACCURACY)
@@ -1444,12 +1444,12 @@ async fn sample_polyline(
async fn simplify(
_: impl Ctx,
/// The vector paths to simplify.
content: Table<Vector>,
content: List<Vector>,
/// The maximum distance the simplified path may deviate from the original.
#[default(5.)]
#[unit(" px")]
tolerance: Length,
) -> Table<Vector> {
) -> List<Vector> {
if tolerance <= 0. {
return content;
}
@@ -1488,12 +1488,12 @@ async fn simplify(
async fn decimate(
_: impl Ctx,
/// 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.
#[default(5.)]
#[unit(" px")]
tolerance: Length,
) -> Table<Vector> {
) -> List<Vector> {
// Tolerance of 0 means no simplification is possible, so return immediately
if tolerance <= 0. {
return content;
@@ -1616,14 +1616,14 @@ async fn decimate(
async fn cut_path(
_: impl Ctx,
/// 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, 01 for one subpath, 12 for a second subpath, and so on.
progression: Progression,
/// Swap the direction of the path.
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.
parameterized_distance: bool,
) -> Table<Vector> {
) -> List<Vector> {
let euclidian = !parameterized_distance;
let bezpaths = content
@@ -1664,7 +1664,7 @@ async fn cut_path(
/// Cuts path segments into separate disconnected pieces where each is a distinct subpath.
#[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
for vector in content.iter_element_values_mut() {
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(
_: impl Ctx,
/// The path to traverse.
content: Table<Vector>,
content: List<Vector>,
/// The factor from the start to the end of the path, 01 for one subpath, 12 for a second subpath, and so on.
progression: Progression,
/// Swap the direction of the path.
@@ -1764,7 +1764,7 @@ async fn position_on_path(
async fn tangent_on_path(
_: impl Ctx,
/// The path to traverse.
content: Table<Vector>,
content: List<Vector>,
/// The factor from the start to the end of the path, 01 for one subpath, 12 for a second subpath, and so on.
progression: Progression,
/// 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)]
async fn scatter_points(
_: impl Ctx,
content: Table<Vector>,
content: List<Vector>,
#[unit(" px")]
#[default(10.)]
#[hard_min(0.01)]
#[range((1., 100.))]
separation: f64,
seed: SeedValue,
) -> Table<Vector> {
) -> List<Vector> {
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
content
@@ -1858,7 +1858,7 @@ async fn scatter_points(
}
#[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
.into_iter()
.filter_map(|mut row| {
@@ -1961,7 +1961,7 @@ fn apply_point_deltas(element: &mut Vector, deltas: &[DVec2], transform: DAffine
async fn jitter_points(
_: impl Ctx,
/// 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.
#[default(5.)]
#[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.
#[default(true)]
along_normals: bool,
) -> Table<Vector> {
) -> List<Vector> {
content
.into_iter()
.map(|mut row| {
@@ -2011,12 +2011,12 @@ async fn jitter_points(
async fn offset_points(
_: impl Ctx,
/// 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.
#[default(10.)]
#[unit(" px")]
distance: f64,
) -> Table<Vector> {
) -> List<Vector> {
content
.into_iter()
.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.
#[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,
/// 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,
/// 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,
@@ -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.
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.
path: Table<Vector>,
) -> Table<Vector> {
path: List<Vector>,
) -> List<Vector> {
/// 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)
/// 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.
let mut graphic_table_content = content.clone().into_graphic_table();
// Preserve original `List<Graphic>` as upstream data so this group layer's nested layers can be edited by the tools.
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.
let content = content.into_flattened_table::<Vector>();
// If the input isn't a List<Vector>, we convert it into one by flattening any List<Graphic> content.
let content = content.into_flattened_list::<Vector>();
// Not enough elements to interpolate between, so we return the input as-is
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.
if lerped_transform.matrix2.determinant().abs() > f64::EPSILON {
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;
}
}
@@ -2411,9 +2411,9 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
let mut attributes = content.clone_item_attributes(endpoint_index);
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 {
@@ -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 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 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)
.with_attribute(ATTR_TRANSFORM, lerped_transform)
.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_CLIPPING_MASK, lerped_clip)
.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))]
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
.into_iter()
.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))]
fn close_path(_: impl Ctx, source: Table<Vector>) -> Table<Vector> {
fn close_path(_: impl Ctx, source: List<Vector>) -> List<Vector> {
source
.into_iter()
.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))]
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| {
let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
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: (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))]
async fn count_elements(_: impl Ctx, content: TableDyn) -> f64 {
async fn count_elements(_: impl Ctx, content: ListDyn) -> f64 {
content.len() as f64
}
#[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()
}
/// 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.
#[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))]
async fn index_points(
_: impl Ctx,
/// 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.
index: f64,
) -> DVec2 {
@@ -2932,7 +2932,7 @@ async fn index_points(
}
#[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())
.map(|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))]
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 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))]
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 vector = content.eval(new_ctx).await;
@@ -3042,8 +3042,8 @@ mod test {
}
}
fn vector_node_from_bezpath(bezpath: BezPath) -> Table<Vector> {
Table::new_from_element(Vector::from_bezpath(bezpath))
fn vector_node_from_bezpath(bezpath: BezPath) -> List<Vector> {
List::new_from_element(Vector::from_bezpath(bezpath))
}
fn create_vector_item(bezpath: BezPath, transform: DAffine2) -> Item<Vector> {
@@ -3070,7 +3070,7 @@ mod test {
// Test a rectangular path with non-zero rotation
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));
let bounding_box = BoundingBoxNode { content: FutureWrapperNode(square) }.eval(Footprint::default()).await;
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 transform = DAffine2::from_scale(DVec2::new(2., 2.));
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)
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());
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();
// Geometry stays in local space (original rectangle coordinates)
assert_eq!(
@@ -3259,11 +3259,11 @@ mod test {
source.push(curve.as_path_el());
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();
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 beveled_table = super::bevel(Footprint::default(), vector_node_from_bezpath(subpath), 5.);
let beveled = beveled_table.element(0).unwrap();
let beveled_list = super::bevel(Footprint::default(), vector_node_from_bezpath(subpath), 5.);
let beveled = beveled_list.element(0).unwrap();
assert_eq!(beveled.point_domain.positions().len(), 6);
assert_eq!(beveled.segment_domain.ids().len(), 5);