M12: a mind map in a section or frame without a place is centred on the view

board.mindMap's default x and y came from the view's centre, which is on the page, but x and y are the root's place in its parent: under a section at (1000, 500) the map landed 1000 right and 500 down of the view's centre. The default now takes the centre through the parent's inverse world transform (a parent that can't be inverted gives a non-finite centre, which the coordinate check refuses). Explicit x and y stay parent-local. Tests: two in board_mindmap_tests (a section; a frame turned 30 degrees, with a given place carried through the frame).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Opus 5.5 briefed; Sonnet 5.5 wrote at medium
This commit is contained in:
Trevor BrownandClaude Sonnet 5.5 committed 2026-11-13 00:45:11 -05:00
1 parent 7b34dbab00
commit e0e57dd42a
3 files changed
+45 -3

No files matched your search

+38
View File
@@ -383,3 +383,41 @@ fn the_caps_refuse_what_is_too_far_and_cut_what_is_too_long() {
assert_eq!(text_data(&s, id(&out, "id")).characters.chars().count(), 500);
assert_eq!(text_data(&s, id(&out["children"][0], "id")).characters.chars().count(), 500);
}
/// Where a node's own origin (the place `x`/`y` name) is on the page.
fn origin(s: &Session, n: NodeId) -> Point {
s.doc().unwrap().world_transform(n) * Point::new(0.0, 0.0)
}
#[test]
fn a_mind_map_in_a_section_without_a_place_is_centred_on_the_view() {
let mut s = board();
let (vx, vy) = {
let v = &s.ds().unwrap().view;
(v.cx, v.cy)
};
let sec = id(&s.execute("node.create", &json!({"type": "section", "x": 1000, "y": 500, "width": 4000, "height": 4000})).unwrap(), "id");
let out = s.execute("board.mindMap", &json!({"parent": sec})).unwrap();
let c = origin(&s, id(&out, "id"));
assert!(same(c.x, vx) && same(c.y, vy), "the root's place {c:?} is the view's centre ({vx}, {vy})");
}
#[test]
fn a_mind_map_in_a_turned_frame_without_a_place_is_centred_on_the_view_and_a_given_place_is_the_frames() {
let mut s = board();
let (vx, vy) = {
let v = &s.ds().unwrap().view;
(v.cx, v.cy)
};
let fr = id(&s.execute("node.create", &json!({"type": "frame", "x": 300, "y": 200, "width": 2000, "height": 2000})).unwrap(), "id");
s.execute("node.set", &json!({"id": fr, "props": {"rotation": 30}})).unwrap();
let out = s.execute("board.mindMap", &json!({"parent": fr})).unwrap();
let c = origin(&s, id(&out, "id"));
assert!(same(c.x, vx) && same(c.y, vy), "the root's place {c:?} is the view's centre ({vx}, {vy})");
// A given place is the frame's own, as on the page: (10, 20) of the frame, carried through it.
let given = id(&s.execute("board.mindMap", &json!({"parent": fr, "x": 10, "y": 20})).unwrap(), "id");
let want = s.doc().unwrap().world_transform(fr) * Point::new(10.0, 20.0);
let got = origin(&s, given);
assert!(same(got.x, want.x) && same(got.y, want.y), "{got:?} against {want:?}");
}
+6 -2
View File
@@ -168,9 +168,13 @@ pub(super) fn mind_map_cmd(s: &mut Session, p: &Value) -> Result<Value> {
let cmd = "board.mindMap";
require(s, cmd)?;
let parent = parent_of(cmd, s, p)?;
// The view's centre is on the page, and `x`/`y` are in the parent: a map in a section or frame
// takes the centre in that parent's coordinates (a parent that can't be inverted gives a
// non-finite centre, which `coord` refuses).
let (vx, vy) = {
let v = &s.ds()?.view;
(v.cx, v.cy)
let ds = s.ds()?;
let c = ds.doc.world_transform(parent).inverse() * figcraft_geom::Point::new(ds.view.cx, ds.view.cy);
(c.x, c.y)
};
let (x, y) = (coord(cmd, p, "x", vx)?, coord(cmd, p, "y", vy)?);
let texts = starters(cmd, p)?;
+1 -1
View File
@@ -498,7 +498,7 @@ one undo step and returns the ids it made:
in the app does both).
- `board.mindMap {x?, y?, text?, children?, parent?, edit?}` → `{id, text, children: [{id, text, connector}]}`:
a mind map: a first node (`mindNode`, bold 24, no box; `text` defaults to `Topic`, `x`/`y` to the
middle of the view) with up to 20 `children` (texts; default `Idea 1`, `Idea 2`, `Idea 3`)
view's centre, in the parent's coordinates) with up to 20 `children` (texts; default `Idea 1`, `Idea 2`, `Idea 3`)
on its right, each joined to it by a curved branch (a connector with `branch: true`: stroke 4,
`#757575`, round caps). The map lays itself out: children stand in one column 88 units right of
the widest node of their parent's column, 48 apart, centred on their parent; a node is its text,