diff options
| author | Christian Kolset <christian.kolset@gmail.com> | 2026-06-04 11:39:33 -0600 |
|---|---|---|
| committer | Christian Kolset <christian.kolset@gmail.com> | 2026-06-04 11:39:33 -0600 |
| commit | 0388bebbc806d23abab945d52a0f285f966b412a (patch) | |
| tree | 7de136976aac58ed7db03881c54c1f4daa5e8529 /ui | |
| parent | 7651f515728533506391e6cc719fe8ff32c339e7 (diff) | |
Fix layout canvas drag-drop: zone logic, pop-out bug, canvas height
- Fix squeeze zone pop-out: _adjacent_tile used self._drag_tile (already
None at drop time) to exclude the dragged tile from neighbor candidates.
Pass dragged_idx explicitly via exclude_idx param instead.
- Swap split/squeeze detection areas: outer 1/6 now triggers squeeze
(insert-between), inner 1/6→1/3 band triggers split (bisect target).
- Extend squeeze detection to gaps between tiles and canvas edges via a
second pass in _zone_at that expands tile rects by _LC_GAP+2 and finds
the closest outside edge. Canvas-edge squeeze falls through to bisect.
- Dynamic canvas height: sync_panes sets height to
max(160, n*(min_tile+gap)+2*gap) so 4+ panes never get crushed below
minimum tile size.
- Remove _tree_grid_size cap of 12: LCM was truncated prematurely,
causing unequal rowspans in the strip chart for 5+ panes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'ui')
| -rw-r--r-- | ui/windows/plot_window.py | 49 |
1 files changed, 38 insertions, 11 deletions
diff --git a/ui/windows/plot_window.py b/ui/windows/plot_window.py index f929261..95b9fea 100644 --- a/ui/windows/plot_window.py +++ b/ui/windows/plot_window.py @@ -246,7 +246,7 @@ def tree_to_grid(node: dict, row: int, col: int, rowspan: int, colspan: int, def _tree_grid_size(node: dict) -> int: - """Minimal grid = LCM of all split denominators (leaf counts), capped at 12.""" + """Minimal grid = LCM of all split denominators (leaf counts).""" denoms: List[int] = [] _collect_denoms(node, denoms) if not denoms: @@ -254,8 +254,6 @@ def _tree_grid_size(node: dict) -> int: result = 1 for d in denoms: result = result * d // gcd(result, d) - if result >= 12: - return 12 return result @@ -536,6 +534,8 @@ class LayoutCanvas(QFrame): if tree is None or not _tree_valid(tree, n): tree = _tree_default(n) self._tree = tree + h = max(_LC_HEIGHT, n * (_LC_MIN_TILE + _LC_GAP) + 2 * _LC_GAP) + self.setFixedHeight(h) self._rebuild_tiles() def update_tile_title(self, pane_index: int, title: str) -> None: @@ -599,7 +599,7 @@ class LayoutCanvas(QFrame): def _zone_at(self, pos: QPoint) -> Optional[Tuple[int, str]]: """ Returns (pane_idx, zone) where zone is one of: - squeeze_left/right/top/bottom — outer 1/6, insert between tiles + squeeze_left/right/top/bottom — outer 1/6 of tile OR gap between tiles, insert between split_left/right/top/bottom — 1/6 to 1/3, bisect tile center — inner 1/3, swap/move """ @@ -643,19 +643,46 @@ class LayoutCanvas(QFrame): x_frac = min(rx, w - rx) / max(w, 1) y_frac = min(ry, h - ry) / max(h, 1) return (pane_idx, x_zone if x_frac <= y_frac else y_zone) + + # Second pass: mouse in the gap between tiles — squeeze only. + # Expand each tile rect by the gap width and find the closest edge. + gap = _LC_GAP + 2 + best_pane: Optional[int] = None + best_zone: Optional[str] = None + best_dist = float("inf") + for pane_idx, rect in self._tile_rects.items(): + if pane_idx == dragging_idx: + continue + if not rect.adjusted(-gap, -gap, gap, gap).contains(pos): + continue + dl = pos.x() - rect.left() + dr = rect.right() - pos.x() + dt = pos.y() - rect.top() + db = rect.bottom() - pos.y() + candidates = [] + if dl < 0: candidates.append((abs(dl), "squeeze_left")) + if dr < 0: candidates.append((abs(dr), "squeeze_right")) + if dt < 0: candidates.append((abs(dt), "squeeze_top")) + if db < 0: candidates.append((abs(db), "squeeze_bottom")) + if not candidates: + continue + dist, zone = min(candidates) + if dist < best_dist: + best_dist, best_pane, best_zone = dist, pane_idx, zone + if best_pane is not None: + return (best_pane, best_zone) return None - def _adjacent_tile(self, pane_idx: int, direction: str) -> Optional[int]: + def _adjacent_tile(self, pane_idx: int, direction: str, exclude_idx: int = -1) -> Optional[int]: """Find the tile immediately adjacent to pane_idx in the given direction.""" r = self._tile_rects.get(pane_idx) if r is None: return None - dragging_idx = self._drag_tile.pane_index if self._drag_tile else -1 tol = _LC_GAP + 3 best: Optional[int] = None best_dist = float("inf") for other_idx, other_r in self._tile_rects.items(): - if other_idx in (pane_idx, dragging_idx): + if other_idx in (pane_idx, exclude_idx): continue if direction == "left": dist = r.left() - other_r.right() @@ -728,7 +755,7 @@ class LayoutCanvas(QFrame): # Determine insert direction from zone name # split_* → bisect target tile - # squeeze_* → insert next to adjacent tile (fall back to split if no neighbor) + # squeeze_* → insert next to adjacent tile; bisect edge tile when at canvas boundary _OPPOSITE = {"left": "right", "right": "left", "top": "bottom", "bottom": "top"} _SPLIT_DIR = {"split_left": "left", "split_right": "right", "split_top": "top", "split_bottom": "bottom", @@ -738,7 +765,7 @@ class LayoutCanvas(QFrame): direction = _SPLIT_DIR.get(zone, "left") if zone.startswith("squeeze_"): - neighbor = self._adjacent_tile(target_idx, direction) + neighbor = self._adjacent_tile(target_idx, direction, exclude_idx=dragged_idx) if neighbor is not None: # Insert dragged tile to the opposite side of the neighbor # so it lands between neighbor and target @@ -754,9 +781,9 @@ class LayoutCanvas(QFrame): self._reposition_tiles() self.arrangement_changed.emit() return - # No neighbor — fall through to split behaviour + # No neighbor (canvas edge) — bisect the edge tile outward - # split (or squeeze with no neighbor) + # split, or squeeze at canvas edge with no neighbor new_tree = _tree_remove(self._tree, dragged_idx) if new_tree is None: self._reposition_tiles() |
