#!/usr/bin/env python # -*- coding: utf-8 -*- """ Created on 2021/1/1 @author: Irony @site: https://pyqt.site , https://github.com/PyQt5 @email: 892768447@qq.com @file: DWaterProgress @see https://github.com/linuxdeepin/dtkwidget/blob/master/src/widgets/dwaterprogress.cpp @description: """ import math try: 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 except ImportError: from PySide2.QtCore import Slot as pyqtSlot, QTimer, QSizeF, Qt, QRectF, QPointF, QRect, QPoint, QSize from PySide2.QtGui import QImage, QColor, QPainter, QLinearGradient, QGradient, QPainterPath, QPixmap, \ QBrush, QPen from PySide2.QtSvg import QSvgRenderer from PySide2.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)