Remove the runtime-named attribute family and its type-erased bridges

This commit is contained in:
Dennis Kobert
2026-08-23 19:05:26 +00:00
parent faa5c9d3db
commit c0eb2ee4ff
5 changed files with 107 additions and 386 deletions

View File

@@ -1,19 +1,18 @@
use glam::{DAffine2, DVec2, IVec2};
use graphene_std::gradient::GradientStops;
use graphene_std::list::{AttributeDyn, AttributeValueDyn, List, ListDyn};
use graphene_std::list::List;
#[cfg(feature = "gpu")]
use graphene_std::raster::GPU;
use graphene_std::raster::color::Color;
use graphene_std::raster::*;
#[cfg(feature = "gpu")]
use graphene_std::SourceId;
use graphene_std::raster::{CPU, Raster};
use graphene_std::registry::{ConstructionError, EdgeHandle, NodeIOTypes, RegistryEntry};
#[cfg(feature = "gpu")]
use graphene_std::SourceId;
#[cfg(feature = "gpu")]
use graphene_std::runtime::RuntimeHandle;
use graphene_std::uuid::NodeId;
use graphene_std::vector::Vector;
use graphene_std::{Artboard, Context, Graphic, ProtoNodeIdentifier, concrete};
use graphene_std::{Context, Graphic, ProtoNodeIdentifier, concrete};
use node_registry_macros::{convert_node, into_node};
use std::collections::HashMap;
#[cfg(feature = "gpu")]
@@ -32,57 +31,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
convert_node!(from: List<Raster<CPU>>, to: List<Graphic>),
#[cfg(feature = "gpu")]
convert_node!(from: List<Raster<GPU>>, to: List<Graphic>),
// Type-erased attribute conversions for the `Attach Attribute` node, so it monomorphizes only over the destination `List` type.
convert_node!(from: List<Artboard>, to: AttributeDyn),
convert_node!(from: List<Graphic>, to: AttributeDyn),
convert_node!(from: List<Vector>, to: AttributeDyn),
convert_node!(from: List<Raster<CPU>>, to: AttributeDyn),
convert_node!(from: List<Color>, to: AttributeDyn),
convert_node!(from: List<GradientStops>, to: AttributeDyn),
convert_node!(from: List<f64>, to: AttributeDyn),
convert_node!(from: List<bool>, to: AttributeDyn),
convert_node!(from: List<String>, to: AttributeDyn),
convert_node!(from: List<DAffine2>, to: AttributeDyn),
convert_node!(from: List<BlendMode>, to: AttributeDyn),
convert_node!(from: List<graphene_std::vector::style::GradientType>, to: AttributeDyn),
convert_node!(from: List<graphene_std::vector::style::GradientSpreadMethod>, to: AttributeDyn),
convert_node!(from: List<Artboard>, to: ListDyn),
convert_node!(from: List<Graphic>, to: ListDyn),
convert_node!(from: List<Vector>, to: ListDyn),
convert_node!(from: List<Raster<CPU>>, to: ListDyn),
#[cfg(feature = "gpu")]
convert_node!(from: List<Raster<GPU>>, to: ListDyn),
convert_node!(from: List<Color>, to: ListDyn),
convert_node!(from: List<GradientStops>, to: ListDyn),
convert_node!(from: List<f64>, to: ListDyn),
convert_node!(from: List<bool>, to: ListDyn),
convert_node!(from: List<String>, to: ListDyn),
convert_node!(from: List<u8>, to: ListDyn),
convert_node!(from: List<NodeId>, to: ListDyn),
convert_node!(from: List<DAffine2>, to: ListDyn),
convert_node!(from: List<BlendMode>, to: ListDyn),
convert_node!(from: List<graphene_std::vector::style::GradientType>, to: ListDyn),
convert_node!(from: List<graphene_std::vector::style::GradientSpreadMethod>, to: ListDyn),
// Type-erased attribute value conversions for the `Write Attribute` node, so it monomorphizes only over the destination `List` type.
convert_node!(from: f64, to: AttributeValueDyn),
convert_node!(from: u32, to: AttributeValueDyn),
convert_node!(from: u64, to: AttributeValueDyn),
convert_node!(from: bool, to: AttributeValueDyn),
convert_node!(from: String, to: AttributeValueDyn),
convert_node!(from: DVec2, to: AttributeValueDyn),
convert_node!(from: DAffine2, to: AttributeValueDyn),
convert_node!(from: Color, to: AttributeValueDyn),
convert_node!(from: BlendMode, to: AttributeValueDyn),
convert_node!(from: graphene_std::vector::style::GradientType, to: AttributeValueDyn),
convert_node!(from: graphene_std::vector::style::GradientSpreadMethod, to: AttributeValueDyn),
convert_node!(from: List<String>, to: AttributeValueDyn),
convert_node!(from: List<NodeId>, to: AttributeValueDyn),
convert_node!(from: List<Color>, to: AttributeValueDyn),
convert_node!(from: List<GradientStops>, to: AttributeValueDyn),
convert_node!(from: List<Vector>, to: AttributeValueDyn),
convert_node!(from: List<Raster<CPU>>, to: AttributeValueDyn),
convert_node!(from: List<Raster<GPU>>, to: AttributeValueDyn),
convert_node!(from: List<Graphic>, to: AttributeValueDyn),
// into_node!(from: List<Raster<CPU>>, to: List<Raster<SRGBA8>>),
convert_node!(from: DVec2, to: DVec2),
convert_node!(from: List<Vector>, to: List<Vector>),
@@ -174,15 +122,17 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
node_types.extend(
graphene_std::graphic::level_to_list_entries()
.into_iter()
.zip(["List<Graphic>", "List<Vector>", "List<Raster<CPU>>", "List<Raster<GPU>>", "List<Color>", "List<GradientStops>", "List<String>"])
.zip([
"List<Graphic>",
"List<Vector>",
"List<Raster<CPU>>",
"List<Raster<GPU>>",
"List<Color>",
"List<GradientStops>",
"List<String>",
])
.map(|(entry, target)| (ProtoNodeIdentifier::with_owned_string(format!("graphene_core::ops::ConvertNode<{target}>")), entry)),
);
// The same bridge into the attribute family's type-erased list.
node_types.extend(
graphene_std::graphic::level_to_list_dyn_entries()
.into_iter()
.map(|entry| (ProtoNodeIdentifier::new("graphene_core::ops::ConvertNode<ListDyn>"), entry)),
);
// =============
// CONVERT NODES
// =============
@@ -209,7 +159,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
.flatten(),
);
let mut map: HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> = HashMap::new();
let insert = |map: &mut HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>>, id: ProtoNodeIdentifier, entry: RegistryEntry| {
let rows = map.entry(id).or_default();
@@ -390,7 +339,6 @@ mod node_registry_macros {
};
}
pub(crate) use convert_node;
pub(crate) use into_node;
}

View File

@@ -2,15 +2,15 @@ use core_types::attribute::{Attr, EditorLayerPath, Transform as TransformAttr};
use core_types::bounds::{BoundingBox, RenderBoundingBox};
use core_types::extent::{ExtentIn, LevelIn, ListIn, ValueIn};
use core_types::gpoll::{Extent, GPoll, GraphError, Interrupt, Level};
use core_types::list::{AttributeDyn, AttributeValueDyn, Item, List, ListDyn};
use core_types::list::List;
use core_types::registry::types::{Angle, SignedInteger};
use core_types::uuid::NodeId;
use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, AnyHash, BlendMode, CacheHash, Color, Context, Ctx, DeriveCtx, ExtractIndex, InjectIndex};
use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, CacheHash, Color, Ctx, DeriveCtx, ExtractIndex, InjectIndex};
use glam::{DAffine2, DVec2};
use graphic_types::graphic::{Graphic, IntoGraphicList};
use graphic_types::{ATTR_EDITOR_MERGED_LAYERS, Artboard, Vector};
use raster_types::{CPU, GPU, Raster};
use vector_types::gradient::{GradientSpreadMethod, GradientType};
use vector_types::{GradientStop, GradientStops, ReferencePoint};
/// Resolves a signed index over `total` lanes: negatives count from the end,
@@ -98,7 +98,8 @@ fn omit_element_extent(list: ExtentIn<'_>, index: ValueIn<'_, f64>, level: Level
pub fn extract_element<T: Clone + Default + Send + Sync + CacheHash + 'static>(
_: impl Ctx + ExtractIndex + InjectIndex + Copy,
/// The `List` of data to extract from.
#[implementations(String, f64, NodeId, Color, GradientStops, Vector, Raster<CPU>, Graphic, Artboard)] list: IList<T>,
#[implementations(String, f64, NodeId, Color, GradientStops, Vector, Raster<CPU>, Graphic, Artboard)]
list: IList<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 {
@@ -274,7 +275,15 @@ fn mirror<'e>(
)>,
Interrupt,
> {
mirror_lane(ctx.arena(), legacy_render_list_of(content), ctx.innermost_index() as usize, relative_to_bounds, offset, angle, keep_original)
mirror_lane(
ctx.arena(),
legacy_render_list_of(content),
ctx.innermost_index() as usize,
relative_to_bounds,
offset,
angle,
keep_original,
)
}
/// The kept originals double the level, counted from the subject's extent
@@ -324,7 +333,15 @@ fn mirror_vector<'e>(
)>,
Interrupt,
> {
mirror_lane(ctx.arena(), legacy_render_list_of(content), ctx.innermost_index() as usize, relative_to_bounds, offset, angle, keep_original)
mirror_lane(
ctx.arena(),
legacy_render_list_of(content),
ctx.innermost_index() as usize,
relative_to_bounds,
offset,
angle,
keep_original,
)
}
fn mirror_vector_extent(
@@ -371,281 +388,6 @@ pub fn stamp_layer_path<'e, T>(ctx: impl Ctx + ExtractArena<'e>, element: T, pat
Ok((element, Attr(parked.as_slice())))
}
/// 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
/// monomorphizes over `T` instead of the cartesian product `(T, U)`.
#[node_macro::node(category("Attributes: Write"))]
fn write_attribute<T: AnyHash + Clone + Send + Sync + CacheHash>(
ctx: impl Ctx + DeriveCtx,
/// The `List` to set the named attribute on (one value per item).
#[implementations(
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: 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<Context<'_>, Output = AttributeValueDyn>,
) -> Result<List<T>, Interrupt> {
let spilled = ctx.index_head();
for index in 0..content.len() {
let row = content.clone_item(index).expect("index is within bounds");
let item = List::new_from_item(row);
let scoped = ctx.push_vararg(&item);
let v = value.eval(&scoped.ctx().promoted(&spilled, index as u64))?;
content.set_attribute_value_dyn(&name, index, v);
}
Ok(content)
}
/// 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 `List` to attach the new attribute to.
#[implementations(
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: List<T>,
/// The source values to attach.
#[expose]
source: AttributeDyn,
/// The name to assign to the new destination attribute.
name: String,
) -> List<T> {
if source.is_empty() {
return content;
}
content.set_attribute_dyn(name, source);
content
}
/// Reads a named `Vector` attribute from the input list, outputting each value as an element of a new `Vector[]`.
#[node_macro::node(category("Attributes: Read"))]
fn read_attribute_vector(
_: impl Ctx,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> 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()));
}
result
}
/// Reads a named numeric attribute (`f64`, `u64`, or `u32`) from the input list, outputting each value as an element of a new `f64[]`. Integer values are converted to `f64`.
#[node_macro::node(category("Attributes: Read"))]
fn read_attribute_number(
_: impl Ctx,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> List<f64> {
let mut result = List::with_capacity(content.len());
for index in 0..content.len() {
let value = content
.attribute::<f64>(&name, index)
.copied()
.or_else(|| content.attribute::<u64>(&name, index).map(|v| *v as f64))
.or_else(|| content.attribute::<u32>(&name, index).map(|v| *v as f64));
let Some(value) = value else { continue };
result.push(Item::new_from_element(value));
}
result
}
/// Reads a named `bool` attribute from the input list, outputting each value as an element of a new `bool[]`.
#[node_macro::node(category("Attributes: Read"))]
fn read_attribute_bool(
_: impl Ctx,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> 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));
}
result
}
/// Reads a named `String` attribute from the input list, outputting each value as an element of a new `String[]`.
#[node_macro::node(category("Attributes: Read"))]
fn read_attribute_string(
_: impl Ctx,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> 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()));
}
result
}
/// Reads a named `DAffine2` transform attribute from the input list, outputting each value as an element of a new `DAffine2[]`.
#[node_macro::node(category("Attributes: Read"))]
fn read_attribute_transform(
_: impl Ctx,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> 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));
}
result
}
/// Reads a named `Color` attribute from the input list, outputting each value as an element of a new `Color[]`.
#[node_macro::node(category("Attributes: Read"))]
fn read_attribute_color(
_: impl Ctx,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> 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));
}
result
}
/// Reads a named `BlendMode` attribute from the input list, outputting each value as an element of a new `BlendMode[]`.
#[node_macro::node(category("Attributes: Read"))]
fn read_attribute_blend_mode(
_: impl Ctx,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> 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));
}
result
}
/// Reads a named `GradientType` attribute from the input list, outputting each value as an element of a new `GradientType[]`.
#[node_macro::node(category("Attributes: Read"))]
fn read_attribute_gradient_type(
_: impl Ctx,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> 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));
}
result
}
/// Reads a named `GradientSpreadMethod` attribute from the input list, outputting each value as an element of a new `GradientSpreadMethod[]`.
#[node_macro::node(category("Attributes: Read"))]
fn read_attribute_spread_method(
_: impl Ctx,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> 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));
}
result
}
/// Reads a named `GradientStops` attribute from the input list, outputting each value as an element of a new `GradientStops[]`.
#[node_macro::node(category("Attributes: Read"))]
fn read_attribute_gradient_stops(
_: impl Ctx,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> 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()));
}
result
}
/// Reads a named `Artboard` attribute from the input list, outputting each value as an element of a new `Artboard[]`.
#[node_macro::node(category("Attributes: Read"))]
fn read_attribute_artboard(
_: impl Ctx,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> 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()));
}
result
}
/// Reads a named `Raster` attribute from the input list, outputting each value as an element of a new `Raster[]`.
#[node_macro::node(category("Attributes: Read"))]
fn read_attribute_raster(
_: impl Ctx,
content: ListDyn,
/// The attribute name (key) to read.
name: String,
) -> 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()));
}
result
}
/// Joins two levels of the same type, the base's lanes followed by the new's.
#[node_macro::node(category("General"), extent(extend_extent))]
pub fn extend<T>(
@@ -783,20 +525,6 @@ pub fn level_to_list<T: Clone + Send + Sync + CacheHash + 'static>(
graphic_types::graphic::run_to_render_list::<T>(&item).expect("the run holds the row's element type")
}
/// The transitional level bridge into the type-erased list the attribute
/// family consumes, as [`level_to_list`].
#[node_macro::node(category(""))]
pub fn level_to_list_dyn<T: Clone + Send + Sync + CacheHash + 'static>(
_: impl Ctx + ExtractIndex + InjectIndex + Copy,
#[implementations(Graphic, Vector, Raster<CPU>, Raster<GPU>, Color, GradientStops, String)] value: IList<T>,
_converter: (),
) -> ListDyn {
// SAFETY: a materialized input's frames are arena-resident.
let item = unsafe { core_types::record::GroupItem::from_resident(value.batch()) };
graphic_types::graphic::run_to_render_list::<T>(&item).expect("the run holds the row's element type").into()
}
pub use _level_to_list_dyn_mod::level_to_list_dyn_entries;
pub use _level_to_list_mod::level_to_list_entries;
pub use _to_graphic_mod::to_graphic_entries;

View File

@@ -8,8 +8,8 @@ use core_types::extent::{ExtentIn, LevelIn, ListIn, ValueIn};
use core_types::gpoll::{Extent, GPoll, GraphError, Interrupt};
use core_types::{ATTR_TRANSFORM, Color, Ctx};
use glam::DAffine2;
use graphic_types::graphic::Graphic;
use graphic_types::Vector;
use graphic_types::graphic::Graphic;
use raster_types::{CPU, Raster};
use vector_types::{GradientStop, GradientStops};
@@ -335,14 +335,27 @@ mod tests {
fn fixture_rows() -> Vec<(Graphic, DAffine2)> {
vec![
(text("a"), translation(1.)),
(group(vec![(text("b"), translation(20.)), (group(vec![(text("c"), translation(300.))]), translation(4000.))]), translation(0.5)),
(
group(vec![(text("b"), translation(20.)), (group(vec![(text("c"), translation(300.))]), translation(4000.))]),
translation(0.5),
),
]
}
macro_rules! build {
($layout:ident, $rows:expr, $fully:expr) => {
install(
FlattenNode::new(RecordSource::new(GraphicSource { layout: $layout.clone(), rows: $rows }, &$layout, &$layout), ValueNode($fully)),
FlattenNode::new(
RecordSource::new(
GraphicSource {
layout: $layout.clone(),
rows: $rows,
},
&$layout,
&$layout,
),
ValueNode($fully),
),
flatten_layout_meta(),
&[Some(&$layout)],
)
@@ -425,7 +438,14 @@ mod tests {
let layout = graphic_layout();
let node = install(
MapNode::<_, _, Graphic>::new(
RecordSource::new(GraphicSource { layout: layout.clone(), rows: ragged_rows() }, &layout, &layout),
RecordSource::new(
GraphicSource {
layout: layout.clone(),
rows: ragged_rows(),
},
&layout,
&layout,
),
PerRowSource { layout: layout.clone() },
&layout,
),
@@ -464,7 +484,14 @@ mod tests {
let layout = graphic_layout();
let flat = install(
FlatMapNode::<_, _, Graphic>::new(
RecordSource::new(GraphicSource { layout: layout.clone(), rows: ragged_rows() }, &layout, &layout),
RecordSource::new(
GraphicSource {
layout: layout.clone(),
rows: ragged_rows(),
},
&layout,
&layout,
),
PerRowSource { layout: layout.clone() },
&layout,
),
@@ -473,7 +500,14 @@ mod tests {
);
let mapped = install(
MapNode::<_, _, Graphic>::new(
RecordSource::new(GraphicSource { layout: layout.clone(), rows: ragged_rows() }, &layout, &layout),
RecordSource::new(
GraphicSource {
layout: layout.clone(),
rows: ragged_rows(),
},
&layout,
&layout,
),
PerRowSource { layout: layout.clone() },
&layout,
),
@@ -536,7 +570,14 @@ mod tests {
let layout = graphic_layout();
let node = install(
FlatMapNode::<_, _, Graphic>::new(
RecordSource::new(GraphicSource { layout: layout.clone(), rows: ragged_rows() }, &layout, &layout),
RecordSource::new(
GraphicSource {
layout: layout.clone(),
rows: ragged_rows(),
},
&layout,
&layout,
),
PerRowSource { layout: layout.clone() },
&layout,
),
@@ -608,7 +649,11 @@ mod tests {
};
assert_eq!(children.len(), 1);
assert_eq!(text_of(children.element(0).unwrap()), "c");
assert_eq!(children.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0).translation.x, 300., "embedded transforms ride untouched");
assert_eq!(
children.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0).translation.x,
300.,
"embedded transforms ride untouched"
);
let transform: DAffine2 = unsafe { rec.read(offset) };
assert_eq!(transform.translation.x, 4000.5);
unsafe { stack::rewind(mark) };
@@ -661,7 +706,7 @@ mod tests {
let layout = graphic_layout();
let rows = vec![(text("a"), translation(1.)), (text("b"), translation(2.))];
let node = install(
WrapNode::new(RecordSource::new(GraphicSource { layout: layout.clone(), rows, }, &layout, &layout), &layout),
WrapNode::new(RecordSource::new(GraphicSource { layout: layout.clone(), rows }, &layout, &layout), &layout),
wrap_layout_meta(),
&[Some(&layout)],
);
@@ -753,12 +798,7 @@ mod tests {
let layout = Layout::default().with_writes(1, record::element_write_hashed::<Color>(), &[]);
let out = Layout::default().with_writes(0, record::element_write_hashed::<GradientStops>(), &[]);
let build = |colors: Vec<Color>| {
install_flip(
ToGradientNode::new(RecordSource::new(ColorSource { layout: layout.clone(), colors }, &layout, &layout), &layout),
&out,
)
};
let build = |colors: Vec<Color>| install_flip(ToGradientNode::new(RecordSource::new(ColorSource { layout: layout.clone(), colors }, &layout, &layout), &layout), &out);
let stops_of = |colors: Vec<Color>| {
let node = build(colors);
let GPoll::Final(value) = node.eval(&ctx) else {
@@ -788,7 +828,7 @@ mod tests {
let layout = graphic_layout();
let rows = vec![(text("a"), translation(1.)), (text("b"), translation(2.))];
let node = install(
WrapNode::new(RecordSource::new(GraphicSource { layout: layout.clone(), rows, }, &layout, &layout), &layout),
WrapNode::new(RecordSource::new(GraphicSource { layout: layout.clone(), rows }, &layout, &layout), &layout),
wrap_layout_meta(),
&[Some(&layout)],
);
@@ -819,7 +859,7 @@ mod tests {
let layout = graphic_layout();
let rows = vec![(text("a"), translation(1.)), (text("b"), translation(2.))];
let wrapped = install(
WrapNode::new(RecordSource::new(GraphicSource { layout: layout.clone(), rows, }, &layout, &layout), &layout),
WrapNode::new(RecordSource::new(GraphicSource { layout: layout.clone(), rows }, &layout, &layout), &layout),
wrap_layout_meta(),
&[Some(&layout)],
);

View File

@@ -10,17 +10,17 @@ use core_types::extent::{ExtentIn, LevelIn, ListIn, ValueIn};
use core_types::gpoll::GraphError;
use core_types::gpoll::Interrupt;
use core_types::gpoll::{Extent, GPoll};
use core_types::list::{Item, ItemAttributeValues, List, ListDyn};
use core_types::list::{Item, ItemAttributeValues, List};
use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLength, Progression, SeedValue};
use core_types::transform::Transform;
use core_types::uuid::NodeId;
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Color, Ctx, DeriveCtx, ExtractIndex, InjectIndex};
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, CacheHash, Color, Ctx, DeriveCtx, ExtractIndex, InjectIndex};
use glam::{DAffine2, DMat2, DVec2};
use graphic_types::Vector;
use graphic_types::graphic::{bake_paint_transforms, graphic_list_at, has_paint_at, is_paint_present, set_paint_attribute_at};
use graphic_types::markers::{EditorMergedLayers, Fill, Stroke as StrokeAttr};
use graphic_types::raster_types::{CPU, GPU, Raster};
use graphic_types::{ATTR_EDITOR_MERGED_LAYERS, ATTR_FILL, ATTR_STROKE, Graphic, IntoGraphicList};
use graphic_types::{Artboard, Vector};
use kurbo::simplify::{SimplifyOptions, simplify_bezpath};
use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveArclen, PathEl, PathSeg, Shape};
use rand::{Rng, SeedableRng};
@@ -3540,7 +3540,10 @@ fn point_inside(_: impl Ctx + ExtractIndex + InjectIndex + Copy, source: IList<V
// 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))]
fn count_elements(_: impl Ctx, content: ListDyn) -> f64 {
fn count_elements<T: Clone + Send + Sync + CacheHash + 'static>(
_: impl Ctx + ExtractIndex + InjectIndex + Copy,
#[implementations(Graphic, Artboard, Vector, Raster<CPU>, Color, GradientStops, String)] content: IList<T>,
) -> f64 {
content.len() as f64
}