You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

252 lines
7.5 KiB

# coding:utf-8
from typing import Iterable
from PySide6.QtCore import Qt, Signal, QSize, QEvent, QRectF
from PySide6.QtGui import QPainter
from PySide6.QtWidgets import QListWidget, QListWidgetItem, QToolButton
from .scroll_area import SmoothScrollBar
from ...common.icon import FluentIcon, isDarkTheme
class ScrollButton(QToolButton):
""" Scroll button """
def __init__(self, icon: FluentIcon, parent=None):
super().__init__(parent=parent)
self._icon = icon
self.isPressed = False
self.installEventFilter(self)
def eventFilter(self, obj, e: QEvent):
if obj is self:
if e.type() == QEvent.MouseButtonPress:
self.isPressed = True
self.update()
elif e.type() == QEvent.MouseButtonRelease:
self.isPressed = False
self.update()
return super().eventFilter(obj, e)
def paintEvent(self, e):
super().paintEvent(e)
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing)
if not self.isPressed:
w, h = 10, 10
else:
w, h = 8, 8
x = (self.width() - w) / 2
y = (self.height() - h) / 2
if not isDarkTheme():
self._icon.render(painter, QRectF(x, y, w, h), fill="#5e5e5e")
else:
self._icon.render(painter, QRectF(x, y, w, h))
class CycleListWidget(QListWidget):
""" Cycle list widget """
currentItemChanged = Signal(QListWidgetItem)
def __init__(self, items: Iterable, itemSize: QSize, align=Qt.AlignCenter, parent=None):
"""
Parameters
----------
items: Iterable[Any]
the items to be added
itemSize: QSize
the size of item
align: Qt.AlignmentFlag
the text alignment of item
parent: QWidget
parent widget
"""
super().__init__(parent=parent)
self.itemSize = itemSize
self.align = align
self.upButton = ScrollButton(FluentIcon.CARE_UP_SOLID, self)
self.downButton = ScrollButton(FluentIcon.CARE_DOWN_SOLID, self)
self.scrollDuration = 250
self.originItems = list(items)
self.vScrollBar = SmoothScrollBar(Qt.Vertical, self)
self.visibleNumber = 9
# repeat adding items to achieve circular scrolling
self.setItems(items)
self.setVerticalScrollMode(self.ScrollMode.ScrollPerPixel)
self.vScrollBar.setScrollAnimation(self.scrollDuration)
self.vScrollBar.setForceHidden(True)
self.setViewportMargins(0, 0, 0, 0)
self.setFixedSize(itemSize.width()+8,
itemSize.height()*self.visibleNumber)
# hide scroll bar
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.upButton.hide()
self.downButton.hide()
self.upButton.clicked.connect(self.scrollUp)
self.downButton.clicked.connect(self.scrollDown)
self.itemClicked.connect(self._onItemClicked)
self.installEventFilter(self)
def setItems(self, items: list):
""" set items in the list
Parameters
----------
items: Iterable[Any]
the items to be added
itemSize: QSize
the size of item
align: Qt.AlignmentFlag
the text alignment of item
"""
self.clear()
self._createItems(items)
def _createItems(self, items: list):
N = len(items)
self.isCycle = N > self.visibleNumber
if self.isCycle:
for _ in range(2):
self._addColumnItems(items)
self._currentIndex = len(items)
super().scrollToItem(
self.item(self.currentIndex()-self.visibleNumber//2), QListWidget.PositionAtTop)
else:
n = self.visibleNumber // 2 # add empty items to enable scrolling
self._addColumnItems(['']*n, True)
self._addColumnItems(items)
self._addColumnItems(['']*n, True)
self._currentIndex = n
def _addColumnItems(self, items, disabled=False):
for i in items:
item = QListWidgetItem(str(i), self)
item.setSizeHint(self.itemSize)
item.setTextAlignment(self.align | Qt.AlignVCenter)
if disabled:
item.setFlags(Qt.NoItemFlags)
self.addItem(item)
def _onItemClicked(self, item):
self.setCurrentIndex(self.row(item))
self.scrollToItem(self.currentItem())
def setSelectedItem(self, text: str):
""" set the selected item """
if text is None:
return
items = self.findItems(str(text), Qt.MatchExactly)
if not items:
return
if len(items) >= 2:
self.setCurrentIndex(self.row(items[1]))
else:
self.setCurrentIndex(self.row(items[0]))
super().scrollToItem(self.currentItem(), QListWidget.ScrollHint.PositionAtCenter)
def scrollToItem(self, item: QListWidgetItem, hint=QListWidget.ScrollHint.PositionAtCenter):
""" scroll to item """
# scroll to center position
index = self.row(item)
y = item.sizeHint().height() * (index - self.visibleNumber // 2)
self.vScrollBar.scrollTo(y)
# clear selection
self.clearSelection()
item.setSelected(False)
self.currentItemChanged.emit(item)
def wheelEvent(self, e):
if e.angleDelta().y() < 0:
self.scrollDown()
else:
self.scrollUp()
def scrollDown(self):
""" scroll down an item """
self.setCurrentIndex(self.currentIndex() + 1)
self.scrollToItem(self.currentItem())
def scrollUp(self):
""" scroll up an item """
self.setCurrentIndex(self.currentIndex() - 1)
self.scrollToItem(self.currentItem())
def enterEvent(self, e):
self.upButton.show()
self.downButton.show()
def leaveEvent(self, e):
self.upButton.hide()
self.downButton.hide()
def resizeEvent(self, e):
self.upButton.resize(self.width(), 34)
self.downButton.resize(self.width(), 34)
self.downButton.move(0, self.height() - 34)
def eventFilter(self, obj, e: QEvent):
if obj is not self or e.type() != QEvent.KeyPress:
return super().eventFilter(obj, e)
if e.key() == Qt.Key_Down:
self.scrollDown()
return True
elif e.key() == Qt.Key_Up:
self.scrollUp()
return True
return super().eventFilter(obj, e)
def currentItem(self):
return self.item(self.currentIndex())
def currentIndex(self):
return self._currentIndex
def setCurrentIndex(self, index: int):
if not self.isCycle:
n = self.visibleNumber // 2
self._currentIndex = max(
n, min(n + len(self.originItems) - 1, index))
else:
N = self.count() // 2
m = (self.visibleNumber + 1) // 2
self._currentIndex = index
# scroll to center to achieve circular scrolling
if index >= self.count() - m:
self._currentIndex = N + index - self.count()
super().scrollToItem(self.item(self.currentIndex() - 1), self.ScrollHint.PositionAtCenter)
elif index <= m - 1:
self._currentIndex = N + index
super().scrollToItem(self.item(N + index + 1), self.ScrollHint.PositionAtCenter)