Double-click on text, mtext or an attribute definition starts TEXTEDIT on it (part of #842) (#868)

This commit is contained in:
Marco Arnulfo authored and GitHub committed 2026-10-13 04:38:18 -04:00
1 parent 5b0238fc26
commit 2effc8bf3e
2 files changed
+104

No files matched your search

+26
View File
@@ -1207,6 +1207,15 @@ pub fn show(app: &mut CadApp, ui: &mut egui::Ui) {
return;
}
}
// Double-click text to edit it, as in AutoCAD: TEXTEDIT with that object already picked.
if inside
&& dbl_primary
&& app.session.running.is_none()
&& let Some(hp) = hover_pos
&& edit_text_at(app, xf.to_world(hp))
{
return;
}
// Double-click a hatch (no command running) to edit it in the Hatch Edit dialog. The click is
// used up: it must not also start a grip edit on the hatch's centre grip below.
@@ -1627,6 +1636,23 @@ pub fn show(app: &mut CadApp, ui: &mut egui::Ui) {
}
/// The model space tiles on screen (none without tiling): each tile's rectangle in `full`.
/// Start TEXTEDIT on the text, mtext or attribute definition at `p` (the objects its shortcut menu
/// offers Edit Text for); false when there is none.
fn edit_text_at(app: &mut CadApp, p: Vec2) -> bool {
use cadcraft_engine::doc::EntityKind;
let is_text = cadcraft_engine::cmd::curves::pick_at(&app.session, p).and_then(|h| {
app.session.doc().ok()?.entity(h).map(|e| matches!(e.kind, EntityKind::Text(_) | EntityKind::MText(_) | EntityKind::AttDef(_)))
});
if is_text != Some(true) {
return false;
}
app.start("textedit");
if let Err(e) = app.session.input(Input::Point(p)) {
app.session.echo(e.to_string());
}
true
}
fn tile_rects(app: &CadApp, full: Rect) -> Vec<(Rect, cadcraft_engine::ModelTile)> {
let x = |f: f64| full.left() + full.width() * f.clamp(0.0, 1.0) as f32;
let y = |f: f64| full.bottom() - full.height() * f.clamp(0.0, 1.0) as f32;
+78
View File
@@ -0,0 +1,78 @@
//! Double-clicking text with no command running edits it, as in AutoCAD (#842).
use cadcraft_engine::doc::EntityKind;
use cadcraft_engine::geom::Vec2;
use cadcraft_engine::{Input, Session};
use cadcraft_ui_egui::{CadApp, Services};
use egui::{Event, Modifiers, PointerButton, Pos2, vec2};
use egui_kittest::Harness;
use serde_json::json;
fn harness() -> Harness<'static, CadApp> {
let mut app = CadApp::new(Session::new(), Services::default());
app.run("text", json!({ "at": [0, 0], "text": "HELLO", "height": 2.5 })).unwrap();
app.run("mtext", json!({ "at": [0, 20], "text": "first\\Psecond", "height": 2.5, "width": 20 })).unwrap();
app.run("line", json!({ "points": [[30, 0], [30, 20]] })).unwrap();
app.session.execute("view.set", &json!({ "center": [15, 10], "height": 40 })).unwrap();
app.canvas.zoom_pending = false;
// 60 frames a second: the default step (0.25 s) spreads two clicks past the double-click delay.
let mut h = Harness::builder().with_size(vec2(1600.0, 1000.0)).with_step_dt(1.0 / 60.0).build_ui_state(
|ui, app: &mut CadApp| {
app.logic(ui.ctx());
app.ui(ui);
},
app,
);
h.run_steps(5);
h
}
fn double_click(h: &mut Harness<'_, CadApp>, at: Vec2) {
let pos: Pos2 = h.state().canvas.xf.unwrap().to_screen(at);
h.event(Event::PointerMoved(pos));
h.run_steps(2);
for _ in 0..2 {
for pressed in [true, false] {
h.event(Event::PointerButton { pos, button: PointerButton::Primary, pressed, modifiers: Modifiers::NONE });
h.run_steps(1);
}
}
h.run_steps(2);
}
fn text_at(h: &Harness<'_, CadApp>, i: usize) -> String {
let d = h.state().session.doc().unwrap();
let e = d.model.iter().nth(i).unwrap();
match &e.kind {
EntityKind::Text(t) => t.value.clone(),
EntityKind::MText(t) => t.contents.clone(),
k => panic!("not text: {}", k.type_name()),
}
}
#[test]
fn double_click_on_text_starts_textedit_for_that_object() {
for (i, at, old, new) in [(0, Vec2::new(4.0, 1.2), "HELLO", "HELLO WORLD"), (1, Vec2::new(3.0, 18.8), "first\nsecond", "edited")] {
let mut h = harness();
double_click(&mut h, at);
let s = &h.state().session;
assert_eq!(s.running.as_ref().map(|r| r.id.as_str()), Some("textedit"), "object {i}");
let p = s.current_prompt().unwrap();
assert_eq!(p.message, "Enter new text", "object {i}: the clicked text is already picked");
assert_eq!(p.default.as_deref(), Some(old), "object {i}");
h.state_mut().session.input(Input::Text(new.into())).unwrap();
h.run_steps(2);
assert_eq!(text_at(&h, i), new);
// TEXTEDIT goes on asking for objects (its Multiple mode), as AutoCAD's does.
assert_eq!(h.state().session.current_prompt().unwrap().message, "Select an annotation object");
}
}
#[test]
fn double_click_elsewhere_starts_no_command() {
for at in [Vec2::new(30.0, 5.0), Vec2::new(15.0, 30.0)] {
let mut h = harness();
double_click(&mut h, at);
assert_eq!(h.state().session.running.as_ref().map(|r| r.id.as_str()), None, "{at:?}");
}
}