fix(ui): text frames inside a selected group show their ports

A selected group drew no in/out ports for the text frames in it, so an
overset frame inside a group had no red mark. The selection walks groups
(bounded depth) and draws each nested text frame's ports with the group's
transform.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 17a3cf9bd1c695a585e94fce125ed9d3a3a5929d)
This commit is contained in:
pepen
2026-10-15 13:15:54 +02:00
parent a930276fd7
commit 3fa4d72d67
+51 -3
View File
@@ -947,9 +947,12 @@ fn draw_selection(app: &DesignApp, painter: &egui::Painter, xf: &Xf, doc: &Docum
}
let b = xf.rect(a.transform_rect_bbox(it.bounds()));
union = Some(union.map_or(b, |u| u.union(b)));
// Text frame ports.
if let Content::Text(tf) = &it.content {
draw_ports(app, painter, xf, doc, layout, it, tf.story, color);
// Text frame ports, also of the frames inside a selected group (so an overset frame in a
// group shows its red mark).
let mut frames = Vec::new();
text_frames_in(it, a, 0, &mut frames);
for (frame, story, _) in frames {
draw_ports(app, painter, xf, doc, layout, frame, story, color);
}
if let Content::Graphic(g) = &it.content
&& sel.content
@@ -1024,6 +1027,23 @@ fn draw_selection(app: &DesignApp, painter: &egui::Painter, xf: &Xf, doc: &Docum
text_rect.or(union)
}
/// How deep a selected group is walked for the text frames whose ports are drawn.
const PORT_GROUP_DEPTH: usize = 32;
/// `it` when it is a text frame, or every text frame nested in it when it is a group, each with
/// its story and the transform of its parent (`a` is the transform of `it`'s parent).
fn text_frames_in<'a>(it: &'a Item, a: Affine, depth: usize, out: &mut Vec<(&'a Item, designcraft_doc::StoryId, Affine)>) {
match &it.content {
Content::Text(tf) => out.push((it, tf.story, a)),
Content::Group { items } if depth < PORT_GROUP_DEPTH => {
for child in items {
text_frames_in(child, a * it.xf, depth + 1, out);
}
}
_ => {}
}
}
/// Port location (canvas): out port at bottom-right, in port at top-left.
fn port_pos(doc: &Document, layout: &CanvasLayout, id: designcraft_doc::ItemId, out: bool) -> Option<Point> {
let it = doc.item(id)?;
@@ -2481,6 +2501,34 @@ mod tests {
}
}
/// A selected group lists the text frames inside it for ports, with the group's transform.
#[test]
fn grouped_text_frames_get_ports() {
let mut s = designcraft_engine::Session::new();
s.execute("file.new", &json!({})).unwrap();
let a = s.execute("frame.create", &json!({"rect": [10, 10, 110, 60], "content": "text"})).unwrap()["id"].as_u64().unwrap();
let b = s.execute("frame.create", &json!({"rect": [10, 80, 110, 130]})).unwrap()["id"].as_u64().unwrap();
s.execute("selection.set", &json!({"ids": [a, b]})).unwrap();
let g = s.execute("object.group", &json!({})).unwrap()["id"].as_u64().unwrap();
s.execute("transform.move", &json!({"dx": 5, "dy": 7, "ids": [g]})).unwrap();
let doc = &s.doc().unwrap().doc;
let group = doc.item(designcraft_doc::ItemId(g)).unwrap();
let mut out = Vec::new();
text_frames_in(group, Affine::IDENTITY, 0, &mut out);
assert_eq!(out.len(), 1, "one text frame in the group");
assert_eq!(out[0].0.id.0, a);
assert_eq!(out[0].2, group.xf);
// A plain text frame lists itself with the given parent transform.
out.clear();
text_frames_in(out_frame(doc, a), Affine::translate((1.0, 2.0)), 0, &mut out);
assert_eq!(out.len(), 1);
assert_eq!(out[0].2, Affine::translate((1.0, 2.0)));
}
fn out_frame(doc: &Document, id: u64) -> &Item {
doc.item(designcraft_doc::ItemId(id)).unwrap()
}
#[test]
fn right_clicking_a_ruler_sets_that_rulers_units() {
let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());