mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 09:48:11 +08:00
Retire the stroke paint order input into fill and stroke node order
The Stroke node no longer takes a paint order value: stroke-below is now the Stroke node sitting upstream of the Fill node, and the editor's paint order control rewires the two nodes through set_stroke_paint_order. Documents with the retired input migrate to the nine-input shape, their stored order replayed as that topology rewrite. The Stroke struct drops its paint_order field; renderers read stroke_below from the resolved appearance alone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
46e0a4f2f5
commit
f726e712bc
@@ -430,10 +430,6 @@ impl TableItemLayout for Vector {
|
|||||||
TextLabel::new("Stroke Transform").narrow(true).widget_instance(),
|
TextLabel::new("Stroke Transform").narrow(true).widget_instance(),
|
||||||
TextLabel::new(format_transform_matrix(stroke.transform)).narrow(true).widget_instance(),
|
TextLabel::new(format_transform_matrix(stroke.transform)).narrow(true).widget_instance(),
|
||||||
]);
|
]);
|
||||||
table_rows.push(vec![
|
|
||||||
TextLabel::new("Stroke Paint Order").narrow(true).widget_instance(),
|
|
||||||
TextLabel::new(stroke.paint_order.to_string()).narrow(true).widget_instance(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let colinear = self.colinear_manipulators.iter().map(|[a, b]| format!("[{a} / {b}]")).collect::<Vec<_>>().join(", ");
|
let colinear = self.colinear_manipulators.iter().map(|[a, b]| format!("[{a} / {b}]")).collect::<Vec<_>>().join(", ");
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use graphene_std::raster::BlendMode;
|
|||||||
use graphene_std::raster_types::Image;
|
use graphene_std::raster_types::Image;
|
||||||
use graphene_std::subpath::Subpath;
|
use graphene_std::subpath::Subpath;
|
||||||
use graphene_std::text::{Font, TypesettingConfig};
|
use graphene_std::text::{Font, TypesettingConfig};
|
||||||
use graphene_std::vector::style::{GradientSpreadMethod, GradientType, Stroke};
|
use graphene_std::vector::style::{GradientSpreadMethod, GradientType, PaintOrder, Stroke};
|
||||||
use graphene_std::vector::{GradientStops, PointId, VectorModificationType};
|
use graphene_std::vector::{GradientStops, PointId, VectorModificationType};
|
||||||
|
|
||||||
#[impl_message(Message, DocumentMessage, GraphOperation)]
|
#[impl_message(Message, DocumentMessage, GraphOperation)]
|
||||||
@@ -63,6 +63,10 @@ pub enum GraphOperationMessage {
|
|||||||
color: Option<Color>,
|
color: Option<Color>,
|
||||||
stroke: Stroke,
|
stroke: Stroke,
|
||||||
},
|
},
|
||||||
|
StrokePaintOrderSet {
|
||||||
|
layer: LayerNodeIdentifier,
|
||||||
|
paint_order: PaintOrder,
|
||||||
|
},
|
||||||
TransformChange {
|
TransformChange {
|
||||||
layer: LayerNodeIdentifier,
|
layer: LayerNodeIdentifier,
|
||||||
transform: DAffine2,
|
transform: DAffine2,
|
||||||
|
|||||||
+9
-2
@@ -13,7 +13,7 @@ use graph_craft::document::{NodeId, NodeInput};
|
|||||||
use graphene_std::list::List;
|
use graphene_std::list::List;
|
||||||
use graphene_std::renderer::convert_usvg_path::convert_usvg_path;
|
use graphene_std::renderer::convert_usvg_path::convert_usvg_path;
|
||||||
use graphene_std::text::{Font, TypesettingConfig};
|
use graphene_std::text::{Font, TypesettingConfig};
|
||||||
use graphene_std::vector::style::{GradientSpreadMethod, GradientStop, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
use graphene_std::vector::style::{GradientSpreadMethod, GradientStop, GradientStops, GradientType, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
use graphene_std::{Artboard, Color};
|
use graphene_std::{Artboard, Color};
|
||||||
|
|
||||||
#[derive(ExtractField)]
|
#[derive(ExtractField)]
|
||||||
@@ -96,6 +96,14 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
|
|||||||
modify_inputs.stroke_set(color, stroke);
|
modify_inputs.stroke_set(color, stroke);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
GraphOperationMessage::StrokePaintOrderSet { layer, paint_order } => {
|
||||||
|
if let Some(stroke_node_id) = crate::messages::tool::common_functionality::graph_modification_utils::get_stroke_id(layer, network_interface) {
|
||||||
|
if super::utility_types::set_stroke_paint_order(network_interface, &[], stroke_node_id, paint_order) {
|
||||||
|
responses.add(PropertiesPanelMessage::Refresh);
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
GraphOperationMessage::TransformChange {
|
GraphOperationMessage::TransformChange {
|
||||||
layer,
|
layer,
|
||||||
transform,
|
transform,
|
||||||
@@ -792,7 +800,6 @@ fn apply_usvg_stroke(stroke: &usvg::Stroke, modify_inputs: &mut ModifyInputsCont
|
|||||||
},
|
},
|
||||||
join_miter_limit: stroke.miterlimit().get() as f64,
|
join_miter_limit: stroke.miterlimit().get() as f64,
|
||||||
align: StrokeAlign::Center,
|
align: StrokeAlign::Center,
|
||||||
paint_order: PaintOrder::StrokeAbove,
|
|
||||||
transform,
|
transform,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use graphene_std::raster::BlendMode;
|
|||||||
use graphene_std::raster_types::Image;
|
use graphene_std::raster_types::Image;
|
||||||
use graphene_std::subpath::Subpath;
|
use graphene_std::subpath::Subpath;
|
||||||
use graphene_std::text::{Font, TypesettingConfig};
|
use graphene_std::text::{Font, TypesettingConfig};
|
||||||
use graphene_std::vector::style::{GradientSpreadMethod, GradientType, HasTransform, Stroke};
|
use graphene_std::vector::style::{GradientSpreadMethod, GradientType, HasTransform, PaintOrder, Stroke};
|
||||||
use graphene_std::vector::{GradientStops, PointId, Vector, VectorModification, VectorModificationType};
|
use graphene_std::vector::{GradientStops, PointId, Vector, VectorModification, VectorModificationType};
|
||||||
use graphene_std::{Artboard, Color, Graphic, NodeInputDecleration};
|
use graphene_std::{Artboard, Color, Graphic, NodeInputDecleration};
|
||||||
|
|
||||||
@@ -732,8 +732,6 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::StrokeJoin(stroke.join), false), true);
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::StrokeJoin(stroke.join), false), true);
|
||||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::MiterLimitInput::INDEX);
|
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::MiterLimitInput::INDEX);
|
||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.join_miter_limit), false), false);
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.join_miter_limit), false), false);
|
||||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::PaintOrderInput::INDEX);
|
|
||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::PaintOrder(stroke.paint_order), false), false);
|
|
||||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashLengthsInput::INDEX);
|
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashLengthsInput::INDEX);
|
||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64Array(stroke.dash_lengths), false), true);
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64Array(stroke.dash_lengths), false), true);
|
||||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashOffsetInput::INDEX);
|
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashOffsetInput::INDEX);
|
||||||
@@ -869,3 +867,65 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The wires feeding off a node's primary output.
|
||||||
|
fn primary_output_consumers(network_interface: &mut NodeNetworkInterface, network_path: &[NodeId], node_id: NodeId) -> Vec<InputConnector> {
|
||||||
|
network_interface
|
||||||
|
.outward_wires(network_path)
|
||||||
|
.and_then(|wires| wires.get(&OutputConnector::node(node_id, 0)).cloned())
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The fill wired directly to the stroke on either side, and whether the stroke currently paints above it.
|
||||||
|
/// The downstream node of the pair appends its cover last, so it paints on top.
|
||||||
|
pub fn adjacent_fill_of_stroke(network_interface: &mut NodeNetworkInterface, network_path: &[NodeId], stroke_node_id: NodeId) -> Option<(NodeId, bool)> {
|
||||||
|
let fill_reference = DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER);
|
||||||
|
let is_fill = |network_interface: &NodeNetworkInterface, node_id: &NodeId| network_interface.reference(node_id, network_path) == Some(fill_reference.clone());
|
||||||
|
|
||||||
|
let stroke_primary_source = match network_interface.input_from_connector(&InputConnector::node(stroke_node_id, 0), network_path) {
|
||||||
|
Some(NodeInput::Node { node_id, output_index: 0, .. }) => Some(*node_id),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(source) = stroke_primary_source.filter(|source| is_fill(network_interface, source)) {
|
||||||
|
return Some((source, true));
|
||||||
|
}
|
||||||
|
let consumers = primary_output_consumers(network_interface, network_path, stroke_node_id);
|
||||||
|
let fill_consumer = consumers.iter().find_map(|connector| match connector {
|
||||||
|
InputConnector::Node { node_id, input_index: 0 } if is_fill(network_interface, node_id) => Some(*node_id),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
fill_consumer.map(|fill_node_id| (fill_node_id, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Swaps a chain's directly adjacent Stroke and Fill nodes when their order disagrees with the requested
|
||||||
|
/// paint order: both nodes append their cover, so the downstream one of the pair paints on top, following
|
||||||
|
/// the painter's algorithm. Without a fill wired directly to the stroke, nothing changes.
|
||||||
|
/// Returns whether the graph changed.
|
||||||
|
pub fn set_stroke_paint_order(network_interface: &mut NodeNetworkInterface, network_path: &[NodeId], stroke_node_id: NodeId, paint_order: PaintOrder) -> bool {
|
||||||
|
let Some((fill_node_id, currently_above)) = adjacent_fill_of_stroke(network_interface, network_path, stroke_node_id) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (paint_order == PaintOrder::StrokeAbove) == currently_above {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap the pair in place: the downstream node takes the upstream one's source, consumers of the
|
||||||
|
// downstream node move over to the upstream one, and the wire linking the pair reverses direction
|
||||||
|
let (upstream, downstream) = if currently_above { (fill_node_id, stroke_node_id) } else { (stroke_node_id, fill_node_id) };
|
||||||
|
let Some(upstream_source) = network_interface.input_from_connector(&InputConnector::node(upstream, 0), network_path).cloned() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let downstream_consumers = primary_output_consumers(network_interface, network_path, downstream);
|
||||||
|
|
||||||
|
network_interface.set_input(&InputConnector::node(downstream, 0), upstream_source, network_path);
|
||||||
|
network_interface.set_input(&InputConnector::node(upstream, 0), NodeInput::node(downstream, 0), network_path);
|
||||||
|
for consumer in &downstream_consumers {
|
||||||
|
if matches!(consumer, InputConnector::Node { node_id, .. } if *node_id == upstream || *node_id == downstream) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
network_interface.set_input(consumer, NodeInput::node(upstream, 0), network_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|||||||
@@ -2760,9 +2760,6 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
|
|||||||
ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context),
|
ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context),
|
||||||
NumberInput::default().min(0.).disabled(miter_limit_disabled),
|
NumberInput::default().min(0.).disabled(miter_limit_disabled),
|
||||||
);
|
);
|
||||||
let paint_order = enum_choice::<PaintOrder>()
|
|
||||||
.for_socket(ParameterWidgetsInfo::new(node_id, PaintOrderInput::INDEX, true, context))
|
|
||||||
.property_row();
|
|
||||||
let disabled_number_input = NumberInput::default().unit(" px").disabled(has_dash_lengths);
|
let disabled_number_input = NumberInput::default().unit(" px").disabled(has_dash_lengths);
|
||||||
let dash_lengths = array_of_number_widget(ParameterWidgetsInfo::new(node_id, DashLengthsInput::INDEX, true, context), TextInput::default().centered(true));
|
let dash_lengths = array_of_number_widget(ParameterWidgetsInfo::new(node_id, DashLengthsInput::INDEX, true, context), TextInput::default().centered(true));
|
||||||
let number_input = disabled_number_input;
|
let number_input = disabled_number_input;
|
||||||
@@ -2775,7 +2772,6 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
|
|||||||
cap,
|
cap,
|
||||||
join,
|
join,
|
||||||
LayoutGroup::row(miter_limit),
|
LayoutGroup::row(miter_limit),
|
||||||
paint_order,
|
|
||||||
LayoutGroup::row(dash_lengths),
|
LayoutGroup::row(dash_lengths),
|
||||||
LayoutGroup::row(dash_offset),
|
LayoutGroup::row(dash_offset),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// TODO: Eventually remove this document upgrade code
|
// TODO: Eventually remove this document upgrade code
|
||||||
// This file contains lots of hacky code for upgrading old documents to the new format
|
// This file contains lots of hacky code for upgrading old documents to the new format
|
||||||
|
|
||||||
|
use crate::messages::portfolio::document::graph_operation::utility_types::set_stroke_paint_order;
|
||||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_document_node_type, resolve_network_node_type, resolve_proto_node_type};
|
use crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_document_node_type, resolve_network_node_type, resolve_proto_node_type};
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};
|
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};
|
||||||
@@ -1745,13 +1746,12 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||||||
inputs_count = 8;
|
inputs_count = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644)
|
// Upgrade Stroke node to reorder parameters and add "Align" (#2644), landing on the shape without the retired "Paint Order" input
|
||||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER) && inputs_count == 8 {
|
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER) && inputs_count == 8 {
|
||||||
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
|
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
|
||||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||||
|
|
||||||
let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false);
|
let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false);
|
||||||
let paint_order_input = NodeInput::value(TaggedValue::PaintOrder(PaintOrder::StrokeAbove), false);
|
|
||||||
|
|
||||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||||
@@ -1760,9 +1760,32 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||||||
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path);
|
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path);
|
||||||
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[6].clone(), network_path);
|
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[6].clone(), network_path);
|
||||||
document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[7].clone(), network_path);
|
document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[7].clone(), network_path);
|
||||||
document.network_interface.set_input(&InputConnector::node(*node_id, 7), paint_order_input, network_path);
|
document.network_interface.set_input(&InputConnector::node(*node_id, 7), old_inputs[3].clone(), network_path);
|
||||||
document.network_interface.set_input(&InputConnector::node(*node_id, 8), old_inputs[3].clone(), network_path);
|
document.network_interface.set_input(&InputConnector::node(*node_id, 8), old_inputs[4].clone(), network_path);
|
||||||
document.network_interface.set_input(&InputConnector::node(*node_id, 9), old_inputs[4].clone(), network_path);
|
|
||||||
|
inputs_count = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Stroke node's "Paint Order" input was retired in favor of the relative order of the Fill and Stroke
|
||||||
|
// nodes in the chain, so the stored value becomes a topology rewrite that reorders the two nodes.
|
||||||
|
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER) && inputs_count == 10 {
|
||||||
|
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
|
||||||
|
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||||
|
|
||||||
|
// A wired paint order input cannot be evaluated statically, so it degrades to the default and leaves its source disconnected
|
||||||
|
let paint_order = match old_inputs.get(7).and_then(|input| input.as_value()) {
|
||||||
|
Some(&TaggedValue::PaintOrder(value)) => value,
|
||||||
|
_ => PaintOrder::StrokeAbove,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (index, input) in old_inputs.iter().enumerate().take(7) {
|
||||||
|
document.network_interface.set_input(&InputConnector::node(*node_id, index), input.clone(), network_path);
|
||||||
|
}
|
||||||
|
document.network_interface.set_input(&InputConnector::node(*node_id, 7), old_inputs[8].clone(), network_path);
|
||||||
|
document.network_interface.set_input(&InputConnector::node(*node_id, 8), old_inputs[9].clone(), network_path);
|
||||||
|
inputs_count = 9;
|
||||||
|
|
||||||
|
set_stroke_paint_order(&mut document.network_interface, network_path, *node_id, paint_order);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016
|
// Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016
|
||||||
|
|||||||
@@ -182,12 +182,18 @@ impl DrawingToolState {
|
|||||||
cap: self.stroke_cap.unwrap_or_default(),
|
cap: self.stroke_cap.unwrap_or_default(),
|
||||||
join: self.stroke_join.unwrap_or_default(),
|
join: self.stroke_join.unwrap_or_default(),
|
||||||
join_miter_limit: self.miter_limit.unwrap_or(4.),
|
join_miter_limit: self.miter_limit.unwrap_or(4.),
|
||||||
paint_order: self.paint_order.unwrap_or_default(),
|
|
||||||
dash_lengths: self.effective_dash_lengths(),
|
dash_lengths: self.effective_dash_lengths(),
|
||||||
dash_offset: self.dash_offset.unwrap_or(0.),
|
dash_offset: self.dash_offset.unwrap_or(0.),
|
||||||
transform: glam::DAffine2::IDENTITY,
|
transform: glam::DAffine2::IDENTITY,
|
||||||
};
|
};
|
||||||
responses.add(GraphOperationMessage::StrokeSet { layer, color, stroke });
|
responses.add(GraphOperationMessage::StrokeSet { layer, color, stroke });
|
||||||
|
// The paint order is the chain order of the Fill and Stroke nodes, so a below choice reorders the new pair
|
||||||
|
if self.paint_order.unwrap_or_default() == graphene_std::vector::style::PaintOrder::StrokeBelow {
|
||||||
|
responses.add(GraphOperationMessage::StrokePaintOrderSet {
|
||||||
|
layer,
|
||||||
|
paint_order: graphene_std::vector::style::PaintOrder::StrokeBelow,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -565,9 +565,22 @@ pub fn get_stroke_options(layer: LayerNodeIdentifier, network_interface: &NodeNe
|
|||||||
Some(TaggedValue::F64(value)) => *value,
|
Some(TaggedValue::F64(value)) => *value,
|
||||||
_ => 4.,
|
_ => 4.,
|
||||||
};
|
};
|
||||||
let paint_order = match read(graphene_std::vector::stroke::PaintOrderInput::INDEX) {
|
// The paint order is the chain order of the Fill and Stroke nodes: the downstream one of an adjacent pair paints on top
|
||||||
Some(TaggedValue::PaintOrder(value)) => *value,
|
let paint_order = {
|
||||||
_ => PaintOrder::default(),
|
let stroke_node_id = get_stroke_id(layer, network_interface);
|
||||||
|
let primary_source = |node_id: Option<NodeId>| {
|
||||||
|
node_id.and_then(|node_id| match network_interface.input_from_connector(&InputConnector::node(node_id, 0), &[]) {
|
||||||
|
Some(NodeInput::Node { node_id, output_index: 0, .. }) => Some(*node_id),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
if primary_source(stroke_node_id).is_some_and(|source| Some(source) == get_fill_id(layer, network_interface)) {
|
||||||
|
PaintOrder::StrokeAbove
|
||||||
|
} else if primary_source(get_fill_id(layer, network_interface)).is_some_and(|source| Some(source) == stroke_node_id) {
|
||||||
|
PaintOrder::StrokeBelow
|
||||||
|
} else {
|
||||||
|
PaintOrder::default()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let dash_lengths = match read(graphene_std::vector::stroke::DashLengthsInput::INDEX) {
|
let dash_lengths = match read(graphene_std::vector::stroke::DashLengthsInput::INDEX) {
|
||||||
Some(TaggedValue::F64Array(value)) => value.clone(),
|
Some(TaggedValue::F64Array(value)) => value.clone(),
|
||||||
|
|||||||
@@ -209,7 +209,10 @@ pub fn apply_miter_limit(drawing: &mut DrawingToolState, limit: f64, document: &
|
|||||||
|
|
||||||
pub fn apply_paint_order(drawing: &mut DrawingToolState, order: PaintOrder, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
pub fn apply_paint_order(drawing: &mut DrawingToolState, order: PaintOrder, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
drawing.paint_order = Some(order);
|
drawing.paint_order = Some(order);
|
||||||
set_stroke_input_for_selected(document, graphene_std::vector::stroke::PaintOrderInput::INDEX, TaggedValue::PaintOrder(order), responses);
|
// The paint order is the chain order of the Fill and Stroke nodes, so applying it reorders the pair
|
||||||
|
for layer in document.network_interface.selected_nodes().selected_layers_except_artboards(&document.network_interface) {
|
||||||
|
responses.add(GraphOperationMessage::StrokePaintOrderSet { layer, paint_order: order });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_dash_lengths(drawing: &mut DrawingToolState, lengths: Vec<f64>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
pub fn apply_dash_lengths(drawing: &mut DrawingToolState, lengths: Vec<f64>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ impl RenderExt for Stroke {
|
|||||||
let stroke_join = (self.join != StrokeJoin::Miter).then_some(self.join);
|
let stroke_join = (self.join != StrokeJoin::Miter).then_some(self.join);
|
||||||
let stroke_join_miter_limit = (self.join_miter_limit != 4.).then_some(self.join_miter_limit);
|
let stroke_join_miter_limit = (self.join_miter_limit != 4.).then_some(self.join_miter_limit);
|
||||||
let stroke_align = (self.align != StrokeAlign::Center).then_some(self.align);
|
let stroke_align = (self.align != StrokeAlign::Center).then_some(self.align);
|
||||||
let paint_order = (self.paint_order != PaintOrder::StrokeAbove || render_params.override_paint_order).then_some(PaintOrder::StrokeBelow);
|
let paint_order = render_params.stroke_below.then_some(PaintOrder::StrokeBelow);
|
||||||
|
|
||||||
// Render the needed stroke attributes
|
// Render the needed stroke attributes
|
||||||
let mut attributes = String::new();
|
let mut attributes = String::new();
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ use graphic_types::vector_types::gradient::{GradientStops, GradientType};
|
|||||||
use graphic_types::vector_types::markers::{GradientType as GradientTypeAttr, SpreadMethod};
|
use graphic_types::vector_types::markers::{GradientType as GradientTypeAttr, SpreadMethod};
|
||||||
use graphic_types::vector_types::subpath::Subpath;
|
use graphic_types::vector_types::subpath::Subpath;
|
||||||
use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint};
|
use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint};
|
||||||
use graphic_types::vector_types::vector::style::{PaintOrder, RenderMode, StrokeAlign, StrokeCap, StrokeJoin};
|
use graphic_types::vector_types::vector::style::{RenderMode, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
use graphic_types::{ATTR_FILL, Artboard, Graphic, Vector};
|
use graphic_types::{ATTR_FILL, Artboard, Graphic, Vector};
|
||||||
use kurbo::{Affine, BezPath, Cap, Join, Shape, StrokeOpts};
|
use kurbo::{Affine, BezPath, Cap, Join, Shape, StrokeOpts};
|
||||||
use num_traits::Zero;
|
use num_traits::Zero;
|
||||||
@@ -302,7 +302,7 @@ pub struct RenderParams {
|
|||||||
/// Are we generating a mask for alignment? Used to prevent unnecessary transforms in masks
|
/// Are we generating a mask for alignment? Used to prevent unnecessary transforms in masks
|
||||||
pub alignment_parent_transform: Option<DAffine2>,
|
pub alignment_parent_transform: Option<DAffine2>,
|
||||||
pub aligned_strokes: bool,
|
pub aligned_strokes: bool,
|
||||||
pub override_paint_order: bool,
|
pub stroke_below: bool,
|
||||||
/// Are we rendering for a pattern content
|
/// Are we rendering for a pattern content
|
||||||
pub inside_pattern: bool,
|
pub inside_pattern: bool,
|
||||||
pub artboard_background: Option<Color>,
|
pub artboard_background: Option<Color>,
|
||||||
@@ -1340,11 +1340,7 @@ fn render_vector_svg<S: LaneSource<Element = Vector>>(source: &S, inherited_appe
|
|||||||
|
|
||||||
// The lane's paint: its own declared appearance, or the nearest ancestor's through the cascade
|
// The lane's paint: its own declared appearance, or the nearest ancestor's through the cascade
|
||||||
let appearance = Appearance::cascade(source.attr::<AppearanceMarker>(index), inherited_appearance);
|
let appearance = Appearance::cascade(source.attr::<AppearanceMarker>(index), inherited_appearance);
|
||||||
let mut resolved = appearance.map(Appearance::fill_and_stroke).unwrap_or_default();
|
let resolved = appearance.map(Appearance::fill_and_stroke).unwrap_or_default();
|
||||||
// The paint order rides the coverage list's row order
|
|
||||||
if let Some(stroke) = &mut resolved.stroke {
|
|
||||||
stroke.paint_order = if resolved.stroke_below { PaintOrder::StrokeBelow } else { PaintOrder::StrokeAbove };
|
|
||||||
}
|
|
||||||
let element_stroke = resolved.stroke.as_ref();
|
let element_stroke = resolved.stroke.as_ref();
|
||||||
|
|
||||||
// Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform
|
// Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform
|
||||||
@@ -1387,7 +1383,8 @@ fn render_vector_svg<S: LaneSource<Element = Vector>>(source: &S, inherited_appe
|
|||||||
let can_use_paint_order = !(fill_graphic.is_none_or(|graphic| !graphic.covers_opaquely()) || mask_type == MaskType::Clip);
|
let can_use_paint_order = !(fill_graphic.is_none_or(|graphic| !graphic.covers_opaquely()) || mask_type == MaskType::Clip);
|
||||||
|
|
||||||
let needs_separate_alignment_fill = can_draw_aligned_stroke && !can_use_paint_order;
|
let needs_separate_alignment_fill = can_draw_aligned_stroke && !can_use_paint_order;
|
||||||
let wants_stroke_below = element_stroke.map(|s| s.paint_order) == Some(PaintOrder::StrokeBelow);
|
// The paint order rides the coverage list's row order
|
||||||
|
let wants_stroke_below = resolved.stroke_below;
|
||||||
let override_paint_order = can_draw_aligned_stroke && can_use_paint_order;
|
let override_paint_order = can_draw_aligned_stroke && can_use_paint_order;
|
||||||
let use_face_fill = vector.use_face_fill();
|
let use_face_fill = vector.use_face_fill();
|
||||||
|
|
||||||
@@ -1474,7 +1471,7 @@ fn render_vector_svg<S: LaneSource<Element = Vector>>(source: &S, inherited_appe
|
|||||||
|
|
||||||
let mut render_params = render_params.clone();
|
let mut render_params = render_params.clone();
|
||||||
render_params.aligned_strokes = can_draw_aligned_stroke;
|
render_params.aligned_strokes = can_draw_aligned_stroke;
|
||||||
render_params.override_paint_order = override_paint_order;
|
render_params.stroke_below = override_paint_order || wants_stroke_below;
|
||||||
|
|
||||||
let stroke_shape_attribute = element_stroke
|
let stroke_shape_attribute = element_stroke
|
||||||
.map(|stroke| {
|
.map(|stroke| {
|
||||||
@@ -1559,8 +1556,6 @@ fn render_vector_vello<S: LaneSource<Element = Vector>>(
|
|||||||
render_params: &RenderParams,
|
render_params: &RenderParams,
|
||||||
) {
|
) {
|
||||||
for index in 0..source.lane_count() {
|
for index in 0..source.lane_count() {
|
||||||
use graphic_types::vector_types::vector;
|
|
||||||
|
|
||||||
let Some(element) = source.element(index) else { continue };
|
let Some(element) = source.element(index) else { continue };
|
||||||
let item_transform: DAffine2 = source.attr::<Transform>(index);
|
let item_transform: DAffine2 = source.attr::<Transform>(index);
|
||||||
let blend_mode_attr: BlendMode = source.attr::<BlendModeAttr>(index);
|
let blend_mode_attr: BlendMode = source.attr::<BlendModeAttr>(index);
|
||||||
@@ -1570,15 +1565,7 @@ fn render_vector_vello<S: LaneSource<Element = Vector>>(
|
|||||||
|
|
||||||
// The lane's paint: its own declared appearance, or the nearest ancestor's through the cascade
|
// The lane's paint: its own declared appearance, or the nearest ancestor's through the cascade
|
||||||
let appearance = Appearance::cascade(source.attr::<AppearanceMarker>(index), inherited_appearance);
|
let appearance = Appearance::cascade(source.attr::<AppearanceMarker>(index), inherited_appearance);
|
||||||
let mut resolved = appearance.map(Appearance::fill_and_stroke).unwrap_or_default();
|
let resolved = appearance.map(Appearance::fill_and_stroke).unwrap_or_default();
|
||||||
// The paint order rides the coverage list's row order
|
|
||||||
if let Some(stroke) = &mut resolved.stroke {
|
|
||||||
stroke.paint_order = if resolved.stroke_below {
|
|
||||||
vector::style::PaintOrder::StrokeBelow
|
|
||||||
} else {
|
|
||||||
vector::style::PaintOrder::StrokeAbove
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let has_real_stroke = resolved.stroke.as_ref().filter(|stroke| stroke.weight() > 0.);
|
let has_real_stroke = resolved.stroke.as_ref().filter(|stroke| stroke.weight() > 0.);
|
||||||
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform_is_invertible(*transform));
|
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform_is_invertible(*transform));
|
||||||
@@ -1641,7 +1628,8 @@ fn render_vector_vello<S: LaneSource<Element = Vector>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let use_layer = can_draw_aligned_stroke;
|
let use_layer = can_draw_aligned_stroke;
|
||||||
let wants_stroke_below = stroke.is_some_and(|s| s.paint_order == vector::style::PaintOrder::StrokeBelow);
|
// The paint order rides the coverage list's row order
|
||||||
|
let wants_stroke_below = resolved.stroke_below;
|
||||||
|
|
||||||
let do_fill_path = |scene: &mut Scene, context: &mut RenderContext, path: &kurbo::BezPath, fill_rule: peniko::Fill| {
|
let do_fill_path = |scene: &mut Scene, context: &mut RenderContext, path: &kurbo::BezPath, fill_rule: peniko::Fill| {
|
||||||
let Some(fill_graphic) = fill_graphic_list else { return };
|
let Some(fill_graphic) = fill_graphic_list else { return };
|
||||||
@@ -1827,7 +1815,7 @@ fn render_vector_vello<S: LaneSource<Element = Vector>>(
|
|||||||
Stroke,
|
Stroke,
|
||||||
}
|
}
|
||||||
|
|
||||||
let order = match stroke.is_some_and(|stroke| !stroke.paint_order.is_default()) {
|
let order = match stroke.is_some() && wants_stroke_below {
|
||||||
true => [Op::Stroke, Op::Fill],
|
true => [Op::Stroke, Op::Fill],
|
||||||
false => [Op::Fill, Op::Stroke], // Default
|
false => [Op::Fill, Op::Stroke], // Default
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -230,8 +230,6 @@ pub struct Stroke {
|
|||||||
pub align: StrokeAlign,
|
pub align: StrokeAlign,
|
||||||
#[cfg_attr(feature = "serde", serde(default = "daffine2_identity"))]
|
#[cfg_attr(feature = "serde", serde(default = "daffine2_identity"))]
|
||||||
pub transform: DAffine2,
|
pub transform: DAffine2,
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
|
||||||
pub paint_order: PaintOrder,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stroke {
|
impl Stroke {
|
||||||
@@ -245,7 +243,6 @@ impl Stroke {
|
|||||||
join_miter_limit: 4.,
|
join_miter_limit: 4.,
|
||||||
align: StrokeAlign::Center,
|
align: StrokeAlign::Center,
|
||||||
transform: DAffine2::IDENTITY,
|
transform: DAffine2::IDENTITY,
|
||||||
paint_order: PaintOrder::StrokeAbove,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +279,6 @@ impl Stroke {
|
|||||||
let skew = DAffine2::from_cols_array(&[1., 0., lerp(s_skew, t_skew), 1., 0., 0.]);
|
let skew = DAffine2::from_cols_array(&[1., 0., lerp(s_skew, t_skew), 1., 0., 0.]);
|
||||||
trs * skew
|
trs * skew
|
||||||
},
|
},
|
||||||
paint_order: if time < 0.5 { self.paint_order } else { other.paint_order },
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,7 +406,6 @@ impl Default for Stroke {
|
|||||||
join_miter_limit: 4.,
|
join_miter_limit: 4.,
|
||||||
align: StrokeAlign::Center,
|
align: StrokeAlign::Center,
|
||||||
transform: DAffine2::IDENTITY,
|
transform: DAffine2::IDENTITY,
|
||||||
paint_order: PaintOrder::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ pub struct CacheKey {
|
|||||||
pub for_mask: bool,
|
pub for_mask: bool,
|
||||||
pub thumbnail: bool,
|
pub thumbnail: bool,
|
||||||
pub aligned_strokes: bool,
|
pub aligned_strokes: bool,
|
||||||
pub override_paint_order: bool,
|
pub stroke_below: bool,
|
||||||
pub animation_time_ms: i64,
|
pub animation_time_ms: i64,
|
||||||
pub real_time_ms: i64,
|
pub real_time_ms: i64,
|
||||||
pub pointer: [u8; 16],
|
pub pointer: [u8; 16],
|
||||||
@@ -60,7 +60,7 @@ impl CacheKey {
|
|||||||
for_mask: bool,
|
for_mask: bool,
|
||||||
thumbnail: bool,
|
thumbnail: bool,
|
||||||
aligned_strokes: bool,
|
aligned_strokes: bool,
|
||||||
override_paint_order: bool,
|
stroke_below: bool,
|
||||||
animation_time: f64,
|
animation_time: f64,
|
||||||
real_time: f64,
|
real_time: f64,
|
||||||
pointer: Option<DVec2>,
|
pointer: Option<DVec2>,
|
||||||
@@ -85,7 +85,7 @@ impl CacheKey {
|
|||||||
for_mask,
|
for_mask,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
aligned_strokes,
|
aligned_strokes,
|
||||||
override_paint_order,
|
stroke_below,
|
||||||
animation_time_ms: (animation_time * 1000.).round() as i64,
|
animation_time_ms: (animation_time * 1000.).round() as i64,
|
||||||
real_time_ms: (real_time * 1000.).round() as i64,
|
real_time_ms: (real_time * 1000.).round() as i64,
|
||||||
pointer: pointer_bytes,
|
pointer: pointer_bytes,
|
||||||
@@ -360,7 +360,7 @@ pub fn render_output_cache(
|
|||||||
render_params.for_mask,
|
render_params.for_mask,
|
||||||
render_params.thumbnail,
|
render_params.thumbnail,
|
||||||
render_params.aligned_strokes,
|
render_params.aligned_strokes,
|
||||||
render_params.override_paint_order,
|
render_params.stroke_below,
|
||||||
ctx.try_animation_time().unwrap_or(0.),
|
ctx.try_animation_time().unwrap_or(0.),
|
||||||
ctx.try_real_time().unwrap_or(0.),
|
ctx.try_real_time().unwrap_or(0.),
|
||||||
ctx.try_pointer_position(),
|
ctx.try_pointer_position(),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use core_types::list::{Item, List};
|
|||||||
use core_types::uuid::NodeId;
|
use core_types::uuid::NodeId;
|
||||||
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, BlendMode, Color, Ctx};
|
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, BlendMode, Color, Ctx};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphic_types::appearance::{Appearance, CoverPlacement, Coverage};
|
use graphic_types::appearance::{Appearance, Coverage};
|
||||||
use graphic_types::graphic::{GraphicLevel, PaintColumns, PaintReach, bake_paint_transforms, is_paint_present, set_paint_attribute, set_paint_attribute_at};
|
use graphic_types::graphic::{GraphicLevel, PaintColumns, PaintReach, bake_paint_transforms, is_paint_present, set_paint_attribute, set_paint_attribute_at};
|
||||||
use graphic_types::markers::{Appearance as AppearanceMarker, EditorMergedLayers, Fill, Stroke};
|
use graphic_types::markers::{Appearance as AppearanceMarker, EditorMergedLayers, Fill, Stroke};
|
||||||
use graphic_types::raster_types::{CPU, GPU, Raster};
|
use graphic_types::raster_types::{CPU, GPU, Raster};
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ use vector_types::vector::misc::{
|
|||||||
CentroidType, ExtrudeJoiningAlgorithm, HandleId, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, bezpath_from_manipulator_groups,
|
CentroidType, ExtrudeJoiningAlgorithm, HandleId, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, bezpath_from_manipulator_groups,
|
||||||
bezpath_to_manipulator_groups, handles_to_segment, is_linear, point_to_dvec2, segment_to_handles,
|
bezpath_to_manipulator_groups, handles_to_segment, is_linear, point_to_dvec2, segment_to_handles,
|
||||||
};
|
};
|
||||||
use vector_types::vector::style::{GradientStops, HasTransform, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
use vector_types::vector::style::{GradientStops, HasTransform, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
use vector_types::vector::{FillId, PointId, RegionId, SegmentDomain, SegmentId, StrokeId, VectorExt};
|
use vector_types::vector::{FillId, PointId, RegionId, SegmentDomain, SegmentId, StrokeId, VectorExt};
|
||||||
use vector_types::{ATTR_GRADIENT_TYPE, ATTR_SPREAD_METHOD};
|
use vector_types::{ATTR_GRADIENT_TYPE, ATTR_SPREAD_METHOD};
|
||||||
use vector_types::{GradientSpreadMethod, GradientType};
|
use vector_types::{GradientSpreadMethod, GradientType};
|
||||||
@@ -323,15 +323,6 @@ fn park_paint<'e>(arena: &'e core_types::arena::Arena, paint: List<Graphic<'stat
|
|||||||
Ok(parked)
|
Ok(parked)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parks a row's carried appearance, absent rows staying absent.
|
|
||||||
fn park_row_appearance<'e>(arena: &'e core_types::arena::Arena, attributes: &ItemAttributeValues) -> Result<Option<&'e Appearance>, Interrupt> {
|
|
||||||
attributes
|
|
||||||
.get::<Appearance>(graphic_types::ATTR_APPEARANCE)
|
|
||||||
.cloned()
|
|
||||||
.map(|appearance| park_appearance(arena, appearance))
|
|
||||||
.transpose()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Keyed, as [`park_paint`] is, so a group-free appearance's promote moves this header.
|
/// Keyed, as [`park_paint`] is, so a group-free appearance's promote moves this header.
|
||||||
fn park_appearance<'e>(arena: &'e core_types::arena::Arena, appearance: Appearance) -> Result<&'e Appearance, Interrupt> {
|
fn park_appearance<'e>(arena: &'e core_types::arena::Arena, appearance: Appearance) -> Result<&'e Appearance, Interrupt> {
|
||||||
let (parked, _) = arena.alloc_sized_keyed(appearance, 0).ok_or(GraphError {
|
let (parked, _) = arena.alloc_sized_keyed(appearance, 0).ok_or(GraphError {
|
||||||
@@ -464,8 +455,6 @@ fn stroke<'e>(
|
|||||||
/// The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.
|
/// 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.)]
|
#[default(4.)]
|
||||||
miter_limit: f64,
|
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.
|
/// 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>,
|
dash_lengths: IList<f64>,
|
||||||
/// The phase offset distance from the starting point of the dash pattern.
|
/// The phase offset distance from the starting point of the dash pattern.
|
||||||
@@ -482,7 +471,6 @@ fn stroke<'e>(
|
|||||||
join_miter_limit: miter_limit,
|
join_miter_limit: miter_limit,
|
||||||
align,
|
align,
|
||||||
transform: DAffine2::IDENTITY,
|
transform: DAffine2::IDENTITY,
|
||||||
paint_order,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// The coverage records the stroke's authoring space, so the item transform is composed in, translation
|
// The coverage records the stroke's authoring space, so the item transform is composed in, translation
|
||||||
@@ -496,12 +484,9 @@ fn stroke<'e>(
|
|||||||
element.stroke = Some(stroke);
|
element.stroke = Some(stroke);
|
||||||
|
|
||||||
let paint = paint_table(paint);
|
let paint = paint_table(paint);
|
||||||
// The coverage order carries the paint order: a below stroke lands before the fill in the list
|
// The paint order is the coverage row order: appending above follows the painter's algorithm, and a
|
||||||
let placement = match paint_order {
|
// below stroke is expressed by the chain running the stroke node before the fill
|
||||||
PaintOrder::StrokeAbove => CoverPlacement::Above,
|
let appearance = stamped_appearance(*content_appearance, Coverage::new_stroke(&coverage_stroke), &paint, CoverPlacement::Above);
|
||||||
PaintOrder::StrokeBelow => CoverPlacement::Below,
|
|
||||||
};
|
|
||||||
let appearance = stamped_appearance(*content_appearance, Coverage::new_stroke(&coverage_stroke), &paint, placement);
|
|
||||||
let parked = park_paint(ctx.arena(), paint)?;
|
let parked = park_paint(ctx.arena(), paint)?;
|
||||||
let parked_appearance = park_appearance(ctx.arena(), appearance)?;
|
let parked_appearance = park_appearance(ctx.arena(), appearance)?;
|
||||||
Ok((element, Attr(*content_transform), Attr(Some(parked)), Attr(Some(parked_appearance))))
|
Ok((element, Attr(*content_transform), Attr(Some(parked)), Attr(Some(parked_appearance))))
|
||||||
@@ -539,7 +524,6 @@ fn stroke_graphic_leveled<'e>(
|
|||||||
cap: StrokeCap,
|
cap: StrokeCap,
|
||||||
join: StrokeJoin,
|
join: StrokeJoin,
|
||||||
#[default(4.)] miter_limit: f64,
|
#[default(4.)] miter_limit: f64,
|
||||||
paint_order: PaintOrder,
|
|
||||||
dash_lengths: IList<f64>,
|
dash_lengths: IList<f64>,
|
||||||
#[unit(" px")] dash_offset: f64,
|
#[unit(" px")] dash_offset: f64,
|
||||||
) -> Result<(Graphic<'static>, Attr<TransformAttr>, Attr<'e, StrokeAttr>, Attr<'e, AppearanceMarker>), Interrupt> {
|
) -> Result<(Graphic<'static>, Attr<TransformAttr>, Attr<'e, StrokeAttr>, Attr<'e, AppearanceMarker>), Interrupt> {
|
||||||
@@ -553,7 +537,6 @@ fn stroke_graphic_leveled<'e>(
|
|||||||
join_miter_limit: miter_limit,
|
join_miter_limit: miter_limit,
|
||||||
align,
|
align,
|
||||||
transform: DAffine2::IDENTITY,
|
transform: DAffine2::IDENTITY,
|
||||||
paint_order,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// The coverage records the stroke's authoring space at the lane, composing the lane transform as in `stroke` above.
|
// The coverage records the stroke's authoring space at the lane, composing the lane transform as in `stroke` above.
|
||||||
@@ -568,12 +551,9 @@ fn stroke_graphic_leveled<'e>(
|
|||||||
});
|
});
|
||||||
|
|
||||||
let paint = paint_table(paint);
|
let paint = paint_table(paint);
|
||||||
// The coverage order carries the paint order: a below stroke lands before the fill in the list
|
// The paint order is the coverage row order: appending above follows the painter's algorithm, and a
|
||||||
let placement = match paint_order {
|
// below stroke is expressed by the chain running the stroke node before the fill
|
||||||
PaintOrder::StrokeAbove => CoverPlacement::Above,
|
let appearance = stamped_appearance(*content_appearance, Coverage::new_stroke(&coverage_stroke), &paint, CoverPlacement::Above);
|
||||||
PaintOrder::StrokeBelow => CoverPlacement::Below,
|
|
||||||
};
|
|
||||||
let appearance = stamped_appearance(*content_appearance, Coverage::new_stroke(&coverage_stroke), &paint, placement);
|
|
||||||
let parked = park_paint(ctx.arena(), paint)?;
|
let parked = park_paint(ctx.arena(), paint)?;
|
||||||
let parked_appearance = park_appearance(ctx.arena(), appearance)?;
|
let parked_appearance = park_appearance(ctx.arena(), appearance)?;
|
||||||
Ok((element, Attr(*content_transform), Attr(Some(parked)), Attr(Some(parked_appearance))))
|
Ok((element, Attr(*content_transform), Attr(Some(parked)), Attr(Some(parked_appearance))))
|
||||||
@@ -1485,7 +1465,11 @@ fn solidify_rows(flattened: List<Vector>) -> List<Vector> {
|
|||||||
let dash_offset = stroke.dash_offset;
|
let dash_offset = stroke.dash_offset;
|
||||||
let dash_pattern = stroke.dash_lengths;
|
let dash_pattern = stroke.dash_lengths;
|
||||||
let miter_limit = stroke.join_miter_limit;
|
let miter_limit = stroke.join_miter_limit;
|
||||||
let paint_order = stroke.paint_order;
|
// The paint order rides the row's coverage order
|
||||||
|
let stroke_below = attributes
|
||||||
|
.get::<Appearance>(graphic_types::ATTR_APPEARANCE)
|
||||||
|
.map(Appearance::fill_and_stroke)
|
||||||
|
.is_some_and(|resolved| resolved.stroke_below);
|
||||||
|
|
||||||
let stroke_style = kurbo::Stroke::new(stroke.weight)
|
let stroke_style = kurbo::Stroke::new(stroke.weight)
|
||||||
.with_caps(cap)
|
.with_caps(cap)
|
||||||
@@ -1541,10 +1525,10 @@ fn solidify_rows(flattened: List<Vector>) -> List<Vector> {
|
|||||||
|
|
||||||
let stroke_row = Item::from_parts(solidified_stroke, stroke_attributes);
|
let stroke_row = Item::from_parts(solidified_stroke, stroke_attributes);
|
||||||
|
|
||||||
// Ordering based on the paint order. The first item in the `List` is rendered below the second.
|
// Ordering based on the coverage order. The first item in the `List` is rendered below the second.
|
||||||
match paint_order {
|
match stroke_below {
|
||||||
PaintOrder::StrokeAbove => fill_row.into_iter().chain(std::iter::once(stroke_row)).collect::<Vec<_>>(),
|
false => fill_row.into_iter().chain(std::iter::once(stroke_row)).collect::<Vec<_>>(),
|
||||||
PaintOrder::StrokeBelow => std::iter::once(stroke_row).chain(fill_row).collect::<Vec<_>>(),
|
true => std::iter::once(stroke_row).chain(fill_row).collect::<Vec<_>>(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|||||||
Reference in New Issue
Block a user