Settings: fit tools move the playhead to the first frame inside the span

The work area need not sit on frames (a typed start, a frame-rate
change), so rounding its start to the nearest frame could land before
the new layer's in-point. show_span now rounds up to the first frame in
the span and moves nothing when the span holds no frame.
This commit is contained in:
nazar-gavrylyk committed 2026-11-11 09:15:58 +01:00
1 parent 34086899ad
commit 1991ad5d56
2 files changed
+30 -2

No files matched your search

+9 -2
View File
@@ -31,12 +31,19 @@ pub(crate) fn fit_span(l: &mut Layer, span: Option<(Tick, Tick)>) {
}
/// Tools draw on the layer they create: when it was fitted to a span the playhead is outside,
/// move `comp`'s playhead to the span's start so the new layer shows. The layer's timing is kept.
/// move `comp`'s playhead to the first frame in the span so the new layer shows. The layer's timing
/// is kept. The work area need not sit on frames (a typed start, a frame-rate change), so the
/// frame is rounded up from the start, not to the nearest; a span holding no frame moves nothing.
pub(crate) fn show_span(s: &mut Session, comp: ItemId, span: Option<(Tick, Tick)>) {
let Some(fr) = s.project.comp(comp).map(|c| c.frame_rate) else { return };
if let Some((start, end)) = span
&& !(start..end).contains(&s.time_of(comp))
{
s.set_time_of(comp, start);
let first = fr.snap(start);
let first = if first < start { fr.tick_of(fr.frame_at(start).saturating_add(1)) } else { first };
if first < end {
s.set_time_of(comp, first);
}
}
}
+21
View File
@@ -449,6 +449,27 @@ fn fitted_layers_in_another_comp_move_that_comps_playhead() {
}
}
/// A work area off the frame grid (set at 25 fps, then the comp went to 30 fps): the playhead
/// goes to the first frame inside it, not the nearest frame before its start.
#[test]
fn fitted_layers_off_the_frame_grid_move_the_playhead_inside_them() {
let mut s = Session::default();
s.execute("comp.new", json!({"name": "Grid", "duration": 2, "frameRate": 25})).unwrap();
s.execute("prefs.set", json!({"key": "general.fitNewLayersToWorkArea", "value": true})).unwrap();
s.execute("comp.workArea", json!({"start": 0.04, "end": 0.08})).unwrap();
s.execute("comp.settings", json!({"frameRate": 30})).unwrap();
for (cmd, p) in [("layer.newText", json!({"text": "a", "edit": true})), ("shape.newPath", json!({"vertices": [[0, 0], [10, 0], [10, 10]]}))] {
s.set_time(effectcraft_time::Tick::from_seconds_f64(1.0));
s.state.selected_layers.clear();
s.state.text_edit = None;
s.execute(cmd, p).unwrap();
let l = &s.active_comp().unwrap().layers[0];
let t = s.time();
assert!(l.in_point <= t && t < l.out_point, "{cmd}: {t:?} in {:?}..{:?}", l.in_point, l.out_point);
assert_eq!(t, effectcraft_time::Tick::from_seconds_f64(2.0 / 30.0), "{cmd}: the first frame inside");
}
}
#[test]
fn default_spatial_interpolation_linear() {
let mut s = session_with_comp();