Files
tty7/docs
l0ng-aiandl0ng-ai 86799220ca feat(splits): rearrange a tab's panes by dragging one onto the layout (#445)
* feat(splits): rearrange a tab's panes by dragging one onto the layout

Hovering a pane floats a small grip along its top edge; dragging it picks
the pane up and puts it somewhere else in the same tab. Three landings,
resolved from where the pointer is:

* a pane's edge — split that pane and take the side dropped on
* a pane's middle — trade the two panes' places
* the band along the outside of the tab — sit beside everything else as a
  full-width or full-height band, which is the only way to say "make this
  a full-height column" in one gesture from the middle of a 2x2

The landing is highlighted while the drag is in flight, and is offered
only once the tree agrees the drop changes something, so the highlight is
never a promise the drop does not keep.

* pane: move_leaf / move_leaf_to_edge / swap_leaves, each built on a clone
  and installed only when the layout really differs
* pane_drag: the pointer-to-landing geometry, the drag state, and the grip
* tree_sync: reconcile a tab that kept its panes but changed shape with a
  single PaneMove instead of closing and rebuilding the tab

* feat(splits): drop a pane beside its neighbours, not on top of one

Trying the drag out on real layouts turned up three ways the drop model
asked for more precision than it should have.

A drop on a pane's side always halved that pane, so putting a new column
into a row of columns was only reachable at the very edge of the window,
where the band rule took over. A side facing a neighbour in the same row
or column now joins that run: the newcomer takes an equal share and the
others give it up in proportion, keeping whatever relative sizes they
were dragged to. A side facing across the run has no run to join and
still halves the pane it landed on.

The band along the tab's edge was a flat 26px, which on any real window
is a hair's breadth. It is now measured against the pane it is read in —
a sixth of it, floored at 32px and capped at 120 — and only counts on a
side that faces the window rather than another pane. Landing there takes
an even share of the columns that side already has instead of half the
tab, so a third column is a third and not a half.

The highlight is no longer drawn from the rule. The drop is carried out
on a deep copy and the dragged pane's new rectangle is measured off it,
so the preview and the result cannot disagree; the copy is deep because
sharing a run out writes ratios the live tree's splits hold in common.

Also: the grip is a quiet 22x3 bar that grows to 40x5 under a fixed
56x10 target (it needs an id of its own, or gpui settles its size before
the group-hover is known), and every rearrangeable pane keeps an 8px
strip clear above its grid so the grip never sits on the first row.

* fix(splits): pin a drop to the pane it was offered against

Review follow-ups on the pane drag.

A drop zone named its target by position in the tab's leaf order, but it
is read on one frame and carried out on the next: a pane closing in
between shifts every index after it, and the drop lands beside a pane the
user never aimed at. The zone now carries the target itself once the
frame that drew it has resolved it, so a target that has gone refuses the
drop instead of sliding it sideways.

Alongside it:

* `Pane` is no longer `Clone`. The two copies it can be asked for differ
  in whether they share their splits' sizes, which is not a difference to
  leave to whichever one `.clone()` happens to mean; `shallow_clone` is
  now named and private, next to `deep_clone`.
* `edge_landing` no longer hands back a share that only a test read. The
  test reads it off the split the landing produced instead, which is the
  number the drop actually lands.
* A test pins the invariant the drop zones rest on: `leaf_rects` comes
  back in the order `leaves` does.
* Drop a doc comment that had landed on `close_focused` describing a
  different method, and an `Option` in `drop_pane` that was wrapped only
  to be unwrapped two lines later.
* The changelog claimed every rearranged tab now syncs as one `PaneMove`.
  Only a drop beside a single pane does; a drop beside a whole group is
  not something `PaneMove` can name, and still takes the rebuild. Both
  entries move under `Unreleased` — v26.8.2 was tagged before either
  landed.

---------

Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
2026-08-09 22:04:24 +08:00
..