summaryrefslogtreecommitdiff
path: root/ui
diff options
context:
space:
mode:
authorChristian Kolset <christian.kolset@gmail.com>2026-06-04 12:41:14 -0600
committerChristian Kolset <christian.kolset@gmail.com>2026-06-04 12:41:14 -0600
commit254df94e71586216991486242ab4064706177465 (patch)
tree998529093c3fe36a352a24e623f30968ea18db17 /ui
parenta3ff221496f58d67ac962bfa6d011cc3e9d6ad3d (diff)
parentd2a555896e11a315084a00c34ef0ec77b25dcf70 (diff)
Merge plot-builder-layout-feature: layout canvas drag-drop fixes
Diffstat (limited to 'ui')
-rw-r--r--ui/windows/plot_window.py82
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()