Skip to content

Commit b1d47b8

Browse files
committed
Testing out transforms on the overlay strokes
1 parent d1612e7 commit b1d47b8

8 files changed

Lines changed: 99 additions & 48 deletions

File tree

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ mod overlays_message;
33
mod overlays_message_handler;
44
pub mod utility_functions;
55
// Native (non‑wasm)
6-
#[cfg(not(target_family = "wasm"))]
7-
pub mod utility_types_native;
8-
#[cfg(not(target_family = "wasm"))]
9-
pub use utility_types_native as utility_types;
6+
// #[cfg(not(target_family = "wasm"))]
7+
// pub mod utility_types_native;
8+
// #[cfg(not(target_family = "wasm"))]
9+
// pub use utility_types_native as utility_types;
1010

1111
// WebAssembly
12-
#[cfg(target_family = "wasm")]
12+
// #[cfg(target_family = "wasm")]
1313
pub mod utility_types_web;
14-
#[cfg(target_family = "wasm")]
14+
// #[cfg(target_family = "wasm")]
1515
pub use utility_types_web as utility_types;
1616

1717
#[doc(inline)]

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

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use graphene_std::table::Table;
1717
use graphene_std::text::{Font, TextAlign, TypesettingConfig};
1818
use graphene_std::vector::click_target::ClickTargetType;
1919
use graphene_std::vector::misc::point_to_dvec2;
20+
use graphene_std::vector::style::Stroke;
2021
use graphene_std::vector::{PointId, SegmentId, Vector};
2122
use kurbo::{self, BezPath, ParamCurve};
2223
use kurbo::{Affine, PathSeg};
@@ -937,7 +938,7 @@ impl OverlayContextInternal {
937938
path.push(bezier.as_path_el());
938939
}
939940

940-
fn push_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2) -> BezPath {
941+
fn path_from_subpaths(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2) -> BezPath {
941942
let mut path = BezPath::new();
942943

943944
for subpath in subpaths {
@@ -1000,24 +1001,14 @@ impl OverlayContextInternal {
10001001
}
10011002

10021003
if !subpaths.is_empty() {
1003-
let path = self.push_path(subpaths.iter(), transform);
1004+
let path = self.path_from_subpaths(subpaths.iter(), transform);
10041005
let color = color.unwrap_or(COLOR_OVERLAY_BLUE);
10051006

10061007
self.scene.stroke(&kurbo::Stroke::new(1.), self.get_transform(), Self::parse_color(color), None, &path);
10071008
}
10081009
}
10091010

1010-
/// Fills the area inside the path. Assumes `color` is in gamma space.
1011-
/// Used by the Pen tool to show the path being closed.
1012-
fn fill_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {
1013-
let path = self.push_path(subpaths, transform);
1014-
1015-
self.scene.fill(peniko::Fill::NonZero, self.get_transform(), Self::parse_color(color), None, &path);
1016-
}
1017-
1018-
/// Fills the area inside the path with a pattern. Assumes `color` is in gamma space.
1019-
/// Used by the fill tool to show the area to be filled.
1020-
fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &Color) {
1011+
pub fn fill_canvas_pattern_image(&self, color: &Color) -> peniko::ImageBrush {
10211012
const PATTERN_WIDTH: u32 = 4;
10221013
const PATTERN_HEIGHT: u32 = 4;
10231014

@@ -1054,12 +1045,33 @@ impl OverlayContextInternal {
10541045
},
10551046
};
10561047

1057-
let path = self.push_path(subpaths, transform);
1058-
let brush = peniko::Brush::Image(image);
1048+
image
1049+
}
1050+
1051+
/// Fills the area inside the path. Assumes `color` is in gamma space.
1052+
/// Used by the Pen tool to show the path being closed.
1053+
fn fill_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {
1054+
let path = self.path_from_subpaths(subpaths, transform);
1055+
1056+
self.scene.fill(peniko::Fill::NonZero, self.get_transform(), Self::parse_color(color), None, &path);
1057+
}
1058+
1059+
/// Fills the area inside the path with a pattern. Assumes `color` is in gamma space.
1060+
/// Used by the fill tool to show the area to be filled.
1061+
fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &Color) {
1062+
let path = self.path_from_subpaths(subpaths, transform);
1063+
let brush = peniko::Brush::Image(self.fill_canvas_pattern_image(color));
10591064

10601065
self.scene.fill(peniko::Fill::NonZero, self.get_transform(), &brush, None, &path);
10611066
}
10621067

1068+
pub fn fill_stroke(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, overlay_stroke: &Stroke) {
1069+
let path = self.path_from_subpaths(subpaths, transform);
1070+
let brush = peniko::Brush::Image(self.fill_canvas_pattern_image(&overlay_stroke.color.expect("Color should be set for fill_stroke()")));
1071+
1072+
self.scene.stroke(&kurbo::Stroke::new(overlay_stroke.weight), self.get_transform(), &brush, None, &path);
1073+
}
1074+
10631075
fn text(&mut self, text: &str, font_color: &str, background_color: Option<&str>, transform: DAffine2, padding: f64, pivot: [Pivot; 2]) {
10641076
// Use the proper text-to-path system for accurate text rendering
10651077
const FONT_SIZE: f64 = 12.;

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

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -907,12 +907,18 @@ impl OverlayContext {
907907
self.end_dpi_aware_transform();
908908
}
909909

910-
pub fn draw_path_from_subpaths(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2) {
910+
pub fn draw_path_from_subpaths(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, stroke_transform: Option<DAffine2>) {
911911
self.start_dpi_aware_transform();
912912

913+
// let a = stroke_transform.matrix2.x_axis.x;
914+
// let b = stroke_transform.matrix2.y_axis.x;
915+
// let c = stroke_transform.matrix2.x_axis.y;
916+
// let d = stroke_transform.matrix2.y_axis.y;
917+
// let e = stroke_transform.translation.x;
918+
// let f = stroke_transform.translation.y;
913919
self.render_context.begin_path();
914920
for subpath in subpaths {
915-
let subpath = subpath.borrow();
921+
let mut subpath = subpath.borrow().clone();
916922
let mut curves = subpath.iter().peekable();
917923

918924
let Some(&first) = curves.peek() else {
@@ -968,7 +974,7 @@ impl OverlayContext {
968974
});
969975

970976
if !subpaths.is_empty() {
971-
self.draw_path_from_subpaths(subpaths.iter(), transform);
977+
self.draw_path_from_subpaths(subpaths.iter(), transform, None);
972978

973979
let color = color.unwrap_or(COLOR_OVERLAY_BLUE);
974980
self.render_context.set_stroke_style_str(color);
@@ -1016,14 +1022,15 @@ impl OverlayContext {
10161022
&mut self,
10171023
subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>,
10181024
transform: DAffine2,
1025+
stroke_transform: DAffine2,
10191026
color: &Color,
10201027
with_pattern: bool,
10211028
clear_stroke_part: bool,
10221029
stroke_width: Option<f64>,
10231030
) {
10241031
self.render_context.save();
10251032
self.render_context.set_line_width(stroke_width.unwrap_or(1.));
1026-
self.draw_path_from_subpaths(subpaths, transform);
1033+
self.draw_path_from_subpaths(subpaths, transform, Some(stroke_transform));
10271034

10281035
if with_pattern {
10291036
self.render_context.set_fill_style_canvas_pattern(&self.fill_canvas_pattern(color));
@@ -1035,27 +1042,49 @@ impl OverlayContext {
10351042

10361043
// Make the stroke transparent and erase the fill area overlapping the stroke.
10371044
if clear_stroke_part {
1045+
// self.render_context.save();
1046+
// let stroke_transform = Some(stroke_transform).filter(|transform| transform.matrix2.determinant() != 0.).unwrap_or(DAffine2::IDENTITY);
1047+
// let a = stroke_transform.matrix2.x_axis.x;
1048+
// let b = stroke_transform.matrix2.y_axis.x;
1049+
// let c = stroke_transform.matrix2.x_axis.y;
1050+
// let d = stroke_transform.matrix2.y_axis.y;
1051+
// let e = stroke_transform.translation.x;
1052+
// let f = stroke_transform.translation.y;
1053+
// self.render_context.set_transform(a, b, c, d, e, f);
1054+
10381055
self.render_context.set_global_composite_operation("destination-out").expect("Failed to set global composite operation");
10391056
self.render_context.set_stroke_style_str(&"#000000");
10401057
self.render_context.stroke();
1058+
// self.render_context.restore();
10411059
}
10421060

10431061
self.render_context.restore();
10441062
}
10451063

1046-
pub fn fill_stroke(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, overlay_stroke: &Stroke) {
1064+
pub fn fill_stroke(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, overlay_stroke: &Stroke) {
10471065
self.render_context.save();
10481066

10491067
// debug!("overlay_stroke.weight * ptz.zoom(): {:?}", overlay_stroke.weight);
10501068
self.render_context.set_line_width(overlay_stroke.weight);
1051-
self.draw_path_from_subpaths(subpaths, overlay_stroke.transform);
1069+
self.draw_path_from_subpaths(subpaths, transform, Some(overlay_stroke.transform));
1070+
1071+
// self.render_context.save();
1072+
// let stroke_transform = Some(overlay_stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.).unwrap_or(DAffine2::IDENTITY);
1073+
// let a = stroke_transform.matrix2.x_axis.x;
1074+
// let b = stroke_transform.matrix2.y_axis.x;
1075+
// let c = stroke_transform.matrix2.x_axis.y;
1076+
// let d = stroke_transform.matrix2.y_axis.y;
1077+
// let e = stroke_transform.translation.x;
1078+
// let f = stroke_transform.translation.y;
1079+
// self.render_context.set_transform(a, b, c, d, e, f);
10521080

10531081
self.render_context
10541082
.set_stroke_style_canvas_pattern(&self.fill_canvas_pattern(&overlay_stroke.color.expect("Color should be set for fill_stroke()")));
10551083
self.render_context.set_line_cap(overlay_stroke.cap.html_canvas_name().as_str());
10561084
self.render_context.set_line_join(overlay_stroke.join.html_canvas_name().as_str());
10571085
self.render_context.set_miter_limit(overlay_stroke.join_miter_limit);
10581086
self.render_context.stroke();
1087+
// self.render_context.restore();
10591088

10601089
self.render_context.restore();
10611090
}

editor/src/messages/portfolio/document/utility_types/document_metadata.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -87,22 +87,26 @@ impl DocumentMetadata {
8787
footprint * local_transform
8888
}
8989

90-
pub fn transform_to_viewport_with_stroke_transform(&self, layer: LayerNodeIdentifier, stroke: Stroke) -> DAffine2 {
90+
pub fn transform_to_viewport_with_stroke_transform(&self, layer: LayerNodeIdentifier, vector: Vector) -> DAffine2 {
9191
// We're not allowed to convert the root parent to a node id
9292
if layer == LayerNodeIdentifier::ROOT_PARENT {
9393
return self.document_to_viewport;
9494
}
9595

9696
let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);
97-
let local_transform = self.local_transforms.get(&layer.to_node()).copied().unwrap_or_default();
98-
99-
// let has_real_stroke = vector.style.stroke().filter(|stroke| stroke.weight() > 0.);
100-
// let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
101-
// let applied_stroke_transform = set_stroke_transform.unwrap_or(*instance.transform);
102-
// let applied_stroke_transform = render_params.alignment_parent_transform.unwrap_or(applied_stroke_transform);
103-
// let stroke_transform = self.upstream_transform(stroke_node);
104-
105-
footprint * local_transform
97+
let local_transform_for_layer = self.local_transforms.get(&layer.to_node()).copied().unwrap_or_default();
98+
99+
let has_real_stroke = vector.style.stroke().filter(|stroke| stroke.weight() > 0.);
100+
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
101+
if let Some(stroke_transform) = set_stroke_transform {
102+
// Both upstream and downstream (from stroke node) contains stroke transforms
103+
// which shouldn't be the case. This branch should only execute for downstreamed.
104+
footprint * stroke_transform * local_transform_for_layer * stroke_transform.inverse()
105+
} else {
106+
// This branch is not executed on upstream or downstream transforms (from stroke node) layers.
107+
// This branch should only execute for upstreamed.
108+
footprint * local_transform_for_layer
109+
}
106110
}
107111

108112
pub fn transform_to_viewport_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {

editor/src/messages/portfolio/document/utility_types/network_interface.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3086,6 +3086,10 @@ impl NodeNetworkInterface {
30863086
self.document_metadata.layer_vector_data.get(&layer).map(|arc| arc.as_ref().clone())
30873087
}
30883088

3089+
pub fn vector_data_from_layer(&self, layer: LayerNodeIdentifier) -> Option<Vector> {
3090+
self.document_metadata.layer_vector_data.get(&layer).map(|arc| arc.as_ref().clone())
3091+
}
3092+
30893093
/// Loads the structure of layer nodes from a node graph.
30903094
pub fn load_structure(&mut self) {
30913095
self.document_metadata.structure = HashMap::from_iter([(LayerNodeIdentifier::ROOT_PARENT, NodeRelations::default())]);

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -148,31 +148,29 @@ impl Fsm for FillToolFsmState {
148148
}
149149
// Get the layer the user is hovering
150150
if let Some(layer) = document.click(input, viewport) {
151-
if let Some(vector_data) = document.network_interface.compute_modified_vector(layer) {
152-
document.network_interface.compute_modified_vector(layer)
151+
if let Some(vector_data) = document.network_interface.vector_data_from_layer(layer) {
153152
let mut subpaths = vector_data.stroke_bezier_paths();
154153
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface);
155154

156155
// Stroke
157156
let stroke_node = graph_layer.upstream_node_id_from_name(&STROKE_ID);
158157
let stroke_exists_and_visible = stroke_node.is_some_and(|stroke| document.network_interface.is_visible(&stroke, &[]));
159158

160-
let stroke = vector_data.style.stroke().unwrap();
159+
let stroke = vector_data.style.stroke();
161160
let stroke_width = get_stroke_width(layer, &document.network_interface).unwrap_or(1.0);
162161
let zoom = document.document_ptz.zoom();
163-
let modified_stroke_width = stroke_width * zoom * 1.25;
162+
let modified_stroke_width = stroke_width * zoom;
164163
let close_to_stroke = subpaths.any(|subpath| close_to_subpath(input.mouse.position, subpath, stroke_width, zoom, document.metadata().transform_to_viewport(layer)));
165164

166165
// Fill
167166
let fill_node = graph_layer.upstream_node_id_from_name(&FILL_ID);
168167
let fill_exists_and_visible = fill_node.is_some_and(|fill| document.network_interface.is_visible(&fill, &[]));
169168

170169
subpaths = vector_data.stroke_bezier_paths();
171-
// let stroke_transform = document.network_interface.compute_modified_vector(layer).unwrap().style.stroke
172170
if stroke_exists_and_visible && close_to_stroke {
173171
let overlay_stroke = || {
174172
let mut overlay_stroke = Stroke::new(Some(preview_color), modified_stroke_width);
175-
overlay_stroke.transform = document.metadata().transform_to_viewport_with_stroke_transform(layer, stroke);
173+
overlay_stroke.transform = DAffine2::IDENTITY;
176174
let line_cap = graph_layer.find_input(&STROKE_ID, CapInput::INDEX).unwrap();
177175
overlay_stroke.cap = if let TaggedValue::StrokeCap(line_cap) = line_cap { *line_cap } else { StrokeCap::default() };
178176
let line_join = graph_layer.find_input(&STROKE_ID, JoinInput::INDEX).unwrap();
@@ -183,11 +181,12 @@ impl Fsm for FillToolFsmState {
183181
overlay_stroke
184182
};
185183

186-
overlay_context.fill_stroke(subpaths, &overlay_stroke());
184+
overlay_context.fill_stroke(subpaths, document.metadata().transform_to_viewport_with_stroke_transform(layer, vector_data.clone()), &overlay_stroke());
187185
} else if fill_exists_and_visible {
188186
overlay_context.fill_path(
189187
subpaths,
190-
document.metadata().transform_to_viewport_with_stroke_transform(layer, stroke),
188+
document.metadata().transform_to_viewport_with_stroke_transform(layer, vector_data.clone()),
189+
DAffine2::IDENTITY,
191190
&preview_color,
192191
true,
193192
stroke_exists_and_visible,

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, &fill_color, false, false, None);
1683+
overlay_context.fill_path(subpaths.iter(), transform, DAffine2::IDENTITY, &fill_color, false, false, None);
16841684
}
16851685
}
16861686
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -725,14 +725,13 @@ impl Render for Table<Graphic> {
725725
impl Render for Table<Vector> {
726726
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
727727
for row in self.iter() {
728-
let multiplied_transform = *row.transform;
729728
let vector = &row.element;
730729
// Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform
731730
let has_real_stroke = vector.style.stroke().filter(|stroke| stroke.weight() > 0.);
732731
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
733732
let applied_stroke_transform = set_stroke_transform.unwrap_or(*row.transform);
734733
let applied_stroke_transform = render_params.alignment_parent_transform.unwrap_or(applied_stroke_transform);
735-
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
734+
let element_transform = set_stroke_transform.map(|stroke_transform| *row.transform * stroke_transform.inverse());
736735
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
737736
let layer_bounds = vector.bounding_box().unwrap_or_default();
738737
let transformed_bounds = vector.bounding_box_with_transform(applied_stroke_transform).unwrap_or_default();
@@ -743,6 +742,7 @@ impl Render for Table<Vector> {
743742
let mut path = String::new();
744743

745744
for mut bezpath in row.element.stroke_bezpath_iter() {
745+
// Only affects upstream-transformed (from stroke node) layers with row.transform
746746
bezpath.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));
747747
path.push_str(bezpath.to_svg().as_str());
748748
}
@@ -826,6 +826,8 @@ impl Render for Table<Vector> {
826826

827827
render.leaf_tag("path", |attributes| {
828828
attributes.push("d", path.clone());
829+
// Only affects layers with downstream-transformed layers (from stroke node) with row.transform*stroke_transform.inverse()
830+
// and affect layers with upstream-transformed (from stroke node) layers with IDENTITY
829831
let matrix = format_transform_matrix(element_transform);
830832
if !matrix.is_empty() {
831833
attributes.push("transform", matrix);
@@ -871,6 +873,7 @@ impl Render for Table<Vector> {
871873
let selector = format!("url(#{id})");
872874
attributes.push(mask_type.to_attribute(), selector);
873875
}
876+
// Look here to see how to adjust the stroke
874877
attributes.push_val(fill_and_stroke);
875878

876879
let opacity = row.alpha_blending.opacity(render_params.for_mask);

0 commit comments

Comments
 (0)