From 89b8de2025f481a5b709d41d98c1afbcede9f77b Mon Sep 17 00:00:00 2001 From: Irony <892768447@qq.com> Date: Sat, 16 Jan 2021 14:33:05 +0800 Subject: [PATCH] DWaterProgress --- QProgressBar/Lib/DWaterProgress.py | 243 +++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 QProgressBar/Lib/DWaterProgress.py diff --git a/QProgressBar/Lib/DWaterProgress.py b/QProgressBar/Lib/DWaterProgress.py new file mode 100644 index 0000000..0b6e69f --- /dev/null +++ b/QProgressBar/Lib/DWaterProgress.py @@ -0,0 +1,243 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2021/1/1 +@author: Irony +@site: https://pyqt5.com , https://github.com/892768447 +@email: 892768447@qq.com +@file: DWaterProgress +@see https://github.com/linuxdeepin/dtkwidget/blob/master/src/widgets/dwaterprogress.cpp +@description: +""" +import math + +from PyQt5.QtCore import pyqtSlot, QTimer, QSizeF, Qt, QRectF, QPointF, QRect, QPoint, QSize +from PyQt5.QtGui import QImage, QColor, QPainter, QLinearGradient, QGradient, QPainterPath, QPixmap, QBrush, QPen +from PyQt5.QtSvg import QSvgRenderer +from PyQt5.QtWidgets import QProgressBar, QGraphicsDropShadowEffect + +WATER_FRONT = """ + + +""" +WATER_BACK = """ + + +""" + + +class Pop: + # https://github.com/linuxdeepin/dtkwidget/blob/master/src/widgets/dwaterprogress.cpp#L36 + + def __init__(self, size, xs, ys, xo=0, yo=0): + self.size = size + self.xSpeed = xs + self.ySpeed = ys + self.xOffset = xo + self.yOffset = yo + + +class DWaterProgress(QProgressBar): + + def __init__(self, *args, **kwargs): + super(DWaterProgress, self).__init__(*args, **kwargs) + self.waterFrontImage = QImage() + self.waterBackImage = QImage() + self.waterFrontSvg = QSvgRenderer(WATER_FRONT.encode()) + self.waterBackSvg = QSvgRenderer(WATER_BACK.encode()) + self.pops = [] + self.initPops() + self.setTextVisible(True) + self.interval = 33 + self.timer = QTimer(self) + self.timer.setInterval(self.interval) + self.timer.timeout.connect(self.onTimerOut) + self.resizePixmap(self.size()) + self.frontXOffset = self.width() + self.backXOffset = 0 + effect = QGraphicsDropShadowEffect(self) + effect.setOffset(0, 6) + effect.setColor(QColor(1, 153, 248, 255 * 5 / 20)) + effect.setBlurRadius(12) + self.setGraphicsEffect(effect) + + def initPops(self): + self.pops = [Pop(7, -1.8, 0.6), Pop(8, 1.2, 1.0), Pop(11, 0.8, 1.6)] + + @pyqtSlot() + def start(self): + self.timer.start() + + @pyqtSlot() + def stop(self): + self.timer.stop() + + def resizePixmap(self, sz): + # https://github.com/linuxdeepin/dtkwidget/blob/master/src/widgets/dwaterprogress.cpp#L192 + # resize water + waterWidth = 500 * sz.width() / 100 + waterHeight = 110 * sz.height() / 100 + waterSize = QSizeF(waterWidth, waterHeight).toSize() + + if self.waterFrontImage.size() != waterSize: + image = QImage(waterWidth, waterHeight, QImage.Format_ARGB32) + image.fill(Qt.transparent) + waterPainter = QPainter(image) + self.waterFrontSvg.render(waterPainter) + self.waterFrontImage = image + + if self.waterBackImage.size() != waterSize: + image = QImage(waterWidth, waterHeight, QImage.Format_ARGB32) + image.fill(Qt.transparent) # partly transparent red-ish background + waterPainter = QPainter(image) + self.waterBackSvg.render(waterPainter) + self.waterBackImage = image + + def onTimerOut(self): + # interval can not be zero, and limit to 1 + self.interval = max(1, self.interval) + # move 60% per second + frontXDeta = 40.0 / (1000.0 / self.interval) + # move 90% per second + backXDeta = 60.0 / (1000.0 / self.interval) + + canvasWidth = int(self.width() * self.devicePixelRatioF()) + self.frontXOffset -= frontXDeta * canvasWidth / 100 + self.backXOffset += backXDeta * canvasWidth / 100 + + if self.frontXOffset > canvasWidth: + self.frontXOffset = canvasWidth + + if self.frontXOffset < - (self.waterFrontImage.width() - canvasWidth): + self.frontXOffset = canvasWidth + + if self.backXOffset > self.waterBackImage.width(): + self.backXOffset = 0 + + # update pop + # move 25% per second default + speed = 25 / (1000.0 / self.interval) # 100 / self.height() + for pop in self.pops: + # yOffset 0 ~ 100 + pop.yOffset += speed * pop.ySpeed + if pop.yOffset < 0: + pass + if pop.yOffset > self.value(): + pop.yOffset = 0 + pop.xOffset = math.sin((pop.yOffset / 100) * 2 * 3.14) * 18 * pop.xSpeed + 50 + self.update() + + def paint(self, painter): + painter.setRenderHint(QPainter.Antialiasing) + + pixelRatio = self.devicePixelRatioF() + rect = QRectF(0, 0, self.width() * pixelRatio, self.height() * pixelRatio) + sz = QSizeF(self.width() * pixelRatio, self.height() * pixelRatio).toSize() + + self.resizePixmap(sz) + + yOffset = rect.toRect().topLeft().y() + (100 - self.value() - 10) * sz.height() / 100 + + # draw water + waterImage = QImage(sz, QImage.Format_ARGB32_Premultiplied) + waterPainter = QPainter() + waterPainter.begin(waterImage) + waterPainter.setRenderHint(QPainter.Antialiasing) + waterPainter.setCompositionMode(QPainter.CompositionMode_Source) + + pointStart = QPointF(sz.width() / 2, 0) + pointEnd = QPointF(sz.width() / 2, sz.height()) + linear = QLinearGradient(pointStart, pointEnd) + startColor = QColor('#1F08FF') + startColor.setAlphaF(1) + endColor = QColor('#50FFF7') + endColor.setAlphaF(0.28) + linear.setColorAt(0, startColor) + linear.setColorAt(1, endColor) + linear.setSpread(QGradient.PadSpread) + waterPainter.setPen(Qt.NoPen) + waterPainter.setBrush(linear) + waterPainter.drawEllipse(waterImage.rect().center(), sz.width() / 2 + 1, sz.height() / 2 + 1) + + waterPainter.setCompositionMode(QPainter.CompositionMode_SourceOver) + waterPainter.drawImage(int(self.backXOffset), yOffset, self.waterBackImage) + waterPainter.drawImage(int(self.backXOffset) - self.waterBackImage.width(), yOffset, self.waterBackImage) + waterPainter.drawImage(int(self.frontXOffset), yOffset, self.waterFrontImage) + waterPainter.drawImage(int(self.frontXOffset) - self.waterFrontImage.width(), yOffset, self.waterFrontImage) + + # draw pop + if self.value() > 30: + for pop in self.pops: + popPath = QPainterPath() + popPath.addEllipse(pop.xOffset * sz.width() / 100, (100 - pop.yOffset) * sz.height() / 100, + pop.size * sz.width() / 100, pop.size * sz.height() / 100) + waterPainter.fillPath(popPath, QColor(255, 255, 255, 255 * 0.3)) + + if self.isTextVisible(): + font = waterPainter.font() + rectValue = QRect() + progressText = self.text().strip('%') + + if progressText == '100': + font.setPixelSize(sz.height() * 35 / 100) + waterPainter.setFont(font) + + rectValue.setWidth(sz.width() * 60 / 100) + rectValue.setHeight(sz.height() * 35 / 100) + rectValue.moveCenter(rect.center().toPoint()) + waterPainter.setPen(Qt.white) + waterPainter.drawText(rectValue, Qt.AlignCenter, progressText) + else: + font.setPixelSize(sz.height() * 40 / 100) + waterPainter.setFont(font) + + rectValue.setWidth(sz.width() * 45 / 100) + rectValue.setHeight(sz.height() * 40 / 100) + rectValue.moveCenter(rect.center().toPoint()) + rectValue.moveLeft(rect.left() + rect.width() * 0.45 * 0.5) + + waterPainter.setPen(Qt.white) + waterPainter.drawText(rectValue, Qt.AlignCenter, progressText) + font.setPixelSize(font.pixelSize() / 2) + waterPainter.setFont(font) + rectPerent = QRect(QPoint(rectValue.right(), rectValue.bottom() - rect.height() * 20 / 100), + QPoint(rectValue.right() + rect.width() * 20 / 100, rectValue.bottom())) + + waterPainter.drawText(rectPerent, Qt.AlignCenter, '%') + + waterPainter.end() + + maskPixmap = QPixmap(sz) + maskPixmap.fill(Qt.transparent) + path = QPainterPath() + path.addEllipse(QRectF(0, 0, sz.width(), sz.height())) + maskPainter = QPainter() + maskPainter.begin(maskPixmap) + maskPainter.setRenderHint(QPainter.Antialiasing) + maskPainter.setPen(QPen(Qt.white, 1)) + maskPainter.fillPath(path, QBrush(Qt.white)) + maskPainter.end() + + mode = QPainter.CompositionMode_SourceIn + contentImage = QImage(sz, QImage.Format_ARGB32_Premultiplied) + contentPainter = QPainter() + contentPainter.begin(contentImage) + contentPainter.setCompositionMode(QPainter.CompositionMode_Source) + contentPainter.fillRect(contentImage.rect(), Qt.transparent) + contentPainter.setCompositionMode(QPainter.CompositionMode_SourceOver) + contentPainter.drawImage(0, 0, maskPixmap.toImage()) + contentPainter.setCompositionMode(mode) + contentPainter.drawImage(0, 0, waterImage) + contentPainter.setCompositionMode(QPainter.CompositionMode_DestinationOver) + contentPainter.end() + + contentImage.setDevicePixelRatio(pixelRatio) + painter.drawImage(self.rect(), contentImage) + + def paintEvent(self, event): + painter = QPainter(self) + self.paint(painter) + + def sizeHint(self): + return QSize(100, 100)