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) <noreply@anthropic.com>
This commit is contained in:
RealtaNua
2026-10-17 23:25:51 +07:00
co-authored by Claude Opus 5.5
parent 0a01188c31
commit 1c16aeae09
3 changed files with 41 additions and 3 deletions
+5 -2
View File
@@ -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);
+3 -1
View File
@@ -1128,7 +1128,9 @@ fn overlays(app: &FigApp, painter: &egui::Painter, t: &Tokens) -> Vec<(NodeId, [
if !ov.pen.is_empty() {
let pts: Vec<Pos2> = 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);
+33
View File
@@ -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}");
}