Scroll Into View: a Mix request lasts one frame, so a hidden track doesn't scroll the Mix when it comes back

This commit is contained in:
Marco Arnulfo
2026-10-21 10:23:03 +02:00
parent 16371a8502
commit 76bac6495e
2 changed files with 22 additions and 4 deletions
+6 -4
View File
@@ -47,11 +47,14 @@ pub(crate) fn content(app: &mut SoundApp, ui: &mut Ui) {
let ids: Vec<TrackId> = app.engine.session().tracks.iter().filter(|x| !x.hidden && x.kind != TrackKind::Video).map(|x| x.id).collect();
// Per-channel peaks for multichannel meters (read once per frame).
let snap = app.player.as_ref().filter(|_| app.meters_live()).map(|p| p.meters());
// Taken for this frame only: a strip that isn't drawn (hidden track) must not scroll the Mix
// whenever it comes back. A request made during this frame (the strip's menu) waits for the next.
let scroll_to = app.mix_scroll_into_view.take();
egui::ScrollArea::horizontal().auto_shrink([false, false]).show(ui, |ui| {
ui.horizontal_top(|ui| {
ui.spacing_mut().item_spacing.x = 1.0;
for id in ids {
strip(app, ui, id, snap.as_ref());
strip(app, ui, id, snap.as_ref(), scroll_to == Some(id));
}
let rest = Rect::from_min_max(pos2(ui.cursor().min.x, ui.clip_rect().min.y), ui.clip_rect().max);
if rest.width() > 4.0 && ui.interact(rest, ui.id().with("past_strips"), Sense::click()).double_clicked() {
@@ -149,7 +152,7 @@ pub(crate) fn route_popup(app: &mut SoundApp, resp: &egui::Response, id: TrackId
egui::Popup::menu(resp).close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside).show(|ui| route_menu(app, ui, id, input));
}
fn strip(app: &mut SoundApp, ui: &mut Ui, id: TrackId, snap: Option<&MeterSnapshot>) {
fn strip(app: &mut SoundApp, ui: &mut Ui, id: TrackId, snap: Option<&MeterSnapshot>, scroll_here: bool) {
let t = Tokens::current();
let session = app.engine.session_arc();
let Some(track) = session.track(id) else { return };
@@ -157,8 +160,7 @@ fn strip(app: &mut SoundApp, ui: &mut Ui, id: TrackId, snap: Option<&MeterSnapsh
let w = strip_width(narrow);
let h = ui.available_height().max(560.0);
let (r, _) = ui.allocate_exact_size(vec2(w, h), Sense::hover());
if app.mix_scroll_into_view == Some(id) {
app.mix_scroll_into_view = None;
if scroll_here {
// egui stops `item_spacing` short of the edge (the scroll area's, not the strips' 1 pt),
// which would leave a sliver of the previous strip showing.
let margin = ui.ctx().global_style().spacing.item_spacing.x;
+16
View File
@@ -150,3 +150,19 @@ fn the_command_takes_a_track_by_name_from_the_control_channel() {
assert!(h.state_mut().run("view.scroll_into_view", json!({"track": "T99"})).is_err());
}
#[test]
fn a_hidden_track_leaves_no_scroll_waiting_in_the_mix() {
let mut h = harness(20);
h.state_mut().ui.window = MainWindow::Mix;
h.state_mut().run("track.hide", json!({"tracks": ["T08"], "hidden": true})).unwrap();
h.run_steps(4);
let first = plate(&h, "T01").expect("the first strip's name").x;
h.state_mut().run("view.scroll_into_view", json!({"track": "T08"})).unwrap();
h.run_steps(4);
// Shown again later, T08 must not pull the Mix to itself: the request was for frames long gone.
h.state_mut().run("track.hide", json!({"tracks": ["T08"], "hidden": false})).unwrap();
h.run_steps(4);
let now = plate(&h, "T01").expect("T01 still on screen").x;
assert!((now - first).abs() < 1.0, "the Mix stayed put: T01 at {now}, was at {first}");
}