diff --git a/crates/ui-egui/src/board_connect_tests.rs b/crates/ui-egui/src/board_connect_tests.rs index b6466a9ff..fffa1f61c 100644 --- a/crates/ui-egui/src/board_connect_tests.rs +++ b/crates/ui-egui/src/board_connect_tests.rs @@ -34,6 +34,12 @@ fn handle(u: &Ui, side: &str) -> Option { u.ctx.memory(|m| m.areas().visible_last_frame(&layer)).then(|| u.ctx.memory(|m| m.area_rect(layer.id))).flatten() } +/// Where the bar above the selection shows, if it does. +fn bar(u: &Ui) -> Option { + let layer = LayerId::new(Order::Middle, Id::unique("board_bar")); + u.ctx.memory(|m| m.areas().visible_last_frame(&layer)).then(|| u.ctx.memory(|m| m.area_rect(layer.id))).flatten() +} + fn click(u: &mut Ui, p: Pos2) { for pressed in [true, false] { u.frame(vec![Event::PointerMoved(p), Event::PointerButton { pos: p, button: PointerButton::Primary, pressed, modifiers: Modifiers::NONE }]); @@ -275,3 +281,31 @@ fn a_drag_ends_when_the_selection_moves_to_another_shape() { u.frame(vec![]); assert_eq!(u.app.session.doc().unwrap().ids().count(), before, "the drag was over"); } + +/// The bar above the selection stays away while a handle is dragged, as for any drag (review): +/// letting go where it was makes the next one there, not behind it, and the bar is back after. +#[test] +fn the_bar_stays_away_while_a_handle_is_dragged() { + let (mut u, a) = board_with_shape(); + u.app.session.execute("select.set", &json!({"ids": [a]})).unwrap(); + u.frame(vec![]); + u.frame(vec![]); + let r = bar(&u).expect("the bar shows"); + let from = handle(&u, "above").expect("a handle above").center(); + let to = r.center(); + u.drag_to(from, to); + assert!(bar(&u).is_none(), "no bar while a handle is dragged"); + u.button(to, false); + u.frame(vec![]); + let doc = u.app.session.doc().unwrap(); + let next = doc.ids().find(|i| *i != a && matches!(doc.node(*i).unwrap().board, Some(BoardItem::Shape { .. }))).expect("a new shape"); + let line = doc.ids().find(|i| matches!(doc.node(*i).unwrap().board, Some(BoardItem::Connector(_)))).expect("a connector"); + let centre = screen_box(&u, next).center(); + assert!((centre - to).length() < 1.0, "centred where the bar was: {centre:?}, not {to:?}"); + assert!(matches!(&doc.node(line).unwrap().board, Some(BoardItem::Connector(c)) if c.from.node() == Some(a) && c.to.node() == Some(next))); + // Typing in the new one keeps the bar away, as before; done typing, it is back. + figcraft_engine::textedit::end(&mut u.app.session).unwrap(); + u.frame(vec![]); + u.frame(vec![]); + assert!(bar(&u).is_some(), "the bar is back for the new selection"); +} diff --git a/crates/ui-egui/src/canvas/board_bar.rs b/crates/ui-egui/src/canvas/board_bar.rs index f9a43935c..d85f14901 100644 --- a/crates/ui-egui/src/canvas/board_bar.rs +++ b/crates/ui-egui/src/canvas/board_bar.rs @@ -191,10 +191,11 @@ pub(crate) fn place(sel: Rect, size: egui::Vec2, canvas: Rect) -> Pos2 { pos2(cx - w / 2.0, y) } -/// The bar above a board's selection, while nothing is being dragged and the file can be edited. +/// The bar above a board's selection, while nothing is being dragged (a "+" handle included) and +/// the file can be edited. pub fn show(app: &mut FigApp, ctx: &egui::Context) { let editable = app.session.active_read_only().is_none(); - if app.session.tool.busy() || !editable || !app.session.doc().is_ok_and(figcraft_engine::board::is_board) { + if app.session.tool.busy() || !editable || !app.session.doc().is_ok_and(figcraft_engine::board::is_board) || crate::board_connect::dragging(ctx) { return; } let Some(h) = has(app) else { return }; diff --git a/crates/ui-egui/src/canvas/board_connect.rs b/crates/ui-egui/src/canvas/board_connect.rs index b78946ef3..ea96e922a 100644 --- a/crates/ui-egui/src/canvas/board_connect.rs +++ b/crates/ui-egui/src/canvas/board_connect.rs @@ -3,7 +3,8 @@ //! connector, and starts typing in it (`board.addConnected`). Dragging one carries the preview //! ("drag the preview to any location on your board", #1919): let go on empty board and the next //! one is made there, joined by a connector leaving by the handle's side; let go on another sticky -//! or shape (inside a section too) and a connector joins the two; let go off the canvas, nothing. +//! or shape (inside a section too) and a connector joins the two; let go off the canvas or over the +//! toolbar, nothing. The bar above the selection stays away while a "+" is dragged, as for any drag. use egui::{Area, Color32, CornerRadius, Id, LayerId, Order, Pos2, Rect, Sense, Shape, Stroke, StrokeKind, pos2, vec2}; use figcraft_doc::{BoardItem, NodeId}; @@ -55,6 +56,12 @@ pub(crate) fn shown(app: &FigApp) -> bool { target(app).is_some() } +/// A "+" is being dragged: the bar above the selection stays away meanwhile, as for any drag, so +/// nothing is made behind it. +pub(crate) fn dragging(ctx: &egui::Context) -> bool { + ctx.data(|d| d.get_temp::(drag_id())).is_some() +} + /// The centre of the "+" for `side` of the object's screen box `r`. fn handle_at(r: Rect, side: &str) -> Pos2 { match side { diff --git a/docs/boards-parity.md b/docs/boards-parity.md index 115885f0b..2ea4f6e95 100644 --- a/docs/boards-parity.md +++ b/docs/boards-parity.md @@ -38,7 +38,7 @@ all end with the same geometry. | Shapes with text | done | 23 outlines: rectangle, rounded rectangle, ellipse, diamond, triangle up and down, parallelogram right and left, trapezoid, pentagon, hexagon, octagon, star, plus, arrow left and right, chevron, speech bubble, and the flowchart symbols database, document, predefined process, manual input and internal storage; text centred and wrapping inside the outline; shape type and text size from the contextual bar | | Shape auto-fit to its text, the advanced shape libraries (engineering and other icon shapes) | missing | | | Connectors | done | from a layer's side (the facing one, or one named) to another layer or a free point, straight, elbow or curved, arrowheads (the stroke's caps), a label in the middle of the route by length (double-click to add); while one is dragged out it shows as it will be made (route, line and arrowhead), the object under the pointer outlined with its four attach points (side middles), the one the end goes to ringed, and within 10 px of one the end keeps that side; re-routed when either end moves or resizes; a deleted end stays where it was | -| Create the next shape already connected | done | a selected sticky or shape shows a "+" beside each side; hovering one shows where the next goes, clicking makes one of the same kind, size and colour there, joined by a connector as the Connector tool makes one, and starts typing in it; one undo step (`board.addConnected {id?, side?, edit?}`, #1597). Shift+Cmd/Ctrl+Enter on a selected (or edited) shape adds the next shape on its right, joined the same way, and goes on typing; on a sticky it adds a sticky beside it without a connector, as before. Dragging a "+" carries the preview ("drag the preview to any location on your board"): let go on empty board and the next one is made there, centred on the pointer, joined by a connector leaving by the handle's side (`at: [x, y]`, a point of the page); let go on another sticky or shape, inside a section too, and a connector joins the two instead, the object outlined while over it; let go off the canvas, nothing (`board_connect_tests`, #1919) | +| Create the next shape already connected | done | a selected sticky or shape shows a "+" beside each side; hovering one shows where the next goes, clicking makes one of the same kind, size and colour there, joined by a connector as the Connector tool makes one, and starts typing in it; one undo step (`board.addConnected {id?, side?, edit?}`, #1597). Shift+Cmd/Ctrl+Enter on a selected (or edited) shape adds the next shape on its right, joined the same way, and goes on typing; on a sticky it adds a sticky beside it without a connector, as before. Dragging a "+" carries the preview ("drag the preview to any location on your board"): let go on empty board and the next one is made there, centred on the pointer, joined by a connector leaving by the handle's side (`at: [x, y]`, a point of the page); let go on another sticky or shape, inside a section too, and a connector joins the two instead, the object outlined while over it; let go off the canvas or over the toolbar, nothing, and the bar above the selection stays away meanwhile (`board_connect_tests`, #1919) | | Curved connectors, elbows around objects, dragging an end to re-attach | done | `board.setStyle {style: curved}` (and the contextual bar): one curve leaving and arriving along the sides; an elbow whose short way would cross what it joins goes around their boxes, 24 units clear; a selected connector shows its two ends as handles, and dragging one onto an object attaches it there (the object is outlined while dragging), onto empty canvas leaves it free (`board.reattach {id, end, target, side?}`, one undo step) | | A connector end on any part of an object | done | holding Cmd/Ctrl while moving an end places it where the pointer is on the object, as a share of its box, so it follows moves and resizes; the line leaves by the side facing the other end. Without the key an end snaps to a side | | Connector paths reshaped by hand | done | a selected elbow or curved connector shows path handles: one on each run long enough to take one, one in the middle of a curve. Dragging a run moves it, and a first or last run gains an extra run to stay on its end; a curve's middle goes where it is dragged (`board.reshape`, one undo step). The shape travels with what the connector joins and is saved with the file; changing the style or moving an end to something else goes back to routing itself. No way back by itself otherwise: the public help describes none | @@ -70,7 +70,7 @@ all end with the same geometry. | Date | Change | Summary | |---|---|---| -| 2026-11-03 | minor | M12, #1919: as the quick create help says ("Click to select and create the next shape in that direction, or drag the preview to any location on your board"; "A new shape is created with a connector and its text field active"), a "+" beside the selected sticky or shape can be dragged: the preview follows the pointer and, let go on empty board, the next one is made there, centred on the pointer, and joined by a connector leaving by the handle's side and arriving by the side facing it (`board.addConnected` gains `at: [x, y]`, a point of the page placed in the parent's coordinates); let go on another sticky or shape, inside a section or frame too (`board::sticky_or_shape_at`), a connector joins the two instead (`board.connector`), that object outlined while the pointer is over it; let go off the canvas, nothing. The click is as before. A shape picker on drop and choosing the connector type are not done (the help doesn't describe them). Tests: `board_connect_tests` (two), engine `board_connected_tests::the_next_one_goes_where_it_is_dropped`. Numbers unchanged. | +| 2026-11-03 | minor | M12, #1919: as the quick create help says ("Click to select and create the next shape in that direction, or drag the preview to any location on your board"; "A new shape is created with a connector and its text field active"), a "+" beside the selected sticky or shape can be dragged: the preview follows the pointer and, let go on empty board, the next one is made there, centred on the pointer, and joined by a connector leaving by the handle's side and arriving by the side facing it (`board.addConnected` gains `at: [x, y]`, a point of the page placed in the parent's coordinates); let go on another sticky or shape, inside a section or frame too (`board::sticky_or_shape_at`), a connector joins the two instead (`board.connector`), that object outlined while the pointer is over it; let go off the canvas or over the toolbar, nothing, and the bar above the selection stays away meanwhile, as for any drag. The click is as before. A shape picker on drop and choosing the connector type are not done (the help doesn't describe them). Tests: `board_connect_tests` (the drag ones), engine `board_connected_tests::the_next_one_goes_where_it_is_dropped`. Numbers unchanged. | | 2026-10-31 | minor | M12: as the help describes, holding Cmd/Ctrl while moving a connector's end places it on any part of an object instead of snapping to a side. It keeps that spot as a fraction of the object's box, so it follows the object when it moves or resizes, and without the key an end snaps as before. `board.connector {fromAt, toAt}` and `board.reattach {at}` take it; saved only when set. Numbers unchanged | | 2026-10-31 | patch | M12, #1708: a connector end dropped on the outer half of a shape takes the side it is towards, instead of only within 10 px of a side's middle, so a drop anywhere along an edge chooses that edge (dragging a selected connector's end and with the connector tool, ring included); a drop in the middle of a shape still takes the facing side. | | 2026-10-31 | minor | M12, #1710: a selected elbow or curved connector shows path handles. Dragging a run moves it and a first or last run gains an extra run to stay on its end; a curve's middle goes where it is dragged (`board.reshape`, one undo step). The shape travels with what the connector joins, is saved with the file, and goes back to routing itself when the style changes or an end moves to something else. |