Refactor the 'Fill' and 'Stroke' nodes to set "fill" and "stroke" attributes for paints (#4257)

* Allow using any graphics type for fill

* Adapt gradient/fill property panels and tools to handle List<T>

* Make the initial gradient transform covers the target's bounding box

* Introduce AnyGraphicListDyn to avoid combinatorial explosion

* Allow using any graphics type for stroke paint

* Add FIll node migration

* Add `for_each_vector_list_mut` instead of `set_paint_attribute`

* Adapt paint flow to read and write attributes instead of legacy Fill/Stroke.color

* Fix responsibilities of paint related node input setters

* Fix Morph by storing List<Graphic> for attributes rather than the concrete types

Store fill/stroke paints as List<Graphic> so Color/Gradient transitions
do not hit set_attribute_value_dyn's type-mismatch fallback to default paint.

* Preserve paint attributes in vector editing ops

* Enhance the clarity between direct and chained fill gradients

* Update demo arts

* Consolidate Fill node gradient appearance inputs

* Fix after the cubic review

* Revert "Consolidate Fill node gradient appearance inputs"

This reverts commit 9622feb20196e2c4da99e98ca95dcc2e34c2c98e.

* Replace AnyGraphicListDyn with generic paint connectors on the Fill and Stroke nodes

* Canonicalize paint attribute storage to List<Graphic> with a single write helper

* Fix Solidify Stroke missing fills stored in the legacy style

* Fix Solidify Stroke producing invisible strokes for legacy-only stroke colors

* Fix the initial gradient transform ignoring the bounding box's vertical extent

* Step paint at the morph midpoint instead of dropping it for unmixable pairings

* Remove migration-stage comments

* Clarify the initial gradient transform helper's doc and name

* Correct the bake_paint_transforms doc and prune dead tolerance arms

* Delete the unused Gradient::lerp

* Fix a comment typo

* Thread network paths through the legacy gradient bake so nested fills migrate

* Restore the legacy fill fallback in Expand Fill and Stroke

* Read gradient stops from the node's own input in the Fill properties panel

* Use the transform input constant instead of a hardcoded index

* Remove the unreachable wired color fallback in the Fill properties solid branch

* Narrow the fill overlay redraw check to actual fill inputs

* Coalesce the fill setter's graph runs into a single dispatch

* Position the Gradient Value node inserted for gradient stops

* Bake backup gradient placement during migration

* Persist pending gradient bakes so unfinished migrations retry on reopen

* Migrate the backup gradient's type and spread method

* Harden the gradient-migration pass against document switches and stale bakes

* Keep the Fill properties UI for layerless and nested Fill nodes

* Leave a wired gradient transform input connected instead of overwriting it

* Refuse to start a gradient chain ahead of existing layer content

* Decode a Fill node's gradient through one shared reader

* Nudge a degenerate bounding box so the Fill gradient transform stays invertible

* Broadcast Fill and Stroke paint with a single attribute-column pass

* Fix Morph stepping the target's stroke in near the source instead of the target

* Tidy conventions: clippy get_first, comment periods, sentence-case test messages

* Reattach the gradient orientation doc to its function

* Re-save demo artwork

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
YohYamasaki
2026-07-04 23:08:21 +00:00
committed by GitHub
co-authored by Keavon Chambers
parent f82b0a8fca
commit 9f9899cfd0
29 changed files with 1351 additions and 659 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ use vector_types::GradientStops;
/// Constructs a single-element `Artboard[]` with the given content and metadata stored as row attributes.
#[node_macro::node(category(""))]
pub async fn create_artboard<T: IntoGraphicList + 'n>(
pub async fn create_artboard<T: IntoGraphicList>(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
/// Graphics to include within the artboard.
#[implementations(
+6 -6
View File
@@ -565,7 +565,7 @@ pub async fn wrap_graphic<T: Into<Graphic> + 'n>(
/// 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 + 'n>(
pub async fn to_graphic<T: IntoGraphicList>(
_: impl Ctx,
#[implementations(
List<Graphic>,
@@ -620,7 +620,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 + 'n + Send + Clone>(_: impl Ctx, #[implementations(List<Graphic>, List<Vector>)] content: T) -> List<Vector> {
pub async 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();
@@ -653,25 +653,25 @@ pub async fn flatten_vector<T: IntoGraphicList + 'n + Send + Clone>(_: impl Ctx,
/// 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 + 'n + Send + Clone>(_: impl Ctx, #[implementations(List<Graphic>, List<Raster<CPU>>)] content: T) -> List<Raster<CPU>> {
pub async 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 + 'n + Send + Clone>(_: impl Ctx, #[implementations(List<Graphic>, List<Color>)] content: T) -> List<Color> {
pub async 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 + 'n + Send + Clone>(_: impl Ctx, #[implementations(List<Graphic>, List<GradientStops>)] content: T) -> List<GradientStops> {
pub async fn flatten_gradient<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<GradientStops>)] content: T) -> List<GradientStops> {
content.into_flattened_list()
}
/// Constructs a gradient from a `Color[]`, where the colors are evenly distributed as gradient stops across the range from 0 to 1.
#[node_macro::node(category("Color"))]
fn colors_to_gradient<T: IntoGraphicList + 'n + Send + Clone>(_: impl Ctx, #[implementations(List<Graphic>, List<Color>)] colors: T) -> List<GradientStops> {
fn colors_to_gradient<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Color>)] colors: T) -> List<GradientStops> {
let colors = colors.into_flattened_list::<Color>();
let total_colors = colors.len();