diff options
| author | Christian Kolset <christian.kolset@gmail.com> | 2026-06-04 12:41:14 -0600 |
|---|---|---|
| committer | Christian Kolset <christian.kolset@gmail.com> | 2026-06-04 12:41:14 -0600 |
| commit | 254df94e71586216991486242ab4064706177465 (patch) | |
| tree | 998529093c3fe36a352a24e623f30968ea18db17 /ui | |
| parent | a3ff221496f58d67ac962bfa6d011cc3e9d6ad3d (diff) | |
| parent | d2a555896e11a315084a00c34ef0ec77b25dcf70 (diff) | |
Merge plot-builder-layout-feature: layout canvas drag-drop fixes
Diffstat (limited to 'ui')
| -rw-r--r-- | ui/windows/plot_window.py | 82 |
1 files changed, 61 insertions, 21 deletions
diff --git a/ui/windows/plot_window.py b/ui/windows/plot_window.py index 95b520c..9edf046 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 @@ -534,6 +532,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: @@ -597,8 +597,8 @@ 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 - split_left/right/top/bottom — 1/6 to 1/3, bisect tile + split_left/right/top/bottom — outer 1/3 of tile, bisect tile + squeeze_left/right/top/bottom — gap between tiles only (second pass) center — inner 1/3, swap/move """ dragging_idx = self._drag_tile.pane_index if self._drag_tile else -1 @@ -610,22 +610,16 @@ class LayoutCanvas(QFrame): rx = pos.x() - rect.x() ry = pos.y() - rect.y() w, h = rect.width(), rect.height() - sq_w = max(1, w // 6) sp_w = max(1, w // 3) - sq_h = max(1, h // 6) sp_h = max(1, h // 3) - # X-axis zone - if rx < sq_w: x_zone = "squeeze_left" - elif rx < sp_w: x_zone = "split_left" - elif rx > w - sq_w: x_zone = "squeeze_right" + # X-axis zone — outer 1/3 = split, inner 1/3 = center + if rx < sp_w: x_zone = "split_left" elif rx > w - sp_w: x_zone = "split_right" else: x_zone = "center" # Y-axis zone - if ry < sq_h: y_zone = "squeeze_top" - elif ry < sp_h: y_zone = "split_top" - elif ry > h - sq_h: y_zone = "squeeze_bottom" + if ry < sp_h: y_zone = "split_top" elif ry > h - sp_h: y_zone = "split_bottom" else: y_zone = "center" @@ -641,19 +635,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() @@ -726,7 +747,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", @@ -736,7 +757,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 @@ -752,9 +773,28 @@ class LayoutCanvas(QFrame): self._reposition_tiles() self.arrangement_changed.emit() return - # No neighbor — fall through to split behaviour + # No neighbor — insert dragged at outer edge of full layout + new_tree = _tree_remove(self._tree, dragged_idx) + if new_tree is None: + self._reposition_tiles(); return + dragged_leaf = {"kind": "leaf", "pane": dragged_idx} + if direction == "left": + new_tree = {"kind": "hsplit", "ratio": 0.5, "first": dragged_leaf, "second": new_tree} + elif direction == "right": + new_tree = {"kind": "hsplit", "ratio": 0.5, "first": new_tree, "second": dragged_leaf} + elif direction == "top": + new_tree = {"kind": "vsplit", "ratio": 0.5, "first": dragged_leaf, "second": new_tree} + else: + new_tree = {"kind": "vsplit", "ratio": 0.5, "first": new_tree, "second": dragged_leaf} + new_tree = _tree_equalize_ratios(new_tree) + self._tree = new_tree + if self._cfg_ref is not None: + self._cfg_ref.tree = new_tree + self._reposition_tiles() + self.arrangement_changed.emit() + return - # split (or squeeze with no neighbor) + # split — bisect target tile new_tree = _tree_remove(self._tree, dragged_idx) if new_tree is None: self._reposition_tiles() |
