Blob Brush: Alt switches to the Smooth tool only before the pointer moves

Alt switched a Blob Brush stroke to the Smooth tool while the stroke held fewer than two
points, so a drag that had moved less than the sampling tolerance still switched when Alt
went down. The tool now records whether the pointer has moved from the press point at all.
A release with Alt held, after a press without Alt and no movement, also smooths instead of
painting a dot, matching the Smooth cursor shown before it.
This commit is contained in:
dotCooCoo
2026-10-29 10:01:53 +07:00
committed by bflatastic
parent 80bf11dd8b
commit 7e25203660
2 changed files with 23 additions and 1 deletions
+11 -1
View File
@@ -73,6 +73,9 @@ pub struct GestureTool {
pub live_fit: bool,
/// Active drag is smoothing with the Pencil or the Blob Brush (Alt held).
smoothing: bool,
/// The pointer has moved from the press point, by any distance (a move too short to add a
/// point counts).
moved: bool,
/// Active Pencil drag draws a straight line (Shift or Alt held at the press): the stroke is
/// the press point and the pointer.
straight: bool,
@@ -143,6 +146,7 @@ impl GestureTool {
live_preview: true,
live_fit: true,
smoothing: false,
moved: false,
straight: false,
unconstrained: false,
shift_raw: vec![],
@@ -193,7 +197,7 @@ impl GestureTool {
/// Would Alt held now switch the Blob Brush to the Smooth tool? It does before a press, and
/// during a drag that has not moved from the press point yet.
fn blob_alt_smooths(&self, m: Mods) -> bool {
self.id == "blobBrush" && m.alt && self.points.len() < 2
self.id == "blobBrush" && m.alt && !self.moved
}
/// Where the stroke takes a pointer at `p`. For the Blob Brush with Shift held, that is the
@@ -237,6 +241,7 @@ impl GestureTool {
/// Forget the stroke.
fn clear(&mut self) {
self.moved = false;
self.points.clear();
self.pressures.clear();
self.previewed = 0;
@@ -455,6 +460,7 @@ impl Tool for GestureTool {
if (!self.began && !self.straight && self.id == "pencil" && self.alt_smooth && ev.mods.alt) || self.blob_alt_smooths(ev.mods) {
self.smoothing = true;
}
self.moved |= self.points.first().is_some_and(|start| p.distance(*start) > vectorcraft_geom::EPS);
let p = self.constrained(cx, p, ev.mods);
let mut end = p;
if self.snaps() && !self.smoothing && !self.straight {
@@ -513,6 +519,10 @@ impl Tool for GestureTool {
if !self.active {
return vec![];
}
// Alt pressed while the pointer stood still at the press point switches the release too.
if self.blob_alt_smooths(ev.mods) {
self.smoothing = true;
}
let p = self.constrained(cx, p, ev.mods);
let end = if self.snaps() && !self.smoothing && (!self.straight || self.unconstrained) { self.snap.drag(cx, p, None) } else { p };
self.snap.clear();
+12
View File
@@ -397,6 +397,18 @@ fn blob_brush_alt_switches_to_the_smooth_tool() {
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 200.0, 150.0).with_mods(alt));
let acts = t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 250.0, 150.0).with_mods(alt));
assert!(matches!(acts.as_slice(), [Action::Exec(c, v)] if c == "path.blob" && v["points"].as_array().unwrap().len() == 4), "{acts:?}");
// So does a stroke that has moved too little to add a point.
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 50.0, 150.0));
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 50.25, 150.0));
assert_eq!(t.cursor(&cx, at, alt), Cursor::BlobBrush);
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 150.0, 150.0).with_mods(alt));
let acts = t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 250.0, 150.0).with_mods(alt));
assert!(matches!(acts.as_slice(), [Action::Exec(c, _)] if c == "path.blob"), "{acts:?}");
// Alt pressed while the pointer stands still at the press point does not paint on the release.
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 50.0, 150.0));
assert_eq!(t.cursor(&cx, at, alt), Cursor::Smooth);
let acts = t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 50.0, 150.0).with_mods(alt));
assert!(acts.is_empty(), "{acts:?}");
// With nothing selected an Alt drag sends nothing.
let none = Selection::default();
let empty = crate::testutil::cx(&d, &none, &p);