Remove the legacy Fill, Gradient, and PathStyle types now that paints live in attributes (#4297)

* Remove `Stroke.color`

* Remove legacy Gradient struct usage

* Fix gradient tool tests

* Remove `Fill` enum

* Fix direction of `gradient_orientation_rightward`

* Remove `Gradient` struct

* Remove `PathStyle` struct

* Fix old `VectorData` migration

* Refactor

- Remove unnecessary wrapper functions
- Use `as_ref` instead of `clone` for stroke if possible
- Comment cleanup

* Clean up stale style comments and bindings left by the paint attribute migration

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
YohYamasaki
2026-07-05 08:08:06 +02:00
committed by GitHub
parent 9f9899cfd0
commit 6a54dcb5da
28 changed files with 887 additions and 1334 deletions

View File

@@ -13,7 +13,7 @@ use core_types::{
};
use glam::{DAffine2, DMat2, DVec2};
use graphic_types::Vector;
use graphic_types::graphic::{bake_paint_transforms, fill_graphic_list_at, has_paint_at, is_paint_present, set_paint_attribute, set_paint_attribute_at, stroke_graphic_list_at};
use graphic_types::graphic::{bake_paint_transforms, graphic_list_at, has_paint_at, is_paint_present, set_paint_attribute_at};
use graphic_types::raster_types::{CPU, GPU, Raster};
use graphic_types::{Graphic, IntoGraphicList};
use kurbo::simplify::{SimplifyOptions, simplify_bezpath};
@@ -143,7 +143,7 @@ where
if fill {
set_paint_attribute_at(vector_list, index, ATTR_FILL, paint.clone());
}
if stroke && vector_list.element(index).and_then(|vector| vector.style.stroke()).is_some() {
if stroke && vector_list.element(index).is_some_and(|vector| vector.stroke.is_some()) {
set_paint_attribute_at(vector_list, index, ATTR_STROKE, paint.clone());
}
@@ -328,8 +328,6 @@ where
let dash_lengths = dash_lengths.into_vec().into_iter().map(|length| length.max(0.)).collect();
let stroke = Stroke {
// TODO: Remove once the deprecated `Stroke.color` field is deleted in favor of the `ATTR_STROKE` attribute
color: None,
weight,
dash_lengths,
dash_offset,
@@ -344,7 +342,7 @@ where
content.for_each_vector_mut(|vector, transform| {
let mut stroke = stroke.clone();
stroke.transform *= transform;
vector.style.set_stroke(stroke);
vector.stroke = Some(stroke);
});
let paint = paint.into_graphic_list();
@@ -471,7 +469,7 @@ async fn round_corners(
let edge_length_limit = edge_length_limit * 0.005;
let mut result = Vector {
style: source.style.clone(),
stroke: source.stroke.clone(),
..Default::default()
};
@@ -850,7 +848,7 @@ async fn box_warp(_: impl Ctx, content: List<Vector>, #[expose] rectangle: List<
});
}
result.style.set_stroke_transform(DAffine2::IDENTITY);
result.set_stroke_transform(DAffine2::IDENTITY);
// Add this to the `List` and reset the transform since we've applied it directly to the points
*row.element_mut() = result;
@@ -1012,7 +1010,7 @@ async fn auto_tangents(
let source = source.element(index).unwrap();
let mut result = Vector {
style: source.style.clone(),
stroke: source.stroke.clone(),
..Default::default()
};
@@ -1162,8 +1160,8 @@ async fn bounding_box(_: impl Ctx, content: List<Vector>) -> List<Vector> {
})
.unwrap_or_default();
result.style = vector.style.clone();
result.style.set_stroke_transform(DAffine2::IDENTITY);
result.stroke = vector.stroke.clone();
result.set_stroke_transform(DAffine2::IDENTITY);
*row.element_mut() = result;
row
@@ -1226,10 +1224,10 @@ async fn offset_path(_: impl Ctx, content: List<Vector>, distance: f64, join: St
let bezpaths = vector.stroke_bezpath_iter();
let mut result = Vector {
style: vector.style.clone(),
stroke: vector.stroke.clone(),
..Default::default()
};
result.style.set_stroke_transform(DAffine2::IDENTITY);
result.set_stroke_transform(DAffine2::IDENTITY);
// Perform operation on all subpaths in this shape.
for mut bezpath in bezpaths {
@@ -1266,10 +1264,8 @@ async fn solidify_stroke<T: IntoGraphicList>(_: impl Ctx, #[implementations(List
let graphic_list = content.into_graphic_list();
let flattened: List<Vector> = graphic_list.clone().into_flattened_list();
// A fill exists when the canonical attribute carries paint or, matching the renderer's fallback, when the legacy `style.fill` does
let has_fills: Vec<bool> = (0..flattened.len())
.map(|index| has_paint_at(&flattened, index, ATTR_FILL) || flattened.element(index).is_some_and(|vector| !vector.style.fill().is_none()))
.collect();
// A fill exists when the canonical attribute carries paint
let has_fills: Vec<bool> = (0..flattened.len()).map(|index| has_paint_at(&flattened, index, ATTR_FILL)).collect();
let mut output: List<Vector> = flattened
.into_iter()
@@ -1277,7 +1273,7 @@ async fn solidify_stroke<T: IntoGraphicList>(_: impl Ctx, #[implementations(List
.flat_map(|(row, has_fill)| {
let (mut vector, attributes) = row.into_parts();
let stroke = vector.style.stroke().clone().unwrap_or_default();
let stroke = vector.stroke.clone().unwrap_or_default();
let bezpaths = vector.stroke_bezpath_iter();
let mut solidified_stroke = Vector::default();
@@ -1326,7 +1322,7 @@ async fn solidify_stroke<T: IntoGraphicList>(_: impl Ctx, #[implementations(List
// If the original vector has a fill, preserve it as a separate item with the stroke cleared.
let fill_row = has_fill.then(|| {
vector.style.clear_stroke();
vector.stroke = None;
let mut fill_attributes = attributes.clone();
// No stroke remains on the fill row
fill_attributes.remove::<List<Graphic>>(ATTR_STROKE);
@@ -1338,13 +1334,6 @@ async fn solidify_stroke<T: IntoGraphicList>(_: impl Ctx, #[implementations(List
stroke_attributes.remove::<List<Graphic>>(ATTR_FILL);
stroke_attributes.rename(ATTR_STROKE, ATTR_FILL);
// Fall back to the legacy stroke color when no canonical stroke paint attribute was carried over
if !stroke_attributes.get::<List<Graphic>>(ATTR_FILL).is_some_and(is_paint_present)
&& let Some(color) = stroke.color
{
set_paint_attribute(&mut stroke_attributes, ATTR_FILL, List::new_from_element(color));
}
let stroke_row = Item::from_parts(solidified_stroke, stroke_attributes);
// Ordering based on the paint order. The first item in the `List` is rendered below the second.
@@ -1389,7 +1378,7 @@ async fn separate_subpaths(_: impl Ctx, content: List<Vector>) -> List<Vector> {
return vec![row];
}
let style = row.element().style.clone();
let stroke = row.element().stroke.clone();
let (_, attributes) = row.into_parts();
bezpaths
@@ -1397,7 +1386,7 @@ async fn separate_subpaths(_: impl Ctx, content: List<Vector>) -> List<Vector> {
.map(|bezpath| {
let mut vector = Vector::default();
vector.append_bezpath(bezpath);
vector.style = style.clone();
vector.stroke = stroke.clone();
Item::from_parts(vector, attributes.clone())
})
@@ -1464,9 +1453,9 @@ pub async fn flatten_path<T: IntoGraphicList>(_: impl Ctx, #[implementations(Lis
let source_transform = flattened.attribute_cloned_or_default(ATTR_TRANSFORM, index);
output.concat(element, source_transform, collision_hash_seed);
// TODO: Make this instead use the first encountered style
// Use the last encountered style as the output style
output.style = element.style.clone();
// TODO: Make this instead use the first encountered stroke
// Use the last encountered stroke as the output stroke
output.stroke = element.stroke.clone();
primary_source = Some((index, source_transform));
}
@@ -1532,10 +1521,10 @@ async fn sample_polyline(
segment_domain: Default::default(),
region_domain: Default::default(),
colinear_manipulators: Default::default(),
style: std::mem::take(&mut row.element_mut().style),
stroke: std::mem::take(&mut row.element_mut().stroke),
};
// Transfer the stroke transform from the input vector content to the result.
result.style.set_stroke_transform(row.attribute_cloned_or_default(ATTR_TRANSFORM));
result.set_stroke_transform(row.attribute_cloned_or_default(ATTR_TRANSFORM));
for local_bezpath in row.element().stroke_bezpath_iter() {
// Apply the transform to compute sample locations in world space (for correct distance-based spacing)
@@ -1606,7 +1595,7 @@ async fn simplify(
let inverse_transform = transform.inverse();
let mut result = Vector {
style: std::mem::take(&mut row.element_mut().style),
stroke: std::mem::take(&mut row.element_mut().stroke),
..Default::default()
};
@@ -1702,7 +1691,7 @@ async fn decimate(
let inverse_transform = transform.inverse();
let mut result = Vector {
style: std::mem::take(&mut row.element_mut().style),
stroke: std::mem::take(&mut row.element_mut().stroke),
..Default::default()
};
@@ -1781,7 +1770,7 @@ async fn cut_path(
if let Some((row_index, bezpath)) = bezpaths.get(index).cloned() {
let mut result_vector = Vector {
style: content.element(row_index).unwrap().style.clone(),
stroke: content.element(row_index).unwrap().stroke.clone(),
..Default::default()
};
@@ -1991,8 +1980,8 @@ async fn scatter_points(
}
// Transfer the style from the input vector content to the result.
result.style = row.element().style.clone();
result.style.set_stroke_transform(DAffine2::IDENTITY);
result.stroke = row.element().stroke.clone();
result.set_stroke_transform(DAffine2::IDENTITY);
*row.element_mut() = result;
row
@@ -2632,8 +2621,7 @@ async fn morph<I: IntoGraphicList>(
return List::new_from_item(Item::from_parts(endpoint_element.clone(), attributes));
}
let mut vector = Vector::default();
vector.style.stroke = match (source_element.style.stroke.as_ref(), target_element.style.stroke.as_ref()) {
let stroke = match (source_element.stroke.as_ref(), target_element.stroke.as_ref()) {
(Some(a), Some(b)) => Some(a.lerp(b, time)),
(Some(a), None) => {
if time < 0.5 {
@@ -2651,15 +2639,16 @@ async fn morph<I: IntoGraphicList>(
}
(None, None) => None,
};
let mut vector = Vector { stroke, ..Default::default() };
let fill_paint = {
let source = fill_graphic_list_at(&content, source_index);
let target = fill_graphic_list_at(&content, target_index);
let source = graphic_list_at(&content, source_index, ATTR_FILL);
let target = graphic_list_at(&content, target_index, ATTR_FILL);
lerp_graphic(source.as_deref(), target.as_deref(), time)
};
let stroke_paint = {
let source = stroke_graphic_list_at(&content, source_index);
let target = stroke_graphic_list_at(&content, target_index);
let source = graphic_list_at(&content, source_index, ATTR_STROKE);
let target = graphic_list_at(&content, target_index, ATTR_STROKE);
lerp_graphic(source.as_deref(), target.as_deref(), time)
};
@@ -3457,7 +3446,7 @@ mod test {
let morphed = super::morph(Footprint::default(), content, 0.5, false, InterpolationDistribution::default(), List::default()).await;
let fill = fill_graphic_list_at(&morphed, 0).expect("Morph should keep the fill paint at the midpoint");
let fill = graphic_list_at(&morphed, 0, ATTR_FILL).expect("Morph should keep the fill paint at the midpoint");
// Interpolated color between red and blue should have >0 value on both R and B
let Some(Graphic::Color(colors)) = fill.element(0) else {