Update Screen: Cong cu, settings, them/Chinh sua task

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 16:58:23 +09:00
co-authored by Claude Sonnet 5
parent e6adfd9350
commit e2d020ef7b
7 changed files with 509 additions and 239 deletions
+94 -6
View File
@@ -5,12 +5,14 @@ from __future__ import annotations
from pathlib import Path
from PySide6.QtCore import QEvent, QObject, QPointF, QRectF, Qt, Signal
from PySide6.QtCore import (
QEvent, QObject, QPoint, QPointF, QRect, QRectF, QSize, Qt, Signal,
)
from PySide6.QtGui import QColor, QPainter, QPen
from PySide6.QtWidgets import (
QAbstractSpinBox, QCheckBox, QComboBox, QDoubleSpinBox, QFrame,
QHBoxLayout, QLabel, QListWidget, QListWidgetItem, QPushButton, QSizePolicy,
QVBoxLayout, QWidget,
QHBoxLayout, QLabel, QLayout, QListWidget, QListWidgetItem, QPushButton,
QSizePolicy, QVBoxLayout, QWidget,
)
from ..core.flows import STEP_DONE, STEP_ERROR, STEP_PENDING, STEP_RUNNING
@@ -37,7 +39,93 @@ def badge_pill_widget(text: str, object_name: str) -> QWidget:
return container
def _style_card(frame: QFrame) -> None:
def enable_height_for_width(widget: QWidget) -> None:
"""Flag ``widget`` as height-for-width so a PARENT layout reserves the
right amount of vertical space for it — needed at every widget boundary
between a :class:`FlowLayout` and the outermost layout, since each
``addWidget()`` hop asks the WIDGET's own sizePolicy, not its layout's
(see FlowLayout's docstring)."""
policy = widget.sizePolicy()
policy.setHeightForWidth(True)
widget.setSizePolicy(policy)
class FlowLayout(QLayout):
"""A left-aligned layout that wraps its children onto new lines as the
container narrows, each item kept at its own natural size — the
``.card`` grids in ui-audit_v2.html ("không kéo giãn lấp đầy hàng": cards
stay sized to their own content, never stretched to fill a row). Qt has
no built-in equivalent; this is the standard recipe (Qt's own C++
FlowLayout example, ported)."""
def __init__(self, parent=None, margin: int = 0, h_spacing: int = 8, v_spacing: int = 8):
super().__init__(parent)
self._h_spacing = h_spacing
self._v_spacing = v_spacing
self._items: list = []
self.setContentsMargins(margin, margin, margin, margin)
if parent is not None:
enable_height_for_width(parent)
def addItem(self, item) -> None: # noqa: N802 - Qt override
self._items.append(item)
def count(self) -> int: # noqa: N802 - Qt override
return len(self._items)
def itemAt(self, index: int): # noqa: N802 - Qt override
return self._items[index] if 0 <= index < len(self._items) else None
def takeAt(self, index: int): # noqa: N802 - Qt override
return self._items.pop(index) if 0 <= index < len(self._items) else None
def expandingDirections(self): # noqa: N802 - Qt override
return Qt.Orientations(Qt.Orientation(0))
def hasHeightForWidth(self) -> bool: # noqa: N802 - Qt override
return True
def heightForWidth(self, width: int) -> int: # noqa: N802 - Qt override
return self._do_layout(QRect(0, 0, width, 0), test_only=True)
def setGeometry(self, rect) -> None: # noqa: N802 - Qt override
super().setGeometry(rect)
self._do_layout(rect, test_only=False)
def sizeHint(self): # noqa: N802 - Qt override
return self.minimumSize()
def minimumSize(self): # noqa: N802 - Qt override
size = QSize()
for item in self._items:
size = size.expandedTo(item.minimumSize())
m = self.contentsMargins()
size += QSize(m.left() + m.right(), m.top() + m.bottom())
return size
def _do_layout(self, rect, test_only: bool) -> int:
m = self.contentsMargins()
effective = QRect(rect.x() + m.left(), rect.y() + m.top(),
rect.width() - m.left() - m.right(),
rect.height() - m.top() - m.bottom())
x, y = effective.x(), effective.y()
line_height = 0
for item in self._items:
hint = item.sizeHint()
next_x = x + hint.width() + self._h_spacing
if next_x - self._h_spacing > effective.right() and line_height > 0:
x = effective.x()
y = y + line_height + self._v_spacing
next_x = x + hint.width() + self._h_spacing
line_height = 0
if not test_only:
item.setGeometry(QRect(QPoint(x, y), hint))
x = next_x
line_height = max(line_height, hint.height())
return y + line_height - rect.y() + m.bottom()
def style_card(frame: QFrame) -> None:
"""Give a stat/budget card its surface. Flat by design: the raised surface
plus a hairline is what separates it from the page — the old drop shadow
made a grid of these look like it was hovering off the screen."""
@@ -54,7 +142,7 @@ class StatCard(QFrame):
def __init__(self):
super().__init__()
self.setFrameShape(QFrame.NoFrame)
_style_card(self)
style_card(self)
lay = QVBoxLayout(self)
self.title_lbl = QLabel("")
self.title_lbl.setObjectName("hint")
@@ -105,7 +193,7 @@ class BudgetCard(QFrame):
def __init__(self):
super().__init__()
self.setFrameShape(QFrame.NoFrame)
_style_card(self)
style_card(self)
lay = QVBoxLayout(self)
self.title_lbl = QLabel("")
self.title_lbl.setObjectName("hint")