Files
soundcraft/crates/engine/src/cmd/zoom_toggle.rs
T
Dylan M. TaylorandDylan Taylor fe4bb3fe0c style: replace flagged glob imports with explicit uses (SonarCloud rust:S2208); move release workflow's write permission to the job (S8233) (#988)
* security: move the release workflow's write permission to the job level (SonarCloud githubactions:S8233)

* style: replace wildcard imports with explicit uses (SonarCloud rust:S2208)

Rewrites every glob `use` Sonar flags into explicit imports, so what each
command module consumes is visible at the top of the file.

* release: the git-sources gate moves into craft-common-actions (PR #78)

The check is shared at release.yml@main now; keep only the reusable call.

---------

Co-authored-by: Dylan Taylor <dylanmtaylor@users.noreply.github.com>
2026-10-23 14:53:00 -04:00

92 lines
4.4 KiB
Rust

//! Zoom Toggle: zoom in on the edit selection, then back out to the view before.
use super::{CommandSpec, always, f64_or};
use soundcraft_model::{TrackHeight, ZoomToggle};
use soundcraft_time::MAX_POSITION;
use crate::{Engine, Result, cmd};
use serde_json::{Value, json};
pub fn specs() -> Vec<CommandSpec> {
vec![cmd!(noundo "view.zoom_toggle", "Zoom Toggle", [], None, "{width_px?: 1200, height_px?: 600}", always, toggle)]
}
fn toggle(e: &mut Engine, p: &Value) -> Result<Value> {
let s = e.session_mut();
if let Some(back) = s.edit.zoom_toggle.take() {
s.edit.zoom.samples_per_px = back.samples_per_px.clamp(0.25, 1_000_000.0);
s.edit.zoom.scroll = back.scroll.clamp(0, MAX_POSITION);
for t in &mut s.tracks {
t.height = back.heights.iter().find(|(id, _)| *id == t.id).map_or(t.height, |(_, h)| *h);
}
return Ok(json!({"on": false}));
}
let heights: Vec<_> = s.tracks.iter().filter(|t| !t.hidden && s.edit.selected_tracks.contains(&t.id)).map(|t| (t.id, t.height)).collect();
// Rows sit 1 point apart.
let each = f64_or(p, "height_px", 600.0) / heights.len().max(1) as f64 - 1.0;
let fit = TrackHeight::Fit(each.clamp(16.0, f64::from(u16::MAX)) as u16);
for t in s.tracks.iter_mut().filter(|t| heights.iter().any(|(id, _)| *id == t.id)) {
t.height = fit;
}
let (r, z) = (s.edit.selection, &mut s.edit.zoom);
s.edit.zoom_toggle = Some(ZoomToggle { samples_per_px: z.samples_per_px, scroll: z.scroll, heights });
if !r.is_empty() {
z.samples_per_px = (r.len() as f64 / f64_or(p, "width_px", 1200.0).max(100.0)).clamp(0.25, 1_000_000.0);
z.scroll = r.start;
}
Ok(json!({"on": true}))
}
#[cfg(test)]
mod tests {
use super::TrackHeight::{Fit, Large, Medium};
use super::*;
use soundcraft_model::TrackId;
use soundcraft_time::Samples;
fn engine(select: Value) -> Engine {
let mut e = Engine::default();
e.execute("track.new", &json!({"count": 3})).unwrap();
e.execute("view.zoom_set", &json!({"samples_per_px": 500.0})).unwrap();
e.execute("view.scroll", &json!({"to": 1000})).unwrap();
e.execute("edit.select", &select).unwrap();
e
}
fn view(e: &Engine) -> (f64, Samples, Vec<TrackHeight>) {
let s = e.session();
(s.edit.zoom.samples_per_px, s.edit.zoom.scroll, s.tracks.iter().map(|t| t.height).collect())
}
#[test]
fn the_selection_fills_the_window_and_toggling_again_goes_back() {
let mut e = engine(json!({"tracks": ["Audio 1", "Audio 3"], "start": 48_000, "end": 96_000}));
let steps = e.undo_history().len();
assert_eq!(e.execute("view.zoom_toggle", &json!({"width_px": 1000.0, "height_px": 500.0})).unwrap(), json!({"on": true}));
assert_eq!(view(&e), (48.0, 48_000, vec![Fit(249), Medium, Fit(249)]));
e.execute("view.zoom_in", &json!({})).unwrap();
e.execute("track.height", &json!({"tracks": ["Audio 2"], "height": "large"})).unwrap();
assert_eq!(e.execute("view.zoom_toggle", &json!({})).unwrap(), json!({"on": false}));
assert_eq!(view(&e), (500.0, 1000, vec![Medium, Large, Medium]));
assert_eq!(e.undo_history().len(), steps + 1);
e.execute("edit.select", &json!({"tracks": ["Audio 2"], "start": 5000})).unwrap();
e.execute("view.zoom_toggle", &json!({"height_px": 300.0})).unwrap();
assert_eq!(view(&e), (500.0, 1000, vec![Medium, Fit(299), Medium]));
}
#[test]
fn hostile_sizes_and_stored_views_stay_in_range() {
let mut e = engine(json!({"tracks": ["Audio 1"], "start": 0, "end": MAX_POSITION}));
e.execute("view.zoom_toggle", &json!({"width_px": -5.0, "height_px": 1e300})).unwrap();
assert_eq!((view(&e).0, view(&e).2[0]), (1_000_000.0, Fit(u16::MAX)));
e.execute("view.zoom_toggle", &json!({})).unwrap();
e.execute("view.zoom_toggle", &json!({"width_px": "wide", "height_px": -1.0})).unwrap();
assert_eq!(view(&e).2[0], Fit(16));
let audio_1 = e.session().tracks[0].id;
let heights = vec![(TrackId(9999), TrackHeight::Jumbo), (audio_1, Fit(0))];
e.session_mut().edit.zoom_toggle = Some(ZoomToggle { samples_per_px: -1.0, scroll: Samples::MIN, heights });
e.execute("view.zoom_toggle", &json!({})).unwrap();
assert_eq!(view(&e), (0.25, 0, vec![Fit(0), Medium, Medium]));
assert_eq!(Fit(0).points(), 16.0);
}
}