331 lines
11 KiB
Python
331 lines
11 KiB
Python
|
|
# coding:utf-8
|
||
|
|
from enum import Enum
|
||
|
|
from PySide6.QtCore import Qt, Signal, QModelIndex, QPoint, Property, QSize, QRectF
|
||
|
|
from PySide6.QtGui import QPixmap, QPainter, QColor
|
||
|
|
from PySide6.QtWidgets import (QStyleOptionViewItem, QStyle, QListWidget, QListWidgetItem, QStyledItemDelegate,
|
||
|
|
QToolButton)
|
||
|
|
|
||
|
|
from ...common.overload import singledispatchmethod
|
||
|
|
from ...common.icon import FluentIcon, drawIcon
|
||
|
|
from ...common.style_sheet import isDarkTheme, FluentStyleSheet
|
||
|
|
from .button import ToolButton
|
||
|
|
from .tool_tip import ToolTipFilter, ToolTipPosition
|
||
|
|
from .scroll_bar import SmoothScrollBar
|
||
|
|
|
||
|
|
|
||
|
|
class PipsScrollButtonDisplayMode(Enum):
|
||
|
|
""" Pips pager scroll button display mode """
|
||
|
|
ALWAYS = 0
|
||
|
|
ON_HOVER = 1
|
||
|
|
NEVER = 2
|
||
|
|
|
||
|
|
|
||
|
|
class ScrollButton(ToolButton):
|
||
|
|
""" Scroll button """
|
||
|
|
|
||
|
|
def _postInit(self):
|
||
|
|
self.setFixedSize(12, 12)
|
||
|
|
|
||
|
|
def paintEvent(self, e):
|
||
|
|
painter = QPainter(self)
|
||
|
|
painter.setRenderHints(QPainter.Antialiasing)
|
||
|
|
painter.setPen(Qt.NoPen)
|
||
|
|
|
||
|
|
if isDarkTheme():
|
||
|
|
color = QColor(255, 255, 255)
|
||
|
|
painter.setOpacity(0.773 if self.isHover or self.isPressed else 0.541)
|
||
|
|
else:
|
||
|
|
color = QColor(0, 0, 0)
|
||
|
|
painter.setOpacity(0.616 if self.isHover or self.isPressed else 0.45)
|
||
|
|
|
||
|
|
if self.isPressed:
|
||
|
|
rect = QRectF(3, 3, 6, 6)
|
||
|
|
else:
|
||
|
|
rect = QRectF(2, 2, 8, 8)
|
||
|
|
|
||
|
|
drawIcon(self._icon, painter, rect, fill=color.name())
|
||
|
|
|
||
|
|
|
||
|
|
class PipsDelegate(QStyledItemDelegate):
|
||
|
|
""" Pips delegate """
|
||
|
|
|
||
|
|
def __init__(self, parent=None):
|
||
|
|
super().__init__(parent=parent)
|
||
|
|
self.hoveredRow = -1
|
||
|
|
self.pressedRow = -1
|
||
|
|
|
||
|
|
def paint(self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex) -> None:
|
||
|
|
painter.save()
|
||
|
|
painter.setRenderHints(QPainter.Antialiasing)
|
||
|
|
painter.setPen(Qt.NoPen)
|
||
|
|
|
||
|
|
isHover = index.row() == self.hoveredRow
|
||
|
|
isPressed = index.row() == self.pressedRow
|
||
|
|
|
||
|
|
# draw pip
|
||
|
|
if isDarkTheme():
|
||
|
|
if isHover or isPressed:
|
||
|
|
color = QColor(255, 255, 255, 197)
|
||
|
|
else:
|
||
|
|
color = QColor(255, 255, 255, 138)
|
||
|
|
else:
|
||
|
|
if isHover or isPressed:
|
||
|
|
color = QColor(0, 0, 0, 157)
|
||
|
|
else:
|
||
|
|
color = QColor(0, 0, 0, 114)
|
||
|
|
|
||
|
|
painter.setBrush(color)
|
||
|
|
|
||
|
|
if option.state & QStyle.State_Selected or (isHover and not isPressed):
|
||
|
|
r = 3
|
||
|
|
else:
|
||
|
|
r = 2
|
||
|
|
|
||
|
|
x = option.rect.x() + 6 - r
|
||
|
|
y = option.rect.y() + 6 - r
|
||
|
|
painter.drawEllipse(QRectF(x, y, 2*r, 2*r))
|
||
|
|
|
||
|
|
painter.restore()
|
||
|
|
|
||
|
|
def setPressedRow(self, row: int):
|
||
|
|
self.pressedRow = row
|
||
|
|
self.parent().viewport().update()
|
||
|
|
|
||
|
|
def setHoveredRow(self, row: bool):
|
||
|
|
self.hoveredRow = row
|
||
|
|
self.parent().viewport().update()
|
||
|
|
|
||
|
|
|
||
|
|
class PipsPager(QListWidget):
|
||
|
|
""" Pips pager
|
||
|
|
|
||
|
|
Constructors
|
||
|
|
------------
|
||
|
|
* PipsPager(`parent`: QWidget = None)
|
||
|
|
* PipsPager(`orient`: Qt.Orientation, `parent`: QWidget = None)
|
||
|
|
"""
|
||
|
|
|
||
|
|
currentIndexChanged = Signal(int)
|
||
|
|
|
||
|
|
@singledispatchmethod
|
||
|
|
def __init__(self, parent=None):
|
||
|
|
super().__init__(parent=parent)
|
||
|
|
self.orientation = Qt.Horizontal
|
||
|
|
self._postInit()
|
||
|
|
|
||
|
|
@__init__.register
|
||
|
|
def _(self, orientation: Qt.Orientation, parent=None):
|
||
|
|
super().__init__(parent=parent)
|
||
|
|
self.orientation = orientation
|
||
|
|
self._postInit()
|
||
|
|
|
||
|
|
def _postInit(self):
|
||
|
|
self._visibleNumber = 5
|
||
|
|
self.isHover = False
|
||
|
|
|
||
|
|
self.delegate = PipsDelegate(self)
|
||
|
|
self.scrollBar = SmoothScrollBar(self.orientation, self)
|
||
|
|
|
||
|
|
self.scrollBar.setScrollAnimation(500)
|
||
|
|
self.scrollBar.setForceHidden(True)
|
||
|
|
|
||
|
|
self.setMouseTracking(True)
|
||
|
|
self.setUniformItemSizes(True)
|
||
|
|
self.setGridSize(QSize(12, 12))
|
||
|
|
self.setItemDelegate(self.delegate)
|
||
|
|
self.setMovement(QListWidget.Static)
|
||
|
|
self.setVerticalScrollMode(self.ScrollMode.ScrollPerPixel)
|
||
|
|
self.setHorizontalScrollMode(self.ScrollMode.ScrollPerPixel)
|
||
|
|
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||
|
|
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||
|
|
|
||
|
|
FluentStyleSheet.PIPS_PAGER.apply(self)
|
||
|
|
|
||
|
|
if self.isHorizontal():
|
||
|
|
self.setFlow(QListWidget.LeftToRight)
|
||
|
|
self.setViewportMargins(15, 0, 15, 0)
|
||
|
|
self.preButton = ScrollButton(FluentIcon.CARE_LEFT_SOLID, self)
|
||
|
|
self.nextButton = ScrollButton(FluentIcon.CARE_RIGHT_SOLID, self)
|
||
|
|
self.setFixedHeight(12)
|
||
|
|
|
||
|
|
self.preButton.installEventFilter(ToolTipFilter(self.preButton, 1000, ToolTipPosition.LEFT))
|
||
|
|
self.nextButton.installEventFilter(ToolTipFilter(self.nextButton, 1000, ToolTipPosition.RIGHT))
|
||
|
|
|
||
|
|
else:
|
||
|
|
self.setViewportMargins(0, 15, 0, 15)
|
||
|
|
self.preButton = ScrollButton(FluentIcon.CARE_UP_SOLID, self)
|
||
|
|
self.nextButton = ScrollButton(FluentIcon.CARE_DOWN_SOLID, self)
|
||
|
|
self.setFixedWidth(12)
|
||
|
|
|
||
|
|
self.preButton.installEventFilter(ToolTipFilter(self.preButton, 1000, ToolTipPosition.TOP))
|
||
|
|
self.nextButton.installEventFilter(ToolTipFilter(self.nextButton, 1000, ToolTipPosition.BOTTOM))
|
||
|
|
|
||
|
|
self.setPreviousButtonDisplayMode(PipsScrollButtonDisplayMode.NEVER)
|
||
|
|
self.setNextButtonDisplayMode(PipsScrollButtonDisplayMode.NEVER)
|
||
|
|
self.preButton.setToolTip(self.tr('Previous Page'))
|
||
|
|
self.nextButton.setToolTip(self.tr('Next Page'))
|
||
|
|
|
||
|
|
# connect signal to slot
|
||
|
|
self.preButton.clicked.connect(self.scrollPrevious)
|
||
|
|
self.nextButton.clicked.connect(self.scrollNext)
|
||
|
|
self.itemPressed.connect(self._setPressedItem)
|
||
|
|
self.itemEntered.connect(self._setHoveredItem)
|
||
|
|
|
||
|
|
def _setPressedItem(self, item: QListWidgetItem):
|
||
|
|
self.delegate.setPressedRow(self.row(item))
|
||
|
|
self.setCurrentIndex(self.row(item))
|
||
|
|
|
||
|
|
def _setHoveredItem(self, item: QListWidgetItem):
|
||
|
|
self.delegate.setHoveredRow(self.row(item))
|
||
|
|
|
||
|
|
def setPageNumber(self, n: int):
|
||
|
|
""" set the number of page """
|
||
|
|
self.clear()
|
||
|
|
self.addItems(['15555'] * n)
|
||
|
|
|
||
|
|
for i in range(n):
|
||
|
|
item = self.item(i)
|
||
|
|
item.setData(Qt.UserRole, i + 1)
|
||
|
|
item.setSizeHint(self.gridSize())
|
||
|
|
|
||
|
|
self.setCurrentIndex(0)
|
||
|
|
self.adjustSize()
|
||
|
|
|
||
|
|
def getPageNumber(self):
|
||
|
|
""" get the number of page """
|
||
|
|
return self.count()
|
||
|
|
|
||
|
|
def getVisibleNumber(self):
|
||
|
|
""" get the number of visible pips """
|
||
|
|
return self._visibleNumber
|
||
|
|
|
||
|
|
def setVisibleNumber(self, n: int):
|
||
|
|
self._visibleNumber = n
|
||
|
|
self.adjustSize()
|
||
|
|
|
||
|
|
def scrollNext(self):
|
||
|
|
""" scroll down an item """
|
||
|
|
self.setCurrentIndex(self.currentIndex() + 1)
|
||
|
|
|
||
|
|
def scrollPrevious(self):
|
||
|
|
""" scroll up an item """
|
||
|
|
self.setCurrentIndex(self.currentIndex() - 1)
|
||
|
|
|
||
|
|
def scrollToItem(self, item: QListWidgetItem, hint=QListWidget.PositionAtCenter):
|
||
|
|
""" scroll to item """
|
||
|
|
# scroll to center position
|
||
|
|
index = self.row(item)
|
||
|
|
size = item.sizeHint()
|
||
|
|
s = size.width() if self.isHorizontal() else size.height()
|
||
|
|
self.scrollBar.scrollTo(s * (index - self.visibleNumber // 2))
|
||
|
|
|
||
|
|
# clear selection
|
||
|
|
self.clearSelection()
|
||
|
|
item.setSelected(False)
|
||
|
|
|
||
|
|
self.currentIndexChanged.emit(index)
|
||
|
|
|
||
|
|
def adjustSize(self) -> None:
|
||
|
|
m = self.viewportMargins()
|
||
|
|
|
||
|
|
if self.isHorizontal():
|
||
|
|
w = self.visibleNumber * self.gridSize().width() + m.left() + m.right()
|
||
|
|
self.setFixedWidth(w)
|
||
|
|
else:
|
||
|
|
h = self.visibleNumber * self.gridSize().height() + m.top() + m.bottom()
|
||
|
|
self.setFixedHeight(h)
|
||
|
|
|
||
|
|
def isHorizontal(self):
|
||
|
|
return self.orientation == Qt.Horizontal
|
||
|
|
|
||
|
|
def setCurrentIndex(self, index: int):
|
||
|
|
""" set current index """
|
||
|
|
if not 0 <= index < self.count():
|
||
|
|
return
|
||
|
|
|
||
|
|
item = self.item(index)
|
||
|
|
self.scrollToItem(item)
|
||
|
|
super().setCurrentItem(item)
|
||
|
|
|
||
|
|
self._updateScrollButtonVisibility()
|
||
|
|
|
||
|
|
def isPreviousButtonVisible(self):
|
||
|
|
if self.currentIndex() <= 0 or self.previousButtonDisplayMode == PipsScrollButtonDisplayMode.NEVER:
|
||
|
|
return False
|
||
|
|
|
||
|
|
if self.previousButtonDisplayMode == PipsScrollButtonDisplayMode.ON_HOVER:
|
||
|
|
return self.isHover
|
||
|
|
|
||
|
|
return True
|
||
|
|
|
||
|
|
def isNextButtonVisible(self):
|
||
|
|
if self.currentIndex() >= self.count() - 1 or self.nextButtonDisplayMode == PipsScrollButtonDisplayMode.NEVER:
|
||
|
|
return False
|
||
|
|
|
||
|
|
if self.nextButtonDisplayMode == PipsScrollButtonDisplayMode.ON_HOVER:
|
||
|
|
return self.isHover
|
||
|
|
|
||
|
|
return True
|
||
|
|
|
||
|
|
def currentIndex(self):
|
||
|
|
return super().currentIndex().row()
|
||
|
|
|
||
|
|
def setPreviousButtonDisplayMode(self, mode: PipsScrollButtonDisplayMode):
|
||
|
|
""" set the display mode of previous button """
|
||
|
|
self.previousButtonDisplayMode = mode
|
||
|
|
self.preButton.setVisible(self.isPreviousButtonVisible())
|
||
|
|
|
||
|
|
def setNextButtonDisplayMode(self, mode: PipsScrollButtonDisplayMode):
|
||
|
|
""" set the display mode of next button """
|
||
|
|
self.nextButtonDisplayMode = mode
|
||
|
|
self.nextButton.setVisible(self.isNextButtonVisible())
|
||
|
|
|
||
|
|
def mouseReleaseEvent(self, e):
|
||
|
|
super().mouseReleaseEvent(e)
|
||
|
|
self.delegate.setPressedRow(-1)
|
||
|
|
|
||
|
|
def enterEvent(self, e):
|
||
|
|
super().enterEvent(e)
|
||
|
|
self.isHover = True
|
||
|
|
self._updateScrollButtonVisibility()
|
||
|
|
|
||
|
|
def leaveEvent(self, e):
|
||
|
|
super().leaveEvent(e)
|
||
|
|
self.isHover = False
|
||
|
|
self.delegate.setHoveredRow(-1)
|
||
|
|
self._updateScrollButtonVisibility()
|
||
|
|
|
||
|
|
def _updateScrollButtonVisibility(self):
|
||
|
|
self.preButton.setVisible(self.isPreviousButtonVisible())
|
||
|
|
self.nextButton.setVisible(self.isNextButtonVisible())
|
||
|
|
|
||
|
|
def wheelEvent(self, e):
|
||
|
|
pass
|
||
|
|
|
||
|
|
def resizeEvent(self, e):
|
||
|
|
w, h = self.width(), self.height()
|
||
|
|
bw, bh = self.preButton.width(), self.preButton.height()
|
||
|
|
|
||
|
|
if self.isHorizontal():
|
||
|
|
self.preButton.move(0, int(h/2 - bh/2))
|
||
|
|
self.nextButton.move(w - bw, int(h/2 - bh/2))
|
||
|
|
else:
|
||
|
|
self.preButton.move(int(w/2-bw/2), 0)
|
||
|
|
self.nextButton.move(int(w/2-bw/2), h-bh)
|
||
|
|
|
||
|
|
visibleNumber = Property(int, getVisibleNumber, setVisibleNumber)
|
||
|
|
pageNumber = Property(int, getPageNumber, setPageNumber)
|
||
|
|
|
||
|
|
|
||
|
|
class HorizontalPipsPager(PipsPager):
|
||
|
|
""" Horizontal pips pager """
|
||
|
|
|
||
|
|
def __init__(self, parent=None):
|
||
|
|
super().__init__(Qt.Horizontal, parent)
|
||
|
|
|
||
|
|
|
||
|
|
class VerticalPipsPager(PipsPager):
|
||
|
|
""" Vertical pips pager """
|
||
|
|
|
||
|
|
def __init__(self, parent=None):
|
||
|
|
super().__init__(Qt.Vertical, parent)
|