Migrate remaining node graph data types from Vec to Table (#4067)

* Move Vec<String> to Table<String>

* Remove old VecDVec2

* Move Vec<u8> to Table<u8>

* Move Vec<f64> to Table<f64>

* Move [f64; 4] to Table<f64>

* Move Vec<NodeId> to Table<NodeId>

* Tidy up the TaggedValue variants

* Move Vec<BrushStroke> to Table<BrushStroke>

* Add missing type implementations

* Fix tests

---------
This commit is contained in:
Keavon Chambers
2026-04-28 13:44:25 -07:00
committed by GitHub
parent cf150b5cff
commit b396d17211
25 changed files with 277 additions and 341 deletions

View File

@@ -18,22 +18,37 @@ impl CornerRadius for f64 {
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_rounded_rectangle(size / -2., size / 2., [clamped_radius; 4])))
}
}
impl CornerRadius for [f64; 4] {
impl CornerRadius for Table<f64> {
fn generate(self, size: DVec2, clamped: bool) -> Table<Vector> {
// Expand to four corners using the CSS `border-radius` shorthand rules.
// - `[a]` → `[a, a, a, a]`
// - `[a, b]` → `[a, b, a, b]`
// - `[a, b, c]` → `[a, b, c, b]`
// - `[a, b, c, d, …]` → `[a, b, c, d]`
// - `[]` → `[0, 0, 0, 0]`
let values: Vec<f64> = self.iter_element_values().copied().collect();
let radii: [f64; 4] = match values.as_slice() {
[] => [0., 0., 0., 0.],
&[a] => [a, a, a, a],
&[a, b] => [a, b, a, b],
&[a, b, c] => [a, b, c, b],
&[a, b, c, d, ..] => [a, b, c, d],
};
let clamped_radius = if clamped {
// Algorithm follows the CSS spec: <https://drafts.csswg.org/css-backgrounds/#corner-overlap>
let mut scale_factor: f64 = 1.;
for i in 0..4 {
let side_length = if i % 2 == 0 { size.x } else { size.y };
let adjacent_corner_radius_sum = self[i] + self[(i + 1) % 4];
let adjacent_corner_radius_sum = radii[i] + radii[(i + 1) % 4];
if side_length < adjacent_corner_radius_sum {
scale_factor = scale_factor.min(side_length / adjacent_corner_radius_sum);
}
}
self.map(|x| x * scale_factor)
radii.map(|x| x * scale_factor)
} else {
self
radii
};
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_rounded_rectangle(size / -2., size / 2., clamped_radius)))
}
@@ -140,7 +155,7 @@ 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, [f64; 4])] corner_radius: T,
#[implementations(f64, Table<f64>)] corner_radius: T,
#[default(true)] clamped: bool,
) -> Table<Vector> {
corner_radius.generate(DVec2::new(width, height), clamped)

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: Table<Vector>, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> Table<Vector> {
async fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Box<VectorModification>, node_path: Table<NodeId>) -> Table<Vector> {
use core_types::table::TableRow;
if vector.is_empty() {
@@ -15,8 +15,11 @@ async fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Bo
}
modification.apply(vector.element_mut(0).expect("push should give one item"));
// Update the source node id
let this_node_path = node_path.iter().rev().nth(1).copied();
// Update the source node id (penultimate element in the path, identifying the user-facing layer node)
let this_node_path = {
let index = node_path.len().wrapping_sub(2);
node_path.element(index).copied()
};
let existing: Option<NodeId> = vector.attribute_cloned_or_default("editor:layer", 0);
vector.set_attribute("editor:layer", 0, existing.or(this_node_path));

View File

@@ -179,9 +179,9 @@ impl IntoF64Vec for f64 {
vec![self]
}
}
impl IntoF64Vec for Vec<f64> {
impl IntoF64Vec for Table<f64> {
fn into_vec(self) -> Vec<f64> {
self
self.into_iter().map(|row| row.into_element()).collect()
}
}
impl IntoF64Vec for String {
@@ -217,7 +217,7 @@ 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(Vec<f64>, f64, String, Vec<f64>, f64, String)]
#[implementations(Table<f64>, f64, String, Table<f64>, f64, String)]
dash_lengths: L,
/// The phase offset distance from the starting point of the dash pattern.
#[unit(" px")]
@@ -2850,11 +2850,6 @@ impl<T> Count for Table<T> {
self.len()
}
}
impl<T> Count for Vec<T> {
fn count(&self) -> usize {
self.len()
}
}
// 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.)
@@ -2868,9 +2863,10 @@ async fn count_elements<I: Count>(
Table<Raster<GPU>>,
Table<Color>,
Table<GradientStops>,
Vec<String>,
Vec<f64>,
Vec<DVec2>,
Table<String>,
Table<f64>,
Table<u8>,
Table<NodeId>,
)]
content: I,
) -> f64 {