pythoncustomfluentfluent-designfluentuiguimodernpyqt5pyqt6pyside2pyside6qtqt5qt6softwareuiwidgetswin11winuiwinui3
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.
331 lines
11 KiB
331 lines
11 KiB
# coding:utf-8 |
|
from enum import Enum |
|
from PyQt5.QtCore import Qt, pyqtSignal, QModelIndex, QPoint, pyqtProperty, QSize, QRectF |
|
from PyQt5.QtGui import QPixmap, QPainter, QColor |
|
from PyQt5.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 = pyqtSignal(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.ScrollPerPixel) |
|
self.setHorizontalScrollMode(self.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 = pyqtProperty(int, getVisibleNumber, setVisibleNumber) |
|
pageNumber = pyqtProperty(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) |