Pen: a new path joins the selected path instead of replacing the Work Path (#4721) (#4730)

This commit is contained in:
F4LCON 2026-10-13 18:03:43 +02:00 committed by GitHub
parent 625141c2ad
commit 1168a9dd73
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 64 additions and 3 deletions

View File

@ -146,7 +146,10 @@ fn dragging_on_the_first_anchor_closes_with_its_handles_in_one_step() {
click(&mut h, [120.0, 20.0]);
click(&mut h, [120.0, 100.0]);
click(&mut h, [20.0, 20.0]);
let sp = h.state().session.active().unwrap().doc.work_path.clone().unwrap().subpaths.remove(0);
// The second path joins the Work Path as its last subpath (#4721).
let subs = h.state().session.active().unwrap().doc.work_path.clone().unwrap().subpaths;
assert_eq!(subs.len(), 2, "the first path stays");
let sp = subs.last().unwrap();
let k = &sp.knots[0];
assert!(sp.closed && sp.knots.len() == 3);
assert!(near([k.in_ctrl.x, k.in_ctrl.y], [0.0, 20.0]) && near([k.out_ctrl.x, k.out_ctrl.y], [40.0, 20.0]), "{k:?}");

View File

@ -389,8 +389,16 @@ fn commit_path(app: &mut PhotocraftApp, pen: &PenPath, closed: bool) {
}
app.run("layer.vectorMask.edit", json!({"layer": id, "path": p}))
} else {
// The drawn work path is selected in the Paths panel, as in Photoshop.
app.run("path.set", json!({"name": "work", "path": path})).inspect(|_| app.ui.selected_path = Some("work".into()))
// Photoshop adds the new subpath to the path selected in the Paths panel, which is the
// Work Path right after drawing one, so path after path builds up there (#4721). With no
// path selected, a new Work Path replaces the old one. The drawn path ends up selected.
let target = app.ui.selected_path.clone().filter(|n| app.session.active().is_some_and(|st| path_exists(&st.doc, n)));
let name = target.clone().unwrap_or_else(|| "work".into());
let mut params = json!({"name": name, "path": path});
if target.is_some() {
params["op"] = json!("combine");
}
app.run("path.set", params).inspect(|_| app.ui.selected_path = Some(name))
};
if let Err(e) = r {
app.ui.status = e;
@ -398,6 +406,11 @@ fn commit_path(app: &mut PhotocraftApp, pen: &PenPath, closed: bool) {
}
}
/// Does `doc` have the path the Paths panel calls `name`: "work", or a saved path's name?
fn path_exists(doc: &Document, name: &str) -> bool {
if name == "work" { doc.work_path.is_some() } else { doc.paths.iter().any(|p| p.name == name) }
}
// ---------------------------------------------------------------------------------------------
// Freeform Pen and Curvature Pen
@ -2048,6 +2061,51 @@ mod tests {
assert_eq!((wp.subpaths[0].knots[0].anchor.x, wp.subpaths[0].knots[0].anchor.y), (30.0, 25.0));
}
/// #4721: a second Pen path joins the Work Path it was drawn after (still selected), as a new
/// subpath, instead of replacing it; a saved path that is selected takes it instead; with no
/// path selected, the new path replaces the Work Path, as in Photoshop.
#[test]
fn a_new_pen_path_joins_the_selected_path() {
let mut app = app();
app.ui.tool = Tool::Pen;
let draw = |app: &mut PhotocraftApp, x: f64| {
for (dx, dy) in [(0.0, 0.0), (40.0, 0.0), (40.0, 40.0)] {
pen_down(app, x + dx, 20.0 + dy);
pen_up(app);
}
pen_down(app, x + 0.5, 20.5); // close
pen_up(app);
};
let work = |app: &PhotocraftApp| app.session.active().unwrap().doc.work_path.clone().unwrap();
draw(&mut app, 20.0);
assert_eq!(app.ui.selected_path.as_deref(), Some("work"));
draw(&mut app, 120.0);
let wp = work(&app);
assert_eq!(wp.subpaths.len(), 2, "the first path stays");
assert!(wp.subpaths.iter().all(|s| s.closed && s.knots.len() == 3));
assert_eq!(wp.subpaths[0].knots[0].anchor.x, 20.0);
assert_eq!(wp.subpaths[1].knots[0].anchor.x, 120.0);
// One History step per path.
app.session.undo();
assert_eq!(work(&app).subpaths.len(), 1, "undo takes back only the second path");
app.session.redo();
// A selected saved path takes the next one.
app.run("path.set", json!({"name": "Saved", "path": {"subpaths": [{"knots": [[2, 2], [12, 2], [12, 8]]}]}})).unwrap();
app.ui.selected_path = Some("Saved".into());
draw(&mut app, 220.0);
let saved = app.session.active().unwrap().doc.paths.iter().find(|p| p.name == "Saved").unwrap().path.clone();
assert_eq!(saved.subpaths.len(), 2);
assert_eq!(work(&app).subpaths.len(), 2, "the Work Path is left alone");
assert_eq!(app.ui.selected_path.as_deref(), Some("Saved"));
// No path selected: a new Work Path replaces the old one.
app.ui.selected_path = None;
draw(&mut app, 320.0);
let wp = work(&app);
assert_eq!(wp.subpaths.len(), 1);
assert_eq!(wp.subpaths[0].knots[0].anchor.x, 320.0);
assert_eq!(app.ui.selected_path.as_deref(), Some("work"));
}
/// #4596: the close-path pointer shows where a click would close the path, and only there:
/// over the first anchor once the Pen has two anchors (the Curvature Pen three), not while an
/// anchor is being dragged.