mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-26 10:47:11 -06:00
Merge pull request #4182 from dotCooCoo/feat/pencil-live-curve-fitting
Pencil: Live curve fitting in Pencil Tool Options shows the stroke's points until the release fits them
This commit is contained in:
@@ -26,7 +26,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Pencil",
|
||||
[],
|
||||
None,
|
||||
"{points: [[x,y]…] or [[x,y,pressure]…] (pen pressure 0..1: the stroke keeps it, for Calligraphic brushes with Pressure variation; a point without one presses fully), fidelity?: pt (1.5), closed?, style?: \"pencil\"|\"brush\", fill?: bool, keepSelected?: bool (true: the drawn or continued path is selected; false: it is not, and the rest of the selection stays), extend?: {id, end: \"start\"|\"end\"}, redraw?: {id, within?: pt (12)}} fit a freehand stroke → {id}. extend continues the path from that end; redraw (Edit Selected Paths) replaces the section of the path between where the stroke starts and ends, each within `within` of one subpath (the shorter way round a closed one), and draws a new path when the stroke doesn't end on it",
|
||||
"{points: [[x,y]…] or [[x,y,pressure]…] (pen pressure 0..1: the stroke keeps it, for Calligraphic brushes with Pressure variation; a point without one presses fully), fidelity?: pt (1.5), fit?: bool (true; false: the points are joined by straight segments, as the Pencil previews a stroke with Live curve fitting off), closed?, style?: \"pencil\"|\"brush\", fill?: bool, keepSelected?: bool (true: the drawn or continued path is selected; false: it is not, and the rest of the selection stays), extend?: {id, end: \"start\"|\"end\"}, redraw?: {id, within?: pt (12)}} fit a freehand stroke → {id}. extend continues the path from that end; redraw (Edit Selected Paths) replaces the section of the path between where the stroke starts and ends, each within `within` of one subpath (the shorter way round a closed one), and draws a new path when the stroke doesn't end on it",
|
||||
has_doc,
|
||||
freehand
|
||||
),
|
||||
@@ -575,11 +575,21 @@ fn fit_freehand(pts: &[Point], tol: f64, closed: bool) -> SubPath {
|
||||
fitted.subpaths.into_iter().next().filter(|s| s.anchors.len() >= 2).unwrap_or(sp)
|
||||
}
|
||||
|
||||
/// The path of a freehand stroke: fitted at `tol` ([`fit_freehand`]), or with no `tol` the points
|
||||
/// joined by straight segments (`path.freehand` with `fit: false`).
|
||||
fn stroke_subpath(pts: &[Point], tol: Option<f64>, closed: bool) -> SubPath {
|
||||
match tol {
|
||||
Some(tol) => fit_freehand(pts, tol, closed),
|
||||
None => SubPath::new(pts.iter().map(|&p| Anchor::corner(p)).collect(), closed),
|
||||
}
|
||||
}
|
||||
|
||||
/// Pencil's Edit Selected Paths in the middle of a path: a stroke from one point of a subpath of
|
||||
/// `pd` to another, each within `within` of it, replaces the section between them (the shorter
|
||||
/// way round a closed subpath) with the stroke fitted at `tol`. The subpath's index and its new
|
||||
/// shape; None when the stroke doesn't start and end on the same subpath, or meets it once.
|
||||
fn redraw_section(pd: &PathData, pts: &[Point], tol: f64, within: f64) -> Option<(usize, SubPath)> {
|
||||
/// way round a closed subpath) with the stroke fitted at `tol` (with no `tol`, unfitted). The
|
||||
/// subpath's index and its new shape; None when the stroke doesn't start and end on the same
|
||||
/// subpath, or meets it once.
|
||||
fn redraw_section(pd: &PathData, pts: &[Point], tol: Option<f64>, within: f64) -> Option<(usize, SubPath)> {
|
||||
use kurbo::ParamCurveArclen;
|
||||
let (first, last) = (*pts.first()?, *pts.last()?);
|
||||
let (si, _, _, _, d_a) = pd.nearest(first)?;
|
||||
@@ -633,7 +643,7 @@ fn redraw_section(pd: &PathData, pts: &[Point], tol: f64, within: f64) -> Option
|
||||
*s0 = s1.p;
|
||||
let (Some(e0), Some(e1)) = (stroke.last_mut(), sp.anchors.get(end)) else { return None };
|
||||
*e0 = e1.p;
|
||||
let fit = fit_freehand(&stroke, tol, false).anchors;
|
||||
let fit = stroke_subpath(&stroke, tol, false).anchors;
|
||||
let (Some(f0), Some(f1)) = (fit.first().copied(), fit.last().copied()) else { return None };
|
||||
let inner = fit.get(1..fit.len().saturating_sub(1)).unwrap_or_default();
|
||||
let (before, after): (Vec<Anchor>, Vec<Anchor>) = if sp.closed {
|
||||
@@ -665,6 +675,8 @@ fn freehand(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let pressure = stroke_pressure(&samples);
|
||||
let mut pts: Vec<Point> = samples.into_iter().map(|(pt, _)| pt).collect();
|
||||
let tol = number_or(p, "fidelity", 1.5, C)?.clamp(0.05, 100.0);
|
||||
// The tolerance the stroke is fitted at; with `fit: false` it is not fitted.
|
||||
let fit_tol = switch_or(p, "fit", true, C)?.then_some(tol);
|
||||
let closed = switch_or(p, "closed", false, C)?;
|
||||
let brush = str_param(p, "style") == Some("brush");
|
||||
let keep_selected = switch_or(p, "keepSelected", true, C)?;
|
||||
@@ -692,7 +704,7 @@ fn freehand(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if closing && let Some(l) = pts.last_mut() {
|
||||
*l = first;
|
||||
}
|
||||
let fit = fit_freehand(&pts, tol, false);
|
||||
let fit = stroke_subpath(&pts, fit_tol, false);
|
||||
let mut add = fit.anchors;
|
||||
if let (Some(last), Some(a0)) = (sp.anchors.last_mut(), add.first()) {
|
||||
last.h_out = a0.h_out;
|
||||
@@ -748,7 +760,7 @@ fn freehand(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let (pd, _) = path_of(&s.doc()?.doc, id)?;
|
||||
// shortcut: a stroke pressure profile is left as it was, measured along the old path; fit
|
||||
// it to the new length if Calligraphic pressure strokes get redrawn.
|
||||
if let Some((si, sp)) = redraw_section(&pd, &pts, tol, within) {
|
||||
if let Some((si, sp)) = redraw_section(&pd, &pts, fit_tol, within) {
|
||||
s.edit(if brush { "Paintbrush" } else { "Pencil" }, |d, sel| {
|
||||
*path_mut(d, id)?.subpaths.get_mut(si).ok_or_else(|| bad(C, "no such subpath"))? = sp;
|
||||
if keep_selected {
|
||||
@@ -764,7 +776,7 @@ fn freehand(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if pts.len() < 2 {
|
||||
return Err(bad(C, "need at least two points"));
|
||||
}
|
||||
let sp = fit_freehand(&pts, tol, closed);
|
||||
let sp = stroke_subpath(&pts, fit_tol, closed);
|
||||
let fill = if filled { s.paint.fill.clone() } else { Paint::None };
|
||||
// A pencil path with a fill keeps a stroke of None (#1679); only one that would show nothing
|
||||
// gets black. A brush is its stroke.
|
||||
|
||||
@@ -267,6 +267,42 @@ fn pencil_with_keep_selected_off_leaves_the_new_path_unselected() {
|
||||
assert!(selected(&s).is_empty());
|
||||
}
|
||||
|
||||
/// `fit: false` (the Pencil's preview with Live curve fitting off) joins the stroke's points with
|
||||
/// straight segments: a new path, a continued one and a redrawn section; by default the stroke is
|
||||
/// fitted.
|
||||
#[test]
|
||||
fn freehand_with_fit_off_joins_the_points_with_straight_segments() {
|
||||
let corners = |sp: &vectorcraft_geom::SubPath| sp.anchors.iter().all(|a| a.h_in == a.p && a.h_out == a.p);
|
||||
let at = |sp: &vectorcraft_geom::SubPath| sp.anchors.iter().map(|a| (a.p.x, a.p.y)).collect::<Vec<_>>();
|
||||
let pts = wave(100.0, 400.0, 100.0);
|
||||
let mut s = session();
|
||||
let fitted = s.execute("path.freehand", &json!({"points": pts.iter().map(|p| [p.0, p.1]).collect::<Vec<_>>()})).unwrap();
|
||||
let fitted = path(&s, NodeId(fitted["id"].as_u64().unwrap()));
|
||||
assert!(!corners(&fitted.subpaths[0]) && fitted.subpaths[0].anchors.len() < pts.len(), "fitted by default");
|
||||
let unfitted = s.execute("path.freehand", &json!({"points": pts.iter().map(|p| [p.0, p.1]).collect::<Vec<_>>(), "fit": false})).unwrap();
|
||||
let sp = path(&s, NodeId(unfitted["id"].as_u64().unwrap())).subpaths[0].clone();
|
||||
assert!(corners(&sp) && at(&sp) == pts, "{:?}", at(&sp));
|
||||
// Continued from the end of a selected open path: the line's anchors, then the stroke's points.
|
||||
let l = line(&mut s, 0.0, 300.0, 100.0, 300.0);
|
||||
s.execute("select.set", &json!({"ids": [l.0]})).unwrap();
|
||||
s.execute("path.freehand", &json!({"points": [[100, 300], [150, 330], [200, 290], [250, 320]], "extend": {"id": l.0}, "fit": false})).unwrap();
|
||||
let sp = path(&s, l).subpaths[0].clone();
|
||||
assert!(corners(&sp) && at(&sp) == [(0.0, 300.0), (100.0, 300.0), (150.0, 330.0), (200.0, 290.0), (250.0, 320.0)], "{:?}", at(&sp));
|
||||
// A redrawn section: the stroke's inner points between where it meets the line.
|
||||
let r = line(&mut s, 0.0, 500.0, 300.0, 500.0);
|
||||
s.execute("select.set", &json!({"ids": [r.0]})).unwrap();
|
||||
s.execute(
|
||||
"path.freehand",
|
||||
&json!({"points": [[100, 501], [125, 470], [150, 460], [175, 470], [200, 501]], "redraw": {"id": r.0, "within": 4}, "fit": false}),
|
||||
)
|
||||
.unwrap();
|
||||
let sp = path(&s, r).subpaths[0].clone();
|
||||
assert!(corners(&sp), "{:?}", sp.anchors);
|
||||
let inner: Vec<_> = at(&sp).into_iter().filter(|p| p.1 < 499.0).collect();
|
||||
assert_eq!(inner, [(125.0, 470.0), (150.0, 460.0), (175.0, 470.0)]);
|
||||
assert!(s.execute("path.freehand", &json!({"points": [[0, 0], [1, 1]], "fit": "no"})).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paintbrush_uses_stroke_blob_brush_fills() {
|
||||
let mut s = session();
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
//! Knife, Smooth, Path Eraser and Join.
|
||||
//!
|
||||
//! Pencil and Paintbrush preview the fitted path live (`path.freehand` re-applied on the snapshot)
|
||||
//! and commit on release; Alt on release (or ending near the start) closes the path, and starting
|
||||
//! and commit on release; with Live curve fitting off, the Pencil's preview joins the stroke's
|
||||
//! points with straight segments and the release fits them; Alt on release (or ending near the start) closes the path, and starting
|
||||
//! near an end of a selected open path continues it. Shift held at the press draws a straight
|
||||
//! Pencil line at 45° steps from the Constrain Angle instead, and Alt held at the press (with Alt
|
||||
//! key toggles to Smooth Tool off) draws a straight line at any angle. The Pencil's new path starts and ends on
|
||||
@@ -50,6 +51,10 @@ pub struct GestureTool {
|
||||
/// "Alt key toggles to Smooth Tool": holding Alt with the Pencil tool smooths selected paths
|
||||
/// instead of closing the path (Pencil only, off by default).
|
||||
pub alt_smooth: bool,
|
||||
/// "Live curve fitting": the Pencil's preview fits the stroke while it is drawn; off, the
|
||||
/// preview shows the points joined by straight segments and the release fits them (Pencil
|
||||
/// only, on by default).
|
||||
pub live_fit: bool,
|
||||
/// Active drag is smoothing with the Pencil tool.
|
||||
smoothing: bool,
|
||||
/// Active Pencil drag draws a straight line (Shift or Alt held at the press): the stroke is
|
||||
@@ -105,6 +110,7 @@ impl GestureTool {
|
||||
keep_selected: id != "blobBrush",
|
||||
// Off, as Pencil Tool Options ships it: Alt keeps drawing a closed path until it is on.
|
||||
alt_smooth: false,
|
||||
live_fit: true,
|
||||
smoothing: false,
|
||||
straight: false,
|
||||
unconstrained: false,
|
||||
@@ -417,7 +423,12 @@ impl Tool for GestureTool {
|
||||
}
|
||||
if crate::previews_at(self.points.len()) {
|
||||
self.previewed = self.points.len();
|
||||
out.push(Action::Preview("path.freehand".into(), self.freehand_params(false)));
|
||||
let mut v = self.freehand_params(false);
|
||||
if !self.live_fit {
|
||||
// Live curve fitting off: the release fits the stroke (`finish`).
|
||||
v["fit"] = json!(false);
|
||||
}
|
||||
out.push(Action::Preview("path.freehand".into(), v));
|
||||
}
|
||||
out
|
||||
}
|
||||
@@ -492,8 +503,9 @@ impl Tool for GestureTool {
|
||||
}
|
||||
fn deactivate(&mut self, _cx: &ToolContext) -> Vec<Action> {
|
||||
let began = self.began;
|
||||
// Drags past the last preview are in the stroke kept, too.
|
||||
let stale = (began && self.previewed != self.points.len() && self.draws_path() && !self.smoothing)
|
||||
// Drags past the last preview are in the stroke kept, too, and with Live curve fitting
|
||||
// off the stroke kept is fitted.
|
||||
let stale = (began && (self.previewed != self.points.len() || !self.live_fit) && self.draws_path() && !self.smoothing)
|
||||
.then(|| Action::Preview("path.freehand".into(), self.freehand_params(false)));
|
||||
self.snap.clear();
|
||||
self.active = false;
|
||||
@@ -557,6 +569,7 @@ impl Tool for GestureTool {
|
||||
"fidelity": self.fidelity,
|
||||
"fill": self.fill,
|
||||
"altSmooth": self.alt_smooth,
|
||||
"liveFit": self.live_fit,
|
||||
"keepSelected": self.keep_selected,
|
||||
"editWithin": self.edit_within,
|
||||
"closeWithin": self.close_within
|
||||
@@ -583,6 +596,7 @@ impl Tool for GestureTool {
|
||||
"size" => self.size = v.as_f64().unwrap_or(self.size).clamp(0.1, 1000.0),
|
||||
"fill" => self.fill = v.as_bool().unwrap_or(self.fill),
|
||||
"altSmooth" => self.alt_smooth = v.as_bool().unwrap_or(self.alt_smooth),
|
||||
"liveFit" if self.id == "pencil" => self.live_fit = v.as_bool().unwrap_or(self.live_fit),
|
||||
"keepSelected" => self.keep_selected = v.as_bool().unwrap_or(self.keep_selected),
|
||||
"mergeOnlySelection" => self.merge_only_selection = v.as_bool().unwrap_or(self.merge_only_selection),
|
||||
"editWithin" => self.edit_within = v.as_f64().unwrap_or(self.edit_within).clamp(0.0, 100.0),
|
||||
@@ -677,6 +691,47 @@ mod tests {
|
||||
assert!(!t.busy());
|
||||
}
|
||||
|
||||
/// Live curve fitting is the Pencil's, on by default. Off, the previews while a stroke is drawn
|
||||
/// ask for the points unfitted (`fit: false`), and the release's preview is fitted as before.
|
||||
#[test]
|
||||
fn pencil_with_live_curve_fitting_off_previews_the_stroke_unfitted() {
|
||||
let (d, _) = doc_with_rect();
|
||||
let s = Selection::default();
|
||||
let p = paint();
|
||||
let cx = cx(&d, &s, &p);
|
||||
let stroke = [(0.0, 0.0), (10.0, 5.0), (20.0, 0.0), (30.0, 6.0), (40.0, 0.0)];
|
||||
// The `fit` each preview sends: those while drawn, then the release's.
|
||||
let fits = |t: &mut GestureTool| {
|
||||
let acts = drag(t, &cx, &stroke, Mods::default());
|
||||
let fit = |a: &Action| if let Action::Preview(_, v) = a { Some(v.get("fit").cloned()) } else { None };
|
||||
let (drawn, release) = acts.split_at(acts.len() - 1);
|
||||
(drawn.iter().flatten().filter_map(fit).collect::<Vec<_>>(), release[0].iter().filter_map(fit).collect::<Vec<_>>())
|
||||
};
|
||||
let mut t = GestureTool::new("pencil");
|
||||
assert_eq!(t.options()["liveFit"], json!(true));
|
||||
let (drawn, release) = fits(&mut t);
|
||||
assert!(!drawn.is_empty() && drawn.iter().all(Option::is_none), "{drawn:?}");
|
||||
assert_eq!(release, [None]);
|
||||
t.set_option("liveFit", &json!(false));
|
||||
assert_eq!(t.options()["liveFit"], json!(false));
|
||||
let (drawn, release) = fits(&mut t);
|
||||
assert!(!drawn.is_empty() && drawn.iter().all(|f| *f == Some(json!(false))), "{drawn:?}");
|
||||
assert_eq!(release, [None], "the release fits the stroke");
|
||||
// Choosing another tool while the stroke is drawn keeps it fitted, too.
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 0.0, 0.0));
|
||||
for &(x, y) in &stroke[1..] {
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, x, y));
|
||||
}
|
||||
let acts = t.deactivate(&cx);
|
||||
assert!(matches!(acts.as_slice(), [Action::Preview(_, v), Action::Commit] if v.get("fit").is_none()), "{acts:?}");
|
||||
// The Paintbrush has no such option, and its previews are fitted.
|
||||
let mut t = GestureTool::new("paintbrush");
|
||||
t.set_option("liveFit", &json!(false));
|
||||
assert!(t.options().get("liveFit").is_none());
|
||||
let (drawn, _) = fits(&mut t);
|
||||
assert!(!drawn.is_empty() && drawn.iter().all(Option::is_none), "{drawn:?}");
|
||||
}
|
||||
|
||||
/// Keep selected is on by default, and every Pencil and Paintbrush preview sends it.
|
||||
#[test]
|
||||
fn pencil_and_paintbrush_send_keep_selected() {
|
||||
|
||||
@@ -35,8 +35,9 @@ const ROWS: &[Row] = &[
|
||||
// Line Segment Tool Options: the Length and Angle a click's dialog opens with, Fill Line.
|
||||
(&["lineSegment"], None, &["length", "angle", "fillLine"]),
|
||||
(&["pencil", "paintbrush"], None, &["fidelity", "fill", "keepSelected", "editWithin", "closeWithin"]),
|
||||
// Pencil Tool Options' Alt key toggles to Smooth Tool (the Paintbrush has no such option).
|
||||
(&["pencil"], None, &["altSmooth"]),
|
||||
// Pencil Tool Options' Alt key toggles to Smooth Tool and Live curve fitting (the Paintbrush
|
||||
// has neither option).
|
||||
(&["pencil"], None, &["altSmooth", "liveFit"]),
|
||||
(&["smooth"], None, &["fidelity"]),
|
||||
(&["blobBrush", "eraser"], None, &["size"]),
|
||||
(&["blobBrush"], None, &["keepSelected", "mergeOnlySelection"]),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Tool Options of the freehand tools (double-click Pencil, Paintbrush, Smooth, Blob Brush or
|
||||
//! Eraser): the options each tool keeps, which last across tool switches and are saved with the
|
||||
//! preferences. OK sets them with `tool.setOption {tool, values}`.
|
||||
//! preferences. OK sets them with `tool.setOption {tool, values}`. The Pencil also has `liveFit`
|
||||
//! (Live curve fitting, on: its preview fits the stroke while it is drawn).
|
||||
//!
|
||||
//! Fields: `tool`; `fidelity` (points; Pencil, Paintbrush and Smooth), `fill` (Pencil and
|
||||
//! Paintbrush), `keepSelected` (Pencil, Paintbrush and Blob Brush), `mergeOnlySelection` (Blob
|
||||
@@ -24,7 +25,7 @@ pub(super) const SPEC: DialogSpec = DialogSpec { heading, body, confirm, min_wid
|
||||
pub const TOOLS: [&str; 5] = ["pencil", "paintbrush", "smooth", "blobBrush", "eraser"];
|
||||
|
||||
/// The options the dialog edits, in the order it shows them (a tool has those it reports).
|
||||
const KEYS: [&str; 8] = ["fidelity", "size", "fill", "altSmooth", "keepSelected", "mergeOnlySelection", "closeWithin", "editWithin"];
|
||||
const KEYS: [&str; 9] = ["fidelity", "size", "fill", "altSmooth", "keepSelected", "mergeOnlySelection", "closeWithin", "editWithin", "liveFit"];
|
||||
|
||||
/// Width of the label column: wide enough for the tolerances' labels when there are any.
|
||||
fn label_w(d: &Dialog) -> f32 {
|
||||
@@ -117,6 +118,10 @@ fn body(_app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
|
||||
if d.fields.contains_key("editWithin") {
|
||||
number(ui, d, ("editWithin", tl!("Edit selected paths within:")), "px", 0, (0.0, 100.0));
|
||||
}
|
||||
if d.fields.contains_key("liveFit") {
|
||||
ui.add_space(6.0);
|
||||
check(ui, d, "liveFit", tl!("Live curve fitting"));
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
@@ -209,6 +214,55 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Live curve fitting (the Pencil's, on by default): on, the path shown while a stroke is
|
||||
/// drawn is fitted; off, it joins the stroke's points with straight segments, and the release
|
||||
/// fits it as with the option on.
|
||||
#[test]
|
||||
fn live_curve_fitting_off_shows_the_points_until_the_release_fits_them() {
|
||||
let mut app = app();
|
||||
// The anchors of the newest path while a wave around height `y0` is drawn and after the
|
||||
// release, with nothing selected (a stroke starting at the end of a selected path would
|
||||
// continue it).
|
||||
let draw = |app: &mut VectorcraftApp, y0: f64| {
|
||||
app.run("select.set", json!({"ids": []})).unwrap();
|
||||
app.select_tool("pencil");
|
||||
let view = app.view_info();
|
||||
let pts: Vec<_> = (0..=40).map(|i| (20.0 + i as f64 * 8.0, y0 + 30.0 * (i as f64 * 0.25).sin())).collect();
|
||||
let pointer = |kind, (x, y)| vectorcraft_tools::PointerEvent::new(kind, x, y);
|
||||
let newest = |app: &VectorcraftApp| {
|
||||
let doc = &app.session.doc().unwrap().doc;
|
||||
doc.layers[0].children().unwrap().last().unwrap().path_data().unwrap().subpaths[0].anchors.clone()
|
||||
};
|
||||
crate::canvas::dispatch(app, &pointer(vectorcraft_tools::PointerKind::Down, pts[0]), view);
|
||||
for p in &pts[1..] {
|
||||
crate::canvas::dispatch(app, &pointer(vectorcraft_tools::PointerKind::Drag, *p), view);
|
||||
}
|
||||
let drawing = newest(app);
|
||||
crate::canvas::dispatch(app, &pointer(vectorcraft_tools::PointerKind::Up, pts[40]), view);
|
||||
(drawing, newest(app))
|
||||
};
|
||||
let corners = |anchors: &[vectorcraft_geom::Anchor]| anchors.iter().all(|a| a.h_in == a.p && a.h_out == a.p);
|
||||
// On by default: the Pencil's dialog shows it and the Paintbrush's does not.
|
||||
app.run("tool.options", json!({"tool": "pencil"})).unwrap();
|
||||
assert!(painted(&mut app).contains("Live curve fitting"));
|
||||
assert!(app.ui.dialog.as_ref().unwrap().bool("liveFit"));
|
||||
super::super::confirm(&mut app).unwrap();
|
||||
let (drawing, on) = draw(&mut app, 100.0);
|
||||
assert!(!corners(&drawing) && drawing.len() < 20, "fitted while drawn: {} anchors", drawing.len());
|
||||
// Off: the points while the stroke is drawn, fitted at the release.
|
||||
app.run("tool.options", json!({"tool": "pencil"})).unwrap();
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("liveFit".into(), json!(false));
|
||||
super::super::confirm(&mut app).unwrap();
|
||||
app.select_tool("selection");
|
||||
assert_eq!(app.session.tool_options_of("pencil")["liveFit"].as_bool(), Some(false), "it lasts while another tool is chosen");
|
||||
let (drawing, off) = draw(&mut app, 200.0);
|
||||
assert!(corners(&drawing) && drawing.len() > 30, "the points while drawn: {} anchors", drawing.len());
|
||||
assert_eq!(off.len(), on.len(), "the release fits the stroke as with the option on");
|
||||
assert!(off.iter().zip(&on).all(|(a, b)| (a.p.x - b.p.x).abs() < 1e-9 && (a.p.y - b.p.y - 100.0).abs() < 1e-9), "{off:?}");
|
||||
app.run("tool.options", json!({"tool": "paintbrush"})).unwrap();
|
||||
assert!(!painted(&mut app).contains("Live curve fitting"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_new_fidelity_changes_what_the_pencil_draws() {
|
||||
let mut app = app();
|
||||
|
||||
@@ -1089,6 +1089,7 @@ font-technology Variable Променливи
|
||||
Fill and Stroke Запълване и щрих
|
||||
Fill new strokes Запълване на нови щрихове
|
||||
Alt key toggles to Smooth Tool Клавишът Alt превключва към инструмент „Гладкост“
|
||||
Live curve fitting Напасване на кривата в реално време
|
||||
Fill Color Цвят на запълване
|
||||
Fill New Type Objects With Placeholder Text Запълване на нови текстови обекти с примесен текст
|
||||
Fill Rule Правило за запълване
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2296,6 +2296,7 @@ lighting Light Světlo
|
||||
Fidelity: Věrnost:
|
||||
Fill new strokes Vyplňovat nové tahy
|
||||
Alt key toggles to Smooth Tool Klávesa Alt přepíná na nástroj Vyhlazení
|
||||
Live curve fitting Průběžné prokládání křivky
|
||||
Keep selected Ponechat vybrané
|
||||
Merge only with selection Sloučit jen s výběrem
|
||||
Consider Open Filled Paths as Closed Považovat otevřené vyplněné cesty za uzavřené
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2106,6 +2106,7 @@ lighting Light Lys
|
||||
Fidelity: Præcision:
|
||||
Fill new strokes Udfyld nye konturer
|
||||
Alt key toggles to Smooth Tool Alt-tasten skifter til Udglatningsværktøj
|
||||
Live curve fitting Kurvetilpasning undervejs
|
||||
The document has no colour swatches. Dokumentet har ingen farveprøver.
|
||||
Style Name: Typografinavn:
|
||||
Crop To Beskær til
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1086,6 +1086,7 @@ font-technology Variable Variabel
|
||||
Fill and Stroke Fläche und Kontur
|
||||
Fill new strokes Neue Striche füllen
|
||||
Alt key toggles to Smooth Tool Alt-Taste schaltet auf Glätten-Werkzeug um
|
||||
Live curve fitting Kurvenanpassung beim Zeichnen
|
||||
Keep selected Ausgewählt lassen
|
||||
Merge only with selection Nur mit Auswahl zusammenfügen
|
||||
Consider Open Filled Paths as Closed Offene gefüllte Pfade als geschlossen behandeln
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1055,6 +1055,7 @@ font-technology Variable Μεταβλητές
|
||||
Fill and Stroke Γέμισμα και περίγραμμα
|
||||
Fill new strokes Γέμισμα νέων περιγραμμάτων
|
||||
Alt key toggles to Smooth Tool Το πλήκτρο Alt αλλάζει στο εργαλείο Εξομάλυνση (Smooth Tool)
|
||||
Live curve fitting Προσαρμογή καμπύλης σε πραγματικό χρόνο
|
||||
Fill Color Χρώμα γεμίσματος
|
||||
Fill New Type Objects With Placeholder Text Συμπλήρωση νέων αντικειμένων κειμένου με κείμενο υπόδειξης
|
||||
Fill Rule Κανόνας γεμίσματος
|
||||
|
||||
|
@@ -1109,6 +1109,7 @@ font-technology Variable Variables
|
||||
Fill and Stroke Relleno y trazo
|
||||
Fill new strokes Rellenar trazos nuevos
|
||||
Alt key toggles to Smooth Tool La tecla Alt cambia a la herramienta Suavizar
|
||||
Live curve fitting Ajuste de curva en tiempo real
|
||||
Keep selected Mantener seleccionado
|
||||
Merge only with selection Combinar solo con la selección
|
||||
Consider Open Filled Paths as Closed Tratar los trazados abiertos con relleno como cerrados
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2253,6 +2253,7 @@ lighting Light Valo
|
||||
Fidelity: Tarkkuus:
|
||||
Fill new strokes Täytä uudet viivat
|
||||
Alt key toggles to Smooth Tool Alt-näppäin vaihtaa Tasoitustyökaluun
|
||||
Live curve fitting Käyrän sovitus piirrettäessä
|
||||
The document has no colour swatches. Asiakirjassa ei ole värimalleja.
|
||||
Style Name: Tyylin nimi:
|
||||
Crop To Rajaa kohteeseen
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1109,6 +1109,7 @@ font-technology Variable Variables
|
||||
Fill and Stroke Fond et contour
|
||||
Fill new strokes Remplir les nouveaux contours
|
||||
Alt key toggles to Smooth Tool La touche Alt bascule sur l’outil Arrondi
|
||||
Live curve fitting Ajustement de la courbe en direct
|
||||
Keep selected Garder sélectionné
|
||||
Merge only with selection Fusionner uniquement avec la sélection
|
||||
Consider Open Filled Paths as Closed Traiter les tracés ouverts avec fond comme fermés
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1395,6 +1395,7 @@ font-technology Variable Változó
|
||||
Fill Color Kitöltőszín
|
||||
Fill new strokes Új vonások kitöltése
|
||||
Alt key toggles to Smooth Tool Az Alt billentyű a Simítás eszközre vált
|
||||
Live curve fitting Görbeillesztés rajzolás közben
|
||||
Keep selected Kijelölés megtartása
|
||||
Merge only with selection Összeolvasztás csak a kijelöléssel
|
||||
Consider Open Filled Paths as Closed A nyitott kitöltött útvonalak zártnak számítanak
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2106,6 +2106,7 @@ lighting Light Cahaya
|
||||
Fidelity: Fidelitas:
|
||||
Fill new strokes Isi stroke baru
|
||||
Alt key toggles to Smooth Tool Tombol Alt beralih ke Alat penghalus
|
||||
Live curve fitting Penyesuaian kurva langsung
|
||||
The document has no colour swatches. Dokumen tidak memiliki swatch warna.
|
||||
Style Name: Nama gaya:
|
||||
Crop To Pangkas ke
|
||||
|
||||
|
@@ -1109,6 +1109,7 @@ font-technology Variable Variabili
|
||||
Fill and Stroke Riempimento e traccia
|
||||
Fill new strokes Riempi nuove tracce
|
||||
Alt key toggles to Smooth Tool Il tasto Alt attiva lo strumento Smussa
|
||||
Live curve fitting Adattamento della curva in tempo reale
|
||||
Keep selected Mantieni selezionato
|
||||
Merge only with selection Unisci solo con la selezione
|
||||
Consider Open Filled Paths as Closed Considera chiusi i tracciati aperti con riempimento
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1513,6 +1513,7 @@ font-technology Variable バリアブル
|
||||
Fill and Stroke 塗りと線
|
||||
Fill new strokes 新しいストロークに塗りを適用
|
||||
Alt key toggles to Smooth Tool Alt キーでスムーズツールに切り替え
|
||||
Live curve fitting 描画中にカーブを調整
|
||||
Keep selected 選択を保持
|
||||
Merge only with selection 選択範囲とのみ結合
|
||||
Consider Open Filled Paths as Closed 塗りのある開いたパスを閉じたパスとみなす
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5329,6 +5329,7 @@ imageTrace Outline კონტური
|
||||
Light/Dark Balance ნათელი/მუქი ბალანსი
|
||||
Move Anchor Point საყრდენი წერტილის გადაადგილება
|
||||
Alt key toggles to Smooth Tool Alt კლავიში ჩართავს გაგლუვების ხელსაწყოს
|
||||
Live curve fitting მრუდის მორგება ხატვისას
|
||||
@msg {label} keeps 1 of the document's {n} artboards: the file reopens with one. Turn on Preserve Editing Capabilities to keep them all. {label} ინახავს დოკუმენტის {n} სამუშაო დაფიდან 1-ს: ფაილი ხელახლა ერთით გაიხსნება. ყველას შესანარჩუნებლად ჩართეთ „რედაქტირების შესაძლებლობების შენარჩუნება“.
|
||||
Chalk & Charcoal… ცარცი და ნახშირი…
|
||||
Chalk & Charcoal ცარცი და ნახშირი
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1508,6 +1508,7 @@ font-technology Variable 가변
|
||||
Fill and Stroke 칠과 선
|
||||
Fill new strokes 새 선 칠하기
|
||||
Alt key toggles to Smooth Tool Alt 키로 매끄럽게 도구로 전환
|
||||
Live curve fitting 실시간 곡선 맞춤
|
||||
Keep selected 선택 유지
|
||||
Merge only with selection 선택 영역과만 병합
|
||||
Consider Open Filled Paths as Closed 칠이 있는 열린 패스를 닫힌 패스로 처리
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2120,6 +2120,7 @@ lighting Light Licht
|
||||
Fidelity: Nauwkeurigheid:
|
||||
Fill new strokes Nieuwe lijnen vullen
|
||||
Alt key toggles to Smooth Tool Alt-toets schakelt over naar Gladmaken-gereedschap
|
||||
Live curve fitting Curve aanpassen tijdens het tekenen
|
||||
The document has no colour swatches. Het document heeft geen kleurstalen.
|
||||
Style Name: Stijlnaam:
|
||||
Crop To Bijsnijden tot
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2211,6 +2211,7 @@ lighting Light Światło
|
||||
Fidelity: Wierność:
|
||||
Fill new strokes Wypełniaj nowe pociągnięcia
|
||||
Alt key toggles to Smooth Tool Klawisz Alt przełącza na narzędzie Wygładzanie
|
||||
Live curve fitting Dopasowywanie krzywej podczas rysowania
|
||||
Keep selected Zachowaj zaznaczenie
|
||||
Merge only with selection Scalaj tylko z zaznaczeniem
|
||||
Consider Open Filled Paths as Closed Traktuj otwarte wypełnione ścieżki jako zamknięte
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -986,6 +986,7 @@
|
||||
Fill and Stroke Preenchimento e Traçado
|
||||
Fill new strokes Preencher novos traçados
|
||||
Alt key toggles to Smooth Tool A tecla Alt alterna para a ferramenta Suavizar
|
||||
Live curve fitting Ajuste de curva em tempo real
|
||||
Keep selected Manter selecionado
|
||||
Merge only with selection Mesclar somente com a seleção
|
||||
Consider Open Filled Paths as Closed Tratar caminhos abertos preenchidos como fechados
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1107,6 +1107,7 @@ font-technology Variable Вариативные
|
||||
Fill and Stroke Заливка и обводка
|
||||
Fill new strokes Заполнять новые обводки
|
||||
Alt key toggles to Smooth Tool Клавиша Alt переключает на инструмент «Сглаживание»
|
||||
Live curve fitting Подгонка кривой во время рисования
|
||||
Keep selected Оставлять выбранным
|
||||
Merge only with selection Объединять только с выделенным
|
||||
Consider Open Filled Paths as Closed Считать открытые контуры с заливкой замкнутыми
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1091,6 +1091,7 @@ font-technology Variable Variabla
|
||||
Fill and Stroke Fyllning och kontur
|
||||
Fill new strokes Fyll nya konturer
|
||||
Alt key toggles to Smooth Tool Alt-tangenten växlar till Utjämningsverktyg
|
||||
Live curve fitting Kurvanpassning medan du ritar
|
||||
Fill Color Fyllnadsfärg
|
||||
Fill New Type Objects With Placeholder Text Fyll nya textobjekt med platshållartext
|
||||
Fill Rule Fyllningsregel
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -5220,6 +5220,7 @@ theme Appearance Görünüm
|
||||
@msg {_1} is an exporter (File › Export › Export As…) {_1} bir dışa aktarma eklentisidir (Dosya › Dışa Aktar › Farklı Dışa Aktar…)
|
||||
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData` {size} bayt (sınır {MAX_DOCUMENT_DATA})
|
||||
Alt key toggles to Smooth Tool Alt tuşu Düzleştirme Aracına geçiş yapar
|
||||
Live curve fitting Çizerken eğri uydurma
|
||||
Auto-Simplify Otomatik Basitleştir
|
||||
Drag an item here, or right-click one, to put it in this group. Bu gruba eklemek için bir öğeyi buraya sürükleyin veya sağ tıklayın.
|
||||
Drop here to take it out of its group Grup dışına çıkarmak için buraya bırakın
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -1103,6 +1103,7 @@ font-technology Variable Варіативні
|
||||
Fill and Stroke Заливка та обведення
|
||||
Fill new strokes Заповнювати нові обведення
|
||||
Alt key toggles to Smooth Tool Клавіша Alt перемикає на інструмент «Згладжування»
|
||||
Live curve fitting Підгонка кривої під час малювання
|
||||
Keep selected Залишати вибраним
|
||||
Merge only with selection Об’єднувати лише з виділеним
|
||||
Consider Open Filled Paths as Closed Вважати відкриті контури із заливкою замкненими
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 749 and column 40.
|
@@ -1272,6 +1272,7 @@ font-technology Variable Biến thiên
|
||||
Fill Grid Tô lưới
|
||||
Fill new strokes Tô nét mới
|
||||
Alt key toggles to Smooth Tool Phím Alt chuyển sang công cụ Làm mượt
|
||||
Live curve fitting Khớp đường cong khi vẽ
|
||||
Keep selected Giữ trạng thái đã chọn
|
||||
Merge only with selection Chỉ hợp nhất với vùng chọn
|
||||
Consider Open Filled Paths as Closed Coi đường dẫn mở có tô là đường dẫn đóng
|
||||
|
||||
|
@@ -1048,6 +1048,7 @@
|
||||
Fill and Stroke 填色和描边
|
||||
Fill new strokes 为新描边应用填色
|
||||
Alt key toggles to Smooth Tool Alt 键切换为平滑工具
|
||||
Live curve fitting 实时曲线拟合
|
||||
Keep selected 保持选择状态
|
||||
Merge only with selection 仅与所选对象合并
|
||||
Consider Open Filled Paths as Closed 将有填色的开放路径视为闭合路径
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1074,6 +1074,7 @@ font-technology Variable 可變
|
||||
Fill Color 填色顏色
|
||||
Fill new strokes 為新筆畫套用填色
|
||||
Alt key toggles to Smooth Tool Alt 鍵切換為平滑工具
|
||||
Live curve fitting 即時曲線擬合
|
||||
Keep selected 保持選取狀態
|
||||
Merge only with selection 僅與選取物件合併
|
||||
Consider Open Filled Paths as Closed 將有填色的開放路徑視為封閉路徑
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -674,6 +674,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
In the Swatches panel, Alt-dragging a swatch onto another swatch of its kind (not a tint swatch) replaces that swatch's color, gradient or pattern, and the swatch keeps its name and place.
|
||||
- **Tool double-clicks:** double-clicking a tool's button opens its options: Hand fits the artboard in the window, Zoom shows 100 %, Rotate, Scale, Reflect and Shear open their Object › Transform dialogs, the Selection, Direct Selection and Group Selection tools the Move dialog (Enter opens these seven tools' dialogs too; Move opens on the offset of the last drag or Move and with the checkboxes it was last confirmed with, so Enter, Enter repeats a move or a copy at the same spacing, as [an Adobe Community thread](https://community.adobe.com/questions-652/move-tool-771221) describes), Gradient, Eyedropper, Blend and the Liquify tools their panels or options, the Pencil, Paintbrush, Smooth, Blob Brush and Eraser tools their Tool Options (Fidelity, fill, Keep selected, the tolerances, the brush size), and (#812) the Flare tool its Flare Tool Options, the Artboard tool the active artboard's Artboard Options, the graph tools Graph Type for the selected graph, the Symbolism tools the Symbolism Tools Options (Diameter, Intensity, Symbol Set Density) and the Magic Wand tool its panel.
|
||||
Double-clicking the Line Segment, Arc, Rectangular Grid or Polar Grid tool opens its Tool Options, as step 3 of the [Rectangular Grid tool](https://helpx.adobe.com/illustrator/using/tool-techniques/rectangular-grid-tool.html) and [Arc tool](https://helpx.adobe.com/illustrator/using/tool-techniques/arc-tool.html) pages and the [Tools](https://helpx.adobe.com/illustrator/using/tools.html) page describe (#2691): the dialog a click with the tool opens, without a place to draw. Line Segment Tool Options has Length, Angle and Fill Line (a new line takes the current fill, `shape.line` `fill`); Arc Segment Tool Options its lengths, Reference Point, Type, Base Along, Slope and Fill Arc; the grids' Tool Options the Reference Point, Default Size, dividers, skews and switches. OK draws nothing: the next drag draws with the options and a click's dialog opens at them, the Length, Angle and Default Size included; Cancel changes nothing. Tests: `toolbar::tests::double_clicking_the_line_arc_and_grid_tools_opens_their_tool_options`, `line_segment_options_and_fill_line`, `grid_default_size_is_an_option_the_drag_leaves_out`, `fill_line_gives_a_new_line_the_current_fill`.
|
||||
Live curve fitting in the Pencil Tool Options is on by default; while it is off, the path shown while a stroke is drawn joins the stroke's points with straight segments, and the release fits the curve, as [Pencil Tool Options](https://web.archive.org/web/20261002104400/https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/pencil-tool-options.html) describes (`path.freehand` `fit`).
|
||||
In the Pencil and Paintbrush Tool Options, Edit selected paths within 0 px turns editing off, so a stroke that starts exactly on an end of a selected open path draws a new path; Close paths when ends are within 0 px turns closing off, so a stroke that ends exactly where it started stays open.
|
||||
Keep selected in the Pencil and Paintbrush Tool Options is on by default; while it is off, the path a stroke draws or continues is not selected afterwards and the rest of the selection stays as it was (`path.freehand` `keepSelected`).
|
||||
Double-clicking the Shape Builder tool opens Shape Builder Tool Options ([Shape Builder tool options](https://web.archive.org/web/20260105172244/https://helpx.adobe.com/illustrator/using/building-new-shapes-using-shape.html)): with Consider Open Filled Paths as Closed off, a filled open path cuts regions as an unfilled one does; Pick Color From Color Swatches fills merged shapes with the current fill, the Left and Right arrow keys step it through the document's color swatches, and Cursor Swatch Preview shows the current fill and the swatches before and after it above the pointer; Selection Straight Line merges what the line from a drag's first point to its last crosses ([Setting Illustrator's Shape Builder Options](https://web.archive.org/web/20260615204453/https://creativepro.com/setting-illustrators-shape-builder-options/)); with Highlight Fill off, merge mode shades no regions (`shapeBuilder.merge` `openFilledAsClosed` and `pickColorFrom`).
|
||||
|
||||
Reference in New Issue
Block a user