mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Drop the legacy fill and stroke rows for single-typed paint
This commit is contained in:
@@ -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::<Graphic>::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::<Graphic>::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::<Graphic>::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);
|
||||
|
||||
@@ -1769,7 +1769,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::<Graphic>::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()
|
||||
|
||||
@@ -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::<f64>::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::<f64>::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::<f64>::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::<f64>::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::<Vec<_>>().join(", "))
|
||||
.on_update(optionally_update_value(move |x: &TextInput| from_string(&x.value), node_id, CornerRadiusInput::<f64>::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::<f64>::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::<Graphic>::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::<Graphic>::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::<Graphic>::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<Color>) => {
|
||||
if let Ok(document_node) = get_document_node(node_id, context) {
|
||||
let color = match document_node.inputs[FillInput::<Graphic>::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::<Graphic>::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::<Graphic>::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::<Graphic>::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::<Graphic>::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::<Graphic>::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::<Graphic>::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.));
|
||||
|
||||
@@ -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<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.inputs.get(graphene_std::vector::fill::FillInput::<Graphic>::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::<Graphic>::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<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.inputs.get(graphene_std::vector::fill::FillInput::<Graphic>::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::<Graphic>::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<Color> {
|
||||
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::<Graphic>::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<FillNodeGradient> {
|
||||
use graphene_std::vector::fill;
|
||||
|
||||
let TaggedValue::Gradient(stops) = fill_node.inputs.get(fill::FillInput::<Graphic>::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<Option<Color>> {
|
||||
let color_index = graphene_std::vector::stroke::PaintInput::<Graphic>::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<Selected
|
||||
let fill_choice = (|| {
|
||||
let fill_node = document.network_interface.document_network().nodes.get(&fill_node_id)?;
|
||||
|
||||
match fill_node.inputs.get(graphene_std::vector::fill::FillInput::<Graphic>::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<Color>, 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::<Graphic>::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 {
|
||||
|
||||
@@ -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::<Graphic>::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::<Graphic>::INDEX),
|
||||
input_connector: InputConnector::node(fill_node_id, fill::FillInput::INDEX),
|
||||
})
|
||||
.await;
|
||||
|
||||
|
||||
@@ -116,14 +116,23 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
|
||||
.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(
|
||||
|
||||
@@ -75,13 +75,6 @@ where
|
||||
|
||||
fn eval(&self, input: &C) -> crate::gpoll::GPoll<crate::record::RecordValue<'e>> {
|
||||
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::<T>(),
|
||||
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())
|
||||
|
||||
@@ -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<Graphic>` or `List<Vector>`.
|
||||
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<Vector>));
|
||||
|
||||
fn vector_count(&self) -> usize;
|
||||
}
|
||||
|
||||
impl VectorListIterMut for List<Graphic> {
|
||||
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::<DAffine2>(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<Vector>)) {
|
||||
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<Graphic> {
|
||||
}
|
||||
|
||||
impl VectorListIterMut for List<Vector> {
|
||||
fn for_each_vector_mut(&mut self, mut f: impl FnMut(&mut Vector, DAffine2)) {
|
||||
let (elements, transforms) = self.element_and_attribute_slices_mut::<DAffine2>(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<Vector>)) {
|
||||
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<V: VectorListIterMut + Send, P: Clone + Send + Sync + CacheHash + 'static>(
|
||||
_: impl Ctx + ExtractIndex + InjectIndex + Copy,
|
||||
/// The content with vector paths to apply the fill style to.
|
||||
#[implementations(
|
||||
List<Vector>, List<Vector>, List<Vector>, List<Vector>, List<Vector>, List<Vector>,
|
||||
List<Graphic>, List<Graphic>, List<Graphic>, List<Graphic>, List<Graphic>, List<Graphic>,
|
||||
)]
|
||||
mut content: V,
|
||||
/// The fill to paint the path with.
|
||||
#[default(Color::BLACK)]
|
||||
#[implementations(
|
||||
Graphic, Vector, Color, GradientStops, Raster<CPU>, Raster<GPU>,
|
||||
Graphic, Vector, Color, GradientStops, Raster<CPU>, Raster<GPU>,
|
||||
)]
|
||||
fill: IList<P>,
|
||||
_backup_color: IList<Color>,
|
||||
_backup_gradient: IList<GradientStops>,
|
||||
_gradient_type: GradientType,
|
||||
_spread_method: GradientSpreadMethod,
|
||||
_transform: Option<DAffine2>,
|
||||
) -> V
|
||||
where
|
||||
List<P>: IntoGraphicList,
|
||||
{
|
||||
let mut fill: List<P> = legacy_list_of(fill);
|
||||
if let Some(gradient) = (&mut fill as &mut dyn std::any::Any).downcast_mut::<List<GradientStops>>() {
|
||||
if gradient.iter_attribute_values::<GradientType>(ATTR_GRADIENT_TYPE).is_none() {
|
||||
for value in gradient.iter_attribute_values_mut_or_default::<GradientType>(ATTR_GRADIENT_TYPE) {
|
||||
*value = _gradient_type;
|
||||
}
|
||||
}
|
||||
|
||||
if gradient.iter_attribute_values::<GradientSpreadMethod>(ATTR_SPREAD_METHOD).is_none() {
|
||||
for value in gradient.iter_attribute_values_mut_or_default::<GradientSpreadMethod>(ATTR_SPREAD_METHOD) {
|
||||
*value = _spread_method;
|
||||
}
|
||||
}
|
||||
|
||||
if gradient.iter_attribute_values::<DAffine2>(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::<DAffine2>(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::<List<Graphic>>(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<V, P: Clone + Send + Sync + CacheHash + 'static>(
|
||||
_: impl Ctx + ExtractIndex + InjectIndex + Copy,
|
||||
/// The content with vector paths to apply the stroke style to.
|
||||
#[implementations(
|
||||
List<Vector>, List<Vector>, List<Vector>, List<Vector>, List<Vector>, List<Vector>,
|
||||
List<Graphic>, List<Graphic>, List<Graphic>, List<Graphic>, List<Graphic>, List<Graphic>,
|
||||
)]
|
||||
mut content: List<V>,
|
||||
/// The stroke paint.
|
||||
#[default(Color::BLACK)]
|
||||
#[implementations(
|
||||
Graphic, Vector, Color, GradientStops, Raster<CPU>, Raster<GPU>,
|
||||
Graphic, Vector, Color, GradientStops, Raster<CPU>, Raster<GPU>,
|
||||
)]
|
||||
paint: IList<P>,
|
||||
/// 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,
|
||||
// <https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>
|
||||
/// 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<f64>,
|
||||
/// The phase offset distance from the starting point of the dash pattern.
|
||||
#[unit(" px")]
|
||||
dash_offset: f64,
|
||||
) -> List<V>
|
||||
where
|
||||
List<V>: VectorListIterMut + Send,
|
||||
List<P>: 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::<List<Graphic>>(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<T: Clone + Send + Sync + 'static>(level: core_types::node::List<'_, T>) -> List<T> {
|
||||
// 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::<T>(&item).expect("the run holds the row's element type")
|
||||
}
|
||||
|
||||
fn park_paint<'e>(arena: &'e core_types::arena::Arena, paint: List<Graphic>) -> Result<&'e List<Graphic>, 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<Graphic>, 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<Graphic> {
|
||||
// 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<Graphic>,
|
||||
_backup_color: IList<Color>,
|
||||
@@ -378,15 +209,15 @@ fn fill_vector_leveled<'e>(
|
||||
_spread_method: GradientSpreadMethod,
|
||||
_transform: Option<DAffine2>,
|
||||
) -> 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<TransformAttr>),
|
||||
/// The stroke paint.
|
||||
#[default(Color::BLACK)]
|
||||
paint: IList<Graphic>,
|
||||
/// 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<f64>,
|
||||
/// The phase offset distance from the starting point of the dash pattern.
|
||||
#[unit(" px")]
|
||||
dash_offset: f64,
|
||||
) -> Result<(Vector, Attr<TransformAttr>, 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<Vector>| {
|
||||
let (elements, transforms) = list.element_and_attribute_slices_mut::<DAffine2>(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<TransformAttr>),
|
||||
#[default(Color::BLACK)]
|
||||
paint: IList<Graphic>,
|
||||
#[unit(" px")]
|
||||
#[default(2.)]
|
||||
weight: f64,
|
||||
align: StrokeAlign,
|
||||
cap: StrokeCap,
|
||||
join: StrokeJoin,
|
||||
#[default(4.)]
|
||||
miter_limit: f64,
|
||||
paint_order: PaintOrder,
|
||||
dash_lengths: IList<f64>,
|
||||
#[unit(" px")]
|
||||
dash_offset: f64,
|
||||
) -> Result<(Graphic, Attr<TransformAttr>, 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<I: Send + Clone>(
|
||||
|
||||
Reference in New Issue
Block a user