369 lines
11 KiB
Python
369 lines
11 KiB
Python
# coding:utf-8
|
|
from typing import List, Union
|
|
from PySide6.QtCore import Qt, Signal, QRectF, Property, QPropertyAnimation, QPoint, QSize
|
|
from PySide6.QtGui import QPixmap, QPainter, QColor, QPainterPath, QFont, QIcon
|
|
from PySide6.QtWidgets import QWidget, QFrame, QVBoxLayout, QHBoxLayout, QLabel
|
|
|
|
from ...common.overload import singledispatchmethod
|
|
from ...common.style_sheet import isDarkTheme, FluentStyleSheet
|
|
from ...common.animation import BackgroundAnimationWidget, DropShadowAnimation
|
|
from ...common.font import setFont
|
|
from ...common.icon import FluentIconBase
|
|
from .label import BodyLabel, CaptionLabel
|
|
from .icon_widget import IconWidget
|
|
|
|
|
|
class CardWidget(BackgroundAnimationWidget, QFrame):
|
|
""" Card widget """
|
|
|
|
clicked = Signal()
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent=parent)
|
|
self._isClickEnabled = False
|
|
self._borderRadius = 5
|
|
|
|
def mouseReleaseEvent(self, e):
|
|
super().mouseReleaseEvent(e)
|
|
self.clicked.emit()
|
|
|
|
def setClickEnabled(self, isEnabled: bool):
|
|
self._isClickEnabled = isEnabled
|
|
self.update()
|
|
|
|
def isClickEnabled(self):
|
|
return self._isClickEnabled
|
|
|
|
def _normalBackgroundColor(self):
|
|
return QColor(255, 255, 255, 13 if isDarkTheme() else 170)
|
|
|
|
def _hoverBackgroundColor(self):
|
|
return QColor(255, 255, 255, 21 if isDarkTheme() else 64)
|
|
|
|
def _pressedBackgroundColor(self):
|
|
return QColor(255, 255, 255, 8 if isDarkTheme() else 64)
|
|
|
|
def getBorderRadius(self):
|
|
return self._borderRadius
|
|
|
|
def setBorderRadius(self, radius: int):
|
|
self._borderRadius = radius
|
|
self.update()
|
|
|
|
def paintEvent(self, e):
|
|
painter = QPainter(self)
|
|
painter.setRenderHints(QPainter.Antialiasing)
|
|
|
|
w, h = self.width(), self.height()
|
|
r = self.borderRadius
|
|
d = 2 * r
|
|
|
|
isDark = isDarkTheme()
|
|
|
|
# draw top border
|
|
path = QPainterPath()
|
|
# path.moveTo(1, h - r)
|
|
path.arcMoveTo(1, h - d - 1, d, d, 240)
|
|
path.arcTo(1, h - d - 1, d, d, 225, -60)
|
|
path.lineTo(1, r)
|
|
path.arcTo(1, 1, d, d, -180, -90)
|
|
path.lineTo(w - r, 1)
|
|
path.arcTo(w - d - 1, 1, d, d, 90, -90)
|
|
path.lineTo(w - 1, h - r)
|
|
path.arcTo(w - d - 1, h - d - 1, d, d, 0, -60)
|
|
|
|
topBorderColor = QColor(0, 0, 0, 20)
|
|
if isDark:
|
|
if self.isPressed:
|
|
topBorderColor = QColor(255, 255, 255, 18)
|
|
elif self.isHover:
|
|
topBorderColor = QColor(255, 255, 255, 13)
|
|
else:
|
|
topBorderColor = QColor(0, 0, 0, 15)
|
|
|
|
painter.strokePath(path, topBorderColor)
|
|
|
|
# draw bottom border
|
|
path = QPainterPath()
|
|
path.arcMoveTo(1, h - d - 1, d, d, 240)
|
|
path.arcTo(1, h - d - 1, d, d, 240, 30)
|
|
path.lineTo(w - r - 1, h - 1)
|
|
path.arcTo(w - d - 1, h - d - 1, d, d, 270, 30)
|
|
|
|
bottomBorderColor = topBorderColor
|
|
if not isDark and self.isHover and not self.isPressed:
|
|
bottomBorderColor = QColor(0, 0, 0, 27)
|
|
|
|
painter.strokePath(path, bottomBorderColor)
|
|
|
|
# draw background
|
|
painter.setPen(Qt.NoPen)
|
|
rect = self.rect().adjusted(1, 1, -1, -1)
|
|
painter.setBrush(self.backgroundColor)
|
|
painter.drawRoundedRect(rect, r, r)
|
|
|
|
borderRadius = Property(int, getBorderRadius, setBorderRadius)
|
|
|
|
|
|
|
|
class SimpleCardWidget(CardWidget):
|
|
""" Simple card widget """
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
|
|
def _normalBackgroundColor(self):
|
|
return QColor(255, 255, 255, 13 if isDarkTheme() else 170)
|
|
|
|
def _hoverBackgroundColor(self):
|
|
return self._normalBackgroundColor()
|
|
|
|
def _pressedBackgroundColor(self):
|
|
return self._normalBackgroundColor()
|
|
|
|
def paintEvent(self, e):
|
|
painter = QPainter(self)
|
|
painter.setRenderHints(QPainter.Antialiasing)
|
|
painter.setBrush(self.backgroundColor)
|
|
|
|
if isDarkTheme():
|
|
painter.setPen(QColor(0, 0, 0, 48))
|
|
else:
|
|
painter.setPen(QColor(0, 0, 0, 12))
|
|
|
|
r = self.borderRadius
|
|
painter.drawRoundedRect(self.rect().adjusted(1, 1, -1, -1), r, r)
|
|
|
|
|
|
|
|
class ElevatedCardWidget(SimpleCardWidget):
|
|
""" Card widget with shadow effect """
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.shadowAni = DropShadowAnimation(self, hoverColor=QColor(0, 0, 0, 20))
|
|
self.shadowAni.setOffset(0, 5)
|
|
self.shadowAni.setBlurRadius(38)
|
|
|
|
self.elevatedAni = QPropertyAnimation(self, b'pos', self)
|
|
self.elevatedAni.setDuration(100)
|
|
|
|
self._originalPos = self.pos()
|
|
self.setBorderRadius(8)
|
|
|
|
def enterEvent(self, e):
|
|
super().enterEvent(e)
|
|
|
|
if self.elevatedAni.state() != QPropertyAnimation.Running:
|
|
self._originalPos = self.pos()
|
|
|
|
self._startElevateAni(self.pos(), self.pos() - QPoint(0, 3))
|
|
|
|
def leaveEvent(self, e):
|
|
super().leaveEvent(e)
|
|
self._startElevateAni(self.pos(), self._originalPos)
|
|
|
|
def mousePressEvent(self, e):
|
|
super().mousePressEvent(e)
|
|
self._startElevateAni(self.pos(), self._originalPos)
|
|
|
|
def _startElevateAni(self, start, end):
|
|
self.elevatedAni.setStartValue(start)
|
|
self.elevatedAni.setEndValue(end)
|
|
self.elevatedAni.start()
|
|
|
|
def _hoverBackgroundColor(self):
|
|
return QColor(255, 255, 255, 16) if isDarkTheme() else QColor(255, 255, 255)
|
|
|
|
def _pressedBackgroundColor(self):
|
|
return QColor(255, 255, 255, 6 if isDarkTheme() else 118)
|
|
|
|
|
|
|
|
class CardSeparator(QWidget):
|
|
""" Card separator """
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent=parent)
|
|
self.setFixedHeight(3)
|
|
|
|
def paintEvent(self, e):
|
|
painter = QPainter(self)
|
|
painter.setRenderHints(QPainter.Antialiasing)
|
|
|
|
if isDarkTheme():
|
|
painter.setPen(QColor(255, 255, 255, 46))
|
|
else:
|
|
painter.setPen(QColor(0, 0, 0, 12))
|
|
|
|
painter.drawLine(2, 1, self.width() - 2, 1)
|
|
|
|
|
|
class HeaderCardWidget(SimpleCardWidget):
|
|
""" Header card widget """
|
|
|
|
@singledispatchmethod
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.headerView = QWidget(self)
|
|
self.headerLabel = QLabel(self)
|
|
self.separator = CardSeparator(self)
|
|
self.view = QWidget(self)
|
|
|
|
self.vBoxLayout = QVBoxLayout(self)
|
|
self.headerLayout = QHBoxLayout(self.headerView)
|
|
self.viewLayout = QHBoxLayout(self.view)
|
|
|
|
self.headerLayout.addWidget(self.headerLabel)
|
|
self.headerLayout.setContentsMargins(24, 0, 16, 0)
|
|
self.headerView.setFixedHeight(48)
|
|
|
|
self.vBoxLayout.setSpacing(0)
|
|
self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
|
|
self.vBoxLayout.addWidget(self.headerView)
|
|
self.vBoxLayout.addWidget(self.separator)
|
|
self.vBoxLayout.addWidget(self.view)
|
|
|
|
self.viewLayout.setContentsMargins(24, 24, 24, 24)
|
|
setFont(self.headerLabel, 15, QFont.DemiBold)
|
|
|
|
self.view.setObjectName('view')
|
|
self.headerView.setObjectName('headerView')
|
|
self.headerLabel.setObjectName('headerLabel')
|
|
FluentStyleSheet.CARD_WIDGET.apply(self)
|
|
|
|
self._postInit()
|
|
|
|
@__init__.register
|
|
def _(self, title: str, parent=None):
|
|
self.__init__(parent)
|
|
self.setTitle(title)
|
|
|
|
def getTitle(self):
|
|
return self.headerLabel.text()
|
|
|
|
def setTitle(self, title: str):
|
|
self.headerLabel.setText(title)
|
|
|
|
def _postInit(self):
|
|
pass
|
|
|
|
title = Property(str, getTitle, setTitle)
|
|
|
|
|
|
|
|
class CardGroupWidget(QWidget):
|
|
|
|
def __init__(self, icon: Union[str, FluentIconBase, QIcon], title: str, content: str, parent=None):
|
|
super().__init__(parent=parent)
|
|
self.vBoxLayout = QVBoxLayout(self)
|
|
self.hBoxLayout = QHBoxLayout()
|
|
|
|
self.iconWidget = IconWidget(icon)
|
|
self.titleLabel = BodyLabel(title)
|
|
self.contentLabel = CaptionLabel(content)
|
|
self.textLayout = QVBoxLayout()
|
|
|
|
self.separator = CardSeparator()
|
|
|
|
self.__initWidget()
|
|
|
|
def __initWidget(self):
|
|
self.separator.hide()
|
|
self.iconWidget.setFixedSize(20, 20)
|
|
self.contentLabel.setTextColor(QColor(96, 96, 96), QColor(206, 206, 206))
|
|
|
|
self.vBoxLayout.setSpacing(0)
|
|
self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
|
|
self.vBoxLayout.addLayout(self.hBoxLayout)
|
|
self.vBoxLayout.addWidget(self.separator)
|
|
|
|
self.textLayout.addWidget(self.titleLabel)
|
|
self.textLayout.addWidget(self.contentLabel)
|
|
self.hBoxLayout.addWidget(self.iconWidget)
|
|
self.hBoxLayout.addLayout(self.textLayout)
|
|
self.hBoxLayout.addStretch(1)
|
|
|
|
self.hBoxLayout.setSpacing(15)
|
|
self.hBoxLayout.setContentsMargins(24, 10, 24, 10)
|
|
self.textLayout.setContentsMargins(0, 0, 0, 0)
|
|
self.textLayout.setSpacing(0)
|
|
self.hBoxLayout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
|
self.textLayout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
def title(self):
|
|
return self.titleLabel.text()
|
|
|
|
def setTitle(self, text: str):
|
|
self.titleLabel.setText(text)
|
|
|
|
def content(self):
|
|
return self.contentLabel.text()
|
|
|
|
def setContent(self, text: str):
|
|
self.contentLabel.setText(text)
|
|
|
|
def icon(self):
|
|
return self.iconWidget.icon
|
|
|
|
def setIcon(self, icon: Union[str, FluentIconBase, QIcon]):
|
|
self.iconWidget.setIcon(icon)
|
|
|
|
def setIconSize(self, size: QSize):
|
|
self.iconWidget.setFixedSize(size)
|
|
|
|
def setSeparatorVisible(self, isVisible: bool):
|
|
self.separator.setVisible(isVisible)
|
|
|
|
def isSeparatorVisible(self):
|
|
return self.separator.isVisible()
|
|
|
|
def addWidget(self, widget: QWidget, stretch=0):
|
|
self.hBoxLayout.addWidget(widget, stretch=stretch)
|
|
|
|
|
|
class GroupHeaderCardWidget(HeaderCardWidget):
|
|
""" Group header card widget """
|
|
|
|
def _postInit(self):
|
|
super()._postInit()
|
|
self.groupWidgets = [] # type: List[CardGroupWidget]
|
|
self.groupLayout = QVBoxLayout()
|
|
|
|
self.groupLayout.setSpacing(0)
|
|
self.viewLayout.setContentsMargins(0, 0, 0, 0)
|
|
self.groupLayout.setContentsMargins(0, 0, 0, 0)
|
|
self.viewLayout.addLayout(self.groupLayout)
|
|
|
|
def addGroup(self, icon: Union[str, FluentIconBase, QIcon], title: str, content: str, widget: QWidget, stretch=0) -> CardGroupWidget:
|
|
""" add widget to a new group
|
|
|
|
Parameters
|
|
----------
|
|
icon: str | QIcon | FluentIconBase
|
|
the icon to be drawn
|
|
|
|
title: str
|
|
the title of card
|
|
|
|
content: str
|
|
the content of card
|
|
|
|
widget: QWidget
|
|
the widget to be added
|
|
|
|
stretch: int
|
|
the layout stretch of widget
|
|
"""
|
|
group = CardGroupWidget(icon, title, content, self)
|
|
group.addWidget(widget, stretch=stretch)
|
|
|
|
if self.groupWidgets:
|
|
self.groupWidgets[-1].setSeparatorVisible(True)
|
|
|
|
self.groupLayout.addWidget(group)
|
|
self.groupWidgets.append(group)
|
|
return group
|
|
|
|
def groupCount(self):
|
|
return len(self.groupWidgets) |