mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 15:57:49 +00:00
Smooth: the cursor shows Not Allowed when no path is selected to smooth
The Smooth tool showed its own cursor with nothing selected, though a drag then changes nothing. It now shows Not Allowed until a path it can act on is selected (an editable path, not a guide, with three or more anchors on one of its subpaths); a selected path anywhere on the canvas gives the Smooth cursor wherever the pointer is. - The Pencil with Alt key toggles to Smooth Tool on and the Blob Brush show Not Allowed the same way when Alt gives them the Smooth tool. The Paintbrush keeps its closing cursor, since its release with Alt closes the stroke. - A Smooth release, from the Smooth tool or from the Pencil, Paintbrush or Blob Brush with Alt, sends path.smoothRegion only while such a path is selected. With only type or other objects selected it sends no command. - Use Precise Cursors leaves Not Allowed as it is. - The cursor catalog and docs/target-app-parity.md describe the rule.
This commit is contained in:
@@ -665,7 +665,6 @@ fn use_precise_cursors_swaps_the_tools_own_pointers_for_a_crosshair() {
|
||||
("blobBrush", Cursor::BlobBrush),
|
||||
("shaper", Cursor::Shaper),
|
||||
("pencil", Cursor::PencilNew),
|
||||
("smooth", Cursor::Smooth),
|
||||
("pathEraser", Cursor::PathEraser),
|
||||
("join", Cursor::Join),
|
||||
("eraser", Cursor::Eraser),
|
||||
@@ -710,6 +709,27 @@ fn use_precise_cursors_swaps_the_tools_own_pointers_for_a_crosshair() {
|
||||
}
|
||||
}
|
||||
|
||||
/// With no selected path, the Smooth tool shows Not Allowed, and Use Precise Cursors leaves it so; with a
|
||||
/// selected path, it shows its own pointer, or a crosshair with the preference on.
|
||||
#[test]
|
||||
fn use_precise_cursors_keeps_the_smooth_tools_not_allowed_pointer() {
|
||||
use vectorcraft_tools::{Cursor, Mods};
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 400, "height": 400})).unwrap();
|
||||
let at = vectorcraft_geom::Point::new(300.0, 300.0);
|
||||
s.select_tool("smooth", ViewInfo::default()).unwrap();
|
||||
for precise in [false, true] {
|
||||
s.execute("prefs.set", &json!({"key": "usePreciseCursors", "value": precise})).unwrap();
|
||||
assert_eq!(s.cursor(at, Mods::default(), ViewInfo::default()), Cursor::NotAllowed, "precise {precise}");
|
||||
}
|
||||
let id = s.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 50, "height": 50})).unwrap()["id"].as_u64().unwrap();
|
||||
s.execute("select.set", &json!({"ids": [id]})).unwrap();
|
||||
for (precise, c) in [(false, Cursor::Smooth), (true, Cursor::Crosshair)] {
|
||||
s.execute("prefs.set", &json!({"key": "usePreciseCursors", "value": precise})).unwrap();
|
||||
assert_eq!(s.cursor(at, Mods::default(), ViewInfo::default()), c, "precise {precise}");
|
||||
}
|
||||
}
|
||||
|
||||
/// With Alt held while drawing, the Pencil and the Paintbrush show their closing cursors, and Use Precise Cursors
|
||||
/// turns those into a crosshair too.
|
||||
#[test]
|
||||
|
||||
@@ -453,6 +453,7 @@ group = "Navigation"
|
||||
shown_when = [
|
||||
"The Rotate, Reflect, Scale and Shear tools with nothing selected.",
|
||||
"Puppet Warp off the mesh, where no pin can go.",
|
||||
"The Smooth tool, and the Pencil or Blob Brush where Alt gives the Smooth tool, when no path is selected that the Smooth tool can act on: a drag changes nothing.",
|
||||
]
|
||||
hotspot = "The centre."
|
||||
designer = false
|
||||
@@ -690,10 +691,10 @@ look = "Only a small dot at the centre, as the Blob Brush's: the tip outline dra
|
||||
id = "Smooth"
|
||||
group = "Drawing and editing tools"
|
||||
shown_when = [
|
||||
"The Smooth tool: a drag along a selected path smooths it.",
|
||||
"The Pencil tool with Alt key toggles to Smooth Tool on: with Alt held before a stroke begins to draw, and through a drag that smooths.",
|
||||
"The Paintbrush tool with Alt held before a press while a path is selected that the Smooth tool can act on (an editable path, not a guide, with three or more anchors on one of its subpaths), and through a drag that smooths.",
|
||||
"The Blob Brush tool with Alt held before a press, or before the drag has moved from the press point, and through a drag that smooths.",
|
||||
"The Smooth tool while a path is selected that it can act on (an editable path, not a guide, with three or more anchors on one of its subpaths), wherever the pointer is: a drag along the path smooths it. With no such path selected, Not Allowed shows.",
|
||||
"The Pencil tool with Alt key toggles to Smooth Tool on and such a path selected: with Alt held before a stroke begins to draw, and through a drag that smooths.",
|
||||
"The Paintbrush tool with Alt held before a press while such a path is selected, and through a drag that smooths.",
|
||||
"The Blob Brush tool with such a path selected: with Alt held before a press, or before the drag has moved from the press point, and through a drag that smooths.",
|
||||
]
|
||||
hotspot = "The tip."
|
||||
|
||||
|
||||
@@ -763,7 +763,8 @@ fn has_selected_smooth_path(cx: &ToolContext) -> bool {
|
||||
cx.selection.objects.iter().filter_map(|id| cx.doc.node(*id)).any(|n| {
|
||||
let mut found = false;
|
||||
n.walk(&mut |child| {
|
||||
if matches!(&child.kind, NodeKind::Path { guide: false, path, .. } if path.control_bounds().is_some() && path.subpaths.iter().any(|sp| sp.anchors.len() >= 3))
|
||||
if !found
|
||||
&& matches!(&child.kind, NodeKind::Path { guide: false, path, .. } if path.subpaths.iter().any(|sp| sp.anchors.len() >= 3) && path.control_bounds().is_some())
|
||||
&& editable(child.id)
|
||||
{
|
||||
found = true;
|
||||
@@ -773,6 +774,13 @@ fn has_selected_smooth_path(cx: &ToolContext) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
/// The Smooth tool's cursor: Not Allowed when no selected path can be smoothed, since a drag then
|
||||
/// changes nothing. A selected path anywhere on the canvas is enough, so the cursor does not depend
|
||||
/// on where the pointer is.
|
||||
fn smooth_cursor(cx: &ToolContext) -> Cursor {
|
||||
if has_selected_smooth_path(cx) { Cursor::Smooth } else { Cursor::NotAllowed }
|
||||
}
|
||||
|
||||
impl GestureTool {
|
||||
pub fn new(id: &str) -> Self {
|
||||
let id: &'static str = match id {
|
||||
@@ -1147,7 +1155,7 @@ impl GestureTool {
|
||||
let tol = cx.tol(self.edit_within);
|
||||
let radius = cx.tol(SMOOTH_RADIUS);
|
||||
if self.smoothing {
|
||||
if has_sel && pts.len() >= 2 {
|
||||
if pts.len() >= 2 && has_selected_smooth_path(cx) {
|
||||
return vec![Action::Exec(
|
||||
"path.smoothRegion".into(),
|
||||
json!({"points": points_json(pts), "radius": radius, "fidelity": self.smooth_fidelity}),
|
||||
@@ -1199,7 +1207,7 @@ impl GestureTool {
|
||||
}),
|
||||
)],
|
||||
"knife" if pts.len() >= 2 => vec![Action::Exec("path.knife".into(), json!({"points": points_json(pts)}))],
|
||||
"smooth" if has_sel && pts.len() >= 2 => {
|
||||
"smooth" if pts.len() >= 2 && has_selected_smooth_path(cx) => {
|
||||
vec![Action::Exec("path.smoothRegion".into(), json!({"points": points_json(pts), "radius": radius, "fidelity": self.fidelity}))]
|
||||
}
|
||||
"pathEraser" if has_sel => vec![Action::Exec("path.eraseSegments".into(), json!({"points": points_json(pts), "width": cx.tol(8.0)}))],
|
||||
@@ -1504,13 +1512,15 @@ impl Tool for GestureTool {
|
||||
}
|
||||
fn cursor(&self, cx: &ToolContext, p: Point, m: Mods) -> Cursor {
|
||||
// An active smoothing drag, or Alt before a press that would switch the Pencil, Paintbrush
|
||||
// or Blob Brush to Smooth. A Paintbrush stroke already drawing keeps drawing with Alt.
|
||||
// or Blob Brush to Smooth. A Paintbrush stroke already drawing keeps drawing with Alt. With
|
||||
// no selected path to smooth, the Pencil and the Blob Brush show Not Allowed, as the Smooth
|
||||
// tool does.
|
||||
if self.smoothing
|
||||
|| (self.id == "pencil" && self.alt_smooth && m.alt && !self.began)
|
||||
|| (!self.active && self.id == "paintbrush" && m.alt && has_selected_smooth_path(cx))
|
||||
|| self.blob_alt_smooths(m)
|
||||
{
|
||||
return Cursor::Smooth;
|
||||
return smooth_cursor(cx);
|
||||
}
|
||||
// Before a press that would draw a straight line: over an end of a selected open path,
|
||||
// the line continues that path from the end (`PointerKind::Down`).
|
||||
@@ -1533,7 +1543,7 @@ impl Tool for GestureTool {
|
||||
"blobBrush" => Cursor::BlobBrush,
|
||||
"eraser" => Cursor::Eraser,
|
||||
"knife" => Cursor::Knife,
|
||||
"smooth" => Cursor::Smooth,
|
||||
"smooth" => smooth_cursor(cx),
|
||||
"pathEraser" => Cursor::PathEraser,
|
||||
"join" => Cursor::Join,
|
||||
// Before a press that draws a new path the Pencil shows its star; over a selected path
|
||||
|
||||
@@ -369,7 +369,7 @@ fn the_freehand_tools_want_every_sample() {
|
||||
|
||||
#[test]
|
||||
fn alt_shows_the_closing_cursor_and_a_release_with_alt_closes() {
|
||||
let (d, _) = doc_with_rect();
|
||||
let (d, rect) = doc_with_rect();
|
||||
let s = Selection::default();
|
||||
let p = paint();
|
||||
let cx = cx(&d, &s, &p);
|
||||
@@ -396,18 +396,26 @@ fn alt_shows_the_closing_cursor_and_a_release_with_alt_closes() {
|
||||
}
|
||||
// With Alt key toggles to Smooth Tool on, Alt lends the Pencil the Smooth tool instead, until
|
||||
// a stroke has begun to draw: Alt then neither smooths nor closes, and the cursor stays the Pencil's.
|
||||
// With nothing selected to smooth, the Smooth tool it lends shows Not Allowed.
|
||||
let mut t = GestureTool::new("pencil");
|
||||
t.set_option("altSmooth", &json!(true));
|
||||
assert_eq!(t.cursor(&cx, at, alt), Cursor::Smooth);
|
||||
assert_eq!(t.cursor(&cx, at, alt), Cursor::NotAllowed);
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 0.0, 0.0));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 10.0, 5.0));
|
||||
assert_eq!(t.cursor(&cx, at, alt), Cursor::Pencil, "a stroke that has begun");
|
||||
let acts = t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 20.0, 0.0).with_mods(alt));
|
||||
assert!(matches!(&acts[0], Action::Preview(_, v) if v["closed"] == false), "{acts:?}");
|
||||
// A smoothing drag keeps the Smooth cursor when Alt is let go.
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 0.0, 0.0).with_mods(alt));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 10.0, 5.0).with_mods(alt));
|
||||
assert_eq!(t.cursor(&cx, at, Mods::default()), Cursor::Smooth, "a smoothing drag");
|
||||
// A smoothing drag keeps the lent cursor when Alt is let go: Smooth with a selected path, Not
|
||||
// Allowed without one.
|
||||
let mut selected = Selection::default();
|
||||
selected.set([rect]);
|
||||
for (sel, c) in [(&s, Cursor::NotAllowed), (&selected, Cursor::Smooth)] {
|
||||
let cx = crate::testutil::cx(&d, sel, &p);
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 0.0, 0.0).with_mods(alt));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 10.0, 5.0).with_mods(alt));
|
||||
assert_eq!(t.cursor(&cx, at, Mods::default()), c, "a smoothing drag");
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 20.0, 0.0).with_mods(alt));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2223,6 +2231,117 @@ fn smooth_needs_selection_and_brackets_resize() {
|
||||
assert_eq!(b.options()["size"], 11.0);
|
||||
}
|
||||
|
||||
/// The Smooth tool shows Not Allowed while no selected path can be smoothed: nothing selected, only
|
||||
/// type, a path of two anchors or a locked path. A selected path anywhere, alone or in a selected
|
||||
/// group, gives the Smooth cursor wherever the pointer is, and the cursor follows the selection while
|
||||
/// the tool stays chosen.
|
||||
#[test]
|
||||
fn smooth_shows_not_allowed_without_a_selected_path_to_smooth() {
|
||||
use std::sync::Arc;
|
||||
let (mut d, rect) = doc_with_rect();
|
||||
let layer = d.layers[0].id;
|
||||
let line_id = d.alloc_id();
|
||||
let line = vectorcraft_geom::PathData::from_bezpath(&vectorcraft_geom::BezPath::from_vec(vec![
|
||||
vectorcraft_geom::PathEl::MoveTo(Point::new(300.0, 300.0)),
|
||||
vectorcraft_geom::PathEl::LineTo(Point::new(400.0, 300.0)),
|
||||
]));
|
||||
d.insert(Some(layer), 1, vectorcraft_doc::Node::path(line_id, line, vectorcraft_doc::Appearance::default_art())).unwrap();
|
||||
let square = vectorcraft_geom::shapes::rectangle(vectorcraft_geom::Rect::new(300.0, 350.0, 350.0, 400.0));
|
||||
let inner = vectorcraft_doc::Node::path(d.alloc_id(), square, vectorcraft_doc::Appearance::default_art());
|
||||
let group = vectorcraft_doc::Node::group(d.alloc_id(), vec![Arc::new(inner)]);
|
||||
let group_id = group.id;
|
||||
d.insert(Some(layer), 2, group).unwrap();
|
||||
let (text_doc, text) = doc_with_area_type();
|
||||
let mut locked = d.clone();
|
||||
locked.node_mut(rect).unwrap().locked = true;
|
||||
let p = paint();
|
||||
let sel = |ids: &[NodeId]| {
|
||||
let mut s = Selection::default();
|
||||
s.set(ids.iter().copied());
|
||||
s
|
||||
};
|
||||
let t = GestureTool::new("smooth");
|
||||
let far = Point::new(480.0, 20.0);
|
||||
let cases: [(&vectorcraft_doc::Document, Selection, Cursor, &str); 7] = [
|
||||
(&d, Selection::default(), Cursor::NotAllowed, "nothing selected"),
|
||||
(&text_doc, sel(&[text]), Cursor::NotAllowed, "type"),
|
||||
(&d, sel(&[line_id]), Cursor::NotAllowed, "a two-anchor path"),
|
||||
(&locked, sel(&[rect]), Cursor::NotAllowed, "a locked path"),
|
||||
(&d, sel(&[rect]), Cursor::Smooth, "a path away from the pointer"),
|
||||
(&d, sel(&[line_id, rect]), Cursor::Smooth, "a path beside one it cannot smooth"),
|
||||
(&d, sel(&[group_id]), Cursor::Smooth, "a group holding a path"),
|
||||
];
|
||||
for (doc, s, c, what) in &cases {
|
||||
assert_eq!(t.cursor(&cx(doc, s, &p), far, Mods::default()), *c, "{what}");
|
||||
}
|
||||
// The cursor changes as the selection does, with the same tool.
|
||||
let (none, one) = (Selection::default(), sel(&[rect]));
|
||||
for (s, c) in [(&none, Cursor::NotAllowed), (&one, Cursor::Smooth), (&none, Cursor::NotAllowed)] {
|
||||
assert_eq!(t.cursor(&cx(&d, s, &p), Point::new(150.0, 150.0), Mods::default()), c);
|
||||
}
|
||||
// A drag with Not Allowed showing sends nothing.
|
||||
let mut t = GestureTool::new("smooth");
|
||||
let acts = drag(&mut t, &cx(&d, &none, &p), &[(50.0, 150.0), (150.0, 150.0), (250.0, 150.0)], Mods::default());
|
||||
assert!(acts.iter().all(|a| a.is_empty()), "{acts:?}");
|
||||
}
|
||||
|
||||
/// The Blob Brush's Alt shows Not Allowed with no selected path to smooth, and the Smooth cursor with
|
||||
/// one; the Paintbrush's Alt without one keeps the closing cursor, since its release closes the stroke.
|
||||
#[test]
|
||||
fn borrowed_smooth_shows_not_allowed_without_a_selected_path_to_smooth() {
|
||||
let (d, rect) = doc_with_rect();
|
||||
let none = Selection::default();
|
||||
let mut one = Selection::default();
|
||||
one.set([rect]);
|
||||
let p = paint();
|
||||
let alt = Mods { alt: true, ..Default::default() };
|
||||
let at = Point::new(400.0, 400.0);
|
||||
let blob = GestureTool::new("blobBrush");
|
||||
assert_eq!(blob.cursor(&cx(&d, &none, &p), at, alt), Cursor::NotAllowed);
|
||||
assert_eq!(blob.cursor(&cx(&d, &one, &p), at, alt), Cursor::Smooth);
|
||||
assert_eq!(blob.cursor(&cx(&d, &none, &p), at, Mods::default()), Cursor::BlobBrush);
|
||||
let mut pencil = GestureTool::new("pencil");
|
||||
pencil.set_option("altSmooth", &json!(true));
|
||||
assert_eq!(pencil.cursor(&cx(&d, &none, &p), at, alt), Cursor::NotAllowed);
|
||||
assert_eq!(pencil.cursor(&cx(&d, &one, &p), at, alt), Cursor::Smooth);
|
||||
let brush = GestureTool::new("paintbrush");
|
||||
assert_eq!(brush.cursor(&cx(&d, &none, &p), at, alt), Cursor::PaintbrushClose);
|
||||
assert_eq!(brush.cursor(&cx(&d, &one, &p), at, alt), Cursor::Smooth);
|
||||
}
|
||||
|
||||
/// A Smooth release sends no command while the selection holds no path it can smooth, from the
|
||||
/// Smooth tool and from the Blob Brush and Pencil with Alt; with a path selected it smooths.
|
||||
#[test]
|
||||
fn smoothing_release_sends_nothing_without_a_selected_path_to_smooth() {
|
||||
let (text_doc, text) = doc_with_area_type();
|
||||
let (d, rect) = doc_with_rect();
|
||||
let mut type_only = Selection::default();
|
||||
type_only.set([text]);
|
||||
let mut one = Selection::default();
|
||||
one.set([rect]);
|
||||
let p = paint();
|
||||
let alt = Mods { alt: true, ..Default::default() };
|
||||
let stroke = [(50.0, 150.0), (150.0, 150.0), (250.0, 160.0)];
|
||||
let smooths = |acts: &[Vec<Action>]| acts.iter().flatten().any(|a| matches!(a, Action::Exec(c, _) if c == "path.smoothRegion"));
|
||||
for (id, mods) in [("smooth", Mods::default()), ("blobBrush", alt), ("pencil", alt)] {
|
||||
let tool = || {
|
||||
let mut t = GestureTool::new(id);
|
||||
if id == "pencil" {
|
||||
t.set_option("altSmooth", &json!(true));
|
||||
}
|
||||
t
|
||||
};
|
||||
let mut c = cx(&text_doc, &type_only, &p);
|
||||
c.smart_guides = false;
|
||||
let acts = drag_held(&mut tool(), &c, &stroke, mods);
|
||||
assert!(!smooths(&acts), "{id} with only type selected: {acts:?}");
|
||||
let mut c = cx(&d, &one, &p);
|
||||
c.smart_guides = false;
|
||||
let acts = drag_held(&mut tool(), &c, &stroke, mods);
|
||||
assert!(smooths(&acts), "{id} with a path selected: {acts:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pencil_alt_toggles_to_smooth_when_enabled_and_closes_when_disabled() {
|
||||
let (d, id) = doc_with_rect();
|
||||
|
||||
+10
-2
@@ -619,8 +619,16 @@ mod cursor_tests {
|
||||
/// Use Precise Cursors turns it into the crosshair.
|
||||
#[test]
|
||||
fn drawing_tools_show_their_own_cursor() {
|
||||
let (d, _) = doc_with_rect();
|
||||
let (d, rect) = doc_with_rect();
|
||||
let away = Point::new(400.0, 400.0);
|
||||
// The Smooth tool shows its own cursor only with a selected path (Not Allowed without one).
|
||||
let mut selected = Selection::default();
|
||||
selected.set([rect]);
|
||||
let paint = paint();
|
||||
let cursor_of = |id: &str| match id {
|
||||
"smooth" => create(id).cursor(&cx(&d, &selected, &paint), away, Mods::default()),
|
||||
_ => cursor_at(id, &d, away),
|
||||
};
|
||||
for (id, c) in [
|
||||
("lasso", Cursor::Lasso),
|
||||
(addons::enclosing_lasso::ID, Cursor::Lasso),
|
||||
@@ -646,7 +654,7 @@ mod cursor_tests {
|
||||
("symbolStyler", Cursor::SymbolStyler),
|
||||
("mesh", Cursor::MeshAdd),
|
||||
] {
|
||||
assert_eq!(cursor_at(id, &d, away), c, "{id}");
|
||||
assert_eq!(cursor_of(id), c, "{id}");
|
||||
assert_eq!(c.precise(), Cursor::Crosshair, "{id}");
|
||||
// The Blob Brush's and the Eraser's dot is drawn: their tip outline goes round it. The
|
||||
// Live Paint Bucket's bucket is drawn.
|
||||
|
||||
@@ -1041,7 +1041,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- **Drawing:** Pen, Curvature, anchor tools, Pencil, Paintbrush, Blob Brush, Smooth, Path Eraser, Join. Direction handles dragged with Direct Selection or the Anchor Point tool snap to smart guides, and Shift keeps them at 45° steps from their anchor. The Pen continues any open path from the end clicked, and while drawing, a click on an end of another open path joins the two into one (#776). The Curvature tool edits any selected path, whatever drew it, keeping its shape except where edited (#798): a click on a point makes it current and a drag moves it, re-curving only its two segments; double-click, or Alt-click on any point but an end of an open path, toggles it smooth or corner and Delete removes it; a click on a segment adds a point without changing the shape; with an end of an open path current, clicks go on from it and its other end closes it (`path.curvatureEdit`). An Alt-click on an end of a selected open path makes that end current without changing it, so the next click goes on from it, as [the help page](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/draw-shapes-with-the-curvature-tool.html) describes for continuing a path; an Alt-click that starts a new path places a corner point, as later Alt-clicks do. Every point a drawing tool places snaps to Smart Guides through one snapper (#506): the Pen's and Curvature tool's anchors, the Pencil's ends, the shape tools' start and dragged corner or radius (Rectangle, Rounded Rectangle, Ellipse, Polygon, Star, Line Segment, Arc, Spiral, the grids), the Type tool's click and frame, a new artboard's corners. A filled Pencil path keeps a stroke of None (#1804, part of #1679). While a Pencil or Paintbrush stroke is drawn back to within Close Paths When Ends Are Within of its start, the cursor shows the closing ring and a release there closes the path, as [Draw straight lines with the Pencil tool](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/draw-straight-lines-with-the-pencil-tool.html) describes for the Pencil. The [ and ] keys reach the Blob Brush, the Eraser and the Symbolism tools in the app (#1734, part of #1661).
|
||||
Blob Brush and Eraser Tool Options' Size is Fixed or Pressure with a Variation (#4590, #3961): with Pressure a pen stroke is Size − Variation wide where it presses lightest and Size + Variation where it presses hardest, and a mouse stroke keeps the Size.
|
||||
With the Blob Brush, Shift held during a drag keeps the stroke on the horizontal or vertical line through the press point, whichever is nearer to the pointer (90° steps from the Constrain Angle preference), and the points dragged since Shift went down move to the other line when the pointer crosses to it; the points dragged while Shift is up follow the pointer.
|
||||
With the Blob Brush, Alt held before the press, or pressed before the drag has moved, switches to the Smooth tool, as the reference app's [Default keyboard shortcuts](https://web.archive.org/web/20260726060015/https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html) page lists: the cursor shows the Smooth tool, the drag shows a thin line and, with a path selected that it can smooth, the dashed band it smooths, without the length label, Shift does not constrain it, and the release smooths the selected paths under the drag (`path.smoothRegion`) instead of painting; with nothing selected the release changes no path. A stroke that has moved keeps painting when Alt goes down.
|
||||
With the Blob Brush, Alt held before the press, or pressed before the drag has moved, switches to the Smooth tool, as the reference app's [Default keyboard shortcuts](https://web.archive.org/web/20260726060015/https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html) page lists: the cursor shows the Smooth tool (Not Allowed with no path selected that it can act on), the drag shows a thin line and, with a path selected that it can smooth, the dashed band it smooths, without the length label, Shift does not constrain it, and the release smooths the selected paths under the drag (`path.smoothRegion`) instead of painting; with nothing selected the release changes no path. A stroke that has moved keeps painting when Alt goes down.
|
||||
Curvature point markers are circular, with no direction handles. While drawing, its own point feedback replaces generic selection markers; existing paths use the selection overlay, including partial selections and Hide Edges. Anchor Size applies, and Direct Selection and Pen retain their handles. The real egui-frame regressions in `crates/ui-egui/src/canvas.rs` verify marker positions, counts and unchanged documents (#1236, in part); the existing-path interpolation request remains open. The curve through the points (`curvature_spline` in `crates/tools/src/draw2.rs`) makes each stretch of three points follow the circle through them: a smooth point's handles lie along that circle's tangent, each `chord / (3 cos²(α/2))` long (α between handle and chord, capped at 90°), and the ends of an open path mirror their neighbour's tangent in their chord, so a J or a U curves evenly, a turn tighter than 30° comes to a point, and uneven spacing doesn't loop; the rubber band, a dragged or inserted point and `path.convertAnchor` to smooth use the same rule, while the Pencil's fitting keeps its own; `tests/draw2.rs` checks that the rubber band is the path the click makes (#1236, in part). The Smooth tool smooths only the stretch of path under the drag (#1547, fixes #1512). A path the Smooth tool changes stays selected whole, and the anchor points and segments selected on it with Direct Selection are deselected. Selected paths the drag does not change keep their selected anchors, and undo restores the earlier selection (`tests/draw2.rs`, `smoothing_a_path_drops_its_direct_selected_anchors`). While the Smooth tool drags with a path selected that it can smooth, a dashed outline shows the band it smooths: everything within its radius of the drag (12 screen pixels, VectorCraft's own value), round at both ends; Alt smoothing with the Pencil, Paintbrush and Blob Brush shows the same band.
|
||||
While Alt is held during a Paintbrush stroke or before a press with no path selected that the Smooth tool can act on, the Paintbrush shows a closing cursor (a crosshair with a ring), and so does the Pencil with Alt key toggles to Smooth Tool off when Alt is pressed after the drag starts; releasing the mouse button with Alt still held closes the path. Before a Paintbrush press with such a path selected, Alt shows the Smooth cursor, and holding Alt at the press smooths that path with the Smooth tool's radius and Smooth Tool Options' Fidelity. A Pencil drag started with Shift or Alt held draws a straight line instead, which stays open.
|
||||
With Shift held at the press, the Pencil draws a straight line from the press point to the pointer at 45° steps from the Constrain Angle, as [Draw straight lines with the Pencil tool](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/draw-straight-lines-with-the-pencil-tool.html) describes; pressed on an end of a selected open path, the line continues the path from that end (part of #3799). With Option/Alt held at the press and Alt key toggles to Smooth Tool off, the Pencil draws a straight line at any angle, whose ends snap to Smart Guides, and it stays open when Alt is still held at the release; Shift and Alt together keep the 45° steps (#3799). While Shift or Alt is held before the press, the Pencil shows a straight-line cursor (a crosshair with a short line), and over an end of a selected open path, where the line continues the path, a path-continuation cursor (the same line leaving a small square), as the page describes.
|
||||
@@ -1118,6 +1118,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
The Pencil's and the Paintbrush's Fidelity is in screen pixels, the same at any zoom: their Tool Options give it in px, from 0.01 px on the logarithmic slider to 20 px and up to 100 px in the field, and the tool sends `path.freehand` the value in points at the current zoom (#5729). The Smooth tool's and the Blob Brush's Fidelity stays in points.
|
||||
`path.freehand` and `brush.freehand` take a Fidelity of up to 100 / 0.0313 pt (about 3194.89 pt), the 100 px field maximum at the lowest zoom (3.13 %), and reject a value outside their range with an error, so a Pencil or Paintbrush stroke drawn zoomed out keeps the Fidelity set in pixels (engine tests `pencil_fit_keeps_its_anchors_on_a_stroke_drawn_zoomed_out` and `pencil_takes_fidelity_within_its_range_and_rejects_the_rest`).
|
||||
Each drag of the Smooth tool smooths the stretch of a selected path in its reach along its length before refitting it within Fidelity, so a wobble wider than Fidelity is smoothed out and another drag over the same place smooths it further, as [the help page](https://helpx.adobe.com/illustrator/using/tool-techniques/smooth-tool.html) describes (`path.smoothRegion`). The smoothing spread (a third of the reach) and the rule that each refit stays within twice the distance the smoothing moved the path, so repeated drags do not drift from it, are VectorCraft's own. A drag over the whole of a closed path smooths it across its first anchor too, so a bump there is smoothed like any other and no corner is left; Object › Path › Smooth smooths a closed path across its first anchor the same way.
|
||||
The Smooth tool shows the Not Allowed cursor while no path is selected that it can act on (an editable path, not a guide, with three or more anchors on one of its subpaths), and the release of a drag then sends no command and changes nothing; [the help page](https://helpx.adobe.com/illustrator/using/tool-techniques/smooth-tool.html) has the path selected first. A selected path anywhere on the canvas gives the Smooth cursor wherever the pointer is. The Pencil with Alt key toggles to Smooth Tool on and the Blob Brush show Not Allowed the same way when Alt gives them the Smooth tool; the Paintbrush keeps its closing cursor, since its release with Alt closes the stroke. Use Precise Cursors leaves Not Allowed as it is. This cursor rule is VectorCraft's own (tools `smooth_shows_not_allowed_without_a_selected_path_to_smooth`, `borrowed_smooth_shows_not_allowed_without_a_selected_path_to_smooth`, `smoothing_release_sends_nothing_without_a_selected_path_to_smooth`; engine `use_precise_cursors_keeps_the_smooth_tools_not_allowed_pointer`).
|
||||
Before a press that draws a new path, the Pencil cursor shows a small star, as [Draw freeform paths with the Pencil tool](https://web.archive.org/web/20260926095638/https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/draw-freeform-paths-with-the-pencil-tool.html) describes; over a selected path within Edit selected paths' distance, which the press continues from an end or redraws, the star is not shown ([Reshape paths with the Pencil tool](https://web.archive.org/web/20260106004407/https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/reshape-paths-with-the-pencil-tool.html)).
|
||||
Keep selected in the Pencil and Paintbrush Tool Options is on by default: while it is on, a stroke selects the path it draws or continues and replaces the earlier selection. While it is off, a new stroke leaves nothing selected, the earlier selection included, as the reference application starts the stroke by dropping it (#5752); a continued or redrawn path is deselected while the rest of the selection stays (`path.freehand` `keepSelected`).
|
||||
With two open paths selected, a Pencil stroke from an end of one that ends within Edit selected paths' distance of an end of the other connects them into one path with the first path's appearance, in one undo step, as [Connect two paths with the Pencil tool](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/connect-two-paths-with-the-pencil-tool.html) describes (`path.freehand` `connect`).
|
||||
|
||||
Reference in New Issue
Block a user