Feature/fsg gamma team ui fix #3

Merged
gitea-admin merged 41 commits from feature/FSG_GammaTeam_UI_Fix into main 2026-08-20 12:12:59 +00:00
5 changed files with 136 additions and 10 deletions
Showing only changes of commit 509ae96854 - Show all commits
+30 -4
View File
@@ -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)
+3 -5
View File
@@ -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. */
+79
View File
@@ -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())
+5 -1
View File
@@ -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()
+19
View File
@@ -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.