From c60cb4a9c2546ae9309ea5b64b6978476b37a4aa Mon Sep 17 00:00:00 2001 From: theironchef <6278036+theironchef@users.noreply.github.com> Date: Tue, 6 Oct 2026 19:24:22 -0700 Subject: [PATCH] Type tool: a drag across text that isn't being edited selects it A press only started a selection inside the text being edited; on other type it waited for the release, which placed the caret at the press and ignored the drag, so the first drag across a word never selected it. A press on type now starts editing it there (the same code a click used on release), so the drag selects at once; a plain click still just places the caret. Fixes #245. Co-Authored-By: Claude Opus 5.5 --- crates/tools/src/text.rs | 31 ++++++++++++++++++++++--------- crates/tools/src/text_tests.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/crates/tools/src/text.rs b/crates/tools/src/text.rs index a152b388..47f46d86 100644 --- a/crates/tools/src/text.rs +++ b/crates/tools/src/text.rs @@ -235,19 +235,26 @@ impl TypeTool { } } - fn on_up(&mut self, cx: &ToolContext, start: Point) -> Vec { + /// Start editing the type under `p` with the caret there → the actions (ending the previous + /// edit, selecting the type); None when no type is under `p`. + fn edit_at(&mut self, cx: &ToolContext, p: Point) -> Option> { + let h = hit_test(cx.doc, p, cx.hit_options())?; + let Some(NodeKind::Text(t)) = cx.doc.node(h.leaf).map(|n| &n.kind) else { return None }; + let lay = self.layout(t); + let byte = vectorcraft_text::hit_byte(&lay, t.xf.inverse() * p); let mut out = self.finish(cx); + self.start_editing(h.leaf, byte); + self.clicks = (Some(p), 1); + out.push(Action::Exec("select.set".into(), json!({"ids": [h.leaf.0]}))); + Some(out) + } + + fn on_up(&mut self, cx: &ToolContext, start: Point) -> Vec { // Click into existing text: place the caret. - if let Some(h) = hit_test(cx.doc, start, cx.hit_options()) - && let Some(NodeKind::Text(t)) = cx.doc.node(h.leaf).map(|n| &n.kind) - { - let lay = self.layout(t); - let byte = vectorcraft_text::hit_byte(&lay, t.xf.inverse() * start); - self.start_editing(h.leaf, byte); - self.clicks = (Some(start), 1); - out.push(Action::Exec("select.set".into(), json!({"ids": [h.leaf.0]}))); + if let Some(out) = self.edit_at(cx, start) { return out; } + let mut out = self.finish(cx); let drag = self.drag.take(); // Area Type / Type on a Path: click a path. if self.mode != Mode::Type && drag.is_none() { @@ -357,6 +364,12 @@ impl Tool for TypeTool { return out; } self.clicks = (None, 0); + // A press on other type edits it from there, so a drag selects its text at once, + // as it does in the type being edited. + if let Some(out) = self.edit_at(cx, ev.pos) { + self.selecting = true; + return out; + } self.press = Some(ev.pos); self.drag = None; vec![] diff --git a/crates/tools/src/text_tests.rs b/crates/tools/src/text_tests.rs index 8188f40c..cbc69110 100644 --- a/crates/tools/src/text_tests.rs +++ b/crates/tools/src/text_tests.rs @@ -176,6 +176,33 @@ fn drag_selects_and_overlay_highlights() { assert_eq!((opts["start"].as_u64(), opts["end"].as_u64()), (Some(7), Some(9))); } +#[test] +fn a_drag_across_type_not_being_edited_selects_its_text() { + let (d, id) = doc_with_text("Select me please"); + let mut tool = TypeTool::new("type"); + let (sel, p) = (Selection::default(), paint()); + let c = cx(&d, &sel, &p); + let t = TypeTool::text(&c, id).unwrap().clone(); + let lay = vectorcraft_text::layout(FontDb::global(), &t); + let pt = |b: usize| { + let (a, bb) = vectorcraft_text::caret_position(&lay, b); + t.xf * a.midpoint(bb) + }; + let (a, b) = (pt(7), pt(9)); + // The press starts editing the type and the drag selects, in one gesture. + let out = tool.pointer(&c, &PointerEvent::new(PointerKind::Down, a.x + 0.1, a.y)); + assert!(out.contains(&Action::Exec("select.set".into(), json!({"ids": [id.0]})))); + tool.pointer(&c, &PointerEvent::new(PointerKind::Drag, b.x + 0.1, b.y)); + tool.pointer(&c, &PointerEvent::new(PointerKind::Up, b.x + 0.1, b.y)); + assert_eq!(tool.editing, Some(id)); + assert_eq!(tool.sel(), (7, 9)); + // A plain click still just places the caret. + let mut tool = TypeTool::new("type"); + tool.pointer(&c, &PointerEvent::new(PointerKind::Down, a.x + 0.1, a.y)); + tool.pointer(&c, &PointerEvent::new(PointerKind::Up, a.x + 0.1, a.y)); + assert_eq!((tool.editing, tool.sel()), (Some(id), (7, 7))); +} + #[test] fn select_all_and_styled_paste() { let (d, _, mut tool) = editing("abc");