fix(timeline): restore the C++ block length anchors and repair pointer edits

The two BlockCore length setters swapped their anchors relative to the
C++ semantics they document, so the ported edit commands produced wrong
geometry on the live UI paths: roll edits kept the seam still, slides
left negative in-points, and trims wrote the timeline in-point into
media_in (playing the wrong media content).

Adopt three stored-range primitives in block.rs:

- set_length_and_media_out: in fixed, out moves, media untouched
  (resize, trim-out, gaps growing rightward).
- set_length_and_media_in: in fixed, out moves, media_in += old-new
  (resize-with-media-in, splice right half, ripple trim-in).
- set_length_keeping_out (new): out fixed, in moves, media_in +=
  old-new (trim-in body and out-neighbour, slide out-neighbour,
  ripple trim-in of the trailing block).

Point every command at the primitive matching its intent (undopointer,
undogeneral, undoripple, undosplit, graphops, cli, nodeops) and fix the
two real defects the swap hid:

- TrackReplaceBlockWithGapCommand grew a following gap rightward,
  swallowing whatever followed it: the "dragging one clip moves
  unrelated clips" regression. The gap now grows leftward over the
  removed block's span; regression test in domain_test.
- The ripple/splice trims now advance media_in instead of rewriting it,
  and BlockSplitCommand writes both halves' ranges and media
  explicitly (the second half continues from the split point).

Rewrite the KNOWN-SWAP expectations to the correct geometry (roll moves
the seam, slide has no negative in-point, insert-gaps grows rightward,
resize-with-media-in yields media_in = 20) and add the missing media
assertions. TrackSlideCommand documents that the caller positions the
sliding blocks (the stored model has no track layout).
This commit is contained in:
2026-09-24 12:41:29 +08:00
parent 415262a7b2
commit d88e2d6ec1
14 changed files with 374 additions and 252 deletions
+24 -9
View File
@@ -90,20 +90,35 @@ impl BlockCore {
self.range = TimeRange::new(self.in_(), out);
}
/// Set the length, keeping the media out anchored (C++
/// `Block::set_length_and_media_out`): the timeline in-point shifts
/// so the out-point stays put, and the media in follows it.
/// Set the length, keeping the timeline in-point and the media in-point
/// anchored (C++ `Block::set_length_and_media_out`): the out-point
/// shifts (and the media out follows it). The stored-range model has no
/// track layout to derive the in-point from, so the caller arranges the
/// neighbouring blocks explicitly when a different anchor is wanted.
pub fn set_length_and_media_out(&mut self, length: Rational) {
let out = self.in_() + self.length();
self.range = TimeRange::new(out - length, out);
self.media_in = self.range.in_();
self.range = TimeRange::new(self.in_(), self.in_() + length);
}
/// Set the length, keeping the media in anchored (C++
/// `Block::set_length_and_media_in`): the in-point stays, the
/// out-point shifts.
/// Set the length, keeping the timeline in-point and the MEDIA OUT
/// anchored (C++ `Block::set_length_and_media_in`): the media in-point
/// moves by `old_length - length` so the media content end stays put
/// while the timeline out-point follows the length.
pub fn set_length_and_media_in(&mut self, length: Rational) {
let delta = self.length() - length;
self.range = TimeRange::new(self.in_(), self.in_() + length);
self.media_in = self.media_in + delta;
}
/// Set the length, keeping the timeline OUT-point and the media content
/// between the two ends anchored: the in-point shifts by the length
/// delta and the media in-point follows it, so the media out stays put
/// too (the stored-range model's net for a head trim, where the C++
/// derives the new in-point from the resized previous block).
pub fn set_length_keeping_out(&mut self, length: Rational) {
let delta = self.length() - length;
let out = self.out();
self.range = TimeRange::new(out - length, out);
self.media_in = self.media_in + delta;
}
/// Media out (in + length; C++ `Block::media_out`).