Scroll Into View is a UI command, view.scroll_into_view, and the track name menu dispatches it

This commit is contained in:
Marco Arnulfo
2026-10-21 10:23:03 +02:00
parent 717ae97af5
commit 16371a8502
4 changed files with 38 additions and 2 deletions
+1 -2
View File
@@ -1480,8 +1480,7 @@ pub(crate) fn track_context_menu(app: &mut SoundApp, ui: &mut Ui, id: TrackId) {
}
// Only the track clicked: the command has one target, and the selection stays as it is.
if ui.button("Scroll Into View").clicked() {
app.edit_scroll_into_view = Some(id);
app.mix_scroll_into_view = Some(id);
let _ = app.run("view.scroll_into_view", json!({"track": id.0}));
}
ui.separator();
ui.menu_button("Track Height", |ui| {
+15
View File
@@ -69,6 +69,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, Option<&str>)] = &[
("window.io_setup", "I/O Setup", "", None),
("window.shortcuts", "Keyboard Shortcuts", "", None),
("view.mix_section", "Mix Window View", "", None),
("view.scroll_into_view", "Scroll Into View", "", None),
("ui.new_tracks_dialog", "New Tracks…", "", None),
("ui.bounce_dialog", "Bounce Mix…", "", None),
("ui.set", "Set UI State", "", None),
@@ -647,6 +648,20 @@ pub fn run_ui_command(app: &mut SoundApp, id: &str, p: &Value) -> Option<Result<
}
json!({"mix_views": app.ui.mix_views})
}
"view.scroll_into_view" => {
// `track` (id or name), else the first selected track: the palette passes no params.
let track = match soundcraft_engine::cmd::track_param(&app.engine, id, p, "track") {
Ok(Some(t)) => t,
Ok(None) => match app.engine.session().edit.selected_tracks.first() {
Some(t) => *t,
None => return Some(Err("view.scroll_into_view: pass `track` or select a track".into())),
},
Err(e) => return Some(Err(e.to_string())),
};
app.edit_scroll_into_view = Some(track);
app.mix_scroll_into_view = Some(track);
json!({"track": track.0})
}
"ui.new_tracks_dialog" => {
return Some(Err("ui.new_tracks_dialog opens a dialog from the menu only; call track.new with parameters".into()));
}
+18
View File
@@ -132,3 +132,21 @@ fn a_track_in_a_closed_folder_scrolls_the_mix_but_not_the_edit_window() {
h.run_steps(4);
assert_eq!(h.state().edit_layout.scroll_y, 0.0);
}
#[test]
fn the_command_takes_a_track_by_name_from_the_control_channel() {
let mut h = harness(20);
h.state_mut().ui.window = MainWindow::Mix;
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);
let now = plate(&h, "T08").expect("T08 on screen").x;
assert!((now - first).abs() < 1.0, "T08 is the leftmost strip: at {now}, the first was at {first}");
h.state_mut().ui.window = MainWindow::Edit;
h.run_steps(4);
assert_eq!(edit_row(&h, "T08")[1], h.state().edit_layout.timeline[1], "the Edit window scrolled too");
assert!(h.state_mut().run("view.scroll_into_view", json!({"track": "T99"})).is_err());
}
+4
View File
@@ -87,6 +87,10 @@ When the integration cannot report an appearance, System uses Dark. Manual Light
fixed when the OS appearance changes. An offscreen render without `--system-theme` supplies no
OS appearance and therefore resolves System to Dark.
`view.scroll_into_view {"track":"Bass"}` (a track id or name; without it, the first selected
track) brings the track to the top of the Edit window and the left of the Mix window, the same as
Scroll Into View in the track name menu. A window not on screen catches up when it is next shown.
## Panel docking
Tracks and Groups, Clips, MIDI Editor, Memory Locations, and Undo History use movable panels. Commands target the active Edit or Mix view; floating panels stay inside the current viewport. Edit and Mix retain separate arrangements; Clips and MIDI Editor belong to Edit. Transport, plug-in, and specialist utility windows retain their existing hosts. The document/session view is protected. Layouts, active tabs, panel visibility, and floating rectangles are saved with the app's existing UI preferences. These operations do not edit document contents.