Convert the remaining nodes and mark genuine async sources

This commit is contained in:
Dennis Kobert
2026-07-26 22:58:40 +00:00
parent 28d37d30d0
commit 28d9e3cbe3
17 changed files with 398 additions and 114 deletions

View File

@@ -0,0 +1,291 @@
use std::any::Any;
use std::mem::MaybeUninit;
use std::ops::Add;
use core_types::arena::{Arena, ArenaCell};
use core_types::context::{ContextImpl, Ctx, EvalScope, ExtractArena, InjectIndex};
use core_types::gnode::{BatchStatus, GNode, StatusCell};
use core_types::gpoll::{ErrorKind, Finality, GPoll, Interrupt};
fn add<A: Add<B>, B, C: Ctx>(_ctx: &C, augend: A, addend: B) -> <A as Add<B>>::Output {
augend + addend
}
struct AddNode<Node0, Node1> {
augend: Node0,
addend: Node1,
}
impl<Node0, Node1> AddNode<Node0, Node1> {
fn new(augend: Node0, addend: Node1) -> Self {
Self { augend, addend }
}
}
impl<A, B, Input, Node0, Node1> GNode<Input> for AddNode<Node0, Node1>
where
A: Add<B>,
Input: Ctx,
Node0: GNode<Input, Output = A>,
Node1: GNode<Input, Output = B>,
{
type Output = <A as Add<B>>::Output;
fn eval(&self, input: &Input) -> GPoll<Self::Output> {
let cell = StatusCell::new();
let augend = match cell.eval_input(0, &self.augend, input) {
Ok(value) => value,
Err(interrupt) => return interrupt.into(),
};
let addend = match cell.eval_input(1, &self.addend, input) {
Ok(value) => value,
Err(interrupt) => return interrupt.into(),
};
cell.finish(add(input, augend, addend))
}
}
struct ValueNode<T>(T);
impl<T: Clone, Input> GNode<Input> for ValueNode<T> {
type Output = T;
fn eval(&self, _input: &Input) -> GPoll<T> {
GPoll::Final(self.0.clone())
}
}
struct ReadIndexNode;
impl<Input: InjectIndex + Copy + ExtractIndexValue> GNode<Input> for ReadIndexNode {
type Output = f64;
fn eval(&self, input: &Input) -> GPoll<f64> {
GPoll::Final(input.index_value() as f64)
}
}
trait ExtractIndexValue {
fn index_value(&self) -> u64;
}
impl ExtractIndexValue for ContextImpl<'_> {
fn index_value(&self) -> u64 {
self.index_head().index
}
}
fn string_length<C: Ctx>(_ctx: &C, value: &String) -> f64 {
value.len() as f64
}
struct LendStringNode {
value: String,
cell: ArenaCell<String>,
}
impl LendStringNode {
fn new(value: String) -> Self {
Self {
value,
cell: ArenaCell::new(),
}
}
}
impl<'e, Input> GNode<Input> for LendStringNode
where
Input: Ctx + ExtractArena<ArenaRef = &'e Arena>,
{
type Output = &'e String;
fn eval(&self, input: &Input) -> GPoll<&'e String> {
let arena = input.arena();
if let Some(value) = self.cell.load(arena) {
return GPoll::Final(value);
}
match arena.alloc(self.value.clone()) {
Some((value, weak)) => {
self.cell.store(weak);
GPoll::Final(value)
}
None => GPoll::arena_exhausted(),
}
}
}
struct StringLengthNode<Node0> {
value: Node0,
}
impl<Node0> StringLengthNode<Node0> {
fn new(value: Node0) -> Self {
Self { value }
}
}
impl<'e, Input, Node0> GNode<Input> for StringLengthNode<Node0>
where
Input: Ctx,
Node0: GNode<Input, Output = &'e String>,
{
type Output = f64;
fn eval(&self, input: &Input) -> GPoll<f64> {
let cell = StatusCell::new();
let value = match cell.eval_input(0, &self.value, input) {
Ok(value) => value,
Err(interrupt) => return interrupt.into(),
};
cell.finish(string_length(input, value))
}
}
type ErasedGNode<T> = dyn for<'c> GNode<ContextImpl<'c>, Output = T>;
type ErasedLendEdge = dyn for<'c> GNode<ContextImpl<'c>, Output = &'c String>;
fn string_length_constructor(args: Vec<Box<dyn Any>>) -> Result<Box<ErasedGNode<f64>>, &'static str> {
let mut args = args.into_iter();
let value = *args.next().ok_or("arity")?.downcast::<Box<ErasedLendEdge>>().map_err(|_| "type")?;
Ok(Box::new(StringLengthNode::new(value)))
}
fn add_constructor_f64(args: Vec<Box<dyn Any>>) -> Result<Box<ErasedGNode<f64>>, &'static str> {
let mut args = args.into_iter();
let augend = *args.next().ok_or("arity")?.downcast::<Box<ErasedGNode<f64>>>().map_err(|_| "type")?;
let addend = *args.next().ok_or("arity")?.downcast::<Box<ErasedGNode<f64>>>().map_err(|_| "type")?;
Ok(Box::new(AddNode::new(augend, addend)))
}
fn scope_fixture<'a>(generations: &'a [(u64, u64)], arena: &'a Arena) -> EvalScope<'a> {
EvalScope::new(Some(0.5), None, None, generations, arena)
}
#[test]
fn hand_expansion_evaluates_through_typed_erased_edges() {
let arena = Arena::new(1024);
let generations = [];
let scope = scope_fixture(&generations, &arena);
let ctx = ContextImpl::root(&scope);
let augend: Box<dyn Any> = Box::new(Box::new(ValueNode(1.0f64)) as Box<ErasedGNode<f64>>);
let addend: Box<dyn Any> = Box::new(Box::new(ValueNode(2.0f64)) as Box<ErasedGNode<f64>>);
let wired = add_constructor_f64(vec![augend, addend]).unwrap();
assert_eq!(wired.eval(&ctx), GPoll::Final(3.0));
}
#[test]
fn wiring_rejects_type_and_arity_mismatches() {
let augend: Box<dyn Any> = Box::new(Box::new(ValueNode(1.0f64)) as Box<ErasedGNode<f64>>);
let addend: Box<dyn Any> = Box::new(Box::new(ValueNode(2u32)) as Box<ErasedGNode<u32>>);
assert_eq!(add_constructor_f64(vec![augend, addend]).map(|_| ()), Err("type"));
let augend: Box<dyn Any> = Box::new(Box::new(ValueNode(1.0f64)) as Box<ErasedGNode<f64>>);
assert_eq!(add_constructor_f64(vec![augend]).map(|_| ()), Err("arity"));
}
#[test]
fn spec_loop_batches_through_the_erased_edge() {
let arena = Arena::new(1024);
let generations = [];
let scope = scope_fixture(&generations, &arena);
let ctx = ContextImpl::root(&scope);
let graph: Box<ErasedGNode<f64>> = Box::new(AddNode::new(ReadIndexNode, ValueNode(10.0f64)));
let mut scratch = [const { MaybeUninit::uninit() }; 4];
let status = graph.eval_batch(&ctx, 2..6, Some(&mut scratch));
let BatchStatus::Filled(lanes, finality) = status else {
panic!("expected filled, got {status:?}");
};
assert_eq!(lanes, &[12.0, 13.0, 14.0, 15.0]);
assert_eq!(finality, Finality::AllFinal);
}
#[test]
fn lending_kernel_clones_once_per_generation_and_lends_after() {
let arena = Arena::new(1024);
let generations = [];
let scope = scope_fixture(&generations, &arena);
let ctx = ContextImpl::root(&scope);
let node = LendStringNode::new("lend me".to_string());
let GPoll::Final(first) = node.eval(&ctx) else {
panic!("first eval must clone into the arena and lend");
};
let GPoll::Final(second) = node.eval(&ctx) else {
panic!("second eval must hit the cell");
};
assert_eq!(first, "lend me");
assert!(std::ptr::eq(first, second));
}
#[test]
fn exhausted_arena_reports_the_operational_error() {
let arena = Arena::new(0);
let generations = [];
let scope = scope_fixture(&generations, &arena);
let ctx = ContextImpl::root(&scope);
let node = LendStringNode::new("too big".to_string());
let GPoll::Error(error) = node.eval(&ctx) else {
panic!("exhaustion must surface as an operational error");
};
assert_eq!(error.kind, ErrorKind::ArenaExhausted);
}
#[test]
fn lending_edges_erase_and_wire_like_owned_edges() {
let arena = Arena::new(1024);
let generations = [];
let scope = scope_fixture(&generations, &arena);
let ctx = ContextImpl::root(&scope);
let value: Box<dyn Any> = Box::new(Box::new(LendStringNode::new("across the boundary".to_string())) as Box<ErasedLendEdge>);
let wired = string_length_constructor(vec![value]).unwrap();
assert_eq!(wired.eval(&ctx), GPoll::Final(19.0));
}
#[test]
fn spec_loop_batches_through_the_erased_lending_edge() {
let arena = Arena::new(1024);
let generations = [];
let scope = scope_fixture(&generations, &arena);
let ctx = ContextImpl::root(&scope);
let graph: Box<ErasedLendEdge> = Box::new(LendStringNode::new("batched".to_string()));
let mut scratch = [const { MaybeUninit::uninit() }; 3];
let status = graph.eval_batch(&ctx, 0..3, Some(&mut scratch));
let BatchStatus::Filled(lanes, finality) = status else {
panic!("expected filled, got {status:?}");
};
assert_eq!(lanes.len(), 3);
assert!(lanes.iter().all(|lane| std::ptr::eq(*lane, lanes[0])));
assert_eq!(*lanes[0], "batched");
assert_eq!(finality, Finality::AllFinal);
}
#[test]
fn fallback_input_records_partiality_invisibly() {
struct FallbackNode;
impl<Input> GNode<Input> for FallbackNode {
type Output = f64;
fn eval(&self, _input: &Input) -> GPoll<f64> {
GPoll::fallback(0.0, "upstream failed")
}
}
let arena = Arena::new(1024);
let generations = [];
let scope = scope_fixture(&generations, &arena);
let ctx = ContextImpl::root(&scope);
let graph = AddNode::new(FallbackNode, ValueNode(5.0f64));
let GPoll::Fallback(boxed) = graph.eval(&ctx) else {
panic!("fallback must propagate with the computed stand-in");
};
assert_eq!(boxed.0, 5.0);
assert!(boxed.1.kind == "upstream failed");
assert_eq!(boxed.1.trace, vec![0]);
}

View File

@@ -3,15 +3,12 @@ use crate::brush_stroke::{BrushStroke, BrushStyle};
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::transform::Transform;
use core_types::uuid::NodeId;
use core_types::value::ClonedNode;
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM};
use core_types::{Ctx, Node};
use core_types::Ctx;
use glam::{DAffine2, DVec2};
use raster_nodes::blending_nodes::blend_colors;
use raster_nodes::std_nodes::{empty_image, extend_image_to_bounds};
@@ -63,7 +60,7 @@ impl<P: Pixel + Alpha> Sample for BrushStampGenerator<P> {
/// The feather exponent is calculated from hardness to determine edge softness.
/// Used internally to create the brush texture before stamping it repeatedly along a stroke path.
#[node_macro::node(category(""), skip_impl)]
fn brush_stamp_generator(#[unit(" px")] diameter: f64, color: Color, hardness: f64, flow: f64) -> BrushStampGenerator<Color> {
fn brush_stamp_generator(_: impl Ctx, #[unit(" px")] diameter: f64, color: Color, hardness: f64, flow: f64) -> BrushStampGenerator<Color> {
// Diameter
let radius = diameter / 2.;
@@ -83,9 +80,9 @@ 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: List<Raster<CPU>>, texture: Raster<CPU>, positions: Vec<DVec2>, blend_mode: BlendFn) -> List<Raster<CPU>>
fn blit<BlendFn>(_: impl Ctx, 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>,
BlendFn: Fn(Color, Color) -> Color,
{
if positions.is_empty() {
return target;
@@ -125,7 +122,7 @@ where
for x in blit_area_offset.x..blit_area_offset.x + blit_area_dimensions.x {
let src_pixel = texture.data[texture_index(x, y)];
let dst_pixel = &mut element.data_mut().data[target_index(x + clamp_start.x, y + clamp_start.y)];
*dst_pixel = blend_mode.eval((src_pixel, *dst_pixel));
*dst_pixel = blend_mode(src_pixel, *dst_pixel);
}
}
}
@@ -134,10 +131,10 @@ where
target
}
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);
pub 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, List::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()
@@ -188,7 +185,7 @@ pub fn blend_with_mode(background: Item<Raster<CPU>>, foreground: Item<Raster<CP
/// Generates the brush strokes painted with the Brush tool as a raster image.
/// If an input image is supplied, strokes are drawn on top of it, expanding bounds as needed.
#[node_macro::node(category("Raster"))]
async fn brush(
fn brush(
_: impl Ctx,
/// Optional raster content that may be drawn onto.
mut background: List<Raster<CPU>>,
@@ -224,7 +221,7 @@ async fn brush(
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((), List::new_from_item(brush_plan.background), background_bounds).into_iter().next() else {
let Some(mut actual_image) = extend_image_to_bounds(&(), List::new_from_item(brush_plan.background), background_bounds).into_iter().next() else {
return List::new();
};
@@ -234,7 +231,7 @@ async fn brush(
// TODO: apply rotation from layer to stamp for non-rotationally-symmetric brushes.
let mut brush_texture = cache.get_cached_brush(&stroke.style);
if brush_texture.is_none() {
let tex = create_brush_texture(&stroke.style).await;
let tex = create_brush_texture(&stroke.style);
cache.store_brush(stroke.style.clone(), tex.clone());
brush_texture = Some(tex);
}
@@ -255,21 +252,14 @@ async fn brush(
let stroke_origin_in_layer = bbox.start - snap_offset - DVec2::splat(stroke.style.diameter / 2.);
let stroke_to_layer = DAffine2::from_translation(stroke_origin_in_layer) * DAffine2::from_scale(stroke_size);
let normal_blend = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Normal, 1.));
let blit_node = BlitNode::new(
FutureWrapperNode::new(ClonedNode::new(brush_texture)),
FutureWrapperNode::new(ClonedNode::new(positions)),
FutureWrapperNode::new(ClonedNode::new(normal_blend)),
);
let blit_target = if idx == 0 {
let target = core::mem::take(&mut brush_plan.first_stroke_texture);
extend_image_to_bounds((), List::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, List::new_from_element(Color::TRANSPARENT))
// EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(())
empty_image(&(), stroke_to_layer, List::new_from_element(Color::TRANSPARENT))
};
let list = blit_node.eval(blit_target).await;
let list = blit(&(), blit_target, brush_texture, positions, |a, b| blend_colors(a, b, BlendMode::Normal, 1.));
assert_eq!(list.len(), 1);
list.into_iter().next().unwrap_or_default()
};
@@ -291,7 +281,7 @@ async fn brush(
for stroke in trace.into_iter().map(|row| row.into_element()) {
let mut brush_texture = cache.get_cached_brush(&stroke.style);
if brush_texture.is_none() {
let tex = create_brush_texture(&stroke.style).await;
let tex = create_brush_texture(&stroke.style);
cache.store_brush(stroke.style.clone(), tex.clone());
brush_texture = Some(tex);
}
@@ -305,17 +295,13 @@ async fn brush(
_ => BlendMode::Restore,
};
let blend_params = FnNode::new(move |(a, b)| blend_colors(a, b, mask_blend_mode, 1.));
let blit_node = BlitNode::new(
FutureWrapperNode::new(ClonedNode::new(brush_texture)),
FutureWrapperNode::new(ClonedNode::new(positions)),
FutureWrapperNode::new(ClonedNode::new(blend_params)),
);
erase_restore_mask = blit_node.eval(List::new_from_item(erase_restore_mask)).await.into_iter().next().unwrap_or_default();
erase_restore_mask = blit(&(), List::new_from_item(erase_restore_mask), brush_texture, positions, move |a, b| blend_colors(a, b, mask_blend_mode, 1.))
.into_iter()
.next()
.unwrap_or_default();
}
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::MultiplyAlpha, 1.));
actual_image = blend_image_closure(erase_restore_mask, actual_image, |a, b| blend_params.eval((a, b)));
actual_image = blend_image_closure(erase_restore_mask, actual_image, |a, b| blend_colors(a, b, BlendMode::MultiplyAlpha, 1.));
}
let transform: DAffine2 = actual_image.attribute_cloned_or_default(ATTR_TRANSFORM);
@@ -410,16 +396,16 @@ mod test {
#[test]
fn test_brush_texture() {
let size = 20.;
let image = brush_stamp_generator(size, Color::BLACK, 100., 100.);
let image = brush_stamp_generator(&(), size, Color::BLACK, 100., 100.);
assert_eq!(image.transform(), DAffine2::from_scale_angle_translation(DVec2::splat(size.ceil()), 0., -DVec2::splat(size / 2.)));
// center pixel should be BLACK
assert_eq!(image.sample(DVec2::splat(0.), DVec2::ONE), Some(Color::BLACK));
}
#[tokio::test]
async fn test_brush_output_size() {
#[test]
fn test_brush_output_size() {
let image = brush(
(),
&(),
&BrushCache::default(),
List::new_from_element(Raster::new_cpu(Image::<Color>::default())),
List::new_from_element(BrushStroke {
@@ -433,8 +419,7 @@ mod test {
blend_mode: BlendMode::Normal,
},
}),
)
.await;
);
assert_eq!(image.element(0).unwrap().width, 20);
}
}

View File

@@ -47,7 +47,7 @@ fn read_gradient(ctx: impl Ctx + ExtractVarArgs) -> List<GradientStops> {
}
#[node_macro::node(category("Context"), path(core_types::vector))]
async fn read_position(
fn read_position(
ctx: impl Ctx + ExtractPosition,
_primary: (),
/// The number of nested loops to traverse outwards (from the innermost loop) to get the position from. The most upstream loop is level 0, and downstream loops add levels.
@@ -64,7 +64,7 @@ async fn read_position(
///
/// Nested loops can enable 2D or higher-dimensional iteration by using the *Loop Level* parameter to read the index from outer levels of loops.
#[node_macro::node(category("Context"), path(core_types::vector))]
async fn read_index(
fn read_index(
ctx: impl Ctx + ExtractIndex,
_primary: (),
/// The number of nested loops to traverse outwards (from the innermost loop) to get the index from. The most upstream loop is level 0, and downstream loops add levels.

View File

@@ -569,7 +569,7 @@ pub fn wrap_graphic<T: Into<Graphic>>(
/// Converts a list of graphical content into a `Graphic[]` by placing it into an element of a new wrapper `Graphic[]`.
/// If it is already a `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: IntoGraphicList>(
pub fn to_graphic<T: IntoGraphicList>(
_: impl Ctx,
#[implementations(
List<Graphic>,
@@ -587,7 +587,7 @@ pub async fn to_graphic<T: IntoGraphicList>(
/// Removes a level of nesting from a `Graphic[]`, or all nesting if "Fully Flatten" is enabled.
#[node_macro::node(category("General"))]
pub async fn flatten_graphic(_: impl Ctx, content: List<Graphic>, fully_flatten: bool) -> List<Graphic> {
pub 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() {
@@ -624,7 +624,7 @@ pub async fn flatten_graphic(_: impl Ctx, content: List<Graphic>, fully_flatten:
/// Converts a `Graphic[]` into a `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: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Vector>)] content: T) -> List<Vector> {
pub fn flatten_vector<T: IntoGraphicList>(_: 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();
@@ -657,19 +657,19 @@ pub async fn flatten_vector<T: IntoGraphicList>(_: impl Ctx, #[implementations(L
/// Converts a `Graphic[]` into a `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: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Raster<CPU>>)] content: T) -> List<Raster<CPU>> {
pub fn flatten_raster<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Raster<CPU>>)] content: T) -> List<Raster<CPU>> {
content.into_flattened_list()
}
/// Converts a `Graphic[]` into a `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: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Color>)] content: T) -> List<Color> {
pub fn flatten_color<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Color>)] content: T) -> List<Color> {
content.into_flattened_list()
}
/// Converts a `Graphic[]` into a `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: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<GradientStops>)] content: T) -> List<GradientStops> {
pub fn flatten_gradient<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<GradientStops>)] content: T) -> List<GradientStops> {
content.into_flattened_list()
}

View File

@@ -137,7 +137,7 @@ fn image_to_bytes(_: impl Ctx, image: List<Raster<CPU>>) -> List<u8> {
/// Loads binary from URLs and local asset paths. Returns a transparent placeholder if the resource fails to load, allowing rendering to continue.
#[node_macro::node(category("Web Request"))]
async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[name("URL")] url: String) -> Arc<[u8]> {
async fn load_resource(_: impl Ctx, _primary: (), #[name("URL")] url: String) -> Arc<[u8]> {
let placeholder = || -> Arc<[u8]> { Arc::from(Vec::<u8>::new()) };
let response = match reqwest::Client::new().get(&url).send().await {
@@ -185,14 +185,14 @@ fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> List<Raster<CPU>> {
#[cfg(target_family = "wasm")]
#[node_macro::node(category(""))]
async fn create_canvas(_: impl Ctx) -> CanvasHandle {
fn create_canvas(_: impl Ctx) -> CanvasHandle {
CanvasHandle::new()
}
/// Renders a view of the input graphic within an area defined by the *Footprint*.
#[cfg(target_family = "wasm")]
#[node_macro::node(category(""))]
async fn rasterize<T: WasmNotSend + Clone + 'n>(
fn rasterize<T: WasmNotSend + Clone>(
_: impl Ctx,
#[implementations(
List<Vector>,
@@ -262,12 +262,12 @@ where
}
#[node_macro::node(category(""), inject_scope)]
pub async fn editor_api<'a: 'n>(_: impl Ctx, #[scope("editor-api")] editor_api: &'a PlatformEditorApi) -> &'a PlatformEditorApi {
pub fn editor_api<'a>(_: impl Ctx, #[scope("editor-api")] editor_api: &'a PlatformEditorApi) -> &'a PlatformEditorApi {
editor_api
}
#[node_macro::node(category(""))]
pub async fn resource<'a: 'n>(_: impl Ctx, hash: ResourceHash, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> Resource {
pub async fn resource<'a>(_: impl Ctx, hash: ResourceHash, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> Resource {
let application_io = editor_api.application_io.as_ref().expect("ApplicationIo must be available when using resources");
application_io.load_resource(hash).await.unwrap_or_else(|| {
panic!("Resource {hash} not found");
@@ -275,7 +275,7 @@ pub async fn resource<'a: 'n>(_: impl Ctx, hash: ResourceHash, #[scope(editor_ap
}
#[node_macro::node(category(""), inject_scope)]
pub async fn wgpu_executor<'a: 'n>(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> &'a ::wgpu_executor::WgpuExecutor {
pub fn wgpu_executor<'a>(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> &'a ::wgpu_executor::WgpuExecutor {
editor_api
.application_io
.as_ref()
@@ -285,6 +285,16 @@ pub async fn wgpu_executor<'a: 'n>(_: impl Ctx, #[scope(editor_api::IDENTIFIER)]
}
#[node_macro::node(category(""), inject_scope)]
pub async fn try_wgpu_executor<'a: 'n>(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> Option<&'a ::wgpu_executor::WgpuExecutor> {
pub fn try_wgpu_executor<'a>(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> Option<&'a ::wgpu_executor::WgpuExecutor> {
editor_api.application_io.as_ref()?.gpu_executor()
}
#[node_macro::node(category(""), inject_scope)]
pub fn wgpu_executor_arc<'a>(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> std::sync::Arc<::wgpu_executor::WgpuExecutor> {
editor_api
.application_io
.as_ref()
.expect("ApplicationIo not available")
.gpu_executor_arc()
.expect("GPU executor not available")
}

View File

@@ -12,7 +12,7 @@ use wgpu::util::DeviceExt;
use wgpu_executor::{AsyncWgpuPipeline, WgpuExecutor, WgpuPipelineCache};
#[node_macro::node(category(""))]
async fn render_background<'a: 'n>(
fn render_background<'a>(
ctx: impl Ctx + ExtractFootprint + ExtractVarArgs,
#[scope(composite_background_pipeline::IDENTIFIER)] pipeline: WgpuPipelineCache,
data: RenderOutput,
@@ -121,7 +121,7 @@ async fn render_background<'a: 'n>(
}
#[node_macro::node(category(""), inject_scope)]
async fn composite_background_pipeline<'a: 'n>(
fn composite_background_pipeline<'a>(
_ctx: impl Ctx,
#[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<&'a WgpuExecutor>,
#[data] pipeline: WgpuPipelineCache,

View File

@@ -321,7 +321,7 @@ fn flood_fill(start: &TileCoord, tile_set: &HashSet<TileCoord>, visited: &mut Ha
}
#[node_macro::node(category(""))]
pub async fn render_output_cache<'a: 'n>(
pub async fn render_output_cache<'a>(
ctx: impl Ctx + ExtractAll + CloneVarArgs + ExtractRealTime + ExtractAnimationTime + ExtractPointerPosition + Sync,
#[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<&'a WgpuExecutor>,
#[scope(crate::platform_application_io::editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi,

View File

@@ -1,7 +1,7 @@
use core_types::gpoll::Interrupt;
use core_types::list::List;
use core_types::transform::{Footprint, Transform};
use core_types::{CloneVarArgs, ExtractAll, ExtractVarArgs};
use core_types::{Color, Context, Ctx, ExtractFootprint, OwnedContextImpl, WasmNotSend};
use core_types::{Color, Context, Ctx, DeriveCtx, ExtractFootprint, ExtractVarArgs, OwnedContextImpl, WasmNotSend};
use graph_craft::document::value::{RenderOutput, RenderOutputType};
use graphene_application_io::{ExportFormat, RenderConfig};
use graphic_types::raster_types::{CPU, Raster};
@@ -23,8 +23,8 @@ pub struct RenderIntermediate {
}
#[node_macro::node(category(""))]
async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send + Sync>(
ctx: impl Ctx + ExtractVarArgs + ExtractAll + CloneVarArgs,
fn render_intermediate<T: 'static + Render + WasmNotSend + Send + Sync>(
ctx: impl Ctx + ExtractVarArgs + DeriveCtx,
#[implementations(
Context -> List<Artboard>,
Context -> List<Graphic>,
@@ -35,20 +35,18 @@ async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send +
Context -> List<String>,
)]
data: impl Node<Context<'_>, Output = T>,
) -> RenderIntermediate {
) -> Result<RenderIntermediate, Interrupt> {
let data = data.eval(&ctx.derived())?;
let render_params = ctx
.vararg(0)
.expect("Did not find var args")
.downcast_ref::<RenderParams>()
.expect("Downcasting render params yielded invalid type");
let ctx = OwnedContextImpl::from(ctx.clone()).into_context();
let data = data.eval(ctx).await;
let footprint = Footprint::default();
let mut metadata = RenderMetadata::default();
data.collect_metadata(&mut metadata, footprint, None);
match &render_params.render_output_type {
Ok(match &render_params.render_output_type {
RenderOutputTypeRequest::Vello => {
let mut scene = vello::Scene::new();
@@ -70,11 +68,11 @@ async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send +
metadata,
}
}
}
})
}
#[node_macro::node(category(""))]
async fn render<'a: 'n>(
fn render<'a>(
ctx: impl Ctx + ExtractFootprint + ExtractVarArgs,
#[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<&'a WgpuExecutor>,
data: RenderIntermediate,
@@ -144,7 +142,7 @@ async fn render<'a: 'n>(
}
#[node_macro::node(category(""))]
async fn create_context<'a: 'n>(
fn create_context<'a>(
// Context injections are defined in the wrap_network_in_scope function
render_config: RenderConfig,
data: impl Node<Context<'_>, Output = RenderOutput>,

View File

@@ -8,7 +8,7 @@ use vector_types::vector::style::RenderMode;
use wgpu_executor::{AsyncWgpuPipeline, WgpuExecutor, WgpuPipelineCache};
#[node_macro::node(category(""))]
pub async fn render_pixel_preview<'a: 'n>(
pub async fn render_pixel_preview<'a>(
ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync,
#[scope(pixel_preview_pipeline::IDENTIFIER)] pipeline: WgpuPipelineCache,
data: impl Node<Context<'_>, Output = RenderOutput> + Send + Sync,
@@ -75,7 +75,7 @@ pub async fn render_pixel_preview<'a: 'n>(
}
#[node_macro::node(category(""), inject_scope)]
async fn pixel_preview_pipeline<'a: 'n>(
fn pixel_preview_pipeline<'a>(
_ctx: impl Ctx,
#[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<&'a WgpuExecutor>,
#[data] pipeline: WgpuPipelineCache,

View File

@@ -23,7 +23,7 @@ pub use vector_types::vector::misc::BooleanOperation;
/// 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::IntoGraphicList>(
fn boolean_operation<I: graphic_types::IntoGraphicList>(
_: impl Ctx,
/// The `List` of vector paths to perform the boolean operation on. Nested `List`s are automatically flattened.
#[implementations(List<Graphic>, List<Vector>)]

View File

@@ -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: List<Raster<CPU>>, strength: Percentage) -> List<Raster<CPU>> {
fn dehaze(_: impl Ctx, image_frame: List<Raster<CPU>>, strength: Percentage) -> List<Raster<CPU>> {
image_frame
.into_iter()
.map(|mut row| {

View File

@@ -87,7 +87,7 @@ fn unpremultiply_gamma_to_linear(buffer: Image<PremultipliedGammaPixel>) -> Imag
/// Blurs the image with a Gaussian or box blur kernel filter.
#[node_macro::node(category("Raster: Filter"))]
async fn blur(
fn blur(
_: impl Ctx,
/// The image to be blurred.
image_frame: List<Raster<CPU>>,
@@ -124,7 +124,7 @@ async fn blur(
/// Applies a median filter to reduce noise while preserving edges.
#[node_macro::node(category("Raster: Filter"))]
async fn median_filter(
fn median_filter(
_: impl Ctx,
/// The image to be filtered.
image_frame: List<Raster<CPU>>,

View File

@@ -10,7 +10,7 @@ use vector_types::GradientStops;
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27grdm%27%20%3D%20Gradient%20Map
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Gradient%20settings%20(Photoshop%206.0)
#[node_macro::node(category("Raster: Adjustment"))]
async fn gradient_map<T: Adjust<Color>>(
fn gradient_map<T: Adjust<Color>>(
_: impl Ctx,
#[implementations(
List<Raster<CPU>>,

View File

@@ -4,7 +4,7 @@ use core_types::list::{Item, List};
use raster_types::{CPU, Raster};
#[node_macro::node(category("Color"))]
async fn image_color_palette(
fn image_color_palette(
_: impl Ctx,
image: List<Raster<CPU>>,
#[default(4)]

View File

@@ -108,7 +108,7 @@ 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 `List`, if present.
#[node_macro::node(category("Math: Transform"), path(core_types::vector))]
async fn extract_transform(_: impl Ctx, content: ListDyn) -> DAffine2 {
fn extract_transform(_: impl Ctx, content: ListDyn) -> DAffine2 {
content.attribute::<DAffine2>(ATTR_TRANSFORM, 0).copied().unwrap_or_default()
}

View File

@@ -7,7 +7,7 @@ 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: List<Vector>, modification: Box<VectorModification>, node_path: List<NodeId>) -> List<Vector> {
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() {
@@ -35,7 +35,7 @@ async fn path_modify(_ctx: impl Ctx, mut vector: List<Vector>, modification: Box
/// 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: List<Vector>) -> List<Vector> {
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

@@ -89,7 +89,7 @@ impl VectorListIterMut for List<Vector> {
/// Uniquely sets the fill and/or stroke style of every vector element to individual colors sampled along a chosen gradient.
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
async fn assign_colors<T>(
fn assign_colors<T>(
_: impl Ctx,
/// The content with vector paths to apply the fill and/or stroke style to.
#[implementations(List<Graphic>, List<Vector>)]
@@ -157,7 +157,7 @@ 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<V: VectorListIterMut+ Send, F: IntoGraphicList+ Send + 'static>(
fn fill<V: VectorListIterMut+ Send, F: IntoGraphicList+ Send + 'static>(
_: impl Ctx,
/// The content with vector paths to apply the fill style to.
#[implementations(
@@ -252,7 +252,7 @@ impl IntoF64Vec for String {
/// Applies a stroke style to the vector content, giving an appearance to the area within the outline of the geometry.
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("stroke_properties"))]
async fn stroke<V, L: IntoF64Vec, P: IntoGraphicList+ Send + 'static>(
fn stroke<V, L: IntoF64Vec, P: IntoGraphicList+ Send + 'static>(
_: impl Ctx,
/// The content with vector paths to apply the stroke style to.
#[implementations(
@@ -357,7 +357,7 @@ where
}
#[node_macro::node(name("Copy to Points"), category("Repeat"), path(core_types::vector))]
async fn copy_to_points<I: Send + Clone>(
fn copy_to_points<I: Send + Clone>(
_: impl Ctx,
points: List<Vector>,
/// Artwork to be copied and placed at each point.
@@ -441,7 +441,7 @@ async fn copy_to_points<I: Send + Clone>(
}
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
async fn round_corners(
fn round_corners(
_: impl Ctx,
source: List<Vector>,
#[hard(0..)]
@@ -778,7 +778,7 @@ pub mod extrude_algorithms {
}
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
async fn extrude(_: impl Ctx, mut source: List<Vector>, direction: DVec2, joining_algorithm: ExtrudeJoiningAlgorithm) -> List<Vector> {
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);
}
@@ -786,7 +786,7 @@ async fn extrude(_: impl Ctx, mut source: List<Vector>, direction: DVec2, joinin
}
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
async fn box_warp(_: impl Ctx, content: List<Vector>, #[expose] rectangle: List<Vector>) -> List<Vector> {
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);
@@ -871,7 +871,7 @@ fn bilinear_interpolate(t: DVec2, quad: &[DVec2; 4]) -> DVec2 {
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn pack_strips<T: Send + Clone>(
fn pack_strips<T: Send + Clone>(
_: impl Ctx,
#[implementations(
List<Graphic>,
@@ -992,7 +992,7 @@ where
/// Automatically constructs tangents (Bézier handles) for anchor points in a vector path.
#[node_macro::node(category("Vector: Modifier"), name("Auto-Tangents"), path(core_types::vector))]
async fn auto_tangents(
fn auto_tangents(
_: impl Ctx,
source: List<Vector>,
/// The amount of spread for the auto-tangents, from 0 (sharp corner) to 1 (full spread).
@@ -1146,7 +1146,7 @@ async fn auto_tangents(
}
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
async fn bounding_box(_: impl Ctx, content: List<Vector>) -> List<Vector> {
fn bounding_box(_: impl Ctx, content: List<Vector>) -> List<Vector> {
content
.into_iter()
.map(|mut row| {
@@ -1171,7 +1171,7 @@ async fn bounding_box(_: impl Ctx, content: List<Vector>) -> List<Vector> {
}
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
async fn dimensions(_: impl Ctx, content: List<Vector>) -> DVec2 {
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)])
@@ -1187,7 +1187,7 @@ fn as_vector(_: impl Ctx, value: List<Vector>) -> List<Vector> {
/// 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: List<Vector>, #[default(true)] closed: bool) -> List<Vector> {
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;
@@ -1215,7 +1215,7 @@ async fn points_to_polyline(_: impl Ctx, mut points: List<Vector>, #[default(tru
}
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector), properties("offset_path_properties"))]
async fn offset_path(_: impl Ctx, content: List<Vector>, distance: f64, join: StrokeJoin, #[default(4.)] miter_limit: f64) -> List<Vector> {
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| {
@@ -1259,7 +1259,7 @@ async fn offset_path(_: impl Ctx, content: List<Vector>, distance: f64, join: St
}
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
async fn solidify_stroke<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Vector>)] content: T) -> List<Vector> {
fn solidify_stroke<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Vector>)] content: T) -> List<Vector> {
// TODO: Make this node support stroke align, which it currently ignores
let graphic_list = content.into_graphic_list();
@@ -1367,7 +1367,7 @@ async fn solidify_stroke<T: IntoGraphicList>(_: impl Ctx, #[implementations(List
}
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
async fn separate_subpaths(_: impl Ctx, content: List<Vector>) -> List<Vector> {
fn separate_subpaths(_: impl Ctx, content: List<Vector>) -> List<Vector> {
content
.into_iter()
.flat_map(|row| {
@@ -1398,7 +1398,7 @@ async fn separate_subpaths(_: impl Ctx, content: List<Vector>) -> List<Vector> {
/// Determines if the subpath at the given index (across all vector element subpaths) is closed, meaning its ends are connected together forming a loop.
#[node_macro::node(name("Path is Closed"), category("Vector: Measure"), path(core_types::vector))]
async fn path_is_closed(
fn path_is_closed(
_: impl Ctx,
/// The vector content whose subpaths are inspected.
content: List<Vector>,
@@ -1431,7 +1431,7 @@ fn map_points(ctx: impl Ctx + DeriveCtx, content: List<Vector>, mapped: impl Nod
// 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: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Vector>)] content: T) -> List<Vector> {
pub fn flatten_path<T: IntoGraphicList>(_: 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>();
@@ -1487,7 +1487,7 @@ pub async fn flatten_path<T: IntoGraphicList>(_: impl Ctx, #[implementations(Lis
/// 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(
fn sample_polyline(
_: impl Ctx,
content: List<Vector>,
spacing: PointSpacingType,
@@ -1573,7 +1573,7 @@ async fn sample_polyline(
/// Simplifies vector paths by reducing the number of curve segments while preserving the overall shape within the given tolerance.
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
async fn simplify(
fn simplify(
_: impl Ctx,
/// The vector paths to simplify.
content: List<Vector>,
@@ -1617,7 +1617,7 @@ async fn simplify(
/// Decimates vector paths into polylines by sampling any curves into line segments, then removing points that don't significantly contribute to the shape using the Ramer-Douglas-Peucker algorithm.
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
async fn decimate(
fn decimate(
_: impl Ctx,
/// The vector paths to decimate.
content: List<Vector>,
@@ -1745,7 +1745,7 @@ async fn decimate(
///
/// If multiple subpaths make up the path, the whole number part of the progression value selects the subpath and the decimal part determines the position along it.
#[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))]
async fn cut_path(
fn cut_path(
_: impl Ctx,
/// The path to insert a cut into.
mut content: List<Vector>,
@@ -1796,7 +1796,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: List<Vector>) -> List<Vector> {
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();
@@ -1855,7 +1855,7 @@ async fn cut_segments(_: impl Ctx, mut content: List<Vector>) -> List<Vector> {
///
/// If multiple subpaths make up the path, the whole number part of the progression value selects the subpath and the decimal part determines the position along it.
#[node_macro::node(name("Position on Path"), category("Vector: Measure"), path(graphene_core::vector))]
async fn position_on_path(
fn position_on_path(
_: impl Ctx,
/// The path to traverse.
content: List<Vector>,
@@ -1893,7 +1893,7 @@ async fn position_on_path(
///
/// If multiple subpaths make up the path, the whole number part of the progression value selects the subpath and the decimal part determines the position along it.
#[node_macro::node(name("Tangent on Path"), category("Vector: Measure"), path(graphene_core::vector))]
async fn tangent_on_path(
fn tangent_on_path(
_: impl Ctx,
/// The path to traverse.
content: List<Vector>,
@@ -1941,7 +1941,7 @@ async fn tangent_on_path(
}
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector), memoize)]
async fn scatter_points(
fn scatter_points(
_: impl Ctx,
content: List<Vector>,
#[unit(" px")]
@@ -1991,7 +1991,7 @@ async fn scatter_points(
}
#[node_macro::node(name("Spline"), category("Vector: Modifier"), path(core_types::vector))]
async fn spline(_: impl Ctx, content: List<Vector>) -> List<Vector> {
fn spline(_: impl Ctx, content: List<Vector>) -> List<Vector> {
content
.into_iter()
.filter_map(|mut row| {
@@ -2091,7 +2091,7 @@ fn apply_point_deltas(element: &mut Vector, deltas: &[DVec2], transform: DAffine
/// Perturbs the positions of anchor points in vector geometry by random amounts and directions.
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
async fn jitter_points(
fn jitter_points(
_: impl Ctx,
/// The vector geometry with points to be jittered.
content: List<Vector>,
@@ -2141,7 +2141,7 @@ async fn jitter_points(
/// Displaces anchor points along their normal direction (perpendicular to the path) by a set distance.
/// Points with 0 or 3+ segment connections have no well-defined normal and are left in place.
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
async fn offset_points(
fn offset_points(
_: impl Ctx,
/// The vector geometry with points to be offset.
content: List<Vector>,
@@ -2178,7 +2178,7 @@ 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: IntoGraphicList>(
fn morph<I: IntoGraphicList>(
_: impl Ctx,
/// The vector objects to interpolate between. Mixed graphic content is deeply flattened to keep only vector elements.
#[implementations(List<Graphic>, List<Vector>)]
@@ -3125,19 +3125,19 @@ fn point_inside(_: impl Ctx, source: List<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: ListDyn) -> f64 {
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: List<Vector>) -> f64 {
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 `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(
fn index_points(
_: impl Ctx,
/// The vector element or elements containing the anchor points to be retrieved.
content: List<Vector>,
@@ -3171,7 +3171,7 @@ async fn index_points(
}
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
async fn path_length(_: impl Ctx, source: List<Vector>) -> f64 {
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);