Timeline: scrolling the video tracks up leaves room to drop a new track (fixes #1522)

This commit is contained in:
Marco Arnulfo committed 2026-10-13 04:22:50 +02:00
1 parent 71438c627b
commit fe7efdaffc
1 file changed
+53 -7
+53 -7
View File
@@ -233,6 +233,19 @@ fn new_track_at(seq: &Sequence, layout: &Layout, y: f32, vh: f32, ah: f32) -> Op
}
}
/// The top of each of `n` video rows `vh` high, V1 first: V1 sits at the bottom of `area` and the
/// tracks stack upward; `v_scroll` (clamped here) shifts them up. Also gives the largest `v_scroll`.
/// The scroll range includes one empty track's height above the top track: without it, tracks that
/// fill the area leave no space where a drop adds a new one (#1522).
fn video_row_tops(area: Rect, n: usize, vh: f32, v_scroll: &mut f32) -> (Vec<f32>, f32) {
let total = (n + 1) as f32 * vh;
let v_off = (area.height() - total).max(0.0);
let max_vs = (total - area.height()).max(0.0);
*v_scroll = v_scroll.clamp(0.0, max_vs);
let tops = (0..n).map(|i| area.min.y + v_off + (n - i) as f32 * vh - (max_vs - *v_scroll)).collect();
(tops, max_vs)
}
/// How far the Timeline reaches, in seconds: ten minutes past the end of the sequence. Premiere
/// Pro's Timeline does the same (on a 6 s sequence its view stops with 00:10:06 at the right
/// edge). The view cannot be scrolled past it and the scroll bar spans it.
@@ -412,14 +425,9 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
let vh = app.ui.timeline.video_track_h;
let ah = app.ui.timeline.audio_track_h;
let mut rows = Vec::new();
// Video: V1 at the bottom of the video area, stacking upward; v_scroll shifts up.
let nv = seq.video_tracks.len();
let video_total = nv as f32 * vh;
let v_off = (video_area.height() - video_total).max(0.0);
let max_vs = (video_total - video_area.height()).max(0.0);
app.ui.timeline.v_scroll = app.ui.timeline.v_scroll.clamp(0.0, max_vs);
for (i, tr) in seq.video_tracks.iter().enumerate() {
let top = video_area.min.y + v_off + (nv - 1 - i) as f32 * vh - (max_vs - app.ui.timeline.v_scroll);
let (video_tops, max_vs) = video_row_tops(video_area, nv, vh, &mut app.ui.timeline.v_scroll);
for ((i, tr), top) in seq.video_tracks.iter().enumerate().zip(video_tops) {
let rect = Rect::from_min_max(pos2(content.min.x, top), pos2(content.max.x, top + vh));
rows.push(Row { track: tr.id, kind: TrackKind::Video, index: i, rect, lane: false });
}
@@ -3201,4 +3209,42 @@ mod new_track_tests {
assert_eq!(at(y), None, "y = {y}");
}
}
/// #1522: when the video tracks fill the video area, scrolling all the way up still leaves a
/// track's height of empty space above the top one, so a drop there can add a fourth track.
#[test]
fn a_full_video_area_scrolls_to_space_for_a_new_track() {
let mut p = Project::new("t");
let id = p.new_sequence("s", SequenceSettings::default(), 3, 1, None);
let seq = p.sequence(id).cloned().expect("sequence");
// video area 0..150 holds V1..V3 exactly; scrolled up as far as it goes
let area = Rect::from_min_max(pos2(0.0, 0.0), pos2(800.0, 150.0));
let mut v_scroll = f32::MAX;
let (tops, _) = video_row_tops(area, 3, 50.0, &mut v_scroll);
let rows = seq
.video_tracks
.iter()
.zip(&tops)
.enumerate()
.map(|(index, (t, &y0))| Row {
track: t.id,
kind: TrackKind::Video,
index,
rect: Rect::from_min_max(pos2(0.0, y0), pos2(800.0, y0 + 50.0)),
lane: false,
})
.collect();
let layout = Layout {
content: Rect::from_min_max(pos2(0.0, 0.0), pos2(800.0, 500.0)),
ruler: Rect::from_min_max(pos2(0.0, -20.0), pos2(800.0, 0.0)),
rows,
pps: 100.0,
scroll: 0.0,
split_y: 150.0,
};
assert_eq!(new_track_at(&seq, &layout, 10.0, 50.0, 50.0), Some((TrackKind::Video, Rect::from_min_max(pos2(0.0, 0.0), pos2(800.0, 50.0)))));
// scrolled to the bottom, V1..V3 fill the area as before
let mut v_scroll = 0.0;
assert_eq!(video_row_tops(area, 3, 50.0, &mut v_scroll).0, vec![100.0, 50.0, 0.0]);
}
}