一个水波纹按钮

本代码是一个组件库中提取出来的,原地址为https://kgithub.com/zhiyiYo/QMaterialWidgets

from enum import Enum
from PyQt5.Qt import *
from qtawesome import icon


class RippleStyle(Enum):
    """ Ripple style """

    CENTERED = 0
    POSITIONED = 1
    NONE = 2


class RippleAnimation(QParallelAnimationGroup):
    """
    涟漪动画
    """

    def __init__(self, center: QPoint, overlay=None, parent=None):
        super().__init__(parent=parent)
        self._radius = 0
        self._opacity = 0
        self.center = center
        self.overlay = overlay  # type: RippleOverlayWidget
        self.brush = QBrush(Qt.GlobalColor.black)

        self.radiusAni = QPropertyAnimation(self, b'radius', self)
        self.opacityAni = QPropertyAnimation(self, b'opacity', self)

        self.opacityAni.setStartValue(0.5)
        self.opacityAni.setEndValue(0)
        self.opacityAni.setDuration(800)
        self.opacityAni.setEasingCurve(QEasingCurve.Type.OutQuad)

        self.radiusAni.setStartValue(0)
        self.radiusAni.setEndValue(300)
        self.radiusAni.setDuration(800)
        self.radiusAni.setEasingCurve(QEasingCurve.Type.OutQuad)

        self.addAnimation(self.radiusAni)
        self.addAnimation(self.opacityAni)

    def setRadiusEndValue(self, radius: float):
        self.radiusAni.setEndValue(radius)

    def setRadiusDuration(self, duration: int):
        self.radiusAni.setDuration(duration)

    def setOpacityStartValue(self, opacity: float):
        self.opacityAni.setStartValue(opacity)

    def setOpacityDuration(self, duration: int):
        self.opacityAni.setDuration(duration)

    @pyqtProperty(float)
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, r):
        self._radius = r
        self.overlay.update()

    @pyqtProperty(float)
    def opacity(self):
        return self._opacity

    @opacity.setter
    def opacity(self, r):
        self._opacity = r
        self.overlay.update()

    def setColor(self, color: QColor):
        if self.brush.color() == color:
            return

        self.brush.setColor(color)
        self.overlay.update()

    def setBrush(self, brush: QBrush):
        self.brush = brush
        self.overlay.update()

    def setOverlay(self, overlay):
        self.overlay = overlay


class OverlayWidget(QWidget):
    """ Overlay widget """

    def __init__(self, parent: QWidget):
        super().__init__(parent=parent)
        parent.installEventFilter(self)

    def eventFilter(self, obj: 'QObject', e: 'QEvent'):
        if obj is not self.parent():
            return super().eventFilter(obj, e)

        if e.type() in [QEvent.Type.Move, QEvent.Type.Resize]:
            self.setGeometry(self.overlayGeometry())

        return super().eventFilter(obj, e)

    def overlayGeometry(self):
        return self.parentWidget().rect()


class RippleOverlayWidget(OverlayWidget):
    """ Ripple overlay widget """

    def __init__(self, parent: QWidget):
        super().__init__(parent)
        self.ripples = []
        self.clipPath = QPainterPath()
        self.isClipEnabled = True
        self.rippleStyle = RippleStyle.POSITIONED
        self.rippleOpacityDuration = 1000
        self.rippleRadiusDuration = 1400
        self.rippleStartOpacity = 0.6
        self.triggeredEvent = QEvent.Type.MouseButtonPress

        parent.installEventFilter(self)

        self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
        self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)

    def setClipEnabled(self, isEnabled: bool):
        self.isClipEnabled = isEnabled
        self.update()

    def setClipPath(self, path):
        self.clipPath = path
        self.update()

    def addRipple(self, ripple):
        """ add ripple animation """
        ripple.setOverlay(self)
        self.ripples.append(ripple)
        ripple.finished.connect(lambda: self.removeRipple(ripple))
        ripple.start()

    def removeRipple(self, ripple):
        if ripple not in self.ripples:
            return

        self.ripples.remove(ripple)
        ripple.deleteLater()
        self.update()

    def eventFilter(self, obj: 'QObject', e: 'QEvent'):
        if obj is not self.parent() or not obj.isEnabled() or self.rippleStyle == RippleStyle.NONE or e.type() != self.triggeredEvent:
            return super().eventFilter(obj, e)

        # add ripple
        if self.rippleStyle == RippleStyle.CENTERED:
            pos = self._rippleCenter()
            radius = self.width() // 2
        else:
            # QMouseEvent
            pos = e.pos()
            radius = max(pos.x(), self.width() - pos.x())

        ripple = RippleAnimation(pos, self, self)
        ripple.setColor(QColor('#aa00ff'))
        ripple.setRadiusEndValue(radius)
        ripple.setOpacityStartValue(self.rippleStartOpacity)
        ripple.setRadiusDuration(self.rippleRadiusDuration)
        ripple.setOpacityDuration(self.rippleOpacityDuration)
        self.addRipple(ripple)
        return super().eventFilter(obj, e)

    def _rippleCenter(self):
        return self.rect().center()

    def paintEvent(self, e):
        painter = QPainter(self)
        painter.setRenderHints(QPainter.RenderHint.Antialiasing)
        painter.setPen(Qt.PenStyle.NoPen)

        if self.isClipEnabled:
            painter.setClipPath(self.clipPath)
        for ripple in self.ripples:
            self._drawRipple(painter, ripple)

    def _drawRipple(self, painter: QPainter, ripple):
        painter.setOpacity(ripple.opacity)
        painter.setBrush(ripple.brush)
        painter.drawEllipse(ripple.center, ripple.radius, ripple.radius)


class PushButton(QPushButton):
    """ push button """

    def __init__(self, text: str = '', parent: QWidget = None):
        super().__init__(parent)
        self.setText(text)
        self.rippleWidget = RippleOverlayWidget(self)
        self.setBorderRadius(-1)

    def setProperty(self, name: str, value) -> bool:
        if name != 'icon':
            return super().setProperty(name, value)

        self.setIcon(value)
        return True

    def resizeEvent(self, e):
        self._updateRippleClipPath()

    def _updateRippleClipPath(self):
        path = QPainterPath()
        path.addRoundedRect(QRectF(self.rect()), self.borderRadius, self.borderRadius)
        self.rippleWidget.setClipPath(path)

    def getBorderRadius(self):
        return self._borderRadius if self._borderRadius >= 0 else self.height() // 2

    def setBorderRadius(self, radius: int):
        """
        设置边框半径,-1表示全圆角
        :param radius:
        :return:
        """
        self._borderRadius = radius
        self._updateRippleClipPath()
        self.update()

    borderRadius = pyqtProperty(int, getBorderRadius, setBorderRadius)


if __name__ == '__main__':
    import sys

    QApplication.setHighDpiScaleFactorRoundingPolicy(
        Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
    )
    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
    QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
    app = QApplication(sys.argv)
    win = QWidget()
    push = PushButton('PushButton', win)
    push.setIcon(icon('ei.caret-down'))
    push.setIconSize(QSize(30, 30))
    push.setText('PushButton')
    push.move(100, 100)
    push.resize(300, 40)
    win.show()
    sys.exit(app.exec())

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值