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

@@ -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);