Type tool: Alt-click on a closed path makes area type too; type along a closed path stays with Type on a Path

This commit is contained in:
ham9zah
2026-11-02 14:36:09 +07:00
committed by bflatastic
parent 6d948f4679
commit 601f4854cd
5 changed files with 45 additions and 31 deletions
+5 -6
View File
@@ -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();
}
+3 -3
View File
@@ -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."
+10 -13
View File
@@ -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<Point>,
/// 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<Action> {
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(
+23 -4
View File
@@ -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]
+4 -5
View File
@@ -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,