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