Refine rounded item-view styling

Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Loren Eteval
2026-09-08 23:42:03 +08:00
parent 69d17d0b8f
commit b455c4cf54
4 changed files with 144 additions and 4 deletions
+1
View File
@@ -473,6 +473,7 @@ def controlStyleSheet(
QComboBox QAbstractItemView {{
padding: 4px;
background-clip: border;
border: 1px solid {palette['border_strong']};
border-radius: 6px;
background-color: {palette['overlay']};
+10 -1
View File
@@ -28,7 +28,10 @@ def dataViewStyleSheet(palette, progressBarStyleSheet, checkIcon):
QTreeView,
QListView {{
border: 1px solid {palette['border']};
border-radius: 8px;
border-radius: 6px;
/* Keep rectangular viewports and headers inside the rounded frame. */
padding: 2px;
background-clip: content;
background-color: {palette['panel']};
alternate-background-color: {palette['panel_alt']};
gridline-color: {palette['border']};
@@ -37,6 +40,12 @@ def dataViewStyleSheet(palette, progressBarStyleSheet, checkIcon):
selection-color: {palette['selection_text']};
}}
QTableView,
QTableWidget {{
padding: 0;
background-clip: border;
}}
QTableView::item,
QTableWidget::item,
QTreeView::item,
+1 -1
View File
@@ -55,7 +55,7 @@ strategy in an individual test.
| Bounded service work, update validation, plugin UI, and worker lifetime | `test_service_runtime.py` |
| Frozenlib state helpers and mocked platform-operation boundaries | `test_frozenlib.py` |
| Settings sandbox, navigation overlay behavior, public exports, and scale/theme isolation | `test_isolation_and_navigation.py`, `test_public_api.py`, `test_layout_matrix.py` |
| Shared Fluent visual states, rounded menu/combo popup transparency and interaction, and native line-edit clear-button alignment and theme changes | `test_stylesheet_states.py` |
| Shared Fluent visual states, zero table padding and intact rounded list corners, rounded menu/combo popup transparency and interaction, and native line-edit clear-button alignment and theme changes | `test_stylesheet_states.py` |
| Theme cross-fade activation, interruption, multi-window cleanup, and animation policy | `test_theme_transition.py` |
| AppQMainWindow lifecycle, subclass policies, geometry restoration, and migration | `test_main_window_geometry.py` |
| AppQDialog first-presentation geometry, native show paths, centering, and async lifetime | `test_dialog_geometry.py` |
+132 -2
View File
@@ -19,11 +19,24 @@
from __future__ import annotations
from Furious.Qt import AppQComboBox, AppQLineEdit, AppQMenu, AppStyleSheet
from Furious.Qt import (
AppQComboBox,
AppQLineEdit,
AppQListView,
AppQMenu,
AppQTableView,
AppStyleSheet,
)
from Furious.Widget.NavigationView import NavigationView
from PySide6 import QtCore
from PySide6.QtGui import QColor, QPalette
from PySide6.QtGui import (
QColor,
QImage,
QPalette,
QStandardItem,
QStandardItemModel,
)
from PySide6.QtTest import QTest
from PySide6.QtWidgets import (
QGraphicsDropShadowEffect,
@@ -91,6 +104,114 @@ class StyleSheetStateRenderingTest(unittest.TestCase):
return color
def assertItemViewInsetsAndListCorners(self, view, theme):
"""Check zero table padding and intact padded list corners."""
if isinstance(view, AppQTableView):
self.assertEqual(view.contentsRect(), view.rect().adjusted(1, 1, -1, -1))
return
actual = view.grab().toImage()
frame = QImage(actual.size(), QImage.Format.Format_ARGB32_Premultiplied)
frame.setDevicePixelRatio(actual.devicePixelRatio())
frame.fill(QtCore.Qt.transparent)
view.render(frame, renderFlags=QWidget.RenderFlag.DrawWindowBackground)
panel = QColor(AppStyleSheet.Palettes[theme]['panel'])
paddingPoint = QtCore.QPoint(view.width() // 2, 2)
host = view.parentWidget()
hostImage = host.grab().toImage()
hostPoint = view.mapTo(host, paddingPoint)
self.assertEqual(
hostImage.pixelColor(
round(hostPoint.x() * hostImage.devicePixelRatio()),
round(hostPoint.y() * hostImage.devicePixelRatio()),
),
QColor(AppStyleSheet.Palettes[theme]['window']),
)
cornerSize = round(8 * actual.devicePixelRatio())
for left in (0, actual.width() - cornerSize):
for top in (0, actual.height() - cornerSize):
checked = 0
for x in range(left, left + cornerSize):
for y in range(top, top + cornerSize):
expected = frame.pixelColor(x, y)
if expected.alpha() and expected != panel:
self.assertEqual(
actual.pixelColor(x, y),
expected,
f"frame pixel {(x, y)}",
)
checked += 1
self.assertGreater(checked, 0)
def testItemViewInsetsAndListCornersAcrossThemes(self):
"""Keep table insets and list corners stable while resizing and scrolling."""
app = application()
originalStyleSheet = app.styleSheet()
try:
for viewType in (AppQTableView, AppQListView):
host = QWidget()
view = viewType(host)
view.move(10, 10)
model = QStandardItemModel(view)
for row in range(20):
model.appendRow([QStandardItem(f'Item {row}') for _ in range(5)])
view.setModel(model)
view.setAlternatingRowColors(True)
try:
for theme in (AppStyleSheet.Light, AppStyleSheet.Dark):
app.setStyleSheet(AppStyleSheet.forTheme(theme))
for direction in (QtCore.Qt.LeftToRight, QtCore.Qt.RightToLeft):
view.setLayoutDirection(direction)
for size in (
QtCore.QSize(360, 180),
QtCore.QSize(620, 340),
):
with self.subTest(
view=viewType,
theme=theme,
direction=direction,
size=size,
):
view.resize(size)
host.resize(size + QtCore.QSize(20, 20))
host.show()
view.setCurrentIndex(model.index(0, 0))
view.scrollToTop()
processQtEvents()
self.assertItemViewInsetsAndListCorners(view, theme)
view.setCurrentIndex(model.index(19, 4))
view.scrollToBottom()
view.horizontalScrollBar().setValue(
view.horizontalScrollBar().maximum()
)
processQtEvents()
self.assertItemViewInsetsAndListCorners(view, theme)
model.clear()
processQtEvents()
self.assertItemViewInsetsAndListCorners(view, AppStyleSheet.Dark)
viewportImage = view.viewport().grab().toImage()
self.assertEqual(
viewportImage.pixelColor(viewportImage.rect().center()),
QColor(AppStyleSheet.Palettes[AppStyleSheet.Dark]['panel']),
)
finally:
host.close()
host.deleteLater()
processQtEvents()
finally:
app.setStyleSheet(originalStyleSheet)
def assertRoundedPopup(self, popup):
"""Require clear outer corners and an opaque painted surface."""
image = popup.grab().toImage()
@@ -184,6 +305,15 @@ class StyleSheetStateRenderingTest(unittest.TestCase):
processQtEvents()
self.assertIs(combo.view().window(), popup)
self.assertRoundedPopup(popup)
# Dropdown padding must remain opaque over the page below.
popupImage = combo.view().grab().toImage()
self.assertEqual(
popupImage.pixelColor(
popupImage.width() // 2,
round(2 * popupImage.devicePixelRatio()),
).alpha(),
255,
)
self.assertFalse(shiboken6.isValid(shadow))
QTest.keyClick(combo.view(), QtCore.Qt.Key_Down)
QTest.keyClick(combo.view(), QtCore.Qt.Key_Return)