ui: timeline view stops at the comp end; a ruler scrub past the edge scrolls with the CTI
Co-Authored-By: Claude <noreply@anthropic.com> (cherry picked from commit 562bfa51bd6ee86d1f8f5558b1ad466c7e4b4517) (cherry picked from commit 0779d6944ede8afdff6ab49ea5d2caad19dfbfa3) (cherry picked from commit 38e224f5c5037b7c79a283ab058582a3ad200339) (cherry picked from commit 5e7b68be23ace400d92672ebf41c59124d29b101)
This commit is contained in:
parent
c4fdbd6064
commit
520ea11059
|
|
@ -1305,6 +1305,11 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
|||
let span = ((graph_x1 - graph_x0 - 6.0) as f64 / pps).max(0.0);
|
||||
app.ui.timeline.start = page_to_cti(app.ui.timeline.start, span, comp.duration.seconds(), app.session.time().seconds());
|
||||
}
|
||||
// As in After Effects, the ruler never scrolls past the comp's end: zoomed in, the last
|
||||
// visible time is at most the comp duration.
|
||||
let visible_secs = ((graph_x1 - graph_x0 - 6.0).max(1.0) as f64 / pps.max(1e-6)).max(0.0);
|
||||
let max_start = (comp.duration.seconds() - visible_secs).max(0.0);
|
||||
app.ui.timeline.start = app.ui.timeline.start.clamp(0.0, max_start);
|
||||
let tm = TMap { x0: graph_x0 + 6.0, start: app.ui.timeline.start, pps };
|
||||
ctx.data_mut(|d| d.insert_temp(tl_map_id(), (tm.x0, tm.start, tm.pps)));
|
||||
let top = rect.min.y;
|
||||
|
|
@ -1591,6 +1596,17 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
|||
let tt = Tick::from_seconds_f64(secs);
|
||||
app.session.set_time(tt);
|
||||
app.stop();
|
||||
// Dragged past either edge of the ruler (over the layer outline, say), the view scrolls so
|
||||
// the current time indicator stays in sight (After Effects).
|
||||
if app.ui.timeline.pps.is_some() {
|
||||
let now = app.session.time().seconds();
|
||||
let span = tm.t(bin.min.x) - tm.start;
|
||||
if now < tm.start {
|
||||
app.ui.timeline.start = now.max(0.0);
|
||||
} else if now > tm.start + span {
|
||||
app.ui.timeline.start = (now - span).max(0.0);
|
||||
}
|
||||
}
|
||||
// Ctrl/Cmd-drag scrubs the audio too (After Effects).
|
||||
if rresp.dragged()
|
||||
&& ui.input(|i| i.modifiers.command)
|
||||
|
|
|
|||
|
|
@ -169,3 +169,29 @@ fn azerty_physical_zoom_keys_zoom_the_graph() {
|
|||
let (_, span2) = view(&h);
|
||||
assert!(span2 > span1 * 1.2, "Physical+Minus zooms the graph out: {span1} -> {span2}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zoomed_in_the_ruler_stops_at_the_comp_end_and_a_scrub_past_the_edge_scrolls() {
|
||||
let mut h = harness(false);
|
||||
for _ in 0..4 {
|
||||
send(&mut h, "ui.key", json!({"key": "="}));
|
||||
}
|
||||
// Scrolled as far right as it goes: the view ends at the comp's end, not past it (AE).
|
||||
h.state_mut().ui.timeline.start = 9.9;
|
||||
h.run_steps(3);
|
||||
let (start, span) = view(&h);
|
||||
assert!(span < 9.0, "zoomed in: {span}");
|
||||
assert!((start + span - 10.0).abs() < 0.05, "the view ends at 10 s: {start} + {span}");
|
||||
// A click at the ruler's right end puts the CTI there.
|
||||
let ruler = h.state().auto.find("timeline.ruler").unwrap().rect;
|
||||
let y = ruler[1] + ruler[3] - 4.0;
|
||||
send(&mut h, "ui.click", json!({"x": ruler[0] + ruler[2] - 26.0, "y": y}));
|
||||
let t = h.state().session.time().seconds();
|
||||
assert!(t > start + span * 0.9, "the CTI jumps to the click near the end: {t}");
|
||||
// Dragged from the ruler out over the layer outline, the view scrolls with the CTI.
|
||||
send(&mut h, "ui.drag", json!({"from": {"x": ruler[0] + 40.0, "y": y}, "to": {"x": ruler[0] - 200.0, "y": y}, "steps": 20}));
|
||||
let (start2, span2) = view(&h);
|
||||
let t2 = h.state().session.time().seconds();
|
||||
assert!(t2 < start, "the CTI moved left of the old view: {t2} vs {start}");
|
||||
assert!(t2 >= start2 - 1e-6 && t2 <= start2 + span2, "the CTI stays in view: {t2} in {start2}+{span2}");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue