fix(oaktimeline): placement sets the block's in point (drop-at-cursor fix)

TrackPlaceBlockCommand::redo now homes the block's in point to the
placement target (capturing the original for undo): the Rust block
stores its position on the block, so a fresh clip that never had its
in point set always rendered at the timeline zero — the 'drops always
land at zero' bug. The original in point is captured on the first redo
and restored on undo, keeping the sync re-place round-trip exact. The
A/V drop test now asserts the clip lands at the drop frame.
This commit is contained in:
2026-08-20 15:33:43 +08:00
parent 1f6ed30423
commit 877f577564
2 changed files with 31 additions and 1 deletions
+18
View File
@@ -556,6 +556,9 @@ pub struct TrackPlaceBlockCommand {
add_track_commands_: Vec<TimelineAddTrackCommand>,
/// Ripple-removal of the area the block occupies (`ripple_remove_command_`).
ripple_remove_command_: Option<TrackRippleRemoveAreaCommand>,
/// The block's in point captured on the first redo (restored on undo
/// so a sync re-place / move round-trips the original position).
old_in_: Option<Rational>,
}
impl TrackPlaceBlockCommand {
@@ -572,11 +575,22 @@ impl TrackPlaceBlockCommand {
block,
add_track_commands_: Vec::new(),
ripple_remove_command_: None,
old_in_: None,
}
}
/// `redo`: place the block destructively.
pub fn redo(&mut self) {
// The module stores the position on the block (no track-order
// derivation like C++), so the placement contract is that the
// block's in point becomes `in_` — fresh clips otherwise keep
// their birth in=0 and every drop lands at the timeline zero. The
// original position is captured once so undo restores it (the
// sync re-place path round-trips the block's position).
if self.old_in_.is_none() {
self.old_in_ = Some(block_in(&self.block));
}
block_set_in(&self.block, self.in_);
// Determine if we need to add tracks
let track_count = tracklist_track_count(&self.timeline) as i32;
@@ -655,6 +669,10 @@ impl TrackPlaceBlockCommand {
// Firstly, remove our insert
track_ripple_remove_block(&t, &self.block);
// Restore the position the block had before the placement.
if let Some(old_in) = self.old_in_ {
block_set_in(&self.block, old_in);
}
if self.ripple_remove_command_.is_some() {
// If we ripple-removed, just undo that
+13 -1
View File
@@ -6225,9 +6225,21 @@ mod tests {
.expect("imported footage is listed");
cx.update(|app| {
engine.update(app, |engine, cx| {
engine.drop_footage(entry.id, TrackKind::Video, 0, Frame(0), cx)
// Drop at a non-zero frame: the placement must land where
// the cursor was (the "always lands at zero" regression).
engine.drop_footage(entry.id, TrackKind::Video, 0, Frame(40), cx)
})
});
// The clip landed at the drop frame (not the timeline zero).
let video_in = cx.read(|app| {
let engine = engine.read(app);
engine
.tracks
.iter()
.find(|t| t.kind == TrackKind::Video && !t.clips.is_empty())
.map(|t| t.clips[0].range.start.0)
});
assert_eq!(video_in, Some(40), "the clip lands at the drop frame");
let clip_count = |engine: &RealEngine, kind: TrackKind| -> usize {
engine