M12: Boards: board toolbar, contextual bar, per-kind file icons, home and recents

This commit is contained in:
bflatastic
2026-10-24 02:09:01 +07:00
parent 5840e70e3b
commit b7991fc034
16 changed files with 603 additions and 71 deletions
+17
View File
@@ -313,3 +313,20 @@ fn board_commands_survive_junk() {
}
}
}
#[test]
fn a_press_on_a_stickys_background_picks_and_drags_it() {
let mut s = board();
let a = id(&s.execute("board.sticky", &json!({"x": 0, "y": 0, "text": "Hi"})).unwrap(), "id");
let b = id(&s.execute("board.sticky", &json!({"x": 400, "y": 0})).unwrap(), "id");
let c = id(&s.execute("board.connector", &json!({"from": a, "to": b})).unwrap(), "id");
s.execute("select.none", &json!({})).unwrap();
// Below the note, on the sticky's own fill (a top-level frame's background is the canvas).
s.pointer(&PointerEvent::at(PointerKind::Down, 100.0, 150.0, 1.0)).unwrap();
s.pointer(&PointerEvent::at(PointerKind::Move, 100.0, 250.0, 1.0)).unwrap();
s.pointer(&PointerEvent::at(PointerKind::Move, 100.0, 450.0, 1.0)).unwrap();
s.pointer(&PointerEvent::at(PointerKind::Up, 100.0, 450.0, 1.0)).unwrap();
assert_eq!(s.selection(), vec![a]);
assert_eq!(node(&s, a).transform.translation().y, 300.0);
assert!(near(drawn(&s, c)[0], Point::new(200.0, 400.0)), "the connector followed the drag: {:?}", drawn(&s, c));
}
+10 -1
View File
@@ -120,6 +120,15 @@ static ROWS: &[Row] = &[
pin(Tools, "tool.slice", &["S"], &[]),
pin(Tools, "file.placeImage", &["Shift+Cmd+K"], &[]),
pin(Tools, "tool.shapeBuilder", &["Shift+M"], &[]),
// Board tools (boards only), on keys no design-file shortcut uses.
pin(Tools, "tool.sticky", &["Q"], &[]),
pin(Tools, "tool.shape", &["B"], &[]),
pin(Tools, "tool.connector", &["X"], &[]),
pin(Tools, "tool.marker", &["M"], &[]),
pin(Tools, "tool.highlighter", &["U"], &[]),
pin(Tools, "tool.eraser", &["E"], &[]),
pin(Tools, "tool.stamp", &["W"], &[]),
pin(Tools, "board.nextSticky", &["Shift+Cmd+Enter"], &[]),
// Vector edit mode
doc(Selection, "Edit the selected shape's points", &["Enter"], &[], Bound("select.children"), "Enter on a shape edits it; on a frame or group it selects the children"),
doc(Shape, "Vector edit: move", &["V"], &[], VectorTool(VTool::Move), ""),
@@ -398,7 +407,7 @@ fn counts_match_the_docs() {
let reserved = documented.iter().filter(|r| !r.reserved.is_empty()).count();
let pinned = ROWS.len() - documented.len();
// docs/ui-parity.md quotes these.
assert_eq!((documented.len(), bound, moved, missing, reserved, pinned), (129, 128, 1, 0, 2, 49));
assert_eq!((documented.len(), bound, moved, missing, reserved, pinned), (129, 128, 1, 0, 2, 57));
}
#[test]
+5 -4
View File
@@ -1233,12 +1233,13 @@ impl Session {
}
// A frame's name label picks the frame itself. The background of a top-level frame with
// layers in it is like the canvas (a press starts a marquee inside it) unless the frame is
// selected; an empty frame (or a widget) is picked and dragged.
// selected; an empty frame (or a widget, or a board object such as a sticky) is picked and
// dragged.
let board_background = |leaf: NodeId| {
!sel.contains(&leaf)
&& doc
.node(leaf)
.is_some_and(|n| n.parent == Some(page) && n.kind.is_frame_like() && !n.children.is_empty() && !crate::widgets::is_widget(n))
&& doc.node(leaf).is_some_and(|n| {
n.parent == Some(page) && n.kind.is_frame_like() && !n.children.is_empty() && !crate::widgets::is_widget(n) && !n.is_board_atom()
})
};
let target = label_at(&doc, page, &self.tool.labels, ev.p()).or_else(|| {
self.hit(&doc, page, ev.p(), ev.tol() * 2.0)