mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-30 12:47:13 -06:00
Fix #4469: Direct Selection's task bar offers the path's anchor actions
With the Direct Selection tool active (after the bar's Edit Path, or switching to it) and a path selected whole, the Contextual Task Bar kept the Selection tool's Edit Path, Offset Path and Simplify: its anchor actions showed only for anchors picked out one by one. The public overview lists, for a path the Direct Selection tool selects, Simplify and the anchor actions. A path selected whole now offers Corner and Smooth (path.convertAnchors converts every anchor of a fully selected path), Connect for an open path, and Simplify. Remove Anchor Points and Cut Path need anchors picked out and still wait for them. Edit Path, which only switches to the tool already in use, is left out, for compound paths too.
This commit is contained in:
@@ -93,6 +93,10 @@ pub(super) fn task_bar(app: &mut VectorcraftApp, ui: &mut Ui, xf: &Xf) {
|
||||
// (<https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/contextual-task-bar-overview.html>).
|
||||
// These run the same commands as the Control bar's anchor buttons.
|
||||
let anchors = !st.selection.anchors.is_empty();
|
||||
// With the Direct Selection tool, a path selected whole offers its anchor points' actions
|
||||
// too, as the reference app's bar does for any path that tool selects (same page), and Edit
|
||||
// Path, which only switches to that tool, is left out (#4469).
|
||||
let direct = app.session.tool_id() == "directSelection";
|
||||
if anchors {
|
||||
items.push((tl!("Remove Anchor Points"), "pen-tool-delete", "path.removeAnchors", none()));
|
||||
items.push((tl!("Connect"), "dc-join", "path.join", none()));
|
||||
@@ -122,7 +126,9 @@ pub(super) fn task_bar(app: &mut VectorcraftApp, ui: &mut Ui, xf: &Xf) {
|
||||
items.push((tl!("Isolate"), "square-dashed", "object.isolate", none()));
|
||||
}
|
||||
} else if is_compound {
|
||||
items.push(edit_path());
|
||||
if !direct {
|
||||
items.push(edit_path());
|
||||
}
|
||||
items.push((tl!("Release Compound Path"), "ungroup", "object.compoundPath.release", none()));
|
||||
} else if let Some(kind) = text {
|
||||
items.push((tl!("Create Outlines"), "type", "type.createOutlines", none()));
|
||||
@@ -134,6 +140,15 @@ pub(super) fn task_bar(app: &mut VectorcraftApp, ui: &mut Ui, xf: &Xf) {
|
||||
}
|
||||
} else if is_image {
|
||||
// Offset Path and Simplify don't apply to an image.
|
||||
} else if !anchors && direct {
|
||||
// Corner and Smooth convert every anchor of a path selected whole; Remove and Cut
|
||||
// need anchors picked out, so they wait for them.
|
||||
if open_path {
|
||||
items.push((tl!("Connect"), "dc-join", "path.join", none()));
|
||||
}
|
||||
items.push((tl!("Corner"), "dc-anchor", TASK_BAR_TO_CORNER, none()));
|
||||
items.push((tl!("Smooth"), "dc-anchor-smooth", TASK_BAR_TO_SMOOTH, none()));
|
||||
items.push((tl!("Simplify"), "spline", "object.path.simplify", none()));
|
||||
} else if !anchors {
|
||||
if open_path {
|
||||
items.push((tl!("Join"), "dc-join", "path.join", none()));
|
||||
|
||||
@@ -163,3 +163,74 @@ fn type_converts_between_point_and_area_type() {
|
||||
click(&mut app, "Convert To Point Type");
|
||||
assert!(matches!(text_kind(&app), TextKind::Point));
|
||||
}
|
||||
|
||||
/// The path's anchors (its first subpath).
|
||||
fn anchors(app: &VectorcraftApp, id: u64) -> Vec<vectorcraft_geom::Anchor> {
|
||||
match kind(app, id) {
|
||||
NodeKind::Path { path, .. } => path.subpaths[0].anchors.clone(),
|
||||
k => panic!("not a path: {k:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// With the Direct Selection tool, a path selected whole offers its anchor points' actions instead
|
||||
/// of Edit Path and Offset Path (#4469), as after the bar's own Edit Path.
|
||||
#[test]
|
||||
fn direct_selection_of_a_whole_path_offers_its_anchor_actions() {
|
||||
let mut app = app();
|
||||
let line = id(app.run("path.create", json!({"anchors": [{"x": 50, "y": 200}, {"x": 150, "y": 80}, {"x": 250, "y": 200}]})).unwrap());
|
||||
select(&mut app, &[line]);
|
||||
click(&mut app, "Edit Path");
|
||||
assert_eq!(app.session.tool_id(), "directSelection");
|
||||
assert!(app.session.active().unwrap().selection.anchors.is_empty(), "the path is selected whole");
|
||||
let (_, texts) = bar(&mut app);
|
||||
for label in ["Connect", "Corner", "Smooth", "Simplify", "Duplicate"] {
|
||||
assert!(has(&texts, label), "{label}: {texts:?}");
|
||||
}
|
||||
for label in ["Edit Path", "Offset Path", "Join", "Remove Anchor Points", "Cut Path"] {
|
||||
assert!(!has(&texts, label), "{label}: {texts:?}");
|
||||
}
|
||||
// Smooth and Corner convert every anchor of the path, one undo step each.
|
||||
click(&mut app, "Smooth");
|
||||
let a = anchors(&app, line);
|
||||
assert_eq!(a[1].kind, vectorcraft_geom::AnchorKind::Smooth, "{a:?}");
|
||||
assert!(a[1].has_in() && a[1].has_out(), "{a:?}");
|
||||
click(&mut app, "Corner");
|
||||
assert!(anchors(&app, line).iter().all(|a| !a.has_in() && !a.has_out()), "{:?}", anchors(&app, line));
|
||||
app.run("edit.undo", json!({})).unwrap();
|
||||
assert!(anchors(&app, line)[1].has_in(), "Corner undoes in one step");
|
||||
// Connect closes the open path; a closed one isn't offered it.
|
||||
click(&mut app, "Connect");
|
||||
assert!(matches!(kind(&app, line), NodeKind::Path { path, .. } if path.is_closed()));
|
||||
let (_, texts) = bar(&mut app);
|
||||
assert!(!has(&texts, "Connect") && has(&texts, "Corner"), "{texts:?}");
|
||||
}
|
||||
|
||||
/// Anchors picked out with the Direct Selection tool keep the full set: Remove and Cut need them.
|
||||
#[test]
|
||||
fn direct_selected_anchors_still_offer_remove_and_cut() {
|
||||
let mut app = app();
|
||||
let line = id(app.run("path.create", json!({"anchors": [{"x": 50, "y": 200}, {"x": 150, "y": 80}, {"x": 250, "y": 200}]})).unwrap());
|
||||
app.run("tool.select", json!({"tool": "directSelection"})).unwrap();
|
||||
app.run("select.anchors", json!({"id": line, "anchors": [[0, 1]]})).unwrap();
|
||||
let (_, texts) = bar(&mut app);
|
||||
for label in ["Remove Anchor Points", "Connect", "Cut Path", "Corner", "Smooth"] {
|
||||
assert!(has(&texts, label), "{label}: {texts:?}");
|
||||
}
|
||||
assert!(!has(&texts, "Edit Path"), "{texts:?}");
|
||||
}
|
||||
|
||||
/// A compound path under the Direct Selection tool isn't offered Edit Path, which would only switch
|
||||
/// to the tool already in use; the Selection tool still offers it.
|
||||
#[test]
|
||||
fn a_compound_path_under_direct_selection_drops_edit_path() {
|
||||
let mut app = app();
|
||||
let a = id(app.run("shape.rectangle", json!({"x": 40, "y": 40, "width": 120, "height": 100})).unwrap());
|
||||
let b = id(app.run("shape.ellipse", json!({"x": 70, "y": 60, "width": 50, "height": 50})).unwrap());
|
||||
select(&mut app, &[a, b]);
|
||||
let c = id(app.run("object.compoundPath.make", json!({})).unwrap());
|
||||
click(&mut app, "Edit Path");
|
||||
assert_eq!(app.session.tool_id(), "directSelection");
|
||||
select(&mut app, &[c]);
|
||||
let (_, texts) = bar(&mut app);
|
||||
assert!(has(&texts, "Release Compound Path") && !has(&texts, "Edit Path"), "{texts:?}");
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
# UI and interaction parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (Character panel menu › Show Snap to Glyph Options shows the Snap to Glyph section (Baseline, x-height, Glyph Bounds, Proximity Guides, Angular Guides, Anchor Point; `view.snapToGlyph.options`) (#4232); scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (Contextual Task Bar: with the Direct Selection tool, a path selected whole offers Corner, Smooth (every anchor), Connect (open paths) and Simplify instead of Edit Path and Offset Path, as the public overview lists for that tool (<https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/contextual-task-bar-overview.html>); a compound path under that tool no longer offers Edit Path (#4469); scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
|
||||
## Feature submission evidence
|
||||
|
||||
@@ -361,6 +361,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-30 | minor | Contextual Task Bar: with the Direct Selection tool, a path selected whole offers Corner, Smooth (every anchor), Connect (open paths) and Simplify instead of Edit Path and Offset Path, as the public overview lists for that tool (<https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/contextual-task-bar-overview.html>); a compound path under that tool no longer offers Edit Path (#4469). Tests in `crates/ui-egui/src/tests/taskbar.rs`; scores and estimates unchanged |
|
||||
| 2026-10-30 | minor | Character panel menu › Show Snap to Glyph Options shows the Snap to Glyph section (Baseline, x-height, Glyph Bounds, Proximity Guides, Angular Guides, Anchor Point; `view.snapToGlyph.options`) (#4232); scores and estimates unchanged |
|
||||
| 2026-10-30 | minor | Align panel menu › Align to Glyph Bounds › Point Text and Area Text align and distribute type by the ink of its glyphs (#4231); the Character and Properties panels' controls for it are not done; scores and estimates unchanged |
|
||||
| 2026-10-30 | minor | Art moved whole with the Direct Selection or Group Selection tool and dragged off the canvas goes to the panels (Asset Export, Graphic Styles, Swatches, Symbols) as with the Selection tool; anchor points and segments dragged off stay a reshape (#4233). Tests in `crates/ui-egui/src/canvas/tests.rs`; scores and estimates unchanged |
|
||||
|
||||
Reference in New Issue
Block a user