Skip to content

Commit 95e6a92

Browse files
committed
Refactor and add overlay support based on paint order
1 parent c5f2a4f commit 95e6a92

4 files changed

Lines changed: 86 additions & 111 deletions

File tree

editor/src/messages/portfolio/document/overlays/utility_types_web.rs

Lines changed: 79 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use graphene_std::subpath::Subpath;
1717
use graphene_std::vector::click_target::ClickTargetType;
1818
use graphene_std::vector::misc::{dvec2_to_point, point_to_dvec2};
1919
use graphene_std::vector::stroke::DashLengthsInput;
20-
use graphene_std::vector::style::Stroke;
20+
use graphene_std::vector::style::{PaintOrder, Stroke};
2121
use graphene_std::vector::{PointId, SegmentId, Vector};
2222
use kurbo::{self, Affine, CubicBez, ParamCurve, PathSeg};
2323
use std::collections::HashMap;
@@ -1031,69 +1031,97 @@ impl OverlayContext {
10311031

10321032
/// Fills the area inside the path (with an optional pattern). Assumes `color` is in gamma space.
10331033
/// Used by the Pen tool to show the path being closed and by the Fill tool to show the area to be filled with a pattern.
1034-
pub fn fill_path(
1035-
&mut self,
1036-
subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>,
1037-
transform: DAffine2,
1038-
stroke_transform: DAffine2,
1039-
color: &Color,
1040-
with_pattern: bool,
1041-
clear_stroke_part: bool,
1042-
stroke_width: Option<f64>,
1043-
) {
1034+
pub fn fill_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &Color) {
1035+
self.draw_path_from_subpaths(subpaths, transform);
1036+
1037+
let color_str = format!("#{:?}", color.to_rgba_hex_srgb());
1038+
self.render_context.set_fill_style_str(&color_str.as_str());
1039+
self.render_context.fill();
1040+
}
1041+
1042+
pub fn fill_overlay(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, layer_to_viewport: DAffine2, color: &Color, stroke: Option<Stroke>) {
1043+
// Render for elements with fill
1044+
// Render for elements with fill only
1045+
// Render for elements with fill and stroke
1046+
//----PaintOrder
1047+
//----StrokeAlign
1048+
10441049
self.render_context.save();
10451050
self.start_dpi_aware_transform();
10461051

1047-
if with_pattern {
1048-
self.render_context.set_fill_style_canvas_pattern(&self.fill_canvas_pattern(color));
1049-
} else {
1050-
let color_str = format!("#{:?}", color.to_rgba_hex_srgb());
1051-
self.render_context.set_fill_style_str(&color_str.as_str());
1052-
}
1053-
// let stroke_transform = Some(stroke_transform).filter(|transform| transform.matrix2.determinant() != 0.).unwrap_or(DAffine2::IDENTITY);
1054-
let a = transform.matrix2.x_axis.x;
1055-
let b = transform.matrix2.y_axis.x;
1056-
let c = transform.matrix2.x_axis.y;
1057-
let d = transform.matrix2.y_axis.y;
1058-
let e = transform.translation.x;
1059-
let f = transform.translation.y;
1060-
self.render_context.transform(a, b, c, d, e, f);
1061-
self.draw_path_from_subpaths(subpaths, stroke_transform);
1062-
self.render_context.fill();
1052+
if let Some(stroke) = stroke {
1053+
let has_real_stroke = stroke.weight() > 0. && stroke.transform.matrix2.determinant() != 0.;
1054+
let applied_stroke_transform = if has_real_stroke { stroke.transform } else { layer_to_viewport };
1055+
let element_transform = if has_real_stroke { layer_to_viewport * stroke.transform.inverse() } else { DAffine2::IDENTITY };
10631056

1064-
// Make the stroke transparent and erase the fill area overlapping the stroke.
1065-
if clear_stroke_part {
1066-
self.render_context.set_line_width(stroke_width.unwrap_or(1.));
1067-
self.render_context.set_global_composite_operation("destination-out").expect("Failed to set global composite operation");
1068-
self.render_context.set_stroke_style_str(&"#000000");
1069-
self.render_context.stroke();
1057+
let [a, b, c, d, e, f] = element_transform.to_cols_array();
1058+
self.render_context.transform(a, b, c, d, e, f);
1059+
1060+
self.draw_path_from_subpaths(subpaths, applied_stroke_transform);
1061+
1062+
match stroke.paint_order {
1063+
PaintOrder::StrokeAbove => {
1064+
self.render_context.set_fill_style_canvas_pattern(&self.fill_canvas_pattern(color));
1065+
self.render_context.fill();
1066+
1067+
// Make the stroke transparent and erase the fill area overlapping the stroke.
1068+
self.render_context.set_stroke_style_str(&"#000000");
1069+
self.render_context.set_line_width(stroke.weight());
1070+
self.render_context.set_global_composite_operation("destination-out").expect("Failed to set global composite operation");
1071+
self.render_context.stroke();
1072+
}
1073+
PaintOrder::StrokeBelow => {
1074+
self.render_context.set_fill_style_canvas_pattern(&self.fill_canvas_pattern(color));
1075+
self.render_context.fill();
1076+
}
1077+
}
1078+
} else {
1079+
self.render_context.set_fill_style_canvas_pattern(&self.fill_canvas_pattern(color));
1080+
self.render_context.fill();
10701081
}
10711082

10721083
self.end_dpi_aware_transform();
10731084
self.render_context.restore();
10741085
}
10751086

1076-
pub fn fill_stroke(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, overlay_stroke: &Stroke) {
1087+
pub fn stroke_overlay(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, layer_to_viewport: DAffine2, color: &Color, stroke: Option<Stroke>) {
1088+
// Render for elements with stroke
1089+
//----StrokeAlign
1090+
// Render for elements with stroke only
1091+
// Render for elements with stroke and fill
1092+
//----PaintOrder
1093+
10771094
self.render_context.save();
10781095
self.start_dpi_aware_transform();
10791096

1080-
self.render_context
1081-
.set_stroke_style_canvas_pattern(&self.fill_canvas_pattern(&overlay_stroke.color.expect("Color should be set for fill_stroke()")));
1082-
self.render_context.set_line_width(overlay_stroke.weight);
1083-
self.render_context.set_line_cap(overlay_stroke.cap.html_canvas_name().as_str());
1084-
self.render_context.set_line_join(overlay_stroke.join.html_canvas_name().as_str());
1085-
self.render_context.set_miter_limit(overlay_stroke.join_miter_limit);
1086-
// let stroke_transform = Some(overlay_stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.).unwrap_or(DAffine2::IDENTITY);
1087-
// let stroke_transform = overlay_stroke.transform;
1088-
let a = transform.matrix2.x_axis.x;
1089-
let b = transform.matrix2.y_axis.x;
1090-
let c = transform.matrix2.x_axis.y;
1091-
let d = transform.matrix2.y_axis.y;
1092-
let e = transform.translation.x;
1093-
let f = transform.translation.y;
1094-
self.render_context.transform(a, b, c, d, e, f);
1095-
self.draw_path_from_subpaths(subpaths, overlay_stroke.transform);
1096-
self.render_context.stroke();
1097+
if let Some(stroke) = stroke {
1098+
let has_real_stroke = stroke.weight() > 0. && stroke.transform.matrix2.determinant() != 0.;
1099+
let applied_stroke_transform = if has_real_stroke { stroke.transform } else { layer_to_viewport };
1100+
let element_transform = if has_real_stroke { layer_to_viewport * stroke.transform.inverse() } else { DAffine2::IDENTITY };
1101+
1102+
let [a, b, c, d, e, f] = element_transform.to_cols_array();
1103+
self.render_context.transform(a, b, c, d, e, f);
1104+
1105+
self.draw_path_from_subpaths(subpaths, applied_stroke_transform);
1106+
1107+
self.render_context.set_stroke_style_canvas_pattern(&self.fill_canvas_pattern(color));
1108+
self.render_context.set_line_width(stroke.weight);
1109+
self.render_context.set_line_cap(stroke.cap.html_canvas_name().as_str());
1110+
self.render_context.set_line_join(stroke.join.html_canvas_name().as_str());
1111+
self.render_context.set_miter_limit(stroke.join_miter_limit);
1112+
match stroke.paint_order {
1113+
PaintOrder::StrokeAbove => {
1114+
self.render_context.stroke();
1115+
}
1116+
PaintOrder::StrokeBelow => {
1117+
self.render_context.stroke();
1118+
1119+
self.render_context.set_fill_style_str(&"#000000");
1120+
self.render_context.set_global_composite_operation("destination-out").expect("Failed to set global composite operation");
1121+
self.render_context.fill();
1122+
}
1123+
}
1124+
}
10971125

10981126
self.end_dpi_aware_transform();
10991127
self.render_context.restore();

editor/src/messages/tool/tool_messages/fill_tool.rs

Lines changed: 5 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@ use super::tool_prelude::*;
22
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
33
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
44
use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer, get_stroke_width};
5-
use graph_craft::document::value::TaggedValue;
6-
use graphene_std::NodeInputDecleration;
75
use graphene_std::subpath::Subpath;
86
use graphene_std::vector::PointId;
9-
use graphene_std::vector::stroke::{AlignInput, CapInput, JoinInput, MiterLimitInput, PaintOrderInput};
10-
use graphene_std::vector::style::{Fill, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
7+
use graphene_std::vector::style::Fill;
118
use kurbo::ParamCurveNearest;
129

1310
#[derive(Default, ExtractField)]
@@ -94,15 +91,6 @@ pub fn close_to_subpath(mouse_pos: DVec2, subpath: Subpath<PointId>, stroke_widt
9491
}
9592

9693
return is_close;
97-
98-
// if let Some((segment_index, t)) = subpath.project(mouse_pos) {
99-
// let nearest_point = subpath.evaluate(SubpathTValue::Parametric { segment_index, t });
100-
// // debug!("max_stroke_distance: {max_stroke_distance}");
101-
// // debug!("mouse-stroke distance: {:?}", (mouse_pos - nearest_point).length());
102-
// (mouse_pos - nearest_point).length_squared() <= max_stroke_distance
103-
// } else {
104-
// false
105-
// }
10694
}
10795

10896
const STROKE_ID: DefinitionIdentifier = DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER);
@@ -157,61 +145,20 @@ impl Fsm for FillToolFsmState {
157145
let stroke_exists_and_visible = stroke_node.is_some_and(|stroke| document.network_interface.is_visible(&stroke, &[]));
158146
let stroke = vector_data.style.stroke();
159147

160-
let has_real_stroke = stroke.filter(|stroke| stroke.weight() > 0.);
161-
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
162-
let applied_stroke_transform = set_stroke_transform.unwrap_or(document.metadata().transform_to_viewport(layer));
163-
let element_transform = set_stroke_transform
164-
.map(|stroke_transform| document.metadata().transform_to_viewport(layer) * stroke_transform.inverse())
165-
.unwrap_or(DAffine2::IDENTITY);
166-
167148
let stroke_width = get_stroke_width(layer, &document.network_interface).unwrap_or(1.0);
168-
let zoom: f64 = document.document_ptz.zoom();
169-
let modified_stroke_width = stroke_width;
149+
let zoom = document.document_ptz.zoom();
170150
let close_to_stroke = subpaths.any(|subpath| close_to_subpath(input.mouse.position, subpath, stroke_width, zoom, document.metadata().transform_to_viewport(layer)));
171151

172152
// Fill
173153
let fill_node = graph_layer.upstream_node_id_from_name(&FILL_ID);
174154
let fill_exists_and_visible = fill_node.is_some_and(|fill| document.network_interface.is_visible(&fill, &[]));
175155

176156
subpaths = vector_data.stroke_bezier_paths();
157+
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
177158
if stroke_exists_and_visible && close_to_stroke {
178-
let overlay_stroke = || {
179-
let mut overlay_stroke = Stroke::new(Some(preview_color), modified_stroke_width);
180-
overlay_stroke.transform = applied_stroke_transform;
181-
182-
let align = graph_layer.find_input(&STROKE_ID, AlignInput::INDEX).unwrap();
183-
overlay_stroke.align = if let TaggedValue::StrokeAlign(align) = align { *align } else { StrokeAlign::default() };
184-
185-
let line_cap = graph_layer.find_input(&STROKE_ID, CapInput::INDEX).unwrap();
186-
overlay_stroke.cap = if let TaggedValue::StrokeCap(line_cap) = line_cap { *line_cap } else { StrokeCap::default() };
187-
188-
let line_join = graph_layer.find_input(&STROKE_ID, JoinInput::INDEX).unwrap();
189-
overlay_stroke.join = if let TaggedValue::StrokeJoin(line_join) = line_join { *line_join } else { StrokeJoin::default() };
190-
191-
let miter_limit = graph_layer.find_input(&STROKE_ID, MiterLimitInput::INDEX).unwrap();
192-
overlay_stroke.join_miter_limit = if let TaggedValue::F64(miter_limit) = miter_limit { *miter_limit } else { f64::default() };
193-
194-
let paint_order = graph_layer.find_input(&STROKE_ID, PaintOrderInput::INDEX).unwrap();
195-
overlay_stroke.paint_order = if let TaggedValue::PaintOrder(paint_order) = paint_order {
196-
*paint_order
197-
} else {
198-
PaintOrder::default()
199-
};
200-
201-
overlay_stroke
202-
};
203-
204-
overlay_context.fill_stroke(subpaths, element_transform, &overlay_stroke());
159+
overlay_context.stroke_overlay(subpaths, layer_to_viewport, &preview_color, stroke);
205160
} else if fill_exists_and_visible {
206-
overlay_context.fill_path(
207-
subpaths,
208-
element_transform,
209-
applied_stroke_transform,
210-
&preview_color,
211-
true,
212-
stroke_exists_and_visible,
213-
Some(modified_stroke_width),
214-
);
161+
overlay_context.fill_overlay(subpaths, layer_to_viewport, &preview_color, stroke);
215162
}
216163
}
217164
}

editor/src/messages/tool/tool_messages/pen_tool.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1680,7 +1680,7 @@ impl Fsm for PenToolFsmState {
16801680
.collect();
16811681

16821682
let fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05);
1683-
overlay_context.fill_path(subpaths.iter(), transform, DAffine2::IDENTITY, &fill_color, false, false, None);
1683+
overlay_context.fill_path(subpaths.iter(), transform, &fill_color);
16841684
}
16851685
}
16861686
}

node-graph/libraries/rendering/src/renderer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,7 @@ impl Render for Table<Vector> {
727727
for row in self.iter() {
728728
let vector = &row.element;
729729
// Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform
730-
let has_real_stroke = vector.style.stroke().filter(|stroke| stroke.weight() > 1.);
730+
let has_real_stroke = vector.style.stroke().filter(|stroke| stroke.weight() > 0.);
731731
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
732732
let applied_stroke_transform = set_stroke_transform.unwrap_or(*row.transform);
733733
let applied_stroke_transform = render_params.alignment_parent_transform.unwrap_or(applied_stroke_transform);

0 commit comments

Comments
 (0)