Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d1612e7
Implement fill tool on strokes
seam0s-dev Feb 18, 2026
b1d47b8
Testing out transforms on the overlay strokes
seam0s-dev Apr 1, 2026
c5f2a4f
Fixed transforms on overlay strokes using stroke transform
seam0s-dev Apr 13, 2026
95e6a92
Refactor and add overlay support based on paint order
seam0s-dev Apr 14, 2026
2adac3b
Add stroke align support on fill overlays
seam0s-dev Apr 15, 2026
f412eb9
Fix overlay pattern scaling and ignore StrokeAlign on open subpaths
seam0s-dev Apr 19, 2026
feb6895
Add StrokeAlign and PaintOrder support on stroke detection
seam0s-dev Apr 22, 2026
e92f543
Merge remote-tracking branch 'origin/master' into 2615-fill-tool-on-s…
seam0s-dev Apr 22, 2026
3754701
Merge branch 'master' into 2615-fill-tool-on-strokes
seam0s-dev Apr 23, 2026
7a9cf59
Implement stroke and fill overlays for desktop
seam0s-dev Apr 26, 2026
a8b0bae
Fix stroke artifacts
seam0s-dev Apr 27, 2026
966c160
Merge remote-tracking branch 'origin/master' into 2615-fill-tool-on-s…
seam0s-dev May 7, 2026
8ab03cc
Improve stroke detection and refactor
seam0s-dev May 9, 2026
9173e22
Fix stroke detection on open subpaths with StrokeAlign::Inside
seam0s-dev May 9, 2026
855fb9d
Merge remote-tracking branch 'origin/master' into 2615-fill-tool-on-s…
seam0s-dev May 9, 2026
129486f
Fix stroke_color_set
seam0s-dev May 9, 2026
e431d69
Fix regression
seam0s-dev May 9, 2026
c5bec01
Try fix regression
seam0s-dev May 10, 2026
96be42c
Add face-by-face rendering support for fill tool overlays
seam0s-dev May 21, 2026
8f5c843
Merge remote-tracking branch 'origin/master' into 2615-fill-tool-on-s…
seam0s-dev May 21, 2026
993212c
Fix merge errors
seam0s-dev May 21, 2026
e39b27e
Refactor fill_overlay() and stroke_overlay() into preview_fill()
seam0s-dev May 22, 2026
628d730
Replace style_type in do_fill and do_stroke for use_as_mask
seam0s-dev May 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ web-sys = { version = "=0.3.77", features = [
"HtmlCanvasElement",
"CanvasRenderingContext2d",
"CanvasPattern",
"DomMatrix",
"SvgMatrix",
"OffscreenCanvas",
"OffscreenCanvasRenderingContext2d",
"TextMetrics",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
responses.add(PortfolioMessage::UpdateDocumentWidgets);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Pivot and Origin overlay checkboxes are bound to the wrong visibility fields, so the checked state and toggle action are inverted.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/portfolio/document/document_message_handler.rs, line 2323:

<comment>Pivot and Origin overlay checkboxes are bound to the wrong visibility fields, so the checked state and toggle action are inverted.</comment>

<file context>
@@ -2319,7 +2320,7 @@ impl DocumentMessageHandler {
 							let checkbox_id = CheckboxId::new();
 							vec![
-								CheckboxInput::new(self.overlays_visibility_settings.pivot)
+								CheckboxInput::new(self.overlays_visibility_settings.origin)
 									.on_update(|optional_input: &CheckboxInput| {
 										DocumentMessage::SetOverlaysVisibility {
</file context>

}
OverlaysType::Handles => visibility_settings.handles = visible,
OverlaysType::FillableIndicator => visibility_settings.fillable_indicator = visible,
}

responses.add(EventMessage::ToolAbort);
Expand Down Expand Up @@ -2319,7 +2320,7 @@ impl DocumentMessageHandler {
widgets: {
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.pivot)
CheckboxInput::new(self.overlays_visibility_settings.origin)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
Expand Down Expand Up @@ -2466,6 +2467,27 @@ impl DocumentMessageHandler {
]
},
},
LayoutGroup::Row {
widgets: vec![TextLabel::new("Fill Tool").widget_instance()],
},
LayoutGroup::Row {
widgets: {
let checkbox_id = CheckboxId::new();
vec![
CheckboxInput::new(self.overlays_visibility_settings.fillable_indicator)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::FillableIndicator),
}
.into()
})
.for_label(checkbox_id.clone())
.widget_instance(),
TextLabel::new("Fillable Indicator".to_string()).for_checkbox(checkbox_id).widget_instance(),
]
},
},
]))
.widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node
use crate::messages::prelude::*;
use glam::{DAffine2, IVec2};
use graph_craft::document::NodeId;
use graphene_std::Artboard;
use graphene_std::brush::brush_stroke::BrushStroke;
use graphene_std::raster::BlendMode;
use graphene_std::raster_types::{CPU, Raster};
Expand All @@ -14,6 +13,7 @@ use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::PointId;
use graphene_std::vector::VectorModificationType;
use graphene_std::vector::style::{Fill, Stroke};
use graphene_std::{Artboard, Color};

#[impl_message(Message, DocumentMessage, GraphOperation)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
Expand Down Expand Up @@ -41,6 +41,10 @@ pub enum GraphOperationMessage {
layer: LayerNodeIdentifier,
stroke: Stroke,
},
StrokeColorSet {
layer: LayerNodeIdentifier,
stroke_color: Color,
},
TransformChange {
layer: LayerNodeIdentifier,
transform: DAffine2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
modify_inputs.stroke_set(stroke);
}
}
GraphOperationMessage::StrokeColorSet { layer, stroke_color } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.stroke_color_set(Some(stroke_color));
}
}
GraphOperationMessage::TransformChange {
layer,
transform,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use glam::{DAffine2, IVec2};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput};
use graph_craft::{ProtoNodeIdentifier, concrete};
use graphene_std::Artboard;
use graphene_std::brush::brush_stroke::BrushStroke;
use graphene_std::raster::BlendMode;
use graphene_std::raster_types::{CPU, Raster};
Expand All @@ -17,7 +16,7 @@ use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::Vector;
use graphene_std::vector::style::{Fill, Stroke};
use graphene_std::vector::{PointId, VectorModificationType};
use graphene_std::{Graphic, NodeInputDecleration};
use graphene_std::{Artboard, Color, Graphic, NodeInputDecleration};

#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
pub enum TransformIn {
Expand Down Expand Up @@ -423,6 +422,16 @@ impl<'a> ModifyInputsContext<'a> {
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.dash_offset), false), true);
}

pub fn stroke_color_set(&mut self, color: Option<Color>) {
let Some(stroke_node_id) = self.existing_proto_node_id(graphene_std::vector::stroke::IDENTIFIER, false) else {
return;
};

let stroke_color = if let Some(color) = color { Table::new_from_element(color) } else { Table::new() };
let input_connector = InputConnector::node(stroke_node_id, 1);
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(stroke_color), false), false);
}

/// Update the transform value of the upstream Transform node based a change to its existing value and the given parent transform.
/// A new Transform node is created if one does not exist, unless it would be given the identity transform.
pub fn transform_change_with_parent(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, skip_rerender: bool) {
Expand Down
12 changes: 6 additions & 6 deletions editor/src/messages/portfolio/document/overlays/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ mod overlays_message;
mod overlays_message_handler;
pub mod utility_functions;
// Native (non‑wasm)
#[cfg(not(target_family = "wasm"))]
pub mod utility_types_native;
#[cfg(not(target_family = "wasm"))]
pub use utility_types_native as utility_types;
// #[cfg(not(target_family = "wasm"))]
// pub mod utility_types_native;
// #[cfg(not(target_family = "wasm"))]
// pub use utility_types_native as utility_types;

// WebAssembly
#[cfg(target_family = "wasm")]
// #[cfg(target_family = "wasm")]
pub mod utility_types_web;
#[cfg(target_family = "wasm")]
// #[cfg(target_family = "wasm")]
pub use utility_types_web as utility_types;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The conditional compilation attributes (#[cfg(...)])) for utility_types have been commented out, forcing the use of utility_types_web even on non-WASM targets. This will cause compilation errors when building for native platforms because utility_types_web depends on web-sys and wasm-bindgen. These should be restored to maintain cross-platform support.


#[doc(inline)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,20 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
canvas_context.clear_rect(0., 0., width, height);

if visibility_settings.all() {
responses.add(DocumentMessage::GridOverlays {
context: OverlayContext {
render_context: canvas_context.clone(),
visibility_settings: visibility_settings.clone(),
viewport: *viewport,
},
});
for provider in &self.overlay_providers {
responses.add(provider(OverlayContext {
render_context: canvas_context.clone(),
visibility_settings: visibility_settings.clone(),
viewport: *viewport,
}));
}
responses.add(DocumentMessage::GridOverlays {
context: OverlayContext {
render_context: canvas_context.clone(),
visibility_settings: visibility_settings.clone(),
viewport: *viewport,
},
});
}
}
#[cfg(all(not(target_family = "wasm"), not(test)))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use graphene_std::table::Table;
use graphene_std::text::{Font, TextAlign, TypesettingConfig};
use graphene_std::vector::click_target::ClickTargetType;
use graphene_std::vector::misc::point_to_dvec2;
use graphene_std::vector::style::Stroke;
use graphene_std::vector::{PointId, SegmentId, Vector};
use kurbo::{self, BezPath, ParamCurve};
use kurbo::{Affine, PathSeg};
Expand Down Expand Up @@ -937,7 +938,7 @@ impl OverlayContextInternal {
path.push(bezier.as_path_el());
}

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

for subpath in subpaths {
Expand Down Expand Up @@ -1000,24 +1001,14 @@ impl OverlayContextInternal {
}

if !subpaths.is_empty() {
let path = self.push_path(subpaths.iter(), transform);
let path = self.path_from_subpaths(subpaths.iter(), transform);
let color = color.unwrap_or(COLOR_OVERLAY_BLUE);

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

/// Fills the area inside the path. Assumes `color` is in gamma space.
/// Used by the Pen tool to show the path being closed.
fn fill_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {
let path = self.push_path(subpaths, transform);

self.scene.fill(peniko::Fill::NonZero, self.get_transform(), Self::parse_color(color), None, &path);
}

/// Fills the area inside the path with a pattern. Assumes `color` is in gamma space.
/// Used by the fill tool to show the area to be filled.
fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &Color) {
pub fn fill_canvas_pattern_image(&self, color: &Color) -> peniko::ImageBrush {
const PATTERN_WIDTH: u32 = 4;
const PATTERN_HEIGHT: u32 = 4;

Expand Down Expand Up @@ -1054,12 +1045,33 @@ impl OverlayContextInternal {
},
};

let path = self.push_path(subpaths, transform);
let brush = peniko::Brush::Image(image);
image
}

/// Fills the area inside the path. Assumes `color` is in gamma space.
/// Used by the Pen tool to show the path being closed.
fn fill_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {
let path = self.path_from_subpaths(subpaths, transform);

self.scene.fill(peniko::Fill::NonZero, self.get_transform(), Self::parse_color(color), None, &path);
}

/// Fills the area inside the path with a pattern. Assumes `color` is in gamma space.
/// Used by the fill tool to show the area to be filled.
fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &Color) {
let path = self.path_from_subpaths(subpaths, transform);
let brush = peniko::Brush::Image(self.fill_canvas_pattern_image(color));

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

pub fn fill_stroke(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, overlay_stroke: &Stroke) {
let path = self.path_from_subpaths(subpaths, transform);
let brush = peniko::Brush::Image(self.fill_canvas_pattern_image(&overlay_stroke.color.expect("Color should be set for fill_stroke()")));
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated

self.scene.stroke(&kurbo::Stroke::new(overlay_stroke.weight), self.get_transform(), &brush, None, &path);
}

fn text(&mut self, text: &str, font_color: &str, background_color: Option<&str>, transform: DAffine2, padding: f64, pivot: [Pivot; 2]) {
// Use the proper text-to-path system for accurate text rendering
const FONT_SIZE: f64 = 12.;
Expand Down
Loading