Type tool: a drag that starts in an empty frame draws a new text frame over it, as in InDesign

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
moppeeer
2026-10-20 22:20:26 +02:00
co-authored by Claude Opus 5.5
parent 8dff7ecdb0
commit 4a65451d40
3 changed files with 39 additions and 0 deletions
+1
View File
@@ -348,6 +348,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
Newest first.
- **2026-10-20** · Type tool: a drag that starts in an empty frame draws a new text frame over it, as in InDesign; a click still turns the frame into a text frame. No readiness percentage change.
- **2026-10-16** · Type › Reload Fonts (`font.reload`): fonts installed, edited or removed while DesignCraft runs are read again without a restart, and the open documents recompose with them (#328, #547). An edited font file is noticed by its length and modification time; bundled, added and document fonts stay as they are. InDesign does this by itself, so noticing changes automatically is still open (gap 6). No readiness percentage change.
- **2026-10-16** · Parents can be deleted from their Pages-panel context menu or `layout.parents.delete {id}`. Applied-page and based-on references are cleared, local override copies survive, and Undo restores the parent. Addresses the deletion portion of #956; no readiness percentage change.
- **2026-10-17** · File › Package writes the document's and the IDML's links relative to the package (`Links/logo.png`, `file:Links/logo.png`), so a package holds no path from the machine that made it; opening a document resolves them to the files beside it. Links › Copy Link(s) To still links by full path. (#340). No readiness percentage change.
+24
View File
@@ -1058,6 +1058,30 @@ fn double_click_on_a_port_breaks_its_link() {
}
}
/// With the Type tool, a drag that starts in an empty rectangle draws a new text frame over it
/// (the rectangle stays as it is), as InDesign's does; a click still turns the rectangle into a
/// text frame with the caret in it.
#[test]
fn type_tool_drag_over_an_empty_frame_draws_a_new_text_frame() {
let mut d = Document::new(&NewDocument::default());
let id = add_rect(&mut d, Rect::new(100.0, 100.0, 300.0, 300.0));
let (s, c, l) = (Selection::default(), Cache::new(), CanvasLayout::new(&d, false));
let cx = ctx(&d, &s, &c, &l);
let off = l.xf(SpreadRef::Doc(0)).translation();
let at = |x: f64, y: f64| (x + off.x, y + off.y);
let mut t = create("type");
let (x, y) = at(120.0, 120.0);
assert!(t.pointer(&cx, &PointerEvent::new(PointerKind::Down, x, y)).is_empty());
let (x1, y1) = at(280.0, 200.0);
let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, x1, y1));
assert!(matches!(&a[..], [Action::Begin(_), Action::Preview(cmd, p)] if cmd == "frame.create" && p["content"] == "text"), "{a:?}");
assert_eq!(t.pointer(&cx, &PointerEvent::new(PointerKind::Up, x1, y1)), vec![Action::Commit]);
// A click: the caret goes into the rectangle, which becomes a text frame.
assert!(t.pointer(&cx, &PointerEvent::new(PointerKind::Down, x, y)).is_empty());
let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Up, x, y));
assert!(matches!(&a[..], [Action::Exec(cmd, p)] if cmd == "text.placeCaret" && p["frame"] == id.0), "{a:?}");
}
#[test]
fn clicks_in_a_row_edit_and_select_text() {
let mut d = Document::new(&NewDocument::default());
+14
View File
@@ -31,6 +31,9 @@ pub struct TypeTool {
start: Option<Point>,
selecting: Option<u64>,
drawing: bool,
/// A press on an empty frame: (frame, press point in spread space). A click turns it into a
/// text frame with the caret in it; a drag draws a new text frame over it, as in InDesign.
convert: Option<(u64, Point)>,
/// A press on a table whose whole rows or columns are selected: (frame, press point in
/// spread space) — a drag moves them, a click places the caret.
cell_drag: Option<(u64, Point)>,
@@ -226,6 +229,7 @@ impl Tool for TypeTool {
self.cell_drag = None;
self.unit = None;
self.border = None;
self.convert = None;
if ev.kind == PointerKind::Down
&& let Some(b) = border_at(cx, ev.pos)
{
@@ -246,6 +250,11 @@ impl Tool for TypeTool {
self.cell_drag = Some((id.0, sp));
return vec![];
}
// An empty frame: wait to see whether this is a click or a drag.
if clicks == 1 && !ev.mods.shift && !it.is_text_frame() {
self.convert = Some((id.0, sp));
return vec![];
}
self.selecting = Some(id.0);
// Two presses select a word (or a run of spaces), three a line, four a
// paragraph, five the story.
@@ -296,6 +305,7 @@ impl Tool for TypeTool {
let mut out = vec![];
if !self.drawing {
self.drawing = true;
self.convert = None;
out.push(Action::Begin("Create Text Frame".into()));
}
out.push(Action::Preview(
@@ -323,6 +333,10 @@ impl Tool for TypeTool {
self.selecting = None;
return vec![Action::Commit];
}
// A click in an empty frame: it becomes a text frame with the caret in it.
if let Some((fid, at)) = self.convert.take() {
return vec![Action::Exec("text.placeCaret".into(), json!({"frame": fid, "point": [at.x, at.y]}))];
}
// Ends a press in text: drops dragged text, or is ignored by the engine.
if let Some(fid) = self.selecting.take() {
let sp = cx.layout.spread_at(ev.pos).map(|(_, p)| p).unwrap_or(ev.pos);