From 877f5775647aa65f6d2721796f7990f27ca6381b Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Thu, 20 Aug 2026 15:33:43 +0800 Subject: [PATCH] fix(oaktimeline): placement sets the block's in point (drop-at-cursor fix) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- crates/oaktimeline/src/undopointer.rs | 18 ++++++++++++++++++ src/oakui/real.rs | 14 +++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) 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