From 601f4854cd26759fe2f7b609a2b3a2ab4cfa3dcb Mon Sep 17 00:00:00 2001 From: ham9zah Date: Fri, 30 Oct 2026 13:47:43 +0300 Subject: [PATCH] Type tool: Alt-click on a closed path makes area type too; type along a closed path stays with Type on a Path --- crates/engine/src/tests/pathtype.rs | 11 +++++------ crates/tools/src/cursors.toml | 6 +++--- crates/tools/src/text.rs | 23 ++++++++++------------- crates/tools/src/text/tests.rs | 27 +++++++++++++++++++++++---- crates/tools/src/text/tool.rs | 9 ++++----- 5 files changed, 45 insertions(+), 31 deletions(-) diff --git a/crates/engine/src/tests/pathtype.rs b/crates/engine/src/tests/pathtype.rs index adf3232f7..5ead0cbd2 100644 --- a/crates/engine/src/tests/pathtype.rs +++ b/crates/engine/src/tests/pathtype.rs @@ -278,21 +278,20 @@ fn direct_selection_drags_an_anchor_of_the_path_and_the_type_reflows() { assert_eq!(path(&s).subpaths[0].anchors[1].p, middle); } -/// Type on a path made by clicking a path with the plain Type tool (an open path, or a closed one -/// with Alt held) shows its brackets as soon as the Selection or Direct Selection tool is chosen, -/// and its centre bracket, dragged across the path, flips it. +/// Type on a path made by clicking a path with the plain Type tool (an open path) or Type on a +/// Path (a closed one) shows its brackets as soon as the Selection or Direct Selection tool is +/// chosen, and its centre bracket, dragged across the path, flips it. #[test] fn type_on_a_path_made_with_the_type_tool_has_brackets_at_once() { let v = ViewInfo::default(); - let alt = Mods { alt: true, ..Mods::default() }; for (tool, d, mods) in [ ("selection", "M100 300 L500 300", Mods::default()), ("directSelection", "M100 300 L500 300", Mods::default()), - ("selection", "M100 300 L500 300 L500 500 L100 500 Z", alt), + ("selection", "M100 300 L500 300 L500 500 L100 500 Z", Mods::default()), ] { let mut s = session(); s.execute("path.create", &json!({ "d": d })).unwrap(); - s.select_tool("type", v).unwrap(); + s.select_tool(if d.ends_with('Z') { "typeOnPath" } else { "type" }, v).unwrap(); for kind in [PointerKind::Down, PointerKind::Up] { s.pointer(&PointerEvent::new(kind, 300.0, 300.0).with_mods(mods), v).unwrap(); } diff --git a/crates/tools/src/cursors.toml b/crates/tools/src/cursors.toml index 53f298f72..5447261e6 100644 --- a/crates/tools/src/cursors.toml +++ b/crates/tools/src/cursors.toml @@ -257,21 +257,21 @@ look = "The Text I-beam turned a quarter turn." [[cursor]] id = "TypeArea" group = "Type" -shown_when = ["Type and Area Type over a closed path's outline, Alt up for Type: a click sets area type inside the path."] +shown_when = ["Type over a closed path's outline, and Area Type over one with Alt up: a click sets area type inside the path."] hotspot = "The I-beam's centre." look = "The Text I-beam with a small badge below right: a square frame with three short lines of type in it." [[cursor]] id = "TypeAreaVertical" group = "Type" -shown_when = ["Vertical Type and Vertical Area Type over a closed path's outline, Alt up for Vertical Type: a click sets vertical area type inside the path."] +shown_when = ["Vertical Type over a closed path's outline, and Vertical Area Type over one with Alt up: a click sets vertical area type inside the path."] hotspot = "The I-beam's centre." look = "TypeArea turned a quarter turn." [[cursor]] id = "TypeOnPath" group = "Type" -shown_when = ["Type over an open path's outline, or a closed one with Alt held; Type on a Path over any path's outline; Area Type over an open path with Alt held: a click sets type along the path."] +shown_when = ["Type over an open path's outline; Type on a Path over any path's outline (Alt up); Area Type over an open path with Alt held: a click sets type along the path."] hotspot = "The I-beam's centre." look = "The Text I-beam with a small badge below right: a short smooth curve with a dot on it." diff --git a/crates/tools/src/text.rs b/crates/tools/src/text.rs index 6e3895d57..7fec4052b 100644 --- a/crates/tools/src/text.rs +++ b/crates/tools/src/text.rs @@ -6,9 +6,8 @@ //! the Type tool […] and click anywhere on the object's path", [Create text](https://web.archive.org/web/20220127090642/https://helpx.adobe.com/illustrator/using/creating-text.html); //! "To create horizontal text along a path, select the Type tool or the Type On A Path tool […] If //! the path is closed, rather than open, you must use the Type On A Path tool", [Create type on a -//! path](https://web.archive.org/web/20220127000000/https://helpx.adobe.com/illustrator/using/creating-type-path.html)). -//! Alt (Option) held over a closed path sets type along it instead: our own shortcut, so the Type -//! tool reaches both without switching tools. +//! path](https://web.archive.org/web/20220127000000/https://helpx.adobe.com/illustrator/using/creating-type-path.html)), +//! Alt or not: type along a closed path stays with Type on a Path. //! Area Type / Type on a Path: click a path to turn it into a text frame or a baseline //! (`text.createInPath`). While type is being edited, a click that lands on no type only //! ends the editing, and the next click makes new type. Point type placed by a click and left @@ -186,8 +185,6 @@ pub struct TypeTool { drag: Option, /// The Type tool the press acts as, with the keys held at the press ([`Self::acting`]). held: Option<(Mode, bool)>, - /// Alt (Option) was held at the press: the Type tool sets type along a closed path, not in it. - held_alt: bool, /// Smart Guides for where new type goes. snap: DrawSnap, /// The press landed in the edited text: dragging selects. @@ -619,9 +616,10 @@ impl TypeTool { self.caret = r.end; } - /// The path whose outline is under `p`, and whether it is closed. + /// The path whose outline is under `p`, and whether it is closed: within the isolated group + /// being edited, and no guide while guides are hidden or locked ([`ToolContext::hit_options`]). fn path_at(cx: &ToolContext, p: Point) -> Option<(NodeId, bool)> { - let h = hit_test(cx.doc, p, vectorcraft_doc::hit::HitOptions { tol: cx.tol(4.0), outline: true, ..Default::default() })?; + let h = hit_test(cx.doc, p, vectorcraft_doc::hit::HitOptions { tol: cx.tol(4.0), outline: true, ..cx.hit_options() })?; match &cx.doc.node(h.leaf)?.kind { NodeKind::Path { path, .. } => Some((h.leaf, path.is_closed())), _ => None, @@ -630,12 +628,12 @@ impl TypeTool { /// What a click at `p` with the tool acting as `mode` makes of the path there: the path, and /// whether the type goes along it (else inside it); None when it makes no type in a path. The - /// Type tools set type inside a closed path and along an open one, or along a closed one with - /// `alt`; Area Type needs a closed path and Type on a Path takes either. - fn in_path(cx: &ToolContext, p: Point, mode: Mode, alt: bool) -> Option<(NodeId, bool)> { + /// Type tools set type inside a closed path and along an open one; Area Type needs a closed + /// path and Type on a Path takes either. + fn in_path(cx: &ToolContext, p: Point, mode: Mode) -> Option<(NodeId, bool)> { let (id, closed) = Self::path_at(cx, p)?; match mode { - Mode::Type => Some((id, alt || !closed)), + Mode::Type => Some((id, !closed)), Mode::Area => closed.then_some((id, false)), Mode::OnPath => Some((id, true)), } @@ -662,7 +660,6 @@ impl TypeTool { /// (#2871). A drag that draws an area-type frame makes it either way. fn on_up(&mut self, cx: &ToolContext, start: Point, at: Point) -> Vec { let (mode, vertical) = self.held.take().unwrap_or((self.mode, self.vertical)); - let alt = std::mem::take(&mut self.held_alt); // Click into existing text: place the caret. if let Some(out) = self.edit_at(cx, start) { return out; @@ -686,7 +683,7 @@ impl TypeTool { } // A click on a path: type in it or along it. if drag.is_none() - && let Some((pid, on_path)) = Self::in_path(cx, start, mode, alt) + && let Some((pid, on_path)) = Self::in_path(cx, start, mode) { let mode = if on_path { "onPath" } else { "area" }; out.push(Action::Exec( diff --git a/crates/tools/src/text/tests.rs b/crates/tools/src/text/tests.rs index 2a2e899e9..ab3229809 100644 --- a/crates/tools/src/text/tests.rs +++ b/crates/tools/src/text/tests.rs @@ -817,9 +817,9 @@ fn the_pointer_shows_the_type_tool_shift_or_alt_switches_to() { } /// The plain Type tools make type in or along a path clicked on its outline: area type inside a -/// closed path, type on a path along an open one or, with Alt held, along a closed one. A click away -/// from paths still makes point type, a drag still draws a frame (even from a path's outline), and -/// a click on existing type edits it. The pointer says which. +/// closed path (Alt held or not: type along a closed path is Type on a Path's) and type on a path +/// along an open one. A click away from paths still makes point type, a drag still draws a frame +/// (even from a path's outline), and a click on existing type edits it. The pointer says which. #[test] fn the_type_tool_makes_type_in_or_along_a_clicked_path() { let (mut d, rect, line) = doc_with_rect_and_line(); @@ -831,7 +831,7 @@ fn the_type_tool_makes_type_in_or_along_a_clicked_path() { for (at, mods, path, mode, cursor) in [ (on_rect, Mods::default(), rect, "area", if vertical { Cursor::TypeAreaVertical } else { Cursor::TypeArea }), (on_line, Mods::default(), line, "onPath", if vertical { Cursor::TypeOnPathVertical } else { Cursor::TypeOnPath }), - (on_rect, ALT, rect, "onPath", if vertical { Cursor::TypeOnPathVertical } else { Cursor::TypeOnPath }), + (on_rect, ALT, rect, "area", if vertical { Cursor::TypeAreaVertical } else { Cursor::TypeArea }), (on_line, ALT, line, "onPath", if vertical { Cursor::TypeOnPathVertical } else { Cursor::TypeOnPath }), ] { assert_eq!(TypeTool::new(id).cursor(&c, Point::new(at.0, at.1), mods), cursor, "{id} {at:?} {mods:?}"); @@ -874,6 +874,25 @@ fn the_type_tool_makes_type_in_or_along_a_clicked_path() { assert_eq!(tool.editing, Some(tid)); } +/// A guide is no path to set type in or along while guides are hidden or locked: the click makes +/// point type, as it does on the selection tools' terms. +#[test] +fn the_type_tool_leaves_hidden_or_locked_guides_alone() { + let (mut d, rect, _) = doc_with_rect_and_line(); + if let Some(NodeKind::Path { guide, .. }) = d.node_mut(rect).map(|n| &mut n.kind) { + *guide = true; + } + let (sel, p) = (Selection::default(), paint()); + let mut c = cx(&d, &sel, &p); + c.guides = false; + assert_eq!(TypeTool::new("type").cursor(&c, Point::new(100.0, 150.0), Mods::default()), Cursor::Text); + let acts = gesture(&mut TypeTool::new("type"), &c, (100.0, 150.0), None, Mods::default(), Mods::default()); + assert_eq!(made(&acts).map(|(cmd, _)| cmd), Some("text.create"), "{acts:?}"); + c.guides = true; + let acts = gesture(&mut TypeTool::new("type"), &c, (100.0, 150.0), None, Mods::default(), Mods::default()); + assert_eq!(made(&acts).map(|(cmd, _)| cmd), Some("text.createInPath"), "{acts:?}"); +} + /// While type is being edited, Shift and Alt switch no tool: a frame dragged out is the tool /// chosen's, and the pointer stays its own. #[test] diff --git a/crates/tools/src/text/tool.rs b/crates/tools/src/text/tool.rs index b20f924d6..c983205da 100644 --- a/crates/tools/src/text/tool.rs +++ b/crates/tools/src/text/tool.rs @@ -55,7 +55,6 @@ impl Tool for TypeTool { } self.press = Some((ev.pos, self.snap.press(cx, ev.pos, self.editing.as_slice(), None))); self.held = Some(self.acting(ev.mods)); - self.held_alt = ev.mods.alt && self.editing.is_none(); self.drag = None; vec![] } @@ -633,12 +632,12 @@ impl Tool for TypeTool { /// The pointer of the Type tool that a press would act as with the keys held, or, while a /// press is held, of the one it acts as ([`TypeTool::acting`]). fn cursor(&self, cx: &ToolContext, p: Point, m: Mods) -> Cursor { - let ((mode, vertical), alt) = match (self.press, self.held) { - (Some(_), Some(held)) => (held, self.held_alt), - _ => (self.acting(m), m.alt && self.editing.is_none()), + let (mode, vertical) = match (self.press, self.held) { + (Some(_), Some(held)) => held, + _ => self.acting(m), }; // While type is being edited a click on a path only ends the editing. - let in_path = if self.editing.is_some() { None } else { Self::in_path(cx, p, mode, alt) }; + let in_path = if self.editing.is_some() { None } else { Self::in_path(cx, p, mode) }; match (in_path, vertical) { (Some((_, false)), false) => Cursor::TypeArea, (Some((_, false)), true) => Cursor::TypeAreaVertical,