Feature/fsg gamma team ui fix #3
@@ -20,6 +20,7 @@ from . import APP_NAME, DISPLAY_NAME, __version__
|
||||
from .config import PROVIDER_LABELS, AppConfig
|
||||
from .i18n import LANGUAGE_SHORT, LANGUAGES, get_language, on_language_changed, set_language, tr
|
||||
from .state import AppContext
|
||||
from .ui.widgets import widen_popup
|
||||
from .theme import current_palette, set_active_theme, stylesheet
|
||||
from .core.task_scheduler import TaskScheduler
|
||||
from .ui.cowork_tab import CoworkTab
|
||||
@@ -46,6 +47,11 @@ _NAV_COLLAPSED_WIDTH = 54
|
||||
# of a 1440 screen and more than a quarter of a 1280 one, where it left the
|
||||
# seven Kanban lanes 920px of the 1067 they need. A share behaves the same on
|
||||
# every monitor.
|
||||
# Where a rail row starts, and how much air sits between its icon and its
|
||||
# label. The tree rows get these from the style; anything laid out by hand
|
||||
# beside them has to use the same two numbers or it will not line up.
|
||||
_NAV_ROW_INSET = 4
|
||||
_NAV_ROW_GAP = 6
|
||||
_NAV_MIN_WIDTH = 132
|
||||
_NAV_MAX_SHARE = 0.22
|
||||
_NAV_MAX_CEILING = 360
|
||||
@@ -255,6 +261,7 @@ class MainWindow(QMainWindow):
|
||||
self.nav_project.setObjectName("navProjectPick")
|
||||
self.nav_project.setToolTip(tr("app.nav.project_pick"))
|
||||
self.nav_project.currentIndexChanged.connect(self._on_rail_project_pick)
|
||||
widen_popup(self.nav_project)
|
||||
self.nav_new_chat = QPushButton(tr("cowork.new_chat"))
|
||||
self.nav_new_chat.setObjectName("navNewChatBtn")
|
||||
self.nav_new_chat.setIcon(_icon("plus"))
|
||||
@@ -323,12 +330,28 @@ class MainWindow(QMainWindow):
|
||||
# way of the ones you live in. A hairline (styled via #navrailBottom in
|
||||
# theme.py) separates the two lists.
|
||||
nvl.addWidget(self.nav_bottom, 0)
|
||||
self._nav_settings_btn = QPushButton(tr("app.settings"))
|
||||
# Settings reads as one more row under Dashboard / Giám sát, so its icon
|
||||
# and label must start exactly where theirs do. Letting QPushButton place
|
||||
# them does not achieve that: the gap it leaves between icon and text is
|
||||
# the platform style's, and on macOS it is visibly tighter than the tree
|
||||
# rows above — a Windows-tuned nudge only moved the mismatch. So the row
|
||||
# is laid out here, in the same two numbers the tree uses: 4px in, 6px
|
||||
# between.
|
||||
self._nav_settings_btn = QPushButton()
|
||||
self._nav_settings_btn.setObjectName("navSettingsBtn")
|
||||
self._nav_settings_btn.setIcon(_icon("settings"))
|
||||
self._nav_settings_btn.setFlat(True)
|
||||
self._nav_settings_btn.setCursor(Qt.PointingHandCursor)
|
||||
self._nav_settings_btn.clicked.connect(self._open_settings)
|
||||
srow = QHBoxLayout(self._nav_settings_btn)
|
||||
srow.setContentsMargins(_NAV_ROW_INSET, 6, 8, 6)
|
||||
srow.setSpacing(_NAV_ROW_GAP)
|
||||
self._nav_settings_icon = QLabel()
|
||||
self._nav_settings_icon.setPixmap(_icon("settings").pixmap(16, 16))
|
||||
self._nav_settings_icon.setFixedSize(16, 16)
|
||||
self._nav_settings_text = QLabel(tr("app.settings"))
|
||||
srow.addWidget(self._nav_settings_icon)
|
||||
srow.addWidget(self._nav_settings_text)
|
||||
srow.addStretch(1)
|
||||
nvl.addWidget(self._nav_settings_btn)
|
||||
self._account_row = self._build_account_row()
|
||||
nvl.addWidget(self._account_row)
|
||||
@@ -652,6 +675,7 @@ class MainWindow(QMainWindow):
|
||||
if idx >= 0:
|
||||
self.nav_project.setCurrentIndex(idx)
|
||||
has = bool(choices)
|
||||
widen_popup(self.nav_project)
|
||||
self.nav_project.setEnabled(has)
|
||||
self.nav_project_btn.setEnabled(has)
|
||||
self.nav_project_btn.setToolTip(
|
||||
@@ -742,8 +766,8 @@ class MainWindow(QMainWindow):
|
||||
(collapsed = icon only, label moves to the tooltip)."""
|
||||
# force: collapsing leaves the row spec identical, only the text changes.
|
||||
self._rebuild_nav(force=True)
|
||||
self._nav_settings_btn.setText(
|
||||
"" if self._nav_collapsed else tr("app.settings"))
|
||||
self._nav_settings_text.setText(tr("app.settings"))
|
||||
self._nav_settings_text.setVisible(not self._nav_collapsed)
|
||||
self._nav_settings_btn.setToolTip(tr("app.settings"))
|
||||
# Collapsed to 54px there is no room for either control's label; the
|
||||
# picker would be a stub of a name, so it steps aside entirely and the
|
||||
@@ -967,6 +991,7 @@ class MainWindow(QMainWindow):
|
||||
idx = self.language_combo.findData(get_language())
|
||||
if idx >= 0:
|
||||
self.language_combo.setCurrentIndex(idx)
|
||||
widen_popup(self.language_combo)
|
||||
self.language_combo.currentIndexChanged.connect(self._on_language_changed)
|
||||
who.addWidget(self.language_combo)
|
||||
self.theme_btn = self._build_theme_button()
|
||||
@@ -980,6 +1005,7 @@ class MainWindow(QMainWindow):
|
||||
self.provider_combo.setToolTip(tr("app.provider"))
|
||||
for key, label in PROVIDER_LABELS.items():
|
||||
self.provider_combo.addItem(label, key)
|
||||
widen_popup(self.provider_combo)
|
||||
idx = self.provider_combo.findData(self.ctx.config.active_provider)
|
||||
if idx >= 0:
|
||||
self.provider_combo.setCurrentIndex(idx)
|
||||
|
||||
@@ -498,11 +498,9 @@ QToolButton#navProjectPickMini::menu-indicator { image: none; width: 0; }
|
||||
|
||||
QPushButton#navSettingsBtn {
|
||||
background: transparent; border: none; color: $text_muted;
|
||||
padding: 6px 8px 6px 4px; text-align: left; border-radius: ${radius}px;
|
||||
/* A button leaves less air between icon and label than a tree row
|
||||
does, so the word sat 3px left of Dashboard's. Widening the icon
|
||||
box (the glyph stays 16px, left-aligned) closes the gap. */
|
||||
qproperty-iconSize: 19px 16px;
|
||||
/* Padding stays at 0: the row lays its own icon and label out, so that
|
||||
the spacing does not change with the platform's button style. */
|
||||
padding: 0; text-align: left; border-radius: ${radius}px;
|
||||
/* No side margin: Settings reads as one more row under Dashboard/Giám sát,
|
||||
so its icon has to start on their x. A 6px margin put it at 14 — near
|
||||
enough the middle of the collapsed 54px rail to look centred. */
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
"""A drop-list must have room for its text and for the tick beside it.
|
||||
|
||||
macOS marks the current row with a checkmark; Windows does not. The language
|
||||
combo is only as wide as "VN", and the popup inherits that width, so on macOS
|
||||
the tick landed on top of the two letters. Reported from a Mac, so this checks
|
||||
the property that made it possible rather than the platform.
|
||||
"""
|
||||
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, QStyle
|
||||
|
||||
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 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 = []
|
||||
for name in ("language_combo", "nav_project", "provider_combo"):
|
||||
combo = getattr(win, name, None)
|
||||
if combo is None or not combo.count():
|
||||
continue
|
||||
view = combo.view()
|
||||
fm = view.fontMetrics()
|
||||
longest = max(fm.horizontalAdvance(combo.itemText(i))
|
||||
for i in range(combo.count()))
|
||||
tick = combo.style().pixelMetric(QStyle.PM_IndicatorWidth, None, combo)
|
||||
need = longest + tick
|
||||
have = max(view.minimumWidth(), combo.width())
|
||||
print(f"{name:15}: chu dai nhat {longest:>4}px + dau tick {tick:>3}px "
|
||||
f"= can {need:>4}px | popup rong {have:>4}px")
|
||||
if have < need:
|
||||
fails.append(f"{name}: popup {have}px, khong du {need}px cho chu + dau tick")
|
||||
|
||||
print()
|
||||
for f in fails:
|
||||
print("FAIL " + f)
|
||||
print("PASS moi drop-list du cho chu va dau tick" 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())
|
||||
@@ -80,7 +80,11 @@ def main() -> int:
|
||||
|
||||
def btn_text_x(w):
|
||||
"""Left edge of the label: first ink past the icon's gap."""
|
||||
from PySide6.QtGui import QIcon
|
||||
# Settings lays its own row out now, so read the label widget directly
|
||||
# rather than hunting for a gap in the painted pixels.
|
||||
lbl = getattr(win, "_nav_settings_text", None)
|
||||
if w is getattr(win, "_nav_settings_btn", None) and lbl is not None:
|
||||
return lbl.mapTo(rail, QPoint(0, 0)).x() if lbl.isVisible() else None
|
||||
if not w.text():
|
||||
return None
|
||||
img = w.grab().toImage()
|
||||
|
||||
@@ -162,6 +162,25 @@ def guard_wheel(root: QWidget) -> None:
|
||||
w.installEventFilter(_wheel_guard)
|
||||
|
||||
|
||||
def widen_popup(combo) -> None:
|
||||
"""Give a drop-list room for its text AND the tick beside the current item.
|
||||
|
||||
macOS draws a checkmark against the selected row; Windows does not. A combo
|
||||
only as wide as "VN" therefore looked fine here and had its two letters
|
||||
covered there. The popup inherits the combo's width unless told otherwise,
|
||||
so measure what has to fit and say so.
|
||||
"""
|
||||
from PySide6.QtWidgets import QStyle
|
||||
|
||||
view = combo.view()
|
||||
fm = view.fontMetrics()
|
||||
longest = max((fm.horizontalAdvance(combo.itemText(i))
|
||||
for i in range(combo.count())), default=0)
|
||||
tick = combo.style().pixelMetric(QStyle.PM_IndicatorWidth, None, combo)
|
||||
pad = combo.style().pixelMetric(QStyle.PM_FocusFrameHMargin, None, combo) * 2
|
||||
view.setMinimumWidth(longest + tick + pad + 16)
|
||||
|
||||
|
||||
def ui_scale(widget: QWidget) -> float:
|
||||
"""How much bigger this machine draws things than the design baseline.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user