mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 01:28:22 +00:00
Fix #5188: a Shift-click on a corner widget of a whole path picks its corner
With Direct Selection, a Shift-click on a corner widget toggled its corner. With the path selected whole every corner already is, so the first Shift-click deselected the corner clicked and hid its widget, leaving the others. While every corner is selected, a Shift-click now picks that corner alone, as the first click of "click one widget, then Shift-click the others" does; with some corners picked it still toggles.
This commit is contained in:
@@ -257,7 +257,7 @@ pub struct CornerDrag {
|
||||
}
|
||||
|
||||
/// Direct Selection: the drawn anchors of a widget's corner, which a click on the widget selects
|
||||
/// (Shift: toggles), as a click on the corner's anchor does.
|
||||
/// (Shift: toggles, unless every corner is selected), as a click on the corner's anchor does.
|
||||
#[derive(Clone, Debug)]
|
||||
struct CornerClick {
|
||||
id: NodeId,
|
||||
@@ -282,7 +282,11 @@ impl CornerDrag {
|
||||
let path = widgets.paths.get(widget.0)?;
|
||||
let corner = path.widgets.get(widget.1)?.corner.index;
|
||||
let lc = cx.doc.node(path.id).and_then(LiveCorners::of)?;
|
||||
Some(CornerClick { id: path.id, anchors: lc.anchors_of(&[corner].into()).into_iter().collect(), shift: ev.mods.shift })
|
||||
// While every corner is selected (the path selected whole), a Shift-click picks
|
||||
// this corner alone, as the first click of "click one widget, then Shift-click the
|
||||
// others" does: toggling it would deselect it and leave the rest (#5188).
|
||||
let every = lc.all().is_subset(&lc.picked(cx.selection.partial(path.id)));
|
||||
Some(CornerClick { id: path.id, anchors: lc.anchors_of(&[corner].into()).into_iter().collect(), shift: ev.mods.shift && !every })
|
||||
})
|
||||
.flatten();
|
||||
let outlines = widgets
|
||||
|
||||
@@ -196,7 +196,9 @@ fn drag_follows_a_rotated_shape_and_a_click_does_nothing() {
|
||||
|
||||
/// Direct Selection: a click on a widget (no drag) selects its corner, as a click on the
|
||||
/// corner's anchor does, so the next drag on that widget rounds that corner alone; Shift
|
||||
/// toggles the corner. The Selection tool's click still does nothing.
|
||||
/// toggles the corner, except while every corner is selected (#5188, see
|
||||
/// `a_shift_click_on_a_widget_of_a_whole_path_picks_its_corner`). The Selection tool's click
|
||||
/// still does nothing.
|
||||
#[test]
|
||||
fn a_click_on_a_widget_with_direct_selection_selects_its_corner() {
|
||||
let (d, id) = live_rect(10.0, Affine::translate((100.0, 100.0)));
|
||||
@@ -211,20 +213,57 @@ fn a_click_on_a_widget_with_direct_selection_selects_its_corner() {
|
||||
assert!(t.pointer(&cx, &PointerEvent::new(PointerKind::Down, w.x, w.y)).is_empty());
|
||||
let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Up, w.x, w.y));
|
||||
assert_eq!(a, vec![Action::Exec("select.anchors".into(), json!({"id": id.0, "anchors": anchors, "mode": "set"}))]);
|
||||
let shift = Mods { shift: true, ..Mods::default() };
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, w.x, w.y).with_mods(shift));
|
||||
let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Up, w.x, w.y).with_mods(shift));
|
||||
assert_eq!(a, vec![Action::Exec("select.anchors".into(), json!({"id": id.0, "anchors": anchors, "mode": "toggle"}))]);
|
||||
// With that corner selected, its widget alone shows, and a drag rounds it alone
|
||||
// (`direct_selected_corners_show_their_widgets_alone_and_round_alone`).
|
||||
let mut part = selected(id);
|
||||
part.anchors.insert(id, anchors.iter().copied().collect());
|
||||
assert_eq!(CornerWidgets::of(&d, &part, 1.0, true).unwrap().corners(), [2]);
|
||||
// A Shift-click on it then deselects it.
|
||||
let shift = Mods { shift: true, ..Mods::default() };
|
||||
let one = crate::testutil::cx(&d, &part, &p);
|
||||
t.pointer(&one, &PointerEvent::new(PointerKind::Down, w.x, w.y).with_mods(shift));
|
||||
let a = t.pointer(&one, &PointerEvent::new(PointerKind::Up, w.x, w.y).with_mods(shift));
|
||||
assert_eq!(a, vec![Action::Exec("select.anchors".into(), json!({"id": id.0, "anchors": anchors, "mode": "toggle"}))]);
|
||||
let mut t = crate::create("selection");
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, w.x, w.y));
|
||||
assert!(t.pointer(&cx, &PointerEvent::new(PointerKind::Up, w.x, w.y)).is_empty());
|
||||
}
|
||||
|
||||
/// #5188: while every corner of a path is selected (the path selected whole), a Shift-click on a
|
||||
/// widget picks that corner alone, as the first click of "click one widget, then Shift-click the
|
||||
/// others" does, rather than deselecting it and leaving the others. Once some corners are picked,
|
||||
/// Shift-clicks toggle.
|
||||
#[test]
|
||||
fn a_shift_click_on_a_widget_of_a_whole_path_picks_its_corner() {
|
||||
let (d, id) = live_rect(10.0, Affine::translate((100.0, 100.0)));
|
||||
let p = paint();
|
||||
let lc = LiveCorners::of(d.node(id).unwrap()).unwrap();
|
||||
let anchors = |k: usize| -> Vec<(usize, usize)> { lc.anchors_of(&[k].into()).into_iter().collect() };
|
||||
let shift = Mods { shift: true, ..Mods::default() };
|
||||
let click = |s: &Selection, at: Point| {
|
||||
let cx = crate::testutil::cx(&d, s, &p);
|
||||
let mut t = crate::create("directSelection");
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, at.x, at.y).with_mods(shift));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Up, at.x, at.y).with_mods(shift))
|
||||
};
|
||||
let pick = |k: usize, mode: &str| vec![Action::Exec("select.anchors".into(), json!({"id": id.0, "anchors": anchors(k), "mode": mode}))];
|
||||
// Selected whole: every widget shows; a Shift-click on one picks its corner alone.
|
||||
let whole = selected(id);
|
||||
let w = pts(&CornerWidgets::of(&d, &whole, 1.0, true).unwrap());
|
||||
assert_eq!(w.len(), 4);
|
||||
assert_eq!(click(&whole, w[2]), pick(2, "set"));
|
||||
// Every anchor direct-selected is every corner selected too.
|
||||
let mut every = selected(id);
|
||||
every.anchors.insert(id, (0..4).flat_map(anchors).collect());
|
||||
assert_eq!(click(&every, w[1]), pick(1, "set"));
|
||||
// Two corners picked: a Shift-click on either widget deselects that corner.
|
||||
let mut two = selected(id);
|
||||
two.anchors.insert(id, [0, 2].into_iter().flat_map(anchors).collect());
|
||||
let shown = pts(&CornerWidgets::of(&d, &two, 1.0, true).unwrap());
|
||||
assert_eq!(shown, [w[0], w[2]]);
|
||||
assert_eq!(click(&two, w[0]), pick(0, "toggle"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_selected_corners_show_their_widgets_alone_and_round_alone() {
|
||||
let (d, id) = live_rect(0.0, Affine::translate((100.0, 100.0)));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Parity with Adobe Illustrator
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-07 · **Change:** minor (Stroke gradients: where a variable-width stroke crosses or runs close by itself, a narrow part no longer paints over a wide part beside it, #5960; the canvas keeps selection outlines and anchors between frames; Indian-language hyphenation review and license allowlist, #4947; Stroke gradients: an arrowhead aligned to the tip is painted to the corners of its base, #5964; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-07 · **Change:** minor (Live Corners: with every corner selected, a Shift-click on a corner widget picks that corner instead of deselecting it, #5188; the canvas keeps selection outlines and anchors between frames; Indian-language hyphenation review and license allowlist, #4947; Stroke gradients: an arrowhead aligned to the tip is painted to the corners of its base, #5964; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
|
||||
|
||||
## Feature submission evidence
|
||||
|
||||
@@ -31,6 +31,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-07 | minor | Live Corners: with Direct Selection, while every corner of a path is selected (the path selected whole), a Shift-click on a corner widget picks that corner alone, as the first click of "click one widget, then Shift-click the others" does, instead of deselecting it and hiding its widget (#5188). Once some corners are picked, Shift-click still adds or removes one. Test: `corners::tests::a_shift_click_on_a_widget_of_a_whole_path_picks_its_corner`. Scores and estimates unchanged. |
|
||||
| 2026-11-03 | minor | Docked Tools stays between left-side panels and the canvas; redocking accepts canvas and window edges. Requested layout choice; scores and estimates unchanged. |
|
||||
| 2026-11-02 | minor | Selection QoL: saved marquee modes, screen drag direction with a 3 px dead zone, and translated mode/E feedback (#5313); VectorCraft addition, scores and estimates unchanged. |
|
||||
| 2026-11-07 | minor | The canvas keeps selection outlines, anchors and handles between frames, so an idle frame with a large selection redraws them as one mesh; scores and estimates unchanged |
|
||||
@@ -558,6 +559,7 @@ since 2026-10-01.
|
||||
- **Pattern-only backquote drags:** With Selection move, bounding-box scale or rotate, or the Rotate, Reflect, Scale or Shear tool, backquote held at mouse release applies the drag matrix to selected pattern paint while object geometry stays in place. The key may be pressed after mouse press; if released before the mouse button, the drag commits as an ordinary transform. Objects without pattern paint stay still; stroke-only patterns and patterned objects in mixed selections take the same matrix. Regression tests: `backquote_move_uses_release_state_and_one_commit` and `backquote_box_scale_and_rotate_use_pattern_matrix_at_release` in `select/tests.rs`, `backquote_drag_previews_and_commits_pattern_only_for_each_transform_tool` in `xform/transform.rs`, `selection_backquote_drag_changes_pattern_fill_and_stroke_without_moving_mixed_art` and `selection_backquote_drag_changes_stroke_only_pattern` in `tests/pattern.rs`, `egui_backtick_drag_uses_key_state_at_release` in `canvas/tests.rs`, and `synthetic_pointer_backquote_keeps_solid_object_in_place` in `control/tests.rs`. Scores and hours unchanged.
|
||||
- **Existing-guide Escape cancellation (G1):** Escape clears a pending guide press or drag through Selection and Direct/Group Selection, cancelling a begun preview. Eight app-input regressions cover both axes, move/copy, temporary selection-tool borrowing from idle Pen, preselected-guide document/interaction-start selection and Undo/Redo/saved-state preservation, later on-canvas motion/release, Shift-press cancellation, and ordinary-release/Enter/idle controls. Historical focused checks on `8ab574b7` recorded five expected RED failures with three passing controls, then all eight GREEN tests and the Direct Selection Escape control passed. All nine also passed in current full CI. A full CI retry on the five-unit combined source at `a0671777` passed all seven stages with 6,613 test outcomes on 2026-10-14. An earlier full-CI run on the identical source panicked in the randomized `ai_mutated_save_never_panics` test; the cause remains unresolved. This five-fix batch does not claim to repair that failure. The original 1280×960 headless app capture from `b076025a` was reviewed for one vertical Selection guide after Escape; source review found its scene unchanged on `a0671777`. Scores and hours unchanged.
|
||||
- **Escape in more tool drags:** Escape during an Anchor Point drag, a Mesh tool drag, a Direct Selection drag of a mesh point, a corner widget or the brackets of type on a path, or a Shape Builder drag cancels it: the art goes back to how the press found it, no undo step is added, the release does nothing, and the tool stays chosen. Escape during a Measure tool drag brings back the measurement before it. Regression tests: `escape_cancels_*` in `draw2/anchor/tests.rs`, `meshedit.rs`, `direct/tests.rs`, `builder/tests.rs` and `xform/measure.rs`, and `escape_cancels_an_anchor_point_mesh_shape_builder_or_measure_drag` in `tests/toolescape.rs`.
|
||||
- **Shift-click on corner widgets (#5188):** with Direct Selection, when every corner of a path is selected (the path selected whole), a Shift-click on a corner widget picks that corner alone, so the next drag rounds just it; once some corners are picked, Shift-click adds or removes a corner as on its anchor. Corner widgets are chosen by clicking one and Shift-clicking the others ([Logos By Nick, rounded corners in Illustrator](https://logosbynick.com/?p=15787)). Test: `corners::tests::a_shift_click_on_a_widget_of_a_whole_path_picks_its_corner`.
|
||||
- **TIFF in screen and asset export:** both format menus expose the existing TIFF encoder and Format Settings. Headless UI regressions choose TIFF and check scaled dimensions, artboard selection, filenames, encoding options, saved settings and collected-asset output. Public reference: [Export for Screens](https://helpx.adobe.com/illustrator/desktop/save-and-export/export-files-to-different-formats/export-for-screens.html), updated 2026-09-28, lists TIFF for both artboards and assets. Adobe staff [confirmed support in 30.2.1](https://illustrator.uservoice.com/forums/333657-illustrator-desktop-feature-requests/suggestions/49081034-add-tiff-format-into-export-for-screens-assets-d) on 2026-02-19; the older 2025 Collect assets guide omits TIFF.
|
||||
- **Export for Screens of the selected artboards:** while the Artboard tool is in use, File › Export › Export for Screens… (`file.exportForScreens`) opens with the artboards the tool has selected checked on its Artboards tab, and Export Artboard writes only those. Shift-clicks on the canvas, or Shift- or Ctrl/Cmd-clicks on the Artboards panel's rows, select several. The other fields show the settings the document last exported with. With another tool in use, the dialog takes its artboard choice from those settings too. Public reference: [Export selected artboards](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/organize-manage-artboards/export-artboards.html).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user