diff --git a/ROADMAP.md b/ROADMAP.md index e3a7eee4..ed6ba219 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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. diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 0add95c1..41fc86b1 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -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| { + 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());