Replace the 'Vec2 to Point' node with automatic conversion machinery (#4236)

* Replace the 'Vec2 to Point' node with an 'As Vector' node and automatic conversion

* Accept a Vec2 as content for the Wrap Graphic node

* Rename the type-assertion nodes from 'To' to 'As'

* Fix the Transform node reversing a Vec2 input's direction

* Fix text migration bug
This commit is contained in:
Keavon Chambers
2026-06-14 19:35:39 -07:00
parent b56af15e5e
commit 34e0fd7757
19 changed files with 89 additions and 41 deletions

View File

@@ -106,7 +106,19 @@ impl Convert<DVec2, ()> for DVec2 {
}
}
// TODO: Add a DVec2 to List<Vector> anchor point conversion implementation to replace the 'Vec2 to Point' node
/// Constructs `Self` from a single anchor point at the given position. Implemented by the vector crate's
/// path type so the `Convert` impl below can build a single-point path without core-types depending on
/// that crate (mirroring how [`ListConvert`] bridges per-item list conversions).
pub trait FromAnchorPosition {
fn from_anchor_position(position: DVec2) -> Self;
}
// Converts a position into a vector path composed of a single anchor point
impl<T: FromAnchorPosition + Send> Convert<List<T>, ()> for DVec2 {
async fn convert(self, _: Footprint, _: ()) -> List<T> {
List::new_from_item(Item::new_from_element(T::from_anchor_position(self)))
}
}
/// Implements the [`Convert`] trait for conversion between the cartesian product of Rust's primitive numeric types.
macro_rules! impl_convert {

View File

@@ -234,6 +234,6 @@ impl ApplyTransform for DVec2 {
*self = modification.transform_point2(*self);
}
fn left_apply_transform(&mut self, modification: &DAffine2) {
*self = modification.inverse().transform_point2(*self);
*self = modification.transform_point2(*self);
}
}

View File

@@ -1,12 +1,12 @@
use core_types::bounds::{BoundingBox, RenderBoundingBox};
use core_types::graphene_hash::CacheHash;
use core_types::list::{ATTR_FILL, ATTR_STROKE, Item, List};
use core_types::ops::ListConvert;
use core_types::ops::{FromAnchorPosition, ListConvert};
use core_types::render_complexity::RenderComplexity;
use core_types::uuid::NodeId;
use core_types::{ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_GRADIENT_TYPE, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_SPREAD_METHOD, ATTR_TRANSFORM, Color};
use dyn_any::DynAny;
use glam::DAffine2;
use glam::{DAffine2, DVec2};
use raster_types::{CPU, GPU, Raster};
use std::borrow::Cow;
use vector_types::GradientStops;
@@ -393,6 +393,13 @@ impl From<DAffine2> for Graphic {
Graphic::default()
}
}
// DVec2
impl From<DVec2> for Graphic {
fn from(position: DVec2) -> Self {
Graphic::Vector(List::new_from_element(Vector::from_anchor_position(position)))
}
}
// Note: List conversions handled by blanket impl in gcore
impl Graphic {

View File

@@ -55,6 +55,23 @@ impl graphene_hash::CacheHash for Vector {
}
}
impl core_types::ops::FromAnchorPosition for Vector {
fn from_anchor_position(position: DVec2) -> Self {
let mut point_domain = PointDomain::new();
point_domain.push(PointId::generate(), position);
Self { point_domain, ..Default::default() }
}
}
// Identity item conversion so `List<Vector>` satisfies the blanket `Convert<List<U>, ()> for List<T>`, letting its
// auto-inserted input wrapper be a `ConvertNode` (which also accepts a `DVec2` anchor position) rather than an `IntoNode`.
impl core_types::ops::ListConvert<Vector> for Vector {
fn convert_item(self) -> Vector {
self
}
}
impl Vector {
/// Add a subpath to this vector path.
pub fn append_subpath(&mut self, subpath: impl Borrow<Subpath<PointId>>, preserve_id: bool) {
@@ -619,4 +636,17 @@ mod tests {
let generated = vector.stroke_bezier_paths().collect::<Vec<_>>();
assert_subpath_eq(&generated, &[curve, circle]);
}
// Verifies the `DVec2 -> List<Vector>` conversion that replaced the former "Vec2 to Point" node yields a path
// with exactly one anchor point at the given position and no segments
#[test]
fn anchor_position_builds_single_point_path() {
use core_types::ops::FromAnchorPosition;
let vector = Vector::from_anchor_position(DVec2::new(3., 4.));
assert_eq!(vector.point_domain.positions(), [DVec2::new(3., 4.)]);
assert_eq!(vector.point_domain.ids().len(), 1);
assert!(vector.segment_domain.ids().is_empty());
}
}