diff --git a/app.py b/app.py index b7bd920..de07c55 100644 --- a/app.py +++ b/app.py @@ -37,6 +37,12 @@ ASSETS = Path(__file__).resolve().parent / "assets" # shows icon-only (still fully clickable, just narrower). _NAV_EXPANDED_WIDTH = 150 _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: @@ -213,7 +219,8 @@ class MainWindow(QMainWindow): self._collapse_right_icon = collapse_right_icon self._nav_wrap = QWidget() 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.setContentsMargins(0, 0, 0, 0) nvl.setSpacing(0) @@ -326,6 +333,7 @@ class MainWindow(QMainWindow): self.split.setStretchFactor(0, 0) self.split.setStretchFactor(1, 1) self.split.setSizes([_NAV_EXPANDED_WIDTH, 1000]) + self.split.splitterMoved.connect(self._on_split_moved) self.setCentralWidget(self.split) # Landing stays Workspace ▸ Project, exactly as before. Go through _goto # 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: 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: + 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 - width = _NAV_COLLAPSED_WIDTH if self._nav_collapsed else _NAV_EXPANDED_WIDTH - self._nav_wrap.setFixedWidth(width) + if self._nav_collapsed: + 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() # Same chevron convention as every other collapsible panel: right- # pointing (fill-right) means "click to expand", left means "collapse". diff --git a/tools/check_rail_resize.py b/tools/check_rail_resize.py new file mode 100644 index 0000000..aa2df51 --- /dev/null +++ b/tools/check_rail_resize.py @@ -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())