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
3 changed files with 90 additions and 9 deletions
Showing only changes of commit cb03bc1494 - Show all commits
+26 -7
View File
@@ -9,6 +9,7 @@ from typing import List
from PySide6.QtCore import Qt, QTimer
from PySide6.QtGui import QGuiApplication, QIcon
from PySide6.QtWidgets import (
QStyledItemDelegate,
QApplication, QComboBox, QHBoxLayout, QLabel, QMainWindow, QMenu,
QPushButton, QScrollArea, QSizePolicy, QSplitter, QStackedWidget,
QSystemTrayIcon, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
@@ -79,6 +80,23 @@ class _Toast(QLabel):
self._timer.start(ms)
class _NavItemDelegate(QStyledItemDelegate):
"""Keep a rail row's icon on the left edge, whatever the column is doing.
QStyledItemDelegate hands the style decorationAlignment = AlignHCenter, so
a row with no label — every row once the rail collapses to 54px — has its
icon centred inside whatever box the column happens to give it. That box
tracks the column width, which is not stable: stretched to the viewport the
icons land in the middle of the rail, while a column left wider than the
view leaves them at the left. Same code, two different pictures, which is
why a test render disagreed with the running app.
"""
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
option.decorationAlignment = Qt.AlignLeft | Qt.AlignVCenter
class MainWindow(QMainWindow):
# Nav rows (Dashboard/Schedule/Monitoring are lazy; Workspace is the eager home page).
_ROW_DASHBOARD, _ROW_SCHEDULE, _ROW_WORKSPACE, _ROW_MONITORING = 0, 1, 2, 3
@@ -448,13 +466,14 @@ class MainWindow(QMainWindow):
tree.setIndentation(0)
tree.setRootIsDecorated(False)
tree.setUniformRowHeights(True)
# The column follows the viewport instead of the widest label. Left
# to size itself it stayed ~100px wide inside the 54px collapsed
# rail, so a horizontal scrollbar appeared and slid the icons out of
# the position they hold while the rail is open.
from PySide6.QtWidgets import QHeaderView
tree.header().setSectionResizeMode(0, QHeaderView.Stretch)
tree.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
# The column follows the viewport instead of the widest label. Left
# to size itself it stayed ~100px wide inside the 54px collapsed
# rail, so a horizontal scrollbar appeared and slid the icons out of
# the position they hold while the rail is open.
from PySide6.QtWidgets import QHeaderView
tree.header().setSectionResizeMode(0, QHeaderView.Stretch)
tree.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
tree.setItemDelegate(_NavItemDelegate(tree))
return tree
def _nav_rows(self):
+6 -2
View File
@@ -349,7 +349,7 @@ QToolTip {
/* Icons are drawn at text scale, not as decoration. */
QPushButton, QToolButton, QComboBox, QTabBar { qproperty-iconSize: 15px 15px; }
QTreeWidget#navrail { qproperty-iconSize: 16px 16px; }
QTreeWidget#navrail { qproperty-iconSize: 22px 16px; }
/* ---- shell ------------------------------------------------------------ */
QWidget#topbar { background: $bg; border: none; border-bottom: 1px solid $border; }
@@ -471,7 +471,11 @@ QComboBox#navProjectPick {
}
QPushButton#navSettingsBtn {
background: transparent; border: none; color: $text_muted;
padding: 6px 8px 6px 7px; text-align: left; border-radius: ${radius}px;
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;
/* 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. */
+58
View File
@@ -139,6 +139,7 @@ def main() -> int:
win._toggle_nav()
app.processEvents()
fails += compare(theme_name, rail, opened, closed)
fails += column_widths_do_not_move_icons(win, app, theme_name)
print()
for f in fails:
@@ -149,6 +150,63 @@ def main() -> int:
os._exit(1 if fails else 0)
def column_widths_do_not_move_icons(win, app, theme_name):
"""The icon must not care how wide the column is.
On the machine that reported this the column matched the rail and the icons
sat in the middle; in a test render the column stayed wider than the view
and the same code drew them at the left. So sweep the width and require the
icon to hold still.
"""
from PySide6.QtWidgets import QStyle, QStyleOptionViewItem
if not win._nav_collapsed:
win._toggle_nav()
app.processEvents()
fails, seen = [], {}
# The invariant that matters. A centred decoration is the only way Qt can
# put a label-less row's icon anywhere but the left edge, and how far it
# travels depends on the box the column hands it — which is why this
# reproduces on one machine and not another. Require the instruction
# itself, not just the pixel it happens to produce here.
from PySide6.QtCore import Qt as _Qt
for tree_name in ("nav", "nav_bottom"):
tree = getattr(win, tree_name)
index = tree.indexFromItem(tree.topLevelItem(0), 0)
opt = QStyleOptionViewItem()
tree.initViewItemOption(opt)
tree.itemDelegate().initStyleOption(opt, index)
align = int(opt.decorationAlignment)
if align & int(_Qt.AlignHCenter) or not align & int(_Qt.AlignLeft):
fails.append(f"{theme_name} {tree_name}: decorationAlignment={align}"
f" — icon is free to drift off the left edge")
for tree_name in ("nav", "nav_bottom"):
tree = getattr(win, tree_name)
keep = tree.columnWidth(0)
for width in (tree.viewport().width(), 70, 100, 140):
tree.setColumnWidth(0, width)
app.processEvents()
index = tree.indexFromItem(tree.topLevelItem(0), 0)
opt = QStyleOptionViewItem()
tree.initViewItemOption(opt)
opt.rect = tree.visualRect(index)
tree.itemDelegate().initStyleOption(opt, index)
x = tree.style().subElementRect(
QStyle.SE_ItemViewItemDecoration, opt, tree).left()
seen.setdefault(tree_name, []).append((width, x))
tree.setColumnWidth(0, keep)
app.processEvents()
xs = {x for _w, x in seen[tree_name]}
if len(xs) > 1:
fails.append(f"{theme_name} {tree_name}: icon x changes with the "
f"column width — {seen[tree_name]}")
print(f" column sweep: " + " ".join(
f"{n}={[x for _w, x in v]}" for n, v in seen.items()))
win._toggle_nav()
app.processEvents()
return fails
def compare(theme_name, rail, opened, closed):
print()
print(f"theme={theme_name}")