diff --git a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs index e5211280bf..638e2a133d 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -457,7 +457,7 @@ impl<'a> ModifyInputsContext<'a> { let Some(fill_node_id) = self.existing_proto_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, true) else { return; }; - let input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::::INDEX); + let input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::INDEX); let backup_input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::BackupColorInput::INDEX); self.set_input_with_refresh(backup_input_connector, NodeInput::value(TaggedValue::Color(color), false), true); @@ -474,7 +474,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::::INDEX), + InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::INDEX), NodeInput::value(TaggedValue::Gradient(gradient), false), true, ); @@ -715,7 +715,7 @@ impl<'a> ModifyInputsContext<'a> { return; }; - let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::PaintInput::::INDEX); + let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::PaintInput::INDEX); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(color), false), true); let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::WeightInput::INDEX); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.weight), false), true); diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index a4852dae57..52fb20a17f 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -1769,7 +1769,7 @@ impl<'a> MessageHandler> 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::FillInput::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() diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 16848c4909..8f412b19aa 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -16,7 +16,6 @@ use graph_craft::application_io::resource::ResourceId; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput}; use graph_craft::{Type, concrete}; -use graphene_std::Graphic; use graphene_std::NodeInputDecleration; use graphene_std::animation::RealTimeMode; use graphene_std::brush::brush_stroke::BrushStroke; @@ -2172,7 +2171,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties use graphene_std::vector::generator_nodes::rectangle::*; // Corner Radius - let mut corner_radius_row_1 = start_widgets(ParameterWidgetsInfo::new(node_id, CornerRadiusInput::::INDEX, true, context)); + let mut corner_radius_row_1 = start_widgets(ParameterWidgetsInfo::new(node_id, CornerRadiusInput::INDEX, true, context)); corner_radius_row_1.push(Separator::new(SeparatorStyle::Unrelated).widget_instance()); let mut corner_radius_row_2 = vec![Separator::new(SeparatorStyle::Unrelated).widget_instance()]; @@ -2192,7 +2191,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties }; if let Some(&TaggedValue::Bool(is_individual)) = input.as_non_exposed_value() { // Values - let Some(input) = document_node.inputs.get(CornerRadiusInput::::INDEX) else { + let Some(input) = document_node.inputs.get(CornerRadiusInput::INDEX) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; }; @@ -2220,7 +2219,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties .into(), NodeGraphMessage::SetInputValue { node_id, - input_index: CornerRadiusInput::::INDEX, + input_index: CornerRadiusInput::INDEX, value: TaggedValue::F64(uniform_val), } .into(), @@ -2240,7 +2239,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties .into(), NodeGraphMessage::SetInputValue { node_id, - input_index: CornerRadiusInput::::INDEX, + input_index: CornerRadiusInput::INDEX, value: TaggedValue::F64Array(individual_val_for_switch.clone()), } .into(), @@ -2263,13 +2262,13 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties }; TextInput::default() .value(individual_val.iter().map(|v| v.to_string()).collect::>().join(", ")) - .on_update(optionally_update_value(move |x: &TextInput| from_string(&x.value), node_id, CornerRadiusInput::::INDEX)) + .on_update(optionally_update_value(move |x: &TextInput| from_string(&x.value), node_id, CornerRadiusInput::INDEX)) .widget_instance() } else { NumberInput::default() .value(Some(uniform_val)) .unit(" px") - .on_update(update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, CornerRadiusInput::::INDEX)) + .on_update(update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, CornerRadiusInput::INDEX)) .on_commit(commit_value) .widget_instance() }; @@ -2457,13 +2456,13 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte Other, } - let connector = InputConnector::node(node_id, FillInput::::INDEX); + let connector = InputConnector::node(node_id, FillInput::INDEX); let input_type = context.network_interface.input_type(&connector, context.selection_network_path); // 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::::INDEX, false, context)); + let mut widgets_first_row = start_widgets(ParameterWidgetsInfo::new(node_id, FillInput::INDEX, false, context)); - if get_document_node(node_id, context).is_ok_and(|node| node.inputs.get(FillInput::::INDEX).is_some_and(|input| input.is_exposed())) { + if get_document_node(node_id, context).is_ok_and(|node| node.inputs.get(FillInput::INDEX).is_some_and(|input| input.is_exposed())) { return vec![LayoutGroup::row(widgets_first_row)]; } @@ -2474,7 +2473,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte let fill = match input_type.compiled_nested_type() { Some(ty) if ty == &concrete!(List) => { if let Ok(document_node) = get_document_node(node_id, context) { - let color = match document_node.inputs[FillInput::::INDEX].as_value() { + let color = match document_node.inputs[FillInput::INDEX].as_value() { Some(&TaggedValue::Color(c)) => c, _ => None, }; @@ -2525,7 +2524,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte let reverse_button = IconButton::new("Reverse", 24) .tooltip_label("Reverse Stops") .tooltip_description("Reverse the gradient color stops.") - .on_update(update_value(move |_| TaggedValue::Gradient(stops.reversed()), node_id, FillInput::::INDEX)) + .on_update(update_value(move |_| TaggedValue::Gradient(stops.reversed()), node_id, FillInput::INDEX)) .widget_instance(); widgets_first_row.push(Separator::new(SeparatorStyle::Unrelated).widget_instance()); widgets_first_row.push(reverse_button); @@ -2549,7 +2548,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: FillInput::INDEX, value: TaggedValue::Color(color), } .into(), @@ -2566,7 +2565,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: FillInput::INDEX, value: TaggedValue::Gradient(gradient.clone()), } .into(), @@ -2607,11 +2606,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 |_| TaggedValue::Color(backup_color), node_id, FillInput::::INDEX)) + .on_update(update_value(move |_| TaggedValue::Color(backup_color), node_id, FillInput::INDEX)) .on_commit(commit_value), RadioEntryData::new("gradient") .label("Gradient") - .on_update(update_value(move |_| TaggedValue::Gradient(backup_gradient.clone()), node_id, FillInput::::INDEX)) + .on_update(update_value(move |_| TaggedValue::Gradient(backup_gradient.clone()), node_id, FillInput::INDEX)) .on_commit(commit_value), ]; @@ -2724,7 +2723,7 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) - let miter_limit_disabled = join_value != &StrokeJoin::Miter; let color = color_widget( - ParameterWidgetsInfo::new(node_id, PaintInput::::INDEX, true, context), + ParameterWidgetsInfo::new(node_id, PaintInput::INDEX, true, context), crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(), ); let weight = number_widget(ParameterWidgetsInfo::new(node_id, WeightInput::INDEX, true, context), NumberInput::default().unit(" px").min(0.)); diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs index 1ad5a38970..ed779bd779 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -16,7 +16,7 @@ use graphene_std::text::{Font, TypesettingConfig}; use graphene_std::vector::misc::ManipulatorPointId; use graphene_std::vector::style::{FillChoice, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin, initial_gradient_transform_for_bounding_box}; use graphene_std::vector::{GradientSpreadMethod, GradientStops, GradientType, PointId, SegmentId, VectorModificationType}; -use graphene_std::{Color, Graphic}; +use graphene_std::Color; use std::collections::VecDeque; /// Returns the ID of the first Spline node in the horizontal flow which is not followed by a `Path` node, or `None` if none exists. @@ -275,14 +275,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 { 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.inputs.get(graphene_std::vector::fill::FillInput::::INDEX)?, NodeInput::Value { .. }).then_some(fill_node_id) + matches!(fill_node.inputs.get(graphene_std::vector::fill::FillInput::INDEX)?, 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::::INDEX) + InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::INDEX) } else { InputConnector::node(layer.to_node(), 1) } @@ -303,7 +303,7 @@ pub fn get_upstream_gradient_value_node_id(layer: LayerNodeIdentifier, network_i pub fn get_fill_input_node_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { 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.inputs.get(graphene_std::vector::fill::FillInput::::INDEX)? else { + let NodeInput::Node { node_id, .. } = fill_node.inputs.get(graphene_std::vector::fill::FillInput::INDEX)? else { return None; }; Some(*node_id) @@ -317,7 +317,7 @@ pub fn get_gradient_stops(layer: LayerNodeIdentifier, network_interface: &NodeNe .document_network() .nodes .get(&fill_node_id) - .and_then(|node| node.inputs.get(graphene_std::vector::fill::FillInput::::INDEX)) + .and_then(|node| node.inputs.get(graphene_std::vector::fill::FillInput::INDEX)) .and_then(|input| input.as_value()) .and_then(|value| if let TaggedValue::Gradient(gradient) = value { Some(gradient.clone()) } else { None }); } @@ -363,7 +363,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 { let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?; - let &TaggedValue::Color(color) = inputs.get(graphene_std::vector::fill::FillInput::::INDEX)?.as_value()? else { + let &TaggedValue::Color(color) = inputs.get(graphene_std::vector::fill::FillInput::INDEX)?.as_value()? else { return None; }; color @@ -638,7 +638,7 @@ pub struct FillNodeGradient { pub fn read_fill_node_gradient(fill_node: &DocumentNode, bounding_box: impl FnOnce() -> [DVec2; 2]) -> Option { use graphene_std::vector::fill; - let TaggedValue::Gradient(stops) = fill_node.inputs.get(fill::FillInput::::INDEX)?.as_value()? else { + let TaggedValue::Gradient(stops) = fill_node.inputs.get(fill::FillInput::INDEX)?.as_value()? else { return None; }; let gradient_type = match fill_node.inputs.get(fill::GradientTypeInput::INDEX).and_then(|input| input.as_value()) { @@ -665,7 +665,7 @@ pub fn read_fill_node_gradient(fill_node: &DocumentNode, bounding_box: impl FnOn } /// Returns the stroke color from a layer's upstream Stroke node. pub fn get_stroke_color(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option> { - let color_index = graphene_std::vector::stroke::PaintInput::::INDEX; + let color_index = graphene_std::vector::stroke::PaintInput::INDEX; let tagged = NodeGraphLayer::new(layer, network_interface).find_input(&DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER), color_index)?; if let TaggedValue::Color(color) = tagged { Some(*color) } else { None } } @@ -699,7 +699,7 @@ pub fn selected_fill_state(document: &DocumentMessageHandler) -> Option::INDEX)?.as_value()? { + match fill_node.inputs.get(graphene_std::vector::fill::FillInput::INDEX)?.as_value()? { &TaggedValue::Color(color) => Some(color.map_or(FillChoice::None, FillChoice::Solid)), TaggedValue::Gradient(stops) => Some(FillChoice::Gradient(stops.clone())), _ => None, @@ -825,7 +825,7 @@ pub fn set_stroke_color_for_selected_layers(color: Option, weight: f64, d let layers: Vec<_> = document.network_interface.selected_nodes().selected_layers_except_artboards(&document.network_interface).collect(); for layer in layers { if let Some(node_id) = get_stroke_id(layer, &document.network_interface) { - let input_index = graphene_std::vector::stroke::PaintInput::::INDEX; + let input_index = graphene_std::vector::stroke::PaintInput::INDEX; let value = TaggedValue::Color(color); responses.add(NodeGraphMessage::SetInputValue { node_id, input_index, value }); } else { diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index 1f62130a70..c887d512ce 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -2058,7 +2058,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 = match fill_node.inputs.get(fill::FillInput::::INDEX)?.as_value()? { + let stops = match fill_node.inputs.get(fill::FillInput::INDEX)?.as_value()? { TaggedValue::Gradient(stops) => stops.clone(), _ => return None, }; @@ -2176,7 +2176,7 @@ mod test_gradient { editor .handle_message(NodeGraphMessage::CreateWire { output_connector: OutputConnector::node(gradient_node_id, 0), - input_connector: InputConnector::node(fill_node_id, fill::FillInput::::INDEX), + input_connector: InputConnector::node(fill_node_id, fill::FillInput::INDEX), }) .await; diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 634622971e..b021f65231 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -116,14 +116,23 @@ fn node_registry() -> HashMap> { .into_iter() .map(|entry| (graphene_std::transform_nodes::transform_nodes::transform::IDENTIFIER.clone(), entry)), ); - // The leveled fill rows, served under the legacy fill's identifier beside - // its legacy list rows. + // The graphic-lane fill and stroke rows, served under their identifiers. node_types.extend( - graphene_std::vector::fill_vector_leveled_entries() + graphene_std::vector::fill_graphic_leveled_entries() .into_iter() - .chain(graphene_std::vector::fill_graphic_leveled_entries()) .map(|entry| (graphene_std::vector::fill::IDENTIFIER.clone(), entry)), ); + node_types.extend( + graphene_std::vector::stroke_graphic_leveled_entries() + .into_iter() + .map(|entry| (graphene_std::vector::stroke::IDENTIFIER.clone(), entry)), + ); + // The boolean operation's plain vector rows, served under its identifier. + node_types.extend( + graphene_std::path_bool_nodes::boolean_operation_vector_entries() + .into_iter() + .map(|entry| (graphene_std::path_bool_nodes::boolean_operation::IDENTIFIER.clone(), entry)), + ); // Element-wise coercion into `Graphic` for single-typed leveled inputs, // served by the to_graphic rows. node_types.extend( diff --git a/node-graph/libraries/core-types/src/value.rs b/node-graph/libraries/core-types/src/value.rs index b3e9a2aa85..33025b8a02 100644 --- a/node-graph/libraries/core-types/src/value.rs +++ b/node-graph/libraries/core-types/src/value.rs @@ -75,13 +75,6 @@ where fn eval(&self, input: &C) -> crate::gpoll::GPoll> { let Some(value) = self.values.get(input.innermost_index() as usize) else { - eprintln!( - "DEBUG level value past end: {} lane {} of {}\n{}", - std::any::type_name::(), - input.innermost_index(), - self.values.len(), - std::backtrace::Backtrace::force_capture() - ); return crate::gpoll::GPoll::error("value level addressed past its items"); }; crate::record::lift_poll(crate::gpoll::GPoll::Final(value.clone()), &self.layout, input.arena()) diff --git a/node-graph/nodes/vector/src/vector_nodes.rs b/node-graph/nodes/vector/src/vector_nodes.rs index b1eda5aa2e..121014733f 100644 --- a/node-graph/nodes/vector/src/vector_nodes.rs +++ b/node-graph/nodes/vector/src/vector_nodes.rs @@ -10,8 +10,9 @@ use core_types::transform::{Footprint, Transform}; use core_types::uuid::NodeId; use core_types::attribute::Attr; use core_types::gpoll::GraphError; -use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, CacheHash, Color, Ctx, DeriveCtx, ExtractIndex, InjectIndex}; -use graphic_types::markers::Fill; +use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Color, Ctx, DeriveCtx, ExtractIndex, InjectIndex}; +use graphic_types::markers::{Fill, Stroke as StrokeAttr}; +use core_types::attribute::Transform as TransformAttr; use glam::{DAffine2, DMat2, DVec2}; use graphic_types::Vector; use graphic_types::graphic::{bake_paint_transforms, graphic_list_at, has_paint_at, is_paint_present, set_paint_attribute_at}; @@ -40,24 +41,12 @@ use vector_types::{GradientSpreadMethod, GradientType}; /// Implemented for types that contain vector items reachable via mutable access. /// Used for the fill and stroke nodes so they can apply to either `List` or `List`. trait VectorListIterMut { - fn for_each_vector_mut(&mut self, f: impl FnMut(&mut Vector, DAffine2)); - fn for_each_vector_list_mut(&mut self, f: impl FnMut(&mut List)); fn vector_count(&self) -> usize; } impl VectorListIterMut for List { - fn for_each_vector_mut(&mut self, mut f: impl FnMut(&mut Vector, DAffine2)) { - for graphic in self.iter_element_values_mut() { - let Some(vector_list) = graphic.as_vector_mut() else { continue }; - let (elements, transforms) = vector_list.element_and_attribute_slices_mut::(ATTR_TRANSFORM); - for (vector, transform) in elements.iter_mut().zip(transforms.iter()) { - f(vector, *transform); - } - } - } - fn for_each_vector_list_mut(&mut self, mut f: impl FnMut(&mut List)) { for graphic in self.iter_element_values_mut() { if let Some(vector_list) = graphic.as_vector_mut() { @@ -72,13 +61,6 @@ impl VectorListIterMut for List { } impl VectorListIterMut for List { - fn for_each_vector_mut(&mut self, mut f: impl FnMut(&mut Vector, DAffine2)) { - let (elements, transforms) = self.element_and_attribute_slices_mut::(ATTR_TRANSFORM); - for (vector, transform) in elements.iter_mut().zip(transforms.iter()) { - f(vector, *transform); - } - } - fn for_each_vector_list_mut(&mut self, mut f: impl FnMut(&mut List)) { f(self); } @@ -156,167 +138,6 @@ where content } -/// Applies a fill style to the vector content, giving an appearance to the area within the interior of the geometry. -#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))] -fn fill( - _: impl Ctx + ExtractIndex + InjectIndex + Copy, - /// The content with vector paths to apply the fill style to. - #[implementations( - List, List, List, List, List, List, - List, List, List, List, List, List, - )] - mut content: V, - /// The fill to paint the path with. - #[default(Color::BLACK)] - #[implementations( - Graphic, Vector, Color, GradientStops, Raster, Raster, - Graphic, Vector, Color, GradientStops, Raster, Raster, - )] - fill: IList

, - _backup_color: IList, - _backup_gradient: IList, - _gradient_type: GradientType, - _spread_method: GradientSpreadMethod, - _transform: Option, -) -> V -where - List

: IntoGraphicList, -{ - let mut fill: List

= legacy_list_of(fill); - if let Some(gradient) = (&mut fill as &mut dyn std::any::Any).downcast_mut::>() { - if gradient.iter_attribute_values::(ATTR_GRADIENT_TYPE).is_none() { - for value in gradient.iter_attribute_values_mut_or_default::(ATTR_GRADIENT_TYPE) { - *value = _gradient_type; - } - } - - if gradient.iter_attribute_values::(ATTR_SPREAD_METHOD).is_none() { - for value in gradient.iter_attribute_values_mut_or_default::(ATTR_SPREAD_METHOD) { - *value = _spread_method; - } - } - - if gradient.iter_attribute_values::(ATTR_TRANSFORM).is_none() { - let transform = _transform.unwrap_or_else(|| { - // Construct a transform that covers the bounding box of the paint target - let mut bounds: Option<[DVec2; 2]> = None; - content.for_each_vector_mut(|vector, _| { - if let Some([min, max]) = vector.bounding_box() { - bounds = Some(match bounds { - Some([bmin, bmax]) => [bmin.min(min), bmax.max(max)], - None => [min, max], - }); - } - }); - - // Nudge a degenerate axis so the gradient transform stays invertible, matching the editor's `nonzero_bounding_box` - let [min, mut max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]); - if max.x - min.x < 1e-10 { - max.x = min.x + 1.; - } - if max.y - min.y < 1e-10 { - max.y = min.y + 1.; - } - initial_gradient_transform_for_bounding_box([min, max]) - }); - - for value in gradient.iter_attribute_values_mut_or_default::(ATTR_TRANSFORM) { - *value = transform; - } - } - } - - let fill = fill.into_graphic_list(); - content.for_each_vector_list_mut(|vector_list| { - // Broadcast the same paint to every item, scanning the attribute column once instead of per index - for slot in vector_list.iter_attribute_values_mut_or_default::>(ATTR_FILL) { - *slot = fill.clone(); - } - }); - content -} - -/// Applies a stroke style to the vector content, giving an appearance to the area within the outline of the geometry. -#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("stroke_properties"))] -fn stroke( - _: impl Ctx + ExtractIndex + InjectIndex + Copy, - /// The content with vector paths to apply the stroke style to. - #[implementations( - List, List, List, List, List, List, - List, List, List, List, List, List, - )] - mut content: List, - /// The stroke paint. - #[default(Color::BLACK)] - #[implementations( - Graphic, Vector, Color, GradientStops, Raster, Raster, - Graphic, Vector, Color, GradientStops, Raster, Raster, - )] - paint: IList

, - /// The stroke thickness. - #[unit(" px")] - #[default(2.)] - weight: f64, - /// The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape. - align: StrokeAlign, - /// The shape of the stroke at open endpoints. - cap: StrokeCap, - /// The curvature of the bent stroke at sharp corners. - join: StrokeJoin, - /// The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio. - #[default(4.)] - miter_limit: f64, - // - /// 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. - dash_lengths: IList, - /// The phase offset distance from the starting point of the dash pattern. - #[unit(" px")] - dash_offset: f64, -) -> List -where - List: VectorListIterMut + Send, - List

: IntoGraphicList, -{ - let dash_lengths = (0..dash_lengths.len()).map(|index| dash_lengths.get(index).max(0.)).collect(); - - let stroke = Stroke { - weight, - dash_lengths, - dash_offset, - cap, - join, - join_miter_limit: miter_limit, - align, - transform: DAffine2::IDENTITY, - paint_order, - }; - - content.for_each_vector_mut(|vector, transform| { - let mut stroke = stroke.clone(); - stroke.transform *= transform; - vector.stroke = Some(stroke); - }); - - let paint = legacy_list_of(paint).into_graphic_list(); - content.for_each_vector_list_mut(|vector_list| { - // Broadcast the same paint to every item, scanning the attribute column once instead of per index - for slot in vector_list.iter_attribute_values_mut_or_default::>(ATTR_STROKE) { - *slot = paint.clone(); - } - }); - content -} - -/// The transitional value bridge: a materialized level as the legacy list the -/// unconverted body consumes. -fn legacy_list_of(level: core_types::node::List<'_, T>) -> List { - // SAFETY: a materialized input's frames are arena-resident. - let item = unsafe { core_types::record::GroupItem::from_resident(level.batch()) }; - graphic_types::graphic::run_to_render_list::(&item).expect("the run holds the row's element type") -} - fn park_paint<'e>(arena: &'e core_types::arena::Arena, paint: List) -> Result<&'e List, Interrupt> { let (parked, _) = arena.alloc(paint).ok_or(GraphError { kind: core_types::gpoll::ErrorKind::ArenaExhausted, @@ -362,14 +183,24 @@ fn default_gradient_paint(paint: &mut List, bounds: Option<[DVec2; 2]>, } } -/// The leveled fill over vector lanes: builds the paint table and writes each -/// lane's fill attribute. Registered under the legacy fill's identifier; the -/// gradient transform fallback spans the lane's own bounds rather than the -/// whole content's. -#[node_macro::node(category(""))] -fn fill_vector_leveled<'e>( +/// The materialized paint level as the canonical paint table. +fn paint_table(paint: core_types::node::List<'_, Graphic>) -> List { + // SAFETY: a materialized input's frames are arena-resident. + let item = unsafe { core_types::record::GroupItem::from_resident(paint.batch()) }; + graphic_types::graphic::group_to_legacy_list(&core_types::record::Group { + row: None, + content: core_types::record::GroupContent::Run(item), + }) +} + +/// Applies a fill style to the vector content, giving an appearance to the area within the interior of the geometry. +/// The gradient transform fallback spans the lane's own bounds rather than the whole content's. +#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))] +fn fill<'e>( ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy, + /// The content with vector paths to apply the fill style to. (element, _content_fill): (Vector, Attr<'e, Fill>), + /// The fill to paint the path with. #[default(Color::BLACK)] fill: IList, _backup_color: IList, @@ -378,15 +209,15 @@ fn fill_vector_leveled<'e>( _spread_method: GradientSpreadMethod, _transform: Option, ) -> Result<(Vector, Attr<'e, Fill>), Interrupt> { - // SAFETY: a materialized input's frames are arena-resident. - let item = unsafe { core_types::record::GroupItem::from_resident(fill.batch()) }; - let mut paint = graphic_types::graphic::group_to_legacy_list(&core_types::record::Group { row: None, content: core_types::record::GroupContent::Run(item) }); + let mut paint = paint_table(fill); default_gradient_paint(&mut paint, element.bounding_box(), _gradient_type, _spread_method, _transform); let parked = park_paint(ctx.arena(), paint)?; Ok((element, Attr(Some(parked)))) } -/// The leveled fill over graphic lanes, as [`fill_vector_leveled`]. +/// The fill over graphic lanes: the marker parks on the lane and the render +/// boundary moves it onto the interior vector lists the legacy paint readers +/// inspect. Registered under the fill's identifier. #[node_macro::node(category(""))] fn fill_graphic_leveled<'e>( ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy, @@ -403,16 +234,135 @@ fn fill_graphic_leveled<'e>( RenderBoundingBox::Rectangle(bounds) => Some(bounds), _ => None, }; - // SAFETY: a materialized input's frames are arena-resident. - let item = unsafe { core_types::record::GroupItem::from_resident(fill.batch()) }; - let mut paint = graphic_types::graphic::group_to_legacy_list(&core_types::record::Group { row: None, content: core_types::record::GroupContent::Run(item) }); + let mut paint = paint_table(fill); default_gradient_paint(&mut paint, bounds, _gradient_type, _spread_method, _transform); let parked = park_paint(ctx.arena(), paint)?; Ok((element, Attr(Some(parked)))) } +/// Applies a stroke style to the vector content, giving an appearance to the area within the outline of the geometry. +#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("stroke_properties"))] +fn stroke<'e>( + ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy, + /// The content with vector paths to apply the stroke style to. + (element, content_transform): (Vector, Attr), + /// The stroke paint. + #[default(Color::BLACK)] + paint: IList, + /// The stroke thickness. + #[unit(" px")] + #[default(2.)] + weight: f64, + /// The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape. + align: StrokeAlign, + /// The shape of the stroke at open endpoints. + cap: StrokeCap, + /// The curvature of the bent stroke at sharp corners. + join: StrokeJoin, + /// The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio. + #[default(4.)] + miter_limit: f64, + /// 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. + dash_lengths: IList, + /// The phase offset distance from the starting point of the dash pattern. + #[unit(" px")] + dash_offset: f64, +) -> Result<(Vector, Attr, Attr<'e, StrokeAttr>), Interrupt> { + let dash_lengths = (0..dash_lengths.len()).map(|index| dash_lengths.get(index).max(0.)).collect(); + let mut stroke = Stroke { + weight, + dash_lengths, + dash_offset, + cap, + join, + join_miter_limit: miter_limit, + align, + transform: DAffine2::IDENTITY, + paint_order, + }; + stroke.transform *= *content_transform; + + let mut element = element; + element.stroke = Some(stroke); + + let paint = paint_table(paint); + let parked = park_paint(ctx.arena(), paint)?; + Ok((element, Attr(*content_transform), Attr(Some(parked)))) +} + +/// The vector items of a graphic lane's interior, one wrap level deep, the +/// reach of the pre-flip broadcast over a legacy list. +fn for_each_interior_vector_mut(element: &mut Graphic, mut f: impl FnMut(&mut Vector, DAffine2)) { + let mut walk_list = |list: &mut List| { + let (elements, transforms) = list.element_and_attribute_slices_mut::(ATTR_TRANSFORM); + for (vector, transform) in elements.iter_mut().zip(transforms.iter()) { + f(vector, *transform); + } + }; + match element { + Graphic::Vector(list) => walk_list(list), + Graphic::Graphic(children) => { + for child in children.iter_element_values_mut() { + if let Some(list) = child.as_vector_mut() { + walk_list(list); + } + } + } + _ => {} + } +} + +/// The stroke over graphic lanes: the style applies to the interior vectors, +/// the paint marker parks on the lane for the render boundary to place. +/// Registered under the stroke's identifier. +#[node_macro::node(category(""))] +fn stroke_graphic_leveled<'e>( + ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy, + (element, content_transform): (Graphic, Attr), + #[default(Color::BLACK)] + paint: IList, + #[unit(" px")] + #[default(2.)] + weight: f64, + align: StrokeAlign, + cap: StrokeCap, + join: StrokeJoin, + #[default(4.)] + miter_limit: f64, + paint_order: PaintOrder, + dash_lengths: IList, + #[unit(" px")] + dash_offset: f64, +) -> Result<(Graphic, Attr, Attr<'e, StrokeAttr>), Interrupt> { + let dash_lengths = (0..dash_lengths.len()).map(|index| dash_lengths.get(index).max(0.)).collect(); + let stroke = Stroke { + weight, + dash_lengths, + dash_offset, + cap, + join, + join_miter_limit: miter_limit, + align, + transform: DAffine2::IDENTITY, + paint_order, + }; + + let mut element = element; + for_each_interior_vector_mut(&mut element, |vector, transform| { + let mut stroke = stroke.clone(); + stroke.transform *= transform; + vector.stroke = Some(stroke); + }); + + let paint = paint_table(paint); + let parked = park_paint(ctx.arena(), paint)?; + Ok((element, Attr(*content_transform), Attr(Some(parked)))) +} + pub use _fill_graphic_leveled_mod::fill_graphic_leveled_entries; -pub use _fill_vector_leveled_mod::fill_vector_leveled_entries; +pub use _stroke_graphic_leveled_mod::stroke_graphic_leveled_entries; #[node_macro::node(name("Copy to Points"), category("Repeat"), path(core_types::vector))] fn copy_to_points(