From 1c16aeae094e81ad08a399e05cb4f3cb3ed5c59d Mon Sep 17 00:00:00 2001 From: RealtaNua Date: Sat, 17 Oct 2026 23:25:51 +0700 Subject: [PATCH] fix(canvas): the Pencil previews its stroke in its own colour and weight (#256) While drawing, the Pencil previewed its stroke in the theme's text colour, 2 points wide: white in dark theme, so over a white shape nothing showed until the mouse was let go. The preview now uses the colour and weight the Pencil draws with (one constant, PENCIL_STROKE, black at 2 pt), scaled by the zoom, at least 1 pt. It doesn't show pen pressure or a brush, which only the finished vector has. Refs #256 ("The 'draw' tool is not visible when you are drawing. You can see it after you release the mouse."). Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/engine/src/tools.rs | 7 +++-- crates/ui-egui/src/canvas.rs | 4 ++- crates/ui-egui/src/canvas_fixes_tests.rs | 33 ++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/crates/engine/src/tools.rs b/crates/engine/src/tools.rs index dbda1894..2674552e 100644 --- a/crates/engine/src/tools.rs +++ b/crates/engine/src/tools.rs @@ -661,6 +661,9 @@ fn on_corner_square(doc: &Document, sel: &[NodeId], p: Point, zoom: f64) -> bool /// handles: the canvas draws just its outline (the handles still answer just outside it). pub const SMALL_BOX_PX: f64 = 16.0; +/// The stroke the Pencil draws: its colour and weight (the canvas previews it the same). +pub const PENCIL_STROKE: (figcraft_color::Color, f64) = (figcraft_color::Color::BLACK, 2.0); + /// The shorter and longer sides, in screen pixels, of the box with `corners` (tl, tr, br, bl). pub fn box_sides_px(corners: &[Point; 4], zoom: f64) -> (f64, f64) { let [a, b, _, d] = corners; @@ -2210,8 +2213,8 @@ impl Session { let mut local = fitted.clone(); local.apply_affine(pw.inverse()); let _ = d; - let mut props = - json!({"fills": [], "strokes": ["#000000"], "strokeWeight": 2, "strokeCap": "round", "strokeJoin": "round", "strokeAlign": "center"}); + let (color, weight) = PENCIL_STROKE; + let mut props = json!({"fills": [], "strokes": [color.to_css_hex()], "strokeWeight": weight, "strokeCap": "round", "strokeJoin": "round", "strokeAlign": "center"}); // Pen pressure becomes the stroke's width profile (brushes follow it too). if let Some(points) = pressure_profile(points, pressure) { props["widthProfile"] = json!(points); diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index bc184270..d7928b76 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -1128,7 +1128,9 @@ fn overlays(app: &FigApp, painter: &egui::Painter, t: &Tokens) -> Vec<(NodeId, [ if !ov.pen.is_empty() { let pts: Vec = ov.pen.iter().map(|[x, y]| app.to_screen(*x, *y)).collect(); if app.session.tool.tool == ToolId::Pencil { - painter.add(Shape::line(pts, stroke(t.text, 2.0))); + // The stroke it will make, in its colour and weight (at least a point when zoomed out). + let (color, weight) = figcraft_engine::tools::PENCIL_STROKE; + painter.add(Shape::line(pts, stroke(doc_c32(painter.ctx(), color), ((weight * zoom) as f32).max(1.0)))); } else { for [x0, y0, x1, y1] in &ov.pen_handles { bezier_handle(painter, app.to_screen(*x0, *y0), app.to_screen(*x1, *y1), 3.5, Color32::WHITE); diff --git a/crates/ui-egui/src/canvas_fixes_tests.rs b/crates/ui-egui/src/canvas_fixes_tests.rs index 3b593dae..4cd32d56 100644 --- a/crates/ui-egui/src/canvas_fixes_tests.rs +++ b/crates/ui-egui/src/canvas_fixes_tests.rs @@ -128,3 +128,36 @@ fn the_pen_draws_the_handles_of_the_point_being_dragged_and_a_curved_band() { let band = shapes.iter().any(|s| matches!(s, Shape::CubicBezier(c) if c.points[0].distance(a) < 0.5 && c.points[1].distance(at(&u, 140.0, 100.0)) < 0.5 && c.stroke.color == accent)); assert!(band, "a curve leaving along the out handle"); } + +/// While drawing, the Pencil shows the stroke it will make, in its colour and weight (#256): in a +/// dark theme the theme's text colour was white, so over a white shape nothing showed until the +/// stroke was let go. +#[test] +fn the_pencil_previews_its_stroke_in_its_colour_and_weight() { + let mut u = Ui::new(Session::new()); + u.app.run("tool.set", json!({"tool": "pencil"})); + let (color, weight) = figcraft_engine::tools::PENCIL_STROKE; + let preview = |shapes: &[Shape]| { + shapes.iter().find_map(|s| match s { + Shape::Path(p) if !p.closed && p.points.len() >= 3 && p.stroke.color == egui::epaint::ColorMode::Solid(crate::theme::c32(color)) => { + Some(p.stroke.width) + } + _ => None, + }) + }; + let at = |u: &Ui, x: f64, y: f64| u.app.to_screen(x, y); + let start = at(&u, 10.0, 10.0); + u.button(start, true); + for i in 1..=6 { + let p = at(&u, 10.0 + 12.0 * f64::from(i), 10.0 + 6.0 * f64::from(i % 2)); + u.frame(vec![egui::Event::PointerMoved(p)]); + } + let zoom = u.app.session.ds().unwrap().view.zoom; + let w = preview(&flat(&u.frame_output(vec![]).shapes)).expect("a preview while drawing"); + assert!((w - (weight * zoom) as f32).abs() < 0.01, "{w} for {weight} at {zoom}"); + // Zoomed far out it stays visible. + u.app.session.ds_mut().unwrap().view.zoom = 0.05; + let p = at(&u, 120.0, 40.0); + let w = preview(&flat(&u.frame_output(vec![egui::Event::PointerMoved(p)]).shapes)).expect("still previewing"); + assert!((w - 1.0).abs() < 0.01, "at least a point: {w}"); +}