#!/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)