Let the splitter handle beside the rail actually resize it
The rail was setFixedWidth, so the handle QSplitter draws between it and the content was inert — it looked draggable and did nothing when dragged. The expanded rail is now a range (132–360px) instead of one number, so the handle has something to give. Collapsing still pins it at 54px exactly, and a width dragged to is remembered across the fold rather than snapping back to the 150px default. Long project names and RECENTS threads are what a wider rail buys. check_rail_resize drives the splitter the way the handle does and asserts the rail follows, clamps at both ends, folds to 54px and comes back to the chosen width. Restoring setFixedWidth fails it — the first attempt at that mutation passed, because setMaximumWidth on the next line quietly undid it. 16/16 checkers pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
bcdfe43c7c
commit
d37a0c1578
@@ -37,6 +37,12 @@ ASSETS = Path(__file__).resolve().parent / "assets"
|
|||||||
# shows icon-only (still fully clickable, just narrower).
|
# shows icon-only (still fully clickable, just narrower).
|
||||||
_NAV_EXPANDED_WIDTH = 150
|
_NAV_EXPANDED_WIDTH = 150
|
||||||
_NAV_COLLAPSED_WIDTH = 54
|
_NAV_COLLAPSED_WIDTH = 54
|
||||||
|
# The splitter between rail and content draws a drag handle. It only means
|
||||||
|
# something if the rail can actually take a width from it, so the expanded rail
|
||||||
|
# is a range rather than one number; long project and thread names in RECENTS
|
||||||
|
# are the reason someone would widen it.
|
||||||
|
_NAV_MIN_WIDTH = 132
|
||||||
|
_NAV_MAX_WIDTH = 360
|
||||||
|
|
||||||
|
|
||||||
def app_icon() -> QIcon:
|
def app_icon() -> QIcon:
|
||||||
@@ -213,7 +219,8 @@ class MainWindow(QMainWindow):
|
|||||||
self._collapse_right_icon = collapse_right_icon
|
self._collapse_right_icon = collapse_right_icon
|
||||||
self._nav_wrap = QWidget()
|
self._nav_wrap = QWidget()
|
||||||
self._nav_wrap.setObjectName("navWrap")
|
self._nav_wrap.setObjectName("navWrap")
|
||||||
self._nav_wrap.setFixedWidth(_NAV_EXPANDED_WIDTH)
|
self._nav_width = _NAV_EXPANDED_WIDTH # remembered across collapses
|
||||||
|
self._set_nav_width_range(_NAV_MIN_WIDTH, _NAV_MAX_WIDTH)
|
||||||
nvl = QVBoxLayout(self._nav_wrap)
|
nvl = QVBoxLayout(self._nav_wrap)
|
||||||
nvl.setContentsMargins(0, 0, 0, 0)
|
nvl.setContentsMargins(0, 0, 0, 0)
|
||||||
nvl.setSpacing(0)
|
nvl.setSpacing(0)
|
||||||
@@ -326,6 +333,7 @@ class MainWindow(QMainWindow):
|
|||||||
self.split.setStretchFactor(0, 0)
|
self.split.setStretchFactor(0, 0)
|
||||||
self.split.setStretchFactor(1, 1)
|
self.split.setStretchFactor(1, 1)
|
||||||
self.split.setSizes([_NAV_EXPANDED_WIDTH, 1000])
|
self.split.setSizes([_NAV_EXPANDED_WIDTH, 1000])
|
||||||
|
self.split.splitterMoved.connect(self._on_split_moved)
|
||||||
self.setCentralWidget(self.split)
|
self.setCentralWidget(self.split)
|
||||||
# Landing stays Workspace ▸ Project, exactly as before. Go through _goto
|
# Landing stays Workspace ▸ Project, exactly as before. Go through _goto
|
||||||
# so the page is actually shown — selecting the row alone only moves the
|
# so the page is actually shown — selecting the row alone only moves the
|
||||||
@@ -747,10 +755,28 @@ class MainWindow(QMainWindow):
|
|||||||
def _nav_new_chat_enabled(self) -> bool:
|
def _nav_new_chat_enabled(self) -> bool:
|
||||||
return bool(self.workspace.project_choices())
|
return bool(self.workspace.project_choices())
|
||||||
|
|
||||||
|
def _set_nav_width_range(self, lo: int, hi: int) -> None:
|
||||||
|
"""setFixedWidth would leave the splitter handle inert — visible, and
|
||||||
|
doing nothing when dragged."""
|
||||||
|
self._nav_wrap.setMinimumWidth(lo)
|
||||||
|
self._nav_wrap.setMaximumWidth(hi)
|
||||||
|
|
||||||
|
def _on_split_moved(self, _pos: int, _index: int) -> None:
|
||||||
|
if not self._nav_collapsed:
|
||||||
|
self._nav_width = max(_NAV_MIN_WIDTH,
|
||||||
|
min(_NAV_MAX_WIDTH, self._nav_wrap.width()))
|
||||||
|
|
||||||
def _toggle_nav(self) -> None:
|
def _toggle_nav(self) -> None:
|
||||||
|
if not self._nav_collapsed:
|
||||||
|
self._nav_width = max(_NAV_MIN_WIDTH,
|
||||||
|
min(_NAV_MAX_WIDTH, self._nav_wrap.width()))
|
||||||
self._nav_collapsed = not self._nav_collapsed
|
self._nav_collapsed = not self._nav_collapsed
|
||||||
width = _NAV_COLLAPSED_WIDTH if self._nav_collapsed else _NAV_EXPANDED_WIDTH
|
if self._nav_collapsed:
|
||||||
self._nav_wrap.setFixedWidth(width)
|
width = _NAV_COLLAPSED_WIDTH
|
||||||
|
self._set_nav_width_range(width, width)
|
||||||
|
else:
|
||||||
|
width = self._nav_width
|
||||||
|
self._set_nav_width_range(_NAV_MIN_WIDTH, _NAV_MAX_WIDTH)
|
||||||
self._apply_nav_labels()
|
self._apply_nav_labels()
|
||||||
# Same chevron convention as every other collapsible panel: right-
|
# Same chevron convention as every other collapsible panel: right-
|
||||||
# pointing (fill-right) means "click to expand", left means "collapse".
|
# pointing (fill-right) means "click to expand", left means "collapse".
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
"""The splitter handle beside the rail has to actually move the rail.
|
||||||
|
|
||||||
|
setFixedWidth left it drawn but inert: it looked draggable and did nothing.
|
||||||
|
Also checks that a width the user drags to survives a collapse/expand, and
|
||||||
|
that collapsing still pins the rail at 54px.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
REPO = Path(__file__).resolve().parent.parent
|
||||||
|
sys.path.insert(0, str(REPO.parent))
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||||
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||||
|
|
||||||
|
from capture_screens import ( # noqa: E402
|
||||||
|
_apply_theme, _freeze_schedulers, _isolate_home, _load_fonts)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
sandbox = _isolate_home()
|
||||||
|
from PySide6.QtWidgets import QApplication
|
||||||
|
|
||||||
|
app = QApplication([])
|
||||||
|
_load_fonts()
|
||||||
|
_freeze_schedulers()
|
||||||
|
_apply_theme(app)
|
||||||
|
|
||||||
|
from cowork_local.config import CONFIG_DIR
|
||||||
|
assert str(sandbox) in str(CONFIG_DIR), f"isolation failed: {CONFIG_DIR}"
|
||||||
|
|
||||||
|
from seed_demo_data import seed
|
||||||
|
seed()
|
||||||
|
|
||||||
|
from cowork_local.app import (
|
||||||
|
_NAV_COLLAPSED_WIDTH, _NAV_MAX_WIDTH, _NAV_MIN_WIDTH, MainWindow)
|
||||||
|
from cowork_local.config import AppConfig
|
||||||
|
from cowork_local.i18n import set_language
|
||||||
|
from cowork_local.state import AppContext
|
||||||
|
|
||||||
|
set_language("vi")
|
||||||
|
win = MainWindow(AppContext(AppConfig.load()), user_name="local")
|
||||||
|
win.resize(1400, 900)
|
||||||
|
win.show()
|
||||||
|
app.processEvents()
|
||||||
|
|
||||||
|
fails = []
|
||||||
|
rail, split = win._nav_wrap, win.split
|
||||||
|
|
||||||
|
def drag_to(px):
|
||||||
|
"""What the splitter does when the handle is dragged."""
|
||||||
|
total = sum(split.sizes())
|
||||||
|
split.setSizes([px, max(1, total - px)])
|
||||||
|
app.processEvents()
|
||||||
|
win._on_split_moved(px, 1)
|
||||||
|
app.processEvents()
|
||||||
|
return rail.width()
|
||||||
|
|
||||||
|
start = rail.width()
|
||||||
|
wide = drag_to(300)
|
||||||
|
print(f"keo rong : {start} -> {wide}px")
|
||||||
|
if wide <= start:
|
||||||
|
fails.append(f"keo tay nam ra 300px ma rail van {wide}px")
|
||||||
|
|
||||||
|
narrow = drag_to(_NAV_MIN_WIDTH)
|
||||||
|
print(f"keo hep : {wide} -> {narrow}px")
|
||||||
|
if narrow >= wide:
|
||||||
|
fails.append(f"keo hep lai khong an: {narrow}px")
|
||||||
|
|
||||||
|
over = drag_to(_NAV_MAX_WIDTH + 200)
|
||||||
|
print(f"keo qua max: {over}px (tran {_NAV_MAX_WIDTH})")
|
||||||
|
if over > _NAV_MAX_WIDTH:
|
||||||
|
fails.append(f"rail vuot tran: {over} > {_NAV_MAX_WIDTH}")
|
||||||
|
|
||||||
|
under = drag_to(20)
|
||||||
|
print(f"keo duoi min: {under}px (san {_NAV_MIN_WIDTH})")
|
||||||
|
if under < _NAV_MIN_WIDTH:
|
||||||
|
fails.append(f"rail thap hon san: {under} < {_NAV_MIN_WIDTH}")
|
||||||
|
|
||||||
|
# a dragged width has to come back after a fold
|
||||||
|
chosen = drag_to(280)
|
||||||
|
win._toggle_nav()
|
||||||
|
app.processEvents()
|
||||||
|
folded = rail.width()
|
||||||
|
print(f"thu gon : {folded}px")
|
||||||
|
if folded != _NAV_COLLAPSED_WIDTH:
|
||||||
|
fails.append(f"thu gon phai la {_NAV_COLLAPSED_WIDTH}px, dang {folded}px")
|
||||||
|
win._toggle_nav()
|
||||||
|
app.processEvents()
|
||||||
|
back = rail.width()
|
||||||
|
print(f"mo lai : {back}px (da chon {chosen}px)")
|
||||||
|
if abs(back - chosen) > 4:
|
||||||
|
fails.append(f"mo lai quen be rong da keo: {back} thay vi {chosen}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
for f in fails:
|
||||||
|
print("FAIL " + f)
|
||||||
|
print("PASS tay nam keo duoc, nho be rong qua lan gap" if not fails
|
||||||
|
else f"{len(fails)} problem(s)")
|
||||||
|
sys.stdout.flush()
|
||||||
|
os._exit(1 if fails else 0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user