160 lines
6.0 KiB
Python
160 lines
6.0 KiB
Python
"""Round 80: scrollbars lose their arrow buttons, and the thumb is a button.
|
|
|
|
- No arrows: the trough runs the bar's full length and the arrow rects are
|
|
empty, so nothing paints or hit-tests there.
|
|
- The thumb gets the buttons' corner radius and emboss, with rims as tall in
|
|
pixels as a button's, however long the thumb.
|
|
- The bar's thickness comes from the UI scale through the style, not QSS
|
|
(any QSS rule on a scrollbar part makes Qt draw the arrows itself).
|
|
"""
|
|
from PyQt6.QtCore import Qt
|
|
from PyQt6.QtGui import QColor
|
|
from PyQt6.QtWidgets import QPushButton, QScrollBar, QStyle, QStyleOptionSlider
|
|
|
|
from lintunes import theme
|
|
from lintunes.gui.track_table import ClickToJumpScrollStyle
|
|
|
|
SC = QStyle.SubControl
|
|
|
|
|
|
def _option(bar):
|
|
opt = QStyleOptionSlider()
|
|
bar.initStyleOption(opt)
|
|
return opt
|
|
|
|
|
|
def _bar(qapp, orientation=Qt.Orientation.Vertical):
|
|
bar = QScrollBar(orientation)
|
|
bar.setStyle(ClickToJumpScrollStyle())
|
|
bar.setRange(0, 900)
|
|
bar.setPageStep(100)
|
|
bar.resize(14, 400) if orientation == Qt.Orientation.Vertical \
|
|
else bar.resize(400, 14)
|
|
return bar
|
|
|
|
|
|
def test_no_arrow_buttons(qapp):
|
|
bar = _bar(qapp)
|
|
style, opt = bar.style(), _option(bar)
|
|
cc = QStyle.ComplexControl.CC_ScrollBar
|
|
assert style.subControlRect(cc, opt, SC.SC_ScrollBarAddLine, bar).isEmpty()
|
|
assert style.subControlRect(cc, opt, SC.SC_ScrollBarSubLine, bar).isEmpty()
|
|
assert style.subControlRect(cc, opt, SC.SC_ScrollBarGroove, bar) == opt.rect
|
|
|
|
|
|
def test_thumb_spans_the_full_trough(qapp):
|
|
for orientation in (Qt.Orientation.Vertical, Qt.Orientation.Horizontal):
|
|
bar = _bar(qapp, orientation)
|
|
style, cc = bar.style(), QStyle.ComplexControl.CC_ScrollBar
|
|
bar.setValue(0)
|
|
top = style.subControlRect(cc, _option(bar), SC.SC_ScrollBarSlider, bar)
|
|
bar.setValue(900)
|
|
end = style.subControlRect(cc, _option(bar), SC.SC_ScrollBarSlider, bar)
|
|
if orientation == Qt.Orientation.Vertical:
|
|
assert top.top() == 0 and end.bottom() == 399
|
|
assert top.height() == 40 # 100 / (900 + 100) of 400
|
|
else:
|
|
assert top.left() == 0 and end.right() == 399
|
|
|
|
|
|
def test_tiny_page_keeps_a_grabbable_thumb(qapp):
|
|
bar = _bar(qapp)
|
|
bar.setRange(0, 100000)
|
|
rect = bar.style().subControlRect(QStyle.ComplexControl.CC_ScrollBar,
|
|
_option(bar), SC.SC_ScrollBarSlider, bar)
|
|
assert rect.height() == ClickToJumpScrollStyle.extent * 2
|
|
|
|
|
|
def test_thickness_follows_ui_scale(qapp):
|
|
class Prefs(dict):
|
|
def get(self, key, default=None):
|
|
return "large" if key == "ui_scale" else default
|
|
before = ClickToJumpScrollStyle.extent
|
|
try:
|
|
theme.apply_theme(qapp, Prefs())
|
|
assert ClickToJumpScrollStyle.extent == theme.UI_SCALES["large"]["scrollbar"]
|
|
assert "QScrollBar" not in qapp.styleSheet()
|
|
finally:
|
|
ClickToJumpScrollStyle.extent = before
|
|
|
|
|
|
def test_thumb_is_shaded_like_a_real_button(qapp):
|
|
qapp.setStyleSheet(theme.button_emboss_qss(QColor("#d0d0d0")))
|
|
try:
|
|
button = QPushButton("OK")
|
|
assert abs(theme.button_height_px(button.fontMetrics())
|
|
- button.sizeHint().height()) <= 2
|
|
finally:
|
|
qapp.setStyleSheet("")
|
|
|
|
|
|
def test_thumb_paints(qapp):
|
|
bar = _bar(qapp)
|
|
bar.setValue(450)
|
|
image = bar.grab().toImage()
|
|
thumb = bar.style().subControlRect(QStyle.ComplexControl.CC_ScrollBar,
|
|
_option(bar), SC.SC_ScrollBarSlider, bar)
|
|
trough = image.pixelColor(7, 5)
|
|
middle = image.pixelColor(7, thumb.center().y())
|
|
assert middle != trough
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
# no album art: a debossed panel on the window, not a box in the list
|
|
# --------------------------------------------------------------------------
|
|
|
|
def _sidebar(qapp, tmp_path):
|
|
from lintunes.gui.main_window import MainWindow
|
|
from lintunes.library_manager import LibraryManager
|
|
from lintunes.models.library import Library
|
|
from lintunes.models.track import Track
|
|
from lintunes.preferences import Preferences
|
|
library = Library(tracks={1: Track(track_id=1, name="One")})
|
|
window = MainWindow(LibraryManager(library, tmp_path), Preferences(tmp_path))
|
|
window.resize(1000, 700)
|
|
window.show()
|
|
qapp.processEvents()
|
|
return window, window.sidebar
|
|
|
|
|
|
def test_no_art_panel_sits_below_the_frame(qapp, tmp_path):
|
|
from lintunes.gui.sidebar import NO_ART_GAP
|
|
window, sidebar = _sidebar(qapp, tmp_path)
|
|
art = sidebar.art
|
|
assert not art.has_art()
|
|
assert art.parentWidget() is sidebar
|
|
assert art.y() >= sidebar.list_area.geometry().bottom()
|
|
assert art.height() == art.width() + NO_ART_GAP
|
|
image = art.grab().toImage()
|
|
window_bg = art.palette().window().color()
|
|
# the gap above is bare window; the panel's middle is the visualizer's
|
|
# color, flat, not shaded gray over its whole height
|
|
assert image.pixelColor(art.width() // 2, 1) == window_bg
|
|
panel = art.palette().alternateBase().color()
|
|
for y in (art.height() // 3, art.height() // 2, 2 * art.height() // 3):
|
|
assert image.pixelColor(art.width() // 2, y) == panel
|
|
window.close()
|
|
|
|
|
|
def test_tall_deboss_copies_a_short_panels_edges(qapp):
|
|
"""Shaded ``like`` a 62 px panel, a 400 px one matches it row for row
|
|
along the top, and is flat in the middle."""
|
|
from PyQt6.QtCore import QRectF
|
|
from PyQt6.QtGui import QImage, QPainter
|
|
color = QColor("#f2f2f2")
|
|
|
|
def paint(height, **kw):
|
|
image = QImage(100, height, QImage.Format.Format_ARGB32)
|
|
image.fill(0)
|
|
painter = QPainter(image)
|
|
theme.paint_deboss(painter, QRectF(0.5, 0.5, 99, height - 1),
|
|
color, 8, **kw)
|
|
painter.end()
|
|
return image
|
|
|
|
short, tall = paint(62), paint(400, like=62)
|
|
for y in (3, 10, 20):
|
|
assert abs(short.pixelColor(50, y).lightness()
|
|
- tall.pixelColor(50, y).lightness()) <= 1
|
|
assert tall.pixelColor(50, 200) == color
|