2 Commits
Author SHA1 Message Date
Brandon ThomasandClaude Opus 5.5 3a28a65769 Release 0.7.0
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-14 09:33:59 -04:00
Brandon ThomasandClaude Opus 5.5 01fd2d0cb4 Narrow Mix strips always use the short section names
The FreeBSD CI VM has no fonts installed, so the "bold" family falls back
to egui's built-in regular face. SENDS A-E and SENDS F-J then measure 47
and 45 pt, inside the 48 pt the measured-fit check allows, so narrow
strips showed them in full next to I A-E and INST, and
mix_narrow_headers failed on FreeBSD.

Narrow strips now always use Pro Tools' short names, whatever the font;
normal strips still shorten a name only when it doesn't fit. A new test
renders the narrow Mix with egui's built-in fonts only, reproducing the
FreeBSD failure on any platform.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-14 09:15:34 -04:00
4 changed files with 61 additions and 30 deletions

No files matched your search

Generated
+18 -18
View File
@@ -3567,7 +3567,7 @@ dependencies = [
[[package]]
name = "soundcraft"
version = "0.6.1"
version = "0.7.0"
dependencies = [
"eframe",
"egui",
@@ -3585,7 +3585,7 @@ dependencies = [
[[package]]
name = "soundcraft-au-host"
version = "0.6.1"
version = "0.7.0"
dependencies = [
"log",
"serde",
@@ -3595,7 +3595,7 @@ dependencies = [
[[package]]
name = "soundcraft-audio-io"
version = "0.6.1"
version = "0.7.0"
dependencies = [
"log",
"proptest",
@@ -3607,7 +3607,7 @@ dependencies = [
[[package]]
name = "soundcraft-automation"
version = "0.6.1"
version = "0.7.0"
dependencies = [
"log",
"serde_json",
@@ -3617,7 +3617,7 @@ dependencies = [
[[package]]
name = "soundcraft-clap-host"
version = "0.6.1"
version = "0.7.0"
dependencies = [
"clap-sys",
"libloading",
@@ -3630,7 +3630,7 @@ dependencies = [
[[package]]
name = "soundcraft-cli"
version = "0.6.1"
version = "0.7.0"
dependencies = [
"serde_json",
"soundcraft-audio-io",
@@ -3641,7 +3641,7 @@ dependencies = [
[[package]]
name = "soundcraft-dsp"
version = "0.6.1"
version = "0.7.0"
dependencies = [
"log",
"proptest",
@@ -3652,7 +3652,7 @@ dependencies = [
[[package]]
name = "soundcraft-engine"
version = "0.6.1"
version = "0.7.0"
dependencies = [
"log",
"proptest",
@@ -3674,7 +3674,7 @@ dependencies = [
[[package]]
name = "soundcraft-midi"
version = "0.6.1"
version = "0.7.0"
dependencies = [
"proptest",
"serde",
@@ -3684,7 +3684,7 @@ dependencies = [
[[package]]
name = "soundcraft-mix"
version = "0.6.1"
version = "0.7.0"
dependencies = [
"log",
"rayon",
@@ -3700,7 +3700,7 @@ dependencies = [
[[package]]
name = "soundcraft-model"
version = "0.6.1"
version = "0.7.0"
dependencies = [
"proptest",
"serde",
@@ -3713,7 +3713,7 @@ dependencies = [
[[package]]
name = "soundcraft-playback"
version = "0.6.1"
version = "0.7.0"
dependencies = [
"cpal",
"log",
@@ -3728,7 +3728,7 @@ dependencies = [
[[package]]
name = "soundcraft-time"
version = "0.6.1"
version = "0.7.0"
dependencies = [
"proptest",
"serde",
@@ -3738,7 +3738,7 @@ dependencies = [
[[package]]
name = "soundcraft-ui-egui"
version = "0.6.1"
version = "0.7.0"
dependencies = [
"craft-ui",
"egui",
@@ -3764,7 +3764,7 @@ dependencies = [
[[package]]
name = "soundcraft-video"
version = "0.6.1"
version = "0.7.0"
dependencies = [
"image",
"proptest",
@@ -3775,7 +3775,7 @@ dependencies = [
[[package]]
name = "soundcraft-vst3-host"
version = "0.6.1"
version = "0.7.0"
dependencies = [
"libloading",
"log",
@@ -3791,7 +3791,7 @@ dependencies = [
[[package]]
name = "soundcraft-web"
version = "0.6.1"
version = "0.7.0"
dependencies = [
"eframe",
"egui",
@@ -5348,7 +5348,7 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
[[package]]
name = "xtask"
version = "0.6.1"
version = "0.7.0"
dependencies = [
"serde_json",
]
+1 -1
View File
@@ -4,7 +4,7 @@ members = ["crates/*", "apps/*", "xtask"]
default-members = ["crates/*", "apps/*", "xtask"]
[workspace.package]
version = "0.6.1"
version = "0.7.0"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.90"
+11 -6
View File
@@ -103,7 +103,7 @@ fn channel_levels(ui: &Ui, key: (&str, u64), peaks: &[f32], n: usize) -> Vec<f32
lv
}
/// Pro Tools' names for sections in strips too narrow for the full ones.
/// Pro Tools' names for sections in narrow strips, and in any strip too narrow for the full ones.
fn short_label(text: &str) -> &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));
+31 -5
View File
@@ -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<FontDefinitions>) -> 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);