Merge pull request #284 from theironchef/fix/double-click-type-caret

Canvas: double-clicking type with a selection tool puts the caret there
This commit is contained in:
BFlat 2026-10-07 13:31:10 +07:00 committed by GitHub
commit 6a62dc58ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
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.
- **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.
- **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.
- **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.

View File

@ -576,7 +576,15 @@ fn handle_input(app: &mut VectorcraftApp, ui: &Ui, resp: &egui::Response, rect:
&& let Some(p) = hover
{
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);
// 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() {
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:?}");
}
#[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]
fn middle_drag_pans_the_view_with_any_tool() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());