diff --git a/crates/ui-egui/src/panels/project.rs b/crates/ui-egui/src/panels/project.rs index 2f58b87b..86e91ff9 100644 --- a/crates/ui-egui/src/panels/project.rs +++ b/crates/ui-egui/src/panels/project.rs @@ -212,8 +212,11 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { }, } app.auto.add("project.thumbnail", thumb, &it.name); + // The details stay inside the panel (a margin on the right), cut short with "…". let tx = thumb.max.x + 10.0; - p.text(pos2(tx, thumb.min.y + 6.0), Align2::LEFT_CENTER, &it.name, Tokens::semibold(12.0), t.text); + let max_w = head.max.x - 10.0 - tx; + let r = widgets::text_fit(&p, pos2(tx, thumb.min.y + 6.0), Align2::LEFT_CENTER, &it.name, Tokens::semibold(12.0), max_w, t.text); + app.auto.add("project.details.name", r, &it.name); let mut lines = vec![]; if let Some((w, h)) = it.dimensions() { lines.push(format!("{w} x {h} (1.00)")); @@ -226,10 +229,13 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { lines.push(f.codec.clone()); } for (i, l) in lines.iter().enumerate() { - p.text(pos2(tx, thumb.min.y + 22.0 + 13.0 * i as f32), Align2::LEFT_CENTER, l, Tokens::ui(11.0), t.text_dim); + let r = widgets::text_fit(&p, pos2(tx, thumb.min.y + 22.0 + 13.0 * i as f32), Align2::LEFT_CENTER, l, Tokens::ui(11.0), max_w, t.text_dim); + app.auto.add(&format!("project.details.{i}"), r, l); } } else { - p.text(head.center(), Align2::CENTER_CENTER, "Select an item to see its details", Tokens::ui(11.0), t.text_faint); + let hint = "Select an item to see its details"; + let r = widgets::text_fit(&p, head.center(), Align2::CENTER_CENTER, hint, Tokens::ui(11.0), head.width() - 20.0, t.text_faint); + app.auto.add("project.details.hint", r, hint); } // Search. let sr = Rect::from_min_size(pos2(rect.min.x + 8.0, head.max.y + 2.0), vec2(rect.width() - 16.0, 22.0)); diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs index 99787b4e..e483f12b 100644 --- a/crates/ui-egui/src/widgets.rs +++ b/crates/ui-egui/src/widgets.rs @@ -143,6 +143,17 @@ pub fn icon_button(ui: &mut Ui, rect: Rect, icon: Icon, active: bool, t: &Tokens resp } +/// One line of `text` at `pos` (placed by `align`), cut short with "…" past `max_w` points so it +/// never runs out of its panel. Returns where it was painted. +pub fn text_fit(p: &egui::Painter, pos: egui::Pos2, align: Align2, text: &str, font: egui::FontId, max_w: f32, color: Color32) -> Rect { + let mut job = egui::text::LayoutJob::simple_singleline(text.to_owned(), font, color); + job.wrap = egui::text::TextWrapping { max_width: max_w.max(0.0), max_rows: 1, break_anywhere: true, overflow_character: Some('…') }; + let galley = p.layout_job(job); + let rect = align.anchor_size(pos, galley.size()); + p.galley(rect.min, galley, color); + rect +} + /// Rounded search field with a magnifier icon. pub fn search_field(ui: &mut Ui, rect: Rect, text: &mut String, hint: &str, t: &Tokens) -> Response { let h = rect.height(); diff --git a/crates/ui-egui/tests/ui_tabs.rs b/crates/ui-egui/tests/ui_tabs.rs index bfd8f164..1bb0cf97 100644 --- a/crates/ui-egui/tests/ui_tabs.rs +++ b/crates/ui-egui/tests/ui_tabs.rs @@ -199,3 +199,23 @@ fn panels_dock_beside_others_and_gutters_resize_them() { let wider = rect(&h, "panel.Project"); assert!((wider.width() - project.width() - 60.0).abs() < 2.0, "{} → {}", project.width(), wider.width()); } + +/// Issue #45: the Project panel's details (name, size, duration) stay inside the panel with a +/// margin, cut short with "…" when they don't fit; so does the hint shown with nothing selected. +#[test] +fn project_details_stay_inside_the_panel() { + let (mut h, pre, _) = harness(); + let long = "A composition with a very long name that could never fit beside the thumbnail".to_string(); + h.state_mut().session.execute("project.rename", json!({"item": pre, "name": long})).unwrap(); + h.state_mut().session.state.project_selection = vec![ItemId(pre)]; + h.run_steps(3); + let panel = rect(&h, "panel.Project"); + for id in ["project.details.name", "project.details.0", "project.details.1"] { + let r = rect(&h, id); + assert!(r.max.x <= panel.max.x - 9.0 && r.width() > 10.0, "{id}: {r:?} in {panel:?}"); + } + h.state_mut().session.state.project_selection.clear(); + h.run_steps(3); + let r = rect(&h, "project.details.hint"); + assert!(r.min.x >= panel.min.x + 9.0 && r.max.x <= panel.max.x - 9.0, "{r:?} in {panel:?}"); +}