Merge pull request #246 from theironchef/fix/type-drag-select

Type tool: a drag across text that isn't being edited selects it
This commit is contained in:
BFlat 2026-10-07 10:55:36 +07:00 committed by GitHub
commit cc4992172a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 49 additions and 9 deletions

View File

@ -235,19 +235,26 @@ impl TypeTool {
} }
} }
fn on_up(&mut self, cx: &ToolContext, start: Point) -> Vec<Action> { /// 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<Vec<Action>> {
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); 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<Action> {
// Click into existing text: place the caret. // Click into existing text: place the caret.
if let Some(h) = hit_test(cx.doc, start, cx.hit_options()) if let Some(out) = self.edit_at(cx, start) {
&& 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]})));
return out; return out;
} }
let mut out = self.finish(cx);
let drag = self.drag.take(); let drag = self.drag.take();
// Area Type / Type on a Path: click a path. // Area Type / Type on a Path: click a path.
if self.mode != Mode::Type && drag.is_none() { if self.mode != Mode::Type && drag.is_none() {
@ -357,6 +364,12 @@ impl Tool for TypeTool {
return out; return out;
} }
self.clicks = (None, 0); 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.press = Some(ev.pos);
self.drag = None; self.drag = None;
vec![] vec![]

View File

@ -176,6 +176,33 @@ fn drag_selects_and_overlay_highlights() {
assert_eq!((opts["start"].as_u64(), opts["end"].as_u64()), (Some(7), Some(9))); 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] #[test]
fn select_all_and_styled_paste() { fn select_all_and_styled_paste() {
let (d, _, mut tool) = editing("abc"); let (d, _, mut tool) = editing("abc");