Replace the IntoPaint trait with direct Graphic-typed node inputs (#4442)

This commit is contained in:
Keavon Chambers
2026-09-15 17:50:14 +02:00
committed by Dennis Kobert
parent a41fdb3d75
commit 03262e1bab
18 changed files with 44 additions and 67 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -103,6 +103,7 @@ pub struct DocumentMessageHandler {
pub properties_panel_collapsed_sections: Vec<NodeId>,
/// The full Git commit hash of the Graphite repository that was used to build the editor.
/// We save this to provide a hint about which version of the editor was used to create the document.
#[serde(skip_deserializing, default)]
pub commit_hash: String,
/// The current pan, tilt, and zoom state of the viewport's view of the document canvas.
pub document_ptz: PTZ,

View File

@@ -435,7 +435,7 @@ impl<'a> ModifyInputsContext<'a> {
let Some(fill_node_id) = self.existing_chain_hosted_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, true) else {
return;
};
let input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput);
let input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::PaintInput);
let backup_input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::BackupColorInput);
// The backup remembers the last solid color, so the red-slash "none" choice leaves it untouched
@@ -457,7 +457,7 @@ impl<'a> ModifyInputsContext<'a> {
// Skip the rerender on all but the last input so the whole update triggers a single graph run
self.set_input_with_refresh(
InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput),
InputConnector::node(fill_node_id, graphene_std::vector::fill::PaintInput),
NodeInput::value(TaggedValue::GradientRamp(ramp), false),
true,
);

View File

@@ -1762,7 +1762,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
let is_text_node = reference.as_ref().is_some_and(|r| *r == DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER));
let is_stroke_node = reference.as_ref().is_some_and(|r| *r == DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER));
let is_fill_node = reference.as_ref().is_some_and(|r| *r == DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER));
let is_fill_input = is_fill_node && input_index == graphene_std::vector::fill::FillInput::INDEX;
let is_fill_input = is_fill_node && input_index == graphene_std::vector::fill::PaintInput::INDEX;
let is_shape_generator_node = reference.as_ref().is_some_and(|r| {
[regular_polygon::IDENTIFIER, star::IDENTIFIER, arc::IDENTIFIER, spiral::IDENTIFIER, grid::IDENTIFIER, arrow::IDENTIFIER]
.into_iter()

View File

@@ -2418,9 +2418,9 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
}
// Pass blank_assist=false because the assist slot is filled below ("Reverse Stops" button when in gradient mode)
let mut widgets_first_row = start_widgets(&ParameterWidgetsInfo::new(node_id, FillInput, false, context));
let mut widgets_first_row = start_widgets(&ParameterWidgetsInfo::new(node_id, PaintInput, false, context));
if get_document_node(node_id, context).is_ok_and(|node| node.input(FillInput).is_some_and(|input| input.is_exposed())) {
if get_document_node(node_id, context).is_ok_and(|node| node.input(PaintInput).is_some_and(|input| input.is_exposed())) {
return vec![LayoutGroup::row(widgets_first_row)];
}
@@ -2429,7 +2429,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
let layer = root_layer_for_chain_node(node_id, context);
let fill = match get_document_node(node_id, context) {
Ok(document_node) => match document_node.input_value(FillInput) {
Ok(document_node) => match document_node.input_value(PaintInput) {
Some(TaggedValue::Color(color)) => ResolvedFill::Solid(Some(*color)),
Some(value) if value.is_no_paint() => ResolvedFill::Solid(None),
Some(TaggedValue::GradientRamp(_)) => {
@@ -2477,7 +2477,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
.on_update(update_value(
move |_| TaggedValue::GradientRamp(GradientRamp::from(stops.reversed(settings.cyclic)).with_settings(settings)),
node_id,
FillInput,
PaintInput,
))
.widget_instance();
widgets_first_row.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
@@ -2502,7 +2502,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
let mut messages = vec![
NodeGraphMessage::SetInputValue {
node_id,
input_index: FillInput::INDEX,
input_index: PaintInput::INDEX,
value: color.map_or_else(TaggedValue::no_paint, TaggedValue::Color).into(),
}
.into(),
@@ -2524,7 +2524,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
messages: Box::new([
NodeGraphMessage::SetInputValue {
node_id,
input_index: FillInput::INDEX,
input_index: PaintInput::INDEX,
value: Box::new(TaggedValue::GradientRamp(ramp.clone())),
}
.into(),
@@ -2562,11 +2562,11 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
let entries = vec![
RadioEntryData::new("solid")
.label("Solid")
.on_update(update_value(move |_| backup_color.map_or_else(TaggedValue::no_paint, TaggedValue::Color), node_id, FillInput))
.on_update(update_value(move |_| backup_color.map_or_else(TaggedValue::no_paint, TaggedValue::Color), node_id, PaintInput))
.on_commit(commit_value),
RadioEntryData::new("gradient")
.label("Gradient")
.on_update(update_value(move |_| TaggedValue::GradientRamp(backup_gradient.clone()), node_id, FillInput))
.on_update(update_value(move |_| TaggedValue::GradientRamp(backup_gradient.clone()), node_id, PaintInput))
.on_commit(commit_value),
];

View File

@@ -725,7 +725,7 @@ fn find_fill_node(document: &DocumentMessageHandler) -> (Vec<graph_craft::docume
fn fill_paint_value(document: &DocumentMessageHandler) -> graph_craft::document::value::TaggedValue {
let (network_path, node_id) = find_fill_node(document);
let network = document.network_interface.nested_network(&network_path).expect("the found network path should resolve");
let input = network.nodes[&node_id].input(graphene_std::vector::fill::FillInput).expect("Fill should have a paint input");
let input = network.nodes[&node_id].input(graphene_std::vector::fill::PaintInput).expect("Fill should have a paint input");
input.as_value().expect("the paint input should hold a value").clone()
}
@@ -740,7 +740,7 @@ async fn none_fill_survives_document_reopen() {
editor
.handle_message(NodeGraphMessage::SetInputValue {
node_id: fill_node_id,
input_index: graphene_std::vector::fill::FillInput::INDEX,
input_index: graphene_std::vector::fill::PaintInput::INDEX,
value: Box::new(graph_craft::document::value::TaggedValue::no_paint()),
})
.await;
@@ -785,7 +785,7 @@ async fn legacy_four_input_fill_migrates_to_the_split_transform_shape() {
let fill_node = &network.nodes[&node_id];
assert_eq!(fill_node.inputs.len(), 7, "the legacy Fill should upgrade to the 7-input shape");
let paint = fill_node.input(graphene_std::vector::fill::FillInput);
let paint = fill_node.input(graphene_std::vector::fill::PaintInput);
assert!(
matches!(paint, Some(graph_craft::document::NodeInput::Node { .. })),
"the wired legacy fill should keep its connection, but became {paint:?}"
@@ -844,7 +844,7 @@ async fn eight_input_fill_migrates_the_spread_input_into_the_ramp() {
assert_eq!(fill_node.inputs.len(), 7, "the eight-input Fill should fold down to the 7-input shape");
let paint = fill_node.input_value(graphene_std::vector::fill::FillInput);
let paint = fill_node.input_value(graphene_std::vector::fill::PaintInput);
let Some(TaggedValue::GradientRamp(ramp)) = paint else {
panic!("the fill input should keep its gradient ramp value, but became {paint:?}");
};

View File

@@ -272,14 +272,14 @@ pub fn get_viewport_center(layer: LayerNodeIdentifier, network_interface: &NodeN
pub fn get_fill_node_id_with_direct_fill_input(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
let fill_node_id = NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?;
let fill_node = network_interface.document_network().nodes.get(&fill_node_id)?;
matches!(fill_node.input(graphene_std::vector::fill::FillInput)?, NodeInput::Value { .. }).then_some(fill_node_id)
matches!(fill_node.input(graphene_std::vector::fill::PaintInput)?, NodeInput::Value { .. }).then_some(fill_node_id)
}
/// Determine the input connector where the gradient chain enters the layer.
/// Returns Fill's fill input if the layer has a "Fill" node, otherwise returns the layer's content input.
pub fn gradient_chain_target_input(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> InputConnector {
if let Some(fill_node_id) = NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER)) {
InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput)
InputConnector::node(fill_node_id, graphene_std::vector::fill::PaintInput)
} else {
InputConnector::layer_secondary_input(layer.to_node())
}
@@ -382,7 +382,7 @@ pub fn get_upstream_color_value_node_id(layer: LayerNodeIdentifier, network_inte
pub fn get_fill_input_node_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
let fill_node_id = NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?;
let fill_node = network_interface.document_network().nodes.get(&fill_node_id)?;
let NodeInput::Node { node_id, .. } = fill_node.input(graphene_std::vector::fill::FillInput)? else {
let NodeInput::Node { node_id, .. } = fill_node.input(graphene_std::vector::fill::PaintInput)? else {
return None;
};
Some(*node_id)
@@ -410,7 +410,7 @@ pub fn get_gradient_stops(layer: LayerNodeIdentifier, network_interface: &NodeNe
.document_network()
.nodes
.get(&fill_node_id)
.and_then(|node| node.input(graphene_std::vector::fill::FillInput))
.and_then(|node| node.input(graphene_std::vector::fill::PaintInput))
.and_then(|input| input.as_value())
.and_then(|value| if let TaggedValue::GradientRamp(ramp) = value { Some(Gradient::from(ramp)) } else { None });
}
@@ -499,7 +499,7 @@ pub fn gradient_orientation_rightward(transform: glam::DAffine2) -> bool {
/// Get the current fill of a layer from the closest "Fill" node.
pub fn get_fill_color(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Color> {
let TaggedValue::Color(color) = NodeGraphLayer::new(layer, network_interface).parameter_value(graphene_std::vector::fill::FillInput)? else {
let TaggedValue::Color(color) = NodeGraphLayer::new(layer, network_interface).parameter_value(graphene_std::vector::fill::PaintInput)? else {
return None;
};
Some(*color)
@@ -767,7 +767,7 @@ pub struct FillNodeGradient {
pub fn read_fill_node_gradient(fill_node: &DocumentNode, bounding_box: impl FnOnce() -> [DVec2; 2]) -> Option<FillNodeGradient> {
use graphene_std::vector::fill;
let TaggedValue::GradientRamp(ramp) = fill_node.input(fill::FillInput)?.as_value()? else {
let TaggedValue::GradientRamp(ramp) = fill_node.input(fill::PaintInput)?.as_value()? else {
return None;
};
let settings = GradientSettings::from(ramp);
@@ -840,7 +840,7 @@ pub fn selected_fill_state(document: &DocumentMessageHandler) -> Option<Selected
let fill_choice = (|| {
let fill_node = document.network_interface.document_network().nodes.get(&fill_node_id)?;
match fill_node.input(graphene_std::vector::fill::FillInput)?.as_value()? {
match fill_node.input(graphene_std::vector::fill::PaintInput)?.as_value()? {
TaggedValue::Color(color) => Some(FillChoice::Solid(*color)),
TaggedValue::GradientRamp(ramp) => Some(FillChoice::Gradient(ramp.clone())),
value if value.is_no_paint() => Some(FillChoice::None),

View File

@@ -269,7 +269,7 @@ mod test_fill {
};
instrumented
.grab_all_input_level::<fill::FillInput, Color>(&editor.runtime)
.grab_all_input_level::<fill::PaintInput, Color>(&editor.runtime)
.flat_map(|list| list.iter_element_values().cloned().collect::<Vec<_>>())
.collect()
}
@@ -498,7 +498,7 @@ mod test_fill {
let ellipse_fill_id = get_fill_node_id_with_direct_fill_input(ellipse, &editor.active_document().network_interface).expect("the ellipse should have a Fill node");
editor.active_document_mut().network_interface.create_wire(
&OutputConnector::primary_output(shared_transform_id),
&InputConnector::node(ellipse_fill_id, graphene_std::vector::fill::FillInput),
&InputConnector::node(ellipse_fill_id, graphene_std::vector::fill::PaintInput),
&[],
);
@@ -518,7 +518,7 @@ mod test_fill {
assert_eq!(
document
.network_interface
.upstream_output_connector(&InputConnector::node(ellipse_fill_id, graphene_std::vector::fill::FillInput), &[])
.upstream_output_connector(&InputConnector::node(ellipse_fill_id, graphene_std::vector::fill::PaintInput), &[])
.and_then(|output| output.node_id()),
Some(shared_transform_id),
"the ellipse should keep being painted by the shared chain"

View File

@@ -2165,7 +2165,7 @@ mod test_gradient {
let fill_node_id = get_fill_node_id_with_direct_fill_input(layer, &document.network_interface)?;
let fill_node = document.network_interface.document_network().nodes.get(&fill_node_id)?;
let (stops, gradient_spread) = match fill_node.input(fill::FillInput)?.as_value()? {
let (stops, gradient_spread) = match fill_node.input(fill::PaintInput)?.as_value()? {
TaggedValue::GradientRamp(ramp) => (Gradient::from(ramp), ramp.gradient_spread),
_ => return None,
};
@@ -2305,7 +2305,7 @@ mod test_gradient {
editor
.handle_message(NodeGraphMessage::CreateWire {
output_connector: OutputConnector::primary_output(gradient_node_id),
input_connector: InputConnector::node(fill_node_id, fill::FillInput),
input_connector: InputConnector::node(fill_node_id, fill::PaintInput),
})
.await;
@@ -3031,7 +3031,7 @@ mod test_gradient {
editor
.handle_message(NodeGraphMessage::CreateWire {
output_connector: OutputConnector::primary_output(gradient_value_id),
input_connector: InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput),
input_connector: InputConnector::node(fill_node_id, graphene_std::vector::fill::PaintInput),
})
.await;
editor

View File

@@ -6,8 +6,6 @@ use canvas_utils::{Canvas, CanvasHandle};
use core_types::attribute::{Attr, OwnedAttr, Transform};
use core_types::color::SRGBA8;
use core_types::gpoll::GPoll;
#[cfg(target_family = "wasm")]
use core_types::list::List;
#[cfg(target_family = "wasm")]
use core_types::ATTR_TRANSFORM;
@@ -25,16 +23,10 @@ pub use graphene_canvas_utils as canvas_utils;
#[cfg(target_family = "wasm")]
use graphic_types::Graphic;
#[cfg(target_family = "wasm")]
use graphic_types::IntoGraphicList;
#[cfg(target_family = "wasm")]
use graphic_types::Vector;
#[cfg(target_family = "wasm")]
use graphic_types::markers::EditorMergedLayers;
use graphic_types::raster_types::Image;
use graphic_types::raster_types::{CPU, GPU, Raster};
#[cfg(target_family = "wasm")]
use graphic_types::vector_types::gradient::Gradient;
#[cfg(target_family = "wasm")]
use rendering::{Render, RenderParams, RenderSvgSegmentList, SvgRender};
use std::sync::Arc;
@@ -204,23 +196,7 @@ fn create_canvas(_: impl Ctx) -> CanvasHandle {
/// Renders a view of the input graphic within an area defined by the *Footprint*.
#[cfg(target_family = "wasm")]
#[node_macro::node(category(""))]
async fn rasterize<T: Clone + Send + Sync + dyn_any::StaticTypeSized>(
_: impl Ctx,
_: (),
#[implementations(
Vector,
Raster<CPU>,
Graphic,
Color,
Gradient,
)]
mut data: IList<T>,
footprint: Footprint,
mut canvas: CanvasHandle,
) -> (Raster<CPU>, Attr<Transform>, OwnedAttr<EditorMergedLayers>)
where
List<T>: Render + Clone + IntoGraphicList,
{
async fn rasterize(_: impl Ctx, _: (), mut data: IList<Graphic<'static>>, footprint: Footprint, mut canvas: CanvasHandle) -> (Raster<CPU>, Attr<Transform>, OwnedAttr<EditorMergedLayers>) {
use glam::{DAffine2, DVec2};
if footprint.transform.matrix2.determinant() == 0. {
@@ -232,7 +208,7 @@ where
// Snapshot the input as a List<Graphic> so the renderer can recurse into the original child layers
// when collecting metadata, exposing their click targets to editor tools (same mechanism as Boolean Operation).
// The copy is owned before the first await: the input's arena content dies with the spawning evaluation.
let upstream_graphic_list = data.clone().into_graphic_list();
let upstream_graphic_list = data.clone();
let merged_layers = OwnedAttr::new(Some(&upstream_graphic_list));
let mut render = SvgRender::new();

View File

@@ -350,16 +350,16 @@ fn fill<'e>(
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
/// The content with vector paths to apply the fill style to.
(element, content_appearance): (Vector, Attr<AppearanceMarker>),
/// The fill to paint the path with.
/// The paint to fill the path's interior with.
#[default(Color::BLACK)]
fill: IList<Graphic<'static>>,
paint: IList<Graphic<'static>>,
_backup_color: IList<Color>,
_backup_gradient: IList<Gradient>,
_gradient_form: GradientForm,
_has_transform: bool,
_transform: DAffine2,
) -> Result<(Vector, Attr<'e, AppearanceMarker>), Interrupt> {
let mut paint = paint_table(fill);
let mut paint = paint_table(paint);
default_gradient_paint(&mut paint, element.bounding_box(), _gradient_form, _has_transform.then_some(_transform));
let appearance = stamped_appearance(*content_appearance, Coverage::new_fill(), &paint, CoverPlacement::Above);
let parked_appearance = park_appearance(ctx.arena(), appearance)?;
@@ -372,7 +372,7 @@ fn fill<'e>(
fn fill_graphic_leveled<'e>(
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
(element, content_appearance): (Graphic<'static>, Attr<AppearanceMarker>),
#[default(Color::BLACK)] fill: IList<Graphic<'static>>,
#[default(Color::BLACK)] paint: IList<Graphic<'static>>,
_backup_color: IList<Color>,
_backup_gradient: IList<Gradient>,
_gradient_form: GradientForm,
@@ -383,7 +383,7 @@ fn fill_graphic_leveled<'e>(
RenderBoundingBox::Rectangle(bounds) => Some(bounds),
_ => None,
};
let mut paint = paint_table(fill);
let mut paint = paint_table(paint);
default_gradient_paint(&mut paint, bounds, _gradient_form, _has_transform.then_some(_transform));
let appearance = stamped_appearance(*content_appearance, Coverage::new_fill(), &paint, CoverPlacement::Above);
let parked_appearance = park_appearance(ctx.arena(), appearance)?;