Convert path modify to a flip kernel over one default lane

This commit is contained in:
Dennis Kobert
2026-08-22 17:29:51 +00:00
parent 4e8a44650e
commit 7569bdb9cb
2 changed files with 29 additions and 22 deletions

View File

@@ -325,7 +325,9 @@ macro_rules! tagged_value {
check_level!(List<Graphic>, Graphic);
check_level!(List<Artboard>, Artboard);
check_level!(List<Raster<CPU>>, Raster<CPU>);
check_level!(List<Vector>, Vector);
// One default lane rather than an empty level: an unwired path input
// starts from a blank vector, as the legacy path modify synthesized itself.
if name == std::any::type_name::<List<Vector>>() { return Ok(leveled_record_value_edge(vec![Vector::default()])); }
check_level!(List<String>, String);
if name == std::any::type_name::<DocumentNode>() { return Ok(record_value_edge(DocumentNode::default())); }
if name == std::any::type_name::<Resource>() { return Ok(record_value_edge(Resource::default())); }

View File

@@ -1,37 +1,42 @@
use core_types::attribute::{Attr, EditorLayerPath, RemoveAttr};
use core_types::gpoll::{GraphError, Interrupt};
use core_types::list::List;
use core_types::uuid::NodeId;
use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, Ctx};
use core_types::{ATTR_TRANSFORM, Ctx, ExtractIndex, InjectIndex};
use glam::DAffine2;
use graphic_types::Vector;
use vector_types::ATTR_EDITOR_CLICK_TARGET;
use vector_types::markers::EditorClickTarget;
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.
/// The modification applies to the level's first lane only; an unwired input serves one blank lane through the
/// `TypeDefault` value form, so a fresh path starts from a default vector.
#[node_macro::node(category(""))]
fn path_modify(_ctx: impl Ctx, mut vector: List<Vector>, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> List<Vector> {
use core_types::list::Item;
if vector.is_empty() {
vector.push(Item::default());
fn path_modify<'e>(
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
(element, existing): (Vector, Attr<'e, EditorLayerPath>),
modification: Box<VectorModification>,
node_path: Vec<NodeId>,
) -> Result<(Vector, Attr<'e, EditorLayerPath>, RemoveAttr<EditorClickTarget>), Interrupt> {
let mut element = element;
if ctx.innermost_index() == 0 {
modification.apply(&mut element);
}
modification.apply(vector.element_mut(0).expect("push should give one item"));
// Drop stale click-target override so hit testing uses the geometry the user is now editing
vector.remove_attribute(ATTR_EDITOR_CLICK_TARGET);
// 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: List<NodeId> = {
let len = node_path.len();
node_path.into_iter().take(len.saturating_sub(1)).map(Item::new_from_element).collect()
let path = match existing.is_empty() {
false => existing.to_vec(),
true => {
let len = node_path.len();
node_path.into_iter().take(len.saturating_sub(1)).collect()
}
};
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 {
warn!("The path modify ran on {} vector items. Only the first can be modified.", vector.len());
}
vector
let (parked, _) = ctx.arena().alloc(path).ok_or(GraphError {
kind: core_types::gpoll::ErrorKind::ArenaExhausted,
trace: Vec::new(),
})?;
Ok((element, Attr(parked.as_slice()), RemoveAttr::new()))
}
/// Applies the vector path's local transformation to its geometry and resets the transform to the identity.