Fix #283: double-clicking type with a selection tool puts the caret there

The Selection tool's double-click on text only switched to the Type
tool, which then had nothing being edited, so typing went to the tool
shortcuts (X swapped the text's fill and stroke). After such a switch
the canvas passes the double-click on to the Type tool as a click, which
starts editing the type with the caret where it was clicked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
theironchef 2026-10-06 22:48:20 -07:00 committed by bflatastic
parent b6649a3fad
commit 11799fa0cc
2 changed files with 48 additions and 0 deletions

View File

@ -110,6 +110,7 @@ grade by behaviour against `plan/illustrator/`, not by whether a menu item exist
- **Live Corners:** drag a live rectangle's corner widgets (Selection or Direct Selection) to round all corners, with a radius readout. - **Live Corners:** drag a live rectangle's corner widgets (Selection or Direct Selection) to round all corners, with a radius readout.
- **Graphs:** Column, Stacked Column, Bar, Stacked Bar, Line, Area, Scatter, Pie and Radar graph tools with Graph Data and Graph Type. - **Graphs:** Column, Stacked Column, Bar, Stacked Bar, Line, Area, Scatter, Pie and Radar graph tools with Graph Data and Graph Type.
- **Other:** Eyedropper, Gradient annotator, Artboard, Measure, Type, Hand, Zoom, Rotate View. - **Other:** Eyedropper, Gradient annotator, Artboard, Measure, Type, Hand, Zoom, Rotate View.
- **Editing type:** double-clicking type with a selection tool switches to the Type tool with the caret where it was clicked.
- **Temporary tools:** Cmd with any other tool drags with the selection tool used last (Selection, Direct Selection or Group Selection). Held Space pans and Cmd+Space zooms (Cmd+Alt+Space out) whatever the tool. - **Temporary tools:** Cmd with any other tool drags with the selection tool used last (Selection, Direct Selection or Group Selection). Held Space pans and Cmd+Space zooms (Cmd+Alt+Space out) whatever the tool.
- **Tool double-clicks:** double-clicking a tool's button opens its options: Hand fits the artboard in the window, Zoom shows 100 %, Rotate, Scale, Reflect and Shear open their Object › Transform dialogs, Gradient, Eyedropper, Blend and the Liquify tools their panels or options. - **Tool double-clicks:** double-clicking a tool's button opens its options: Hand fits the artboard in the window, Zoom shows 100 %, Rotate, Scale, Reflect and Shear open their Object › Transform dialogs, Gradient, Eyedropper, Blend and the Liquify tools their panels or options.
- **Drawing aids:** Smart Guides and snapping, guides dragged out of the rulers, and Draw Normal / Behind / Inside modes. - **Drawing aids:** Smart Guides and snapping, guides dragged out of the rulers, and Draw Normal / Behind / Inside modes.

View File

@ -576,7 +576,15 @@ fn handle_input(app: &mut VectorcraftApp, ui: &Ui, resp: &egui::Response, rect:
&& let Some(p) = hover && let Some(p) = hover
{ {
let ev = PointerEvent { kind: PointerKind::DoubleClick, pos: xf.to_doc(p), mods: mods(m, space), pressure: 1.0 }; let ev = PointerEvent { kind: PointerKind::DoubleClick, pos: xf.to_doc(p), mods: mods(m, space), pressure: 1.0 };
let before = app.session.tool_id().to_string();
dispatch(app, &ev, view); dispatch(app, &ev, view);
// A double-click on type switched a selection tool to the Type tool: the caret goes where
// the type was clicked, so typing edits it at once.
if before != "type" && app.session.tool_id() == "type" {
for kind in [PointerKind::Down, PointerKind::Up] {
dispatch(app, &PointerEvent { kind, ..ev }, view);
}
}
} }
if drag.is_some() || pointer.is_moving() { if drag.is_some() || pointer.is_moving() {
ui.ctx().request_repaint(); ui.ctx().request_repaint();
@ -1638,6 +1646,45 @@ mod tests {
assert!((after.center.x - (before.center.x - 30.0 / before.zoom)).abs() < 1e-6, "{before:?} → {after:?}"); assert!((after.center.x - (before.center.x - 30.0 / before.zoom)).abs() < 1e-6, "{before:?} → {after:?}");
} }
#[test]
fn double_clicking_type_with_the_selection_tool_puts_the_caret_there() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
let id = app.session.execute("text.create", &json!({"x": 100, "y": 150, "text": "Hello world", "size": 24})).unwrap()["id"].as_u64().unwrap();
app.session.execute("select.none", &json!({})).unwrap();
app.select_tool("selection");
let ctx = egui::Context::default();
let timed = |app: &mut VectorcraftApp, time: f64, events: Vec<egui::Event>| {
let screen = egui::Rect::from_min_size(Pos2::ZERO, vec2(800.0, 600.0));
let mut out =
ctx.run_ui(egui::RawInput { screen_rect: Some(screen), time: Some(time), events, ..Default::default() }, |ui| show(app, ui));
out.textures_delta.clear();
};
timed(&mut app, 0.0, vec![]);
let xf = Xf::new(app.canvas_rect.unwrap(), app.view().unwrap());
let text = |app: &VectorcraftApp| match &app.session.active().unwrap().doc.node(vectorcraft_doc::NodeId(id)).unwrap().kind {
NodeKind::Text(t) => t.plain_text(),
_ => panic!("not text"),
};
// Double-click the middle of the text.
let b = app.session.active().unwrap().doc.node(vectorcraft_doc::NodeId(id)).unwrap().geometric_bounds().unwrap();
let at = xf.to_screen(b.center());
let button = |pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
timed(&mut app, 1.0, vec![egui::Event::PointerMoved(at), button(true), button(false), button(true), button(false)]);
timed(&mut app, 1.1, vec![]);
// The Type tool edits the text, with the caret inside it, where it was clicked: typing goes
// into the text, not to tool shortcuts (X would swap fill and stroke).
assert_eq!(app.session.tool_id(), "type");
assert!(app.session.tool_wants_text());
let o = app.session.tool_options();
assert_eq!(o["editing"], json!(id));
let caret = o["caret"].as_u64().unwrap();
assert!((1..11).contains(&caret), "caret {caret}");
app.session.tool_text("X", app.view_info()).unwrap();
let t = text(&app);
assert_eq!((t.len(), t.find('X')), (12, Some(caret as usize)), "{t}");
}
#[test] #[test]
fn middle_drag_pans_the_view_with_any_tool() { fn middle_drag_pans_the_view_with_any_tool() {
let mut app = VectorcraftApp::new(Session::new(), Default::default()); let mut app = VectorcraftApp::new(Session::new(), Default::default());