setReorderPreviewAtIndex method

void setReorderPreviewAtIndex({
  1. required TKey draggedKey,
  2. required int gapVisibleIndex,
  3. Duration? duration,
  4. Curve? curve,
})

Opens (or re-targets) the paint-only make-room preview for a drag of draggedKey, with the gap opening BEFORE the row at gapVisibleIndex: rows at/after that slot shift down by the dragged subtree's visible extent, rows after the vacated slot shift up, and the offsets are HELD until this is called again (new slot) or clearReorderPreview releases them.

gapVisibleIndex is a visible-order index in [0, visibleNodeCount] INCLUSIVE; the terminal value means "after every visible row". Out-of-range clears the preview rather than clamping, since a slot that does not exist has no honest gap.

Structure never changes: no structural listeners fire, no sync diff runs, layout is untouched. The offsets ride the same composed read path as FLIP slide deltas, so painted positions, painted-truth snapshots (FLIP baselines, which is what makes the commit handoff seamless), hit-testing, retention, and overreach all see them automatically. Snaps instead of animating when the effective make-room spec (or the resolved per-call duration) is zero.

The dragged subtree's own rows keep their painted position (the reorderable widget hides them behind the drag proxy in make-room mode).

Implementation

void setReorderPreviewAtIndex({
  required TKey draggedKey,
  required int gapVisibleIndex,
  Duration? duration,
  Curve? curve,
}) {
  final draggedNid = _nids[draggedKey];
  if (draggedNid == null) {
    clearReorderPreview(animate: false);
    return;
  }
  _ensureVisibleOrder();
  final draggedIndex = _order.indexByNid[draggedNid];
  if (draggedIndex < 0 ||
      gapVisibleIndex < 0 ||
      gapVisibleIndex > _order.length) {
    clearReorderPreview(animate: false);
    return;
  }
  final draggedSize = _order.subtreeSizeOf(draggedNid);
  final draggedEnd = draggedIndex + draggedSize;
  final nids = orderNidsView;
  double lift = 0.0;
  for (int i = draggedIndex; i < draggedEnd; i++) {
    lift += getCurrentExtentNid(nids[i]);
  }
  if (lift <= 0.0) {
    clearReorderPreview(animate: false);
    return;
  }
  final gapIndex = gapVisibleIndex;

  // Null timing resolves to the style's effective make-room spec. The
  // snap MODE is resolved here, above the memo check, because it is
  // part of the memo key: the engine's retarget path early-outs on
  // identical targets (so dropped re-sends with different
  // duration/curve VALUES are behavior-identical), but the snap branch
  // has no such early-out: an animating-to-snap re-send with identical
  // geometry forces instant arrival and must not be skipped.
  final resolvedDuration =
      duration ?? _animationStyle.effectiveMakeRoom.duration;
  final resolvedSnap =
      _animationStyle.effectiveMakeRoom.duration == Duration.zero ||
      resolvedDuration == Duration.zero;

  // Geometry memo (see field docs): identical geometry + timing mode
  // already installed, nothing to do. `lift` compares exactly: an
  // unchanged state recomputes the same left-to-right sum bitwise, and
  // any input extent change flows into the sum (or into gapIndex / the
  // structure generation).
  if (_previewMemoValid &&
      draggedNid == _previewMemoDraggedNid &&
      gapIndex == _previewMemoGapIndex &&
      lift == _previewMemoLift &&
      _structureGeneration == _previewMemoStructureGen &&
      resolvedSnap == _previewMemoSnap) {
    return;
  }

  // Per-row shift over the CURRENT visible order (which still contains
  // the dragged rows in place): closing the vacated slot (minus lift for
  // rows after the dragged subtree) composes with opening the gap
  // (+lift for rows at/after the slot). Rows on the far side of both
  // cancel to zero; only the span between the old and new positions
  // moves.
  final targets = <int, double>{};
  // Bound the scan by the VISIBLE count, not the buffer's grow-only
  // capacity: [orderNidsView]'s tail beyond `_order.length` holds stale
  // nids from the high-water mark ("only the first N entries are
  // valid"). The `_ensureVisibleOrder()` above already flushed, so the
  // length is current.
  final scanCount = _order.length;
  debugLastPreviewTargetIterationCount = scanCount;
  for (int i = 0; i < scanCount; i++) {
    if (i >= draggedIndex && i < draggedEnd) {
      continue;
    }
    double shift = 0.0;
    if (i >= draggedEnd) {
      shift -= lift;
    }
    if (i >= gapIndex) {
      shift += lift;
    }
    if (shift != 0.0) {
      targets[nids[i]] = shift;
    }
  }
  debugPreviewInstallCount++;
  _preview.setTargetsForNids(
    targets,
    duration: resolvedDuration,
    curve: curve ?? _animationStyle.effectiveMakeRoom.curve,
    snap: resolvedSnap,
  );
  // Written after the install, for every install, including an
  // empty-target one (own-slot hover), which the engine expresses as
  // "no entries" but is still a held, re-skippable state.
  _preview.setLifted(draggedNid);
  _previewMemoValid = true;
  _previewMemoDraggedNid = draggedNid;
  _previewMemoGapIndex = gapIndex;
  _previewMemoLift = lift;
  _previewMemoStructureGen = _structureGeneration;
  _previewMemoSnap = resolvedSnap;
}