From ce61aef4a63831483d2e64c3884203beb62dcb72 Mon Sep 17 00:00:00 2001 From: bherbruck Date: Fri, 9 Oct 2026 19:06:24 +0000 Subject: [PATCH] Dialog labels show in full: the label column grows to its longest label (up to 150 px, then wraps) instead of cutting labels off. Feature previews that remove material (Hole, pockets) show the cut's new faces through the body in the cut colour, so the hole is seen from any side Fixes #18 Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/dialogs.rs | 12 ++++++++---- crates/ui-egui/src/preview.rs | 22 +++++++++++++++++++++- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index 7ab92060..8d70d940 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -820,13 +820,17 @@ fn hint(inp: &SelInput) -> &'static str { /// Width of the label column; longer labels are cut with "…". const LABEL_W: f32 = 92.0; +/// The label column grows to fit its labels up to this, then long labels wrap. +const LABEL_MAX_W: f32 = 150.0; -/// A row label in the dialog's label column. +/// A row label in the dialog's label column (the grid sizes the column to its widest label). pub(crate) fn row_label(ui: &mut egui::Ui, text: &str) { ROW.with(|r| *r.borrow_mut() = text.to_string()); - ui.allocate_ui_with_layout(vec2(LABEL_W, 22.0), egui::Layout::left_to_right(egui::Align::Center), |ui| { - ui.set_width(LABEL_W); - ui.add(egui::Label::new(text).truncate()).on_hover_text(text); + let full = egui::WidgetText::from(text).into_galley(ui, Some(egui::TextWrapMode::Extend), f32::INFINITY, egui::TextStyle::Body).size().x; + let w = full.clamp(LABEL_W, LABEL_MAX_W); + ui.allocate_ui_with_layout(vec2(w, 22.0), egui::Layout::left_to_right(egui::Align::Center), |ui| { + ui.set_width(w); + ui.add(egui::Label::new(text).wrap()); }); } diff --git a/crates/ui-egui/src/preview.rs b/crates/ui-egui/src/preview.rs index 146cc719..e9ef862e 100644 --- a/crates/ui-egui/src/preview.rs +++ b/crates/ui-egui/src/preview.rs @@ -270,12 +270,19 @@ pub fn build(before: &ModelState, after: &ModelState, c: Colors) -> Built { } let nf = am.tri_face.iter().max().map_or(0, |m| *m as usize + 1); let added: Vec = (0..nf).map(|f| old.as_ref().is_none_or(|bm| face_is_new(&am, f, bm))).collect(); + // Material removed from a body (a hole, a pocket): its new faces also show through the + // body in the cut colour, so the cut is seen from any side. + let cut = old.as_ref().is_some_and(|bm| am.measure().volume < bm.measure().volume - 1e-6); for (t, f) in am.triangles.iter().zip(&am.tri_face) { - let col = if added.get(*f as usize).copied().unwrap_or(true) { c.added } else { c.body }; + let new = added.get(*f as usize).copied().unwrap_or(true); + let col = if new { c.added } else { c.body }; for k in t { let i = *k as usize; if let (Some(p), Some(n)) = (am.positions.get(i), am.normals.get(i)) { sc.tri(p.to_f32(), n.to_f32(), col); + if new && cut { + sc.xray_tri(p.to_f32(), n.to_f32(), c.removed); + } } } } @@ -364,6 +371,19 @@ mod tests { bytes.chunks(stride).filter(|v| v.get(24..28) == Some(&col[..])).count() } + #[test] + fn a_hole_shows_its_cut_through_the_body() { + let mut s = solvecraft_engine::Session::default(); + s.execute("solid.box", &json!({"length": 40, "width": 30, "height": 20})).unwrap(); + let p = s.preview(&[("solid.hole".to_string(), json!({"position": [20, 15, 20], "diameter": 5, "depth": 10}))]).unwrap(); + let b = build(&p.before, &p.after, colors()); + // The hole's walls are drawn see-through in the cut colour (seen from any side). + assert!(count(&b.scene.xray, 28, colors().removed) > 0); + // A feature that only adds material shows no cut. + let p = s.preview(&[("solid.box".to_string(), json!({"length": 10, "width": 10, "height": 30, "operation": "join"}))]).unwrap(); + assert_eq!(count(&build(&p.before, &p.after, colors()).scene.xray, 28, colors().removed), 0); + } + fn plate() -> Session { let mut s = Session::default(); for (id, p) in [