diff --git a/crates/oaktimeline/src/undopointer.rs b/crates/oaktimeline/src/undopointer.rs index 01025b0a7..5bad75139 100644 --- a/crates/oaktimeline/src/undopointer.rs +++ b/crates/oaktimeline/src/undopointer.rs @@ -556,6 +556,9 @@ pub struct TrackPlaceBlockCommand { add_track_commands_: Vec, /// Ripple-removal of the area the block occupies (`ripple_remove_command_`). ripple_remove_command_: Option, + /// 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, } 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 diff --git a/src/oakui/real.rs b/src/oakui/real.rs index 04819485a..e579f3053 100644 --- a/src/oakui/real.rs +++ b/src/oakui/real.rs @@ -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