From 87ad11cccc67a1e5f362e1fc4ef57490acbc2f10 Mon Sep 17 00:00:00 2001 From: CodyG_NeuralDSP Date: Wed, 14 Oct 2026 14:37:08 -0400 Subject: [PATCH 1/4] fix(ui): center track names in edit window track headers --- crates/ui-egui/src/edit_window/header.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ui-egui/src/edit_window/header.rs b/crates/ui-egui/src/edit_window/header.rs index f8a002a2..ab6da146 100644 --- a/crates/ui-egui/src/edit_window/header.rs +++ b/crates/ui-egui/src/edit_window/header.rs @@ -115,7 +115,7 @@ fn name_plate(app: &mut SoundApp, ui: &mut Ui, track: &Track, head: Rect, select track.name.clone() }; let name_color = if selected { t.text_dark } else { t.text.lerp_to_gamma(t.text_dim, 0.4) }; - ui.painter().with_clip_rect(name_r).text(pos2(name_r.min.x + 1.5, name_r.center().y), Align2::LEFT_CENTER, &shown_name, bold(12.5), name_color); + ui.painter().with_clip_rect(name_r).text(name_r.center(), Align2::CENTER_CENTER, &shown_name, bold(12.5), name_color); let nresp = ui.interact(name_r, ui.scope_id().with(("name", id.0)), Sense::click_and_drag()); if nresp.double_clicked() { app.dialogs.open_rename_track(id, &track.name); From d5564bce05197eb85756443aef7b4d0931816562 Mon Sep 17 00:00:00 2001 From: CodyG_NeuralDSP Date: Wed, 14 Oct 2026 15:01:47 -0400 Subject: [PATCH 2/4] fix(ui): position master track view selector directly under track name --- crates/ui-egui/src/edit_window/header.rs | 34 +++++++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/crates/ui-egui/src/edit_window/header.rs b/crates/ui-egui/src/edit_window/header.rs index ab6da146..60b33fde 100644 --- a/crates/ui-egui/src/edit_window/header.rs +++ b/crates/ui-egui/src/edit_window/header.rs @@ -190,8 +190,9 @@ fn toggle_keys(app: &mut SoundApp, ui: &mut Ui, track: &Track, head: Rect) { let h = head.height(); let at = |x: f32, y: f32, w: f32, h: f32| Rect::from_min_size(pos2(head.min.x + x, head.min.y + y), vec2(w, h)); let m = &track.mixer; - if h >= 37.0 { - let (records, mixes) = (track.kind.has_playlist(), track.kind != TrackKind::Master); + let is_master = track.kind == TrackKind::Master; + if h >= 37.0 && !is_master { + let (records, mixes) = (track.kind.has_playlist(), true); let implied = !m.solo && app.engine.session().tracks.iter().any(|t| t.mixer.solo); let keys = [ (HeaderKey::Rec, m.record_arm, false, "mix.record_arm", records), @@ -213,10 +214,13 @@ fn view_selector(app: &mut SoundApp, ui: &mut Ui, track: &Track, head: Rect) { let id = track.id; let h = head.height(); let at = |x: f32, y: f32, w: f32, h: f32| Rect::from_min_size(pos2(head.min.x + x, head.min.y + y), vec2(w, h)); - if h >= 58.0 { + let is_master = track.kind == TrackKind::Master; + let view_y = if is_master { 20.0 } else { 39.0 }; + let view_min_h = if is_master { 37.0 } else { 58.0 }; + if h >= view_min_h { let vresp = header_selector( ui, - at(17.0, 39.0, 105.0, 17.0), + at(17.0, view_y, 105.0, 17.0), ui.scope_id().with(("view", id.0)), &view_label(&track.view), t.text, @@ -225,10 +229,32 @@ fn view_selector(app: &mut SoundApp, ui: &mut Ui, track: &Track, head: Rect) { ); egui::Popup::menu(&vresp).show(|ui| view_menu(app, ui, track)); } + if is_master && h >= 58.0 { + let mode = track.mixer.automation_mode; + let col = match mode { + soundcraft_model::AutomationMode::Read => t.auto_read, + soundcraft_model::AutomationMode::Write => t.auto_write, + soundcraft_model::AutomationMode::Touch | soundcraft_model::AutomationMode::TouchLatch => t.auto_touch, + soundcraft_model::AutomationMode::Latch => t.auto_latch, + _ => t.text_dim, + }; + let mode_name = format!("Automation mode for {}", track.name); + let aresp = header_selector(ui, at(17.0, 39.0, 105.0, 17.0), ui.scope_id().with(("auto_mode", id.0)), mode.label(), col, true, &mode_name); + egui::Popup::menu(&aresp).show(|ui| { + for m in soundcraft_model::AutomationMode::ALL { + if ui.selectable_label(m == mode, m.label()).clicked() { + let _ = app.run("mix.automation_mode", json!({"track": id.0, "mode": m.label()})); + } + } + }); + } } /// Freeze, voice and automation mode. fn voice_row(app: &mut SoundApp, ui: &mut Ui, track: &Track, head: Rect) { + if track.kind == TrackKind::Master { + return; + } let t = Tokens::current(); let id = track.id; let h = head.height(); From 6a4c055094baba12996daf44b89e82fab54d4f93 Mon Sep 17 00:00:00 2001 From: Cody Gilchrist Date: Tue, 3 Nov 2026 11:47:04 -0500 Subject: [PATCH 3/4] fix(ui): preserve start of long track names in edit headers --- crates/ui-egui/src/edit_window/header.rs | 7 +++++- crates/ui-egui/src/edit_window/tests.rs | 29 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/src/edit_window/header.rs b/crates/ui-egui/src/edit_window/header.rs index 60b33fde..f2e78243 100644 --- a/crates/ui-egui/src/edit_window/header.rs +++ b/crates/ui-egui/src/edit_window/header.rs @@ -115,7 +115,12 @@ fn name_plate(app: &mut SoundApp, ui: &mut Ui, track: &Track, head: Rect, select track.name.clone() }; let name_color = if selected { t.text_dark } else { t.text.lerp_to_gamma(t.text_dim, 0.4) }; - ui.painter().with_clip_rect(name_r).text(name_r.center(), Align2::CENTER_CENTER, &shown_name, bold(12.5), name_color); + let font = bold(12.5); + let galley = ui.painter().layout_no_wrap(shown_name.clone(), font.clone(), name_color); + let overflows = galley.size().x > name_r.width() - 3.0; + let (text_pos, align) = + if overflows { (pos2(name_r.min.x + 1.5, name_r.center().y), Align2::LEFT_CENTER) } else { (name_r.center(), Align2::CENTER_CENTER) }; + ui.painter().with_clip_rect(name_r).text(text_pos, align, &shown_name, font, name_color); let nresp = ui.interact(name_r, ui.scope_id().with(("name", id.0)), Sense::click_and_drag()); if nresp.double_clicked() { app.dialogs.open_rename_track(id, &track.name); diff --git a/crates/ui-egui/src/edit_window/tests.rs b/crates/ui-egui/src/edit_window/tests.rs index 3e6705ef..139123c0 100644 --- a/crates/ui-egui/src/edit_window/tests.rs +++ b/crates/ui-egui/src/edit_window/tests.rs @@ -1525,3 +1525,32 @@ fn alt_click_on_a_marker_deletes_it() { assert_eq!(h.state().engine.session().markers.len(), before - 1); assert!(!h.state().engine.session().markers.iter().any(|m| m.name == "Alt-delete me")); } + +#[test] +fn short_track_name_is_centered_and_long_name_is_left_aligned() { + let mut h = harness(2); + let s = h.state().engine.session_arc(); + let first = s.tracks[0].id.0; + let second = s.tracks[1].id.0; + h.state_mut().run("track.rename", json!({"track": first, "name": "Vocal"})).unwrap(); + h.state_mut().run("track.rename", json!({"track": second, "name": "A Very Long Track Name That Exceeds Header Width"})).unwrap(); + h.step(); + + let r0 = row(&h, 0); + let r1 = row(&h, 1); + + let first_text = h.output().shapes.iter().find_map(|s| match &s.shape { + Shape::Text(t) if t.galley.text() == "Vocal" && s.clip_rect.intersects(r0) => Some(t.pos), + _ => None, + }); + let long_text = h.output().shapes.iter().find_map(|s| match &s.shape { + Shape::Text(t) if t.galley.text() == "A Very Long Track Name That Exceeds Header Width" && s.clip_rect.intersects(r1) => Some(t.pos), + _ => None, + }); + + let first_pos = first_text.expect("short track name should be rendered in edit header"); + let long_pos = long_text.expect("long track name should be rendered in edit header"); + + assert_eq!(long_pos.x, r1.min.x + 17.5); + assert!(first_pos.x > r0.min.x + 25.0, "short name should be centered (was at {})", first_pos.x); +} From 76f58fcb811388d0a556156e2936881284b12e59 Mon Sep 17 00:00:00 2001 From: Cody Gilchrist Date: Tue, 3 Nov 2026 12:29:37 -0500 Subject: [PATCH 4/4] fix(ui): add hover tooltip for overflowing track names and assert exact center position in test --- crates/ui-egui/src/edit_window/header.rs | 7 ++++++- crates/ui-egui/src/edit_window/tests.rs | 7 ++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/ui-egui/src/edit_window/header.rs b/crates/ui-egui/src/edit_window/header.rs index f2e78243..447671d8 100644 --- a/crates/ui-egui/src/edit_window/header.rs +++ b/crates/ui-egui/src/edit_window/header.rs @@ -121,7 +121,12 @@ fn name_plate(app: &mut SoundApp, ui: &mut Ui, track: &Track, head: Rect, select let (text_pos, align) = if overflows { (pos2(name_r.min.x + 1.5, name_r.center().y), Align2::LEFT_CENTER) } else { (name_r.center(), Align2::CENTER_CENTER) }; ui.painter().with_clip_rect(name_r).text(text_pos, align, &shown_name, font, name_color); - let nresp = ui.interact(name_r, ui.scope_id().with(("name", id.0)), Sense::click_and_drag()); + let mut nresp = ui.interact(name_r, ui.scope_id().with(("name", id.0)), Sense::click_and_drag()); + if overflows { + nresp = nresp.on_hover_ui(|ui| { + ui.label(&shown_name); + }); + } if nresp.double_clicked() { app.dialogs.open_rename_track(id, &track.name); } else if nresp.clicked() { diff --git a/crates/ui-egui/src/edit_window/tests.rs b/crates/ui-egui/src/edit_window/tests.rs index 139123c0..48e63773 100644 --- a/crates/ui-egui/src/edit_window/tests.rs +++ b/crates/ui-egui/src/edit_window/tests.rs @@ -1540,7 +1540,7 @@ fn short_track_name_is_centered_and_long_name_is_left_aligned() { let r1 = row(&h, 1); let first_text = h.output().shapes.iter().find_map(|s| match &s.shape { - Shape::Text(t) if t.galley.text() == "Vocal" && s.clip_rect.intersects(r0) => Some(t.pos), + Shape::Text(t) if t.galley.text() == "Vocal" && s.clip_rect.intersects(r0) => Some((t.pos, t.galley.size())), _ => None, }); let long_text = h.output().shapes.iter().find_map(|s| match &s.shape { @@ -1548,9 +1548,10 @@ fn short_track_name_is_centered_and_long_name_is_left_aligned() { _ => None, }); - let first_pos = first_text.expect("short track name should be rendered in edit header"); + let (first_pos, first_size) = first_text.expect("short track name should be rendered in edit header"); let long_pos = long_text.expect("long track name should be rendered in edit header"); assert_eq!(long_pos.x, r1.min.x + 17.5); - assert!(first_pos.x > r0.min.x + 25.0, "short name should be centered (was at {})", first_pos.x); + let first_center_x = first_pos.x + first_size.x * 0.5; + assert!((first_center_x - (r0.min.x + 64.5)).abs() <= 0.1, "short name should be centered (center was at {first_center_x})"); }