diff --git a/crates/ui-egui/src/mix_window.rs b/crates/ui-egui/src/mix_window.rs index 7cc98c7..5ef3575 100644 --- a/crates/ui-egui/src/mix_window.rs +++ b/crates/ui-egui/src/mix_window.rs @@ -103,7 +103,7 @@ fn channel_levels(ui: &Ui, key: (&str, u64), peaks: &[f32], n: usize) -> Vec &str { match text { "INSERTS A-E" => "I A-E", @@ -115,9 +115,14 @@ fn short_label(text: &str) -> &str { } } -fn section_label(ui: &Ui, r: Rect, text: &str) { +/// A section's caption. Narrow strips always use the short names, as Pro Tools' Narrow Mix does, so +/// every narrow strip reads the same whatever the UI font: measuring alone left the full name in +/// place wherever a narrower font (egui's built-in face on a system with no fonts) happened to fit. +/// Other strips shorten a name only when it doesn't fit with 4 pt to spare on each side. +fn section_label(ui: &Ui, r: Rect, text: &str, narrow: bool) { let p = ui.painter(); let color = Tokens::current().section_label; + let text = if narrow { short_label(text) } else { text }; let mut galley = p.layout_no_wrap(text.to_owned(), bold(10.0), color); if galley.size().x > r.width() - 8.0 { galley = p.layout_no_wrap(short_label(text).to_owned(), bold(10.0), color); @@ -169,7 +174,7 @@ fn strip(app: &mut SoundApp, ui: &mut Ui, id: TrackId, snap: Option<&MeterSnapsh // Input gain stage ahead of the inserts (the track's trim). let sec = Rect::from_min_size(pos2(x0, y), vec2(inner_w, 34.0)); inset(ui.painter(), sec, t.strip_section); - section_label(ui, sec, "PREAMP"); + section_label(ui, sec, "PREAMP", narrow); if has_input { let kr = Rect::from_min_size(pos2(x0 + 6.0, sec.min.y + 15.0), vec2(inner_w - 12.0, 16.0)); let mut db = track.mixer.trim_db; @@ -187,7 +192,7 @@ fn strip(app: &mut SoundApp, ui: &mut Ui, id: TrackId, snap: Option<&MeterSnapsh if all || has("instruments") { let sec = Rect::from_min_size(pos2(x0, y), vec2(inner_w, 34.0)); inset(ui.painter(), sec, t.strip_section); - section_label(ui, sec, "INSTRUMENT"); + section_label(ui, sec, "INSTRUMENT", narrow); let sr = Rect::from_min_size(pos2(x0 + 2.0, sec.min.y + 16.0), vec2(inner_w - 4.0, 15.0)); let slot = track.mixer.inserts.iter().position(|i| i.as_ref().is_some_and(|i| plugin_info(&i.plugin).is_some_and(|p| p.is_instrument))); match slot { @@ -223,7 +228,7 @@ fn strip(app: &mut SoundApp, ui: &mut Ui, id: TrackId, snap: Option<&MeterSnapsh } let sec = Rect::from_min_size(pos2(x0, y), vec2(inner_w, 16.0 + 5.0 * 17.0)); inset(ui.painter(), sec, t.strip_section); - section_label(ui, sec, title); + section_label(ui, sec, title, narrow); for k in 0..5 { let sr = Rect::from_min_size(pos2(x0 + 2.0, sec.min.y + 15.0 + k as f32 * 17.0), vec2(inner_w - 4.0, 15.0)); if is_send { @@ -276,7 +281,7 @@ fn strip(app: &mut SoundApp, ui: &mut Ui, id: TrackId, snap: Option<&MeterSnapsh if all || has("io") { let sec = Rect::from_min_size(pos2(x0, y), vec2(inner_w, 16.0 + 2.0 * 18.0 + 30.0)); inset(ui.painter(), sec, t.strip_section); - section_label(ui, sec, "I / O"); + section_label(ui, sec, "I / O", narrow); let in_r = Rect::from_min_size(pos2(x0 + 2.0, sec.min.y + 15.0), vec2(inner_w - 4.0, 16.0)); let out_r = Rect::from_min_size(pos2(x0 + 2.0, sec.min.y + 33.0), vec2(inner_w - 4.0, 16.0)); let mut c = ui.new_child(egui::UiBuilder::new().id_salt(("mix-input", id.0)).max_rect(in_r)); diff --git a/crates/ui-egui/tests/mix_narrow_headers.rs b/crates/ui-egui/tests/mix_narrow_headers.rs index 5b19c91..6e248c0 100644 --- a/crates/ui-egui/tests/mix_narrow_headers.rs +++ b/crates/ui-egui/tests/mix_narrow_headers.rs @@ -1,4 +1,4 @@ -use egui::{Shape, vec2}; +use egui::{FontDefinitions, FontFamily, Shape, vec2}; use serde_json::json; use soundcraft_ui_egui::mix_window::strip_width; use soundcraft_ui_egui::{Services, SoundApp}; @@ -6,7 +6,20 @@ use soundcraft_ui_egui::{Services, SoundApp}; const FULL: [&str; 7] = ["INSERTS A-E", "INSERTS F-J", "SENDS A-E", "SENDS F-J", "INSTRUMENT", "comments", "no group"]; const SHORT: [&str; 6] = ["I A-E", "I F-J", "S A-E", "S F-J", "INST", "nogrp"]; +/// egui's built-in fonts only, as on a system with no fonts installed (the FreeBSD CI VM): the +/// "bold" family is then the regular built-in face, narrower than any system bold. +fn builtin_fonts() -> FontDefinitions { + let mut fonts = FontDefinitions::default(); + let base = fonts.families.get(&FontFamily::Proportional).cloned().unwrap_or_default(); + fonts.families.insert(FontFamily::Name("bold".into()), base); + fonts +} + fn painted_labels(narrow: bool) -> Vec<(String, f32, bool)> { + painted_labels_with(narrow, None) +} + +fn painted_labels_with(narrow: bool, fonts: Option) -> Vec<(String, f32, bool)> { let mut app = SoundApp::new(soundcraft_engine::demo::demo_engine(), None, Services::default()); app.run("window.mix", json!({})).unwrap(); app.run("view.mix_section", json!({"section": "all"})).unwrap(); @@ -15,6 +28,9 @@ fn painted_labels(narrow: bool) -> Vec<(String, f32, bool)> { let mut harness = egui_kittest::Harness::builder().with_size(vec2(1600.0, 1000.0)).build_ui_state( |ui, app: &mut SoundApp| { app.logic(ui.ctx()); + if let Some(f) = &fonts { + ui.ctx().set_fonts(f.clone()); + } app.ui(ui); }, app, @@ -35,18 +51,28 @@ fn painted_labels(narrow: bool) -> Vec<(String, f32, bool)> { .collect() } -#[test] -fn narrow_strips_use_short_section_names_that_fit() { - let labels = painted_labels(true); +fn assert_narrow_labels(labels: &[(String, f32, bool)]) { for name in SHORT { assert!(labels.iter().any(|(text, ..)| text == name), "{name} missing in {labels:?}"); } - for (text, width, elided) in &labels { + for (text, width, elided) in labels { assert!(SHORT.contains(&text.as_str()), "{text} painted in a narrow strip, where it doesn't fit"); assert!(*width <= strip_width(true) - 16.0 && !elided, "{text} is {width} pt wide, elided: {elided}"); } } +#[test] +fn narrow_strips_use_short_section_names_that_fit() { + assert_narrow_labels(&painted_labels(true)); +} + +/// With a narrower font SENDS A-E fits a narrow strip, but narrow strips still use the short names +/// (this failed on FreeBSD, whose CI VM has no fonts installed). +#[test] +fn narrow_strips_use_short_section_names_with_the_builtin_fonts() { + assert_narrow_labels(&painted_labels_with(true, Some(builtin_fonts()))); +} + #[test] fn normal_strips_keep_the_full_section_names() { let labels = painted_labels(false);