diff --git a/crates/tools/src/direct.rs b/crates/tools/src/direct.rs index 6023b774f..edafcd86e 100644 --- a/crates/tools/src/direct.rs +++ b/crates/tools/src/direct.rs @@ -486,10 +486,8 @@ impl Tool for DirectSelectionTool { if crate::rulerguide::guide_at(cx, p).is_some() { return vec![]; } - if hit_test(cx.doc, p, cx.selection_hit_options()) - .and_then(|h| cx.doc.node(h.leaf)) - .is_some_and(|n| matches!(n.kind, NodeKind::Text(_))) - { + // A double-click edits what is drawn: not a clipping path's outline. + if hit_test(cx.doc, p, cx.hit_options()).and_then(|h| cx.doc.node(h.leaf)).is_some_and(|n| matches!(n.kind, NodeKind::Text(_))) { return vec![Action::SwitchTool("type".into())]; } vec![] @@ -2042,6 +2040,16 @@ mod tests { } } + /// Direct Selection's double-click on type where a layer's clipping mask edge runs through it + /// edits the type, though a press there takes the mask's segment. + #[test] + fn a_double_click_on_type_at_a_layer_masks_edge_edits_it() { + let (d, _, at) = doc_with_type_on_a_layer_masks_edge(); + let (s, p) = (Selection::default(), paint()); + let acts = DirectSelectionTool::new(false).pointer(&cx(&d, &s, &p), &PointerEvent::new(PointerKind::DoubleClick, at.x, at.y)); + assert_eq!(acts, vec![Action::SwitchTool("type".into())]); + } + #[test] fn double_clicking_type_switches_to_type_tool() { use std::sync::Arc; diff --git a/crates/tools/src/lib.rs b/crates/tools/src/lib.rs index 868da378b..e7a80ccfc 100644 --- a/crates/tools/src/lib.rs +++ b/crates/tools/src/lib.rs @@ -419,6 +419,8 @@ impl ToolContext<'_> { /// [`Self::hit_options`] for the selection tools (Selection, Direct Selection, Group /// Selection): a clipping path is also hit on its outline ([`HitOptions::clip_outlines`]). + /// Only for what a press or a hover selects: a double-click, loaded text or a guide's release + /// acts on what is drawn and takes [`Self::hit_options`]. /// /// [`HitOptions::clip_outlines`]: vectorcraft_doc::hit::HitOptions::clip_outlines pub fn selection_hit_options(&self) -> vectorcraft_doc::hit::HitOptions { @@ -887,6 +889,33 @@ pub(crate) mod testutil { (d, g, clip, art) } + /// A new layer of `d`, above the others, with the unpainted rectangle `mask` as its clipping + /// mask and `art` clipped by it: the layer and the clipping mask. + pub fn add_masked_layer(d: &mut Document, mask: Rect, art: Node) -> (NodeId, NodeId) { + let l = d.add_layer(Some("Masked")); + let clip = d.alloc_id(); + let mut path = Node::path(clip, shapes::rectangle(mask), Default::default()); + if let vectorcraft_doc::NodeKind::Path { clipping, .. } = &mut path.kind { + *clipping = true; + } + d.insert(Some(l), 0, path).unwrap(); + d.insert(Some(l), 1, art).unwrap(); + d.node_mut(l).unwrap().set_clips(true); + (l, clip) + } + + /// A layer clipped to (250, 250)–(360, 400), its mask's right edge through 120 × 40 area type + /// at (300, 300): the type, and a point on it just inside the mask's edge, within the + /// selection tolerance, where the selection tools take the mask. + pub fn doc_with_type_on_a_layer_masks_edge() -> (Document, NodeId, Point) { + let (mut d, _) = doc_with_rect(); + let id = d.alloc_id(); + let mut t = vectorcraft_doc::TextObject::point(Point::new(300.0, 300.0), "Some words", Default::default()); + t.kind = vectorcraft_doc::TextKind::Area { frame: shapes::rectangle(Rect::new(0.0, 0.0, 120.0, 40.0)) }; + add_masked_layer(&mut d, Rect::new(250.0, 250.0, 360.0, 400.0), Node::new(id, vectorcraft_doc::NodeKind::Text(Box::new(t)))); + (d, id, Point::new(358.5, 320.0)) + } + /// [`doc_with_rect`] plus 120 × 40 area type at (300, 300). pub fn doc_with_area_type() -> (Document, NodeId) { let (mut d, _) = doc_with_rect(); diff --git a/crates/tools/src/select.rs b/crates/tools/src/select.rs index d3b4bfdaf..18cce0d67 100644 --- a/crates/tools/src/select.rs +++ b/crates/tools/src/select.rs @@ -372,9 +372,9 @@ fn thread_params(cx: &ToolContext, from: NodeId, start: Point, end: Point) -> Va let frame = |n: &vectorcraft_doc::Node| { is_area_type(n) || matches!(&n.kind, vectorcraft_doc::NodeKind::Path { path, .. } if path.subpaths.first().is_some_and(|s| s.closed)) }; - let to = hit_test(cx.doc, start, cx.selection_hit_options()) - .map(|h| h.top_object(cx.isolation)) - .filter(|id| *id != from && cx.doc.node(*id).is_some_and(frame)); + // The art the text flows into: what is drawn, not a clipping path's outline. + let to = + hit_test(cx.doc, start, cx.hit_options()).map(|h| h.top_object(cx.isolation)).filter(|id| *id != from && cx.doc.node(*id).is_some_and(frame)); match to { Some(to) => json!({ "from": from.0, "to": to.0 }), None => json!({ "from": from.0, "at": [start.x, start.y] }), @@ -489,7 +489,8 @@ impl Tool for SelectionTool { if crate::rulerguide::guide_at(cx, p).is_some() { return vec![]; } - if let Some(h) = hit_test(cx.doc, p, cx.selection_hit_options()) { + // A double-click edits what is drawn: not a clipping path's outline. + if let Some(h) = hit_test(cx.doc, p, cx.hit_options()) { let top = h.top_object(cx.isolation); if cx.doc.node(top).is_some_and(|n| matches!(n.kind, vectorcraft_doc::NodeKind::Text(_))) { return vec![Action::SwitchTool("type".into())]; @@ -845,7 +846,7 @@ pub(crate) fn release_guide_at(cx: &ToolContext, p: Point, m: Mods) -> Option( type_path_only: prefs.type_selection_by_path_only, scope: app.session.active().and_then(|st| st.isolation), skip_guides: !app.ui.view.guides, + ..Default::default() }; test(&app.session.active()?.doc, p, opt) } diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 468dd4a1f..f846a3f83 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -404,7 +404,7 @@ since 2026-10-01. - **Selected Mirror Repeat handles:** a headless canvas regression creates the default mirror repeat and drags its Top bounding-box handle where the axis crosses it; the repeat grows vertically while its width stays fixed. Existing mirror-axis checks cover center movement and end-handle hit priority. The focused UI mirror tests pass. Scores and hours unchanged. - **Warp Options (#1760):** Effect > Warp and existing Appearance effect links share the fifteen existing styles, original schematic previews, orientation choices and percent Bend/Distortion controls. Focused tests passed for real numeric entry and slider input, Preview/Cancel, immediate Enter/OK, mixed-style edits, target/slot/visibility preservation and Undo/Redo. Each applied style stays unchanged until an explicit Style choice. Original style-menu and live-preview captures were reviewed; their numeric values were supplied programmatically. Public references: [Reshaping with effects](https://helpx.adobe.com/illustrator/using/reshaping-objects-effects.html) and [Using effects](https://helpx.adobe.com/illustrator/using/effects.html). Warp geometry, optional Properties integration and remembered defaults are unchanged; no broader parity claim. -- **Clipping paths on their outline (G1):** the selection tools (Selection, Direct Selection, Group Selection), Object Highlighting and a right-click take a clipping path on its outline before the art it clips; Select Behind lists the same; the eyedropper and the tools that act on what is drawn take the art. Doc and tool regression tests; score unchanged. +- **Clipping paths on their outline (G1):** the selection tools (Selection, Direct Selection, Group Selection), Object Highlighting and a right-click take a clipping path on its outline before the art it clips; Select Behind lists the same; the eyedropper, the tools that act on what is drawn and the selection tools' double-clicks, loaded text and guide release take the art. Doc and tool regression tests; score 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. - **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).