Issue
This is exactly what I am trying to re-create
I've tried a 4x4 grid layout with QLabels underneath a QProgressBar but it looks awful and I am wondering if there are any possible ways I could approach creating this?
Solution
That widget must be created using a custom painting as shown below:
import os
from PySide2 import QtCore, QtGui, QtWidgets
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class CustomProgressBar(QtWidgets.QWidget):
stepsChanged = QtCore.Signal(list)
valueChanged = QtCore.Signal(int)
def __init__(self, parent=None):
super().__init__(parent)
self._labels = []
self._value = 0
self._animation = QtCore.QVariantAnimation(
startValue=0.0, endValue=1.0, duration=500
)
self._animation.valueChanged.connect(self.update)
def get_labels(self):
return self._labels
def set_labels(self, labels):
self._labels = labels[:]
self.stepsChanged.emit(self._labels)
labels = QtCore.Property(
list, fget=get_labels, fset=set_labels, notify=stepsChanged
)
def get_value(self):
return self._value
def set_value(self, value):
if 0 <= value < len(self.labels) + 1:
self._value = value
self.valueChanged.emit(value)
self.update()
if self.value < len(self.labels):
self._animation.start()
value = QtCore.Property(int, fget=get_value, fset=set_value, notify=valueChanged)
def sizeHint(self):
return QtCore.QSize(320, 120)
def paintEvent(self, event):
grey = QtGui.QColor("#777")
grey2 = QtGui.QColor("#dfe3e4")
blue = QtGui.QColor("#2183dd")
green = QtGui.QColor("#009900")
white = QtGui.QColor("#fff")
painter = QtGui.QPainter(self)
painter.setRenderHints(QtGui.QPainter.Antialiasing)
height = 5
offset = 10
painter.fillRect(self.rect(), white)
busy_rect = QtCore.QRect(0, 0, self.width(), height)
busy_rect.adjust(offset, 0, -offset, 0)
busy_rect.moveCenter(self.rect().center())
painter.fillRect(busy_rect, grey2)
number_of_steps = len(self.labels)
if number_of_steps == 0:
return
step_width = busy_rect.width() / number_of_steps
x = offset + step_width / 2
y = busy_rect.center().y()
radius = 10
font_text = painter.font()
font_icon = QtGui.QFont("Font Awesome 5 Free")
font_icon.setPixelSize(radius)
r = QtCore.QRect(0, 0, 1.5 * radius, 1.5 * radius)
fm = QtGui.QFontMetrics(font_text)
for i, text in enumerate(self.labels, 1):
r.moveCenter(QtCore.QPoint(x, y))
if i <= self.value:
w = (
step_width
if i < self.value
else self._animation.currentValue() * step_width
)
r_busy = QtCore.QRect(0, 0, w, height)
r_busy.moveCenter(busy_rect.center())
if i < number_of_steps:
r_busy.moveLeft(x)
painter.fillRect(r_busy, blue)
pen = QtGui.QPen(green)
pen.setWidth(3)
painter.setPen(pen)
painter.setBrush(green)
painter.drawEllipse(r)
painter.setFont(font_icon)
painter.setPen(white)
painter.drawText(r, QtCore.Qt.AlignCenter, chr(0xF00C))
painter.setPen(green)
else:
is_active = (self.value + 1) == i
pen = QtGui.QPen(grey if is_active else grey2)
pen.setWidth(3)
painter.setPen(pen)
painter.setBrush(white)
painter.drawEllipse(r)
painter.setPen(blue if is_active else QtGui.QColor("black"))
rect = fm.boundingRect(text)
rect.moveCenter(QtCore.QPoint(x, y + 2 * radius))
painter.setFont(font_text)
painter.drawText(rect, QtCore.Qt.AlignCenter, text)
x += step_width
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
_id = QtGui.QFontDatabase.addApplicationFont(
os.path.join(CURRENT_DIR, "fa-solid-900.ttf")
)
print(QtGui.QFontDatabase.applicationFontFamilies(_id))
progressbar = CustomProgressBar()
progressbar.labels = ["Step One", "Step Two", "Step Three", "Complete"]
button = QtWidgets.QPushButton("Next Step")
def on_clicked():
progressbar.value = (progressbar.value + 1) % (len(progressbar.labels) + 1)
button.clicked.connect(on_clicked)
w = QtWidgets.QWidget()
lay = QtWidgets.QVBoxLayout(w)
lay.addWidget(progressbar)
lay.addWidget(button, alignment=QtCore.Qt.AlignRight)
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Note: To paint the check icon I have used the awesome font so it must be downloaded from here and placed next to the script.
PySide:
import os
from PySide import QtCore, QtGui
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class CustomVariantAnimation(QtCore.QVariantAnimation):
def updateCurrentValue(self, value):
pass
class CustomProgressBar(QtGui.QWidget):
stepsChanged = QtCore.Signal(list)
valueChanged = QtCore.Signal(int)
def __init__(self, parent=None):
super().__init__(parent)
self._labels = []
self._value = 0
self._percentage_width = 0
self._animation = CustomVariantAnimation(startValue=0.0, endValue=1.0)
self._animation.setDuration(500)
self._animation.valueChanged.connect(self.update)
def get_labels(self):
return self._labels
def set_labels(self, labels):
self._labels = labels[:]
self.stepsChanged.emit(self._labels)
labels = QtCore.Property(
list, fget=get_labels, fset=set_labels, notify=stepsChanged
)
def get_value(self):
return self._value
def set_value(self, value):
if 0 <= value < len(self.labels) + 1:
self._value = value
self.valueChanged.emit(value)
self.update()
if self.value < len(self.labels):
self._animation.start()
value = QtCore.Property(int, fget=get_value, fset=set_value, notify=valueChanged)
def sizeHint(self):
return QtCore.QSize(320, 120)
def paintEvent(self, event):
grey = QtGui.QColor("#777")
grey2 = QtGui.QColor("#dfe3e4")
blue = QtGui.QColor("#2183dd")
green = QtGui.QColor("#009900")
white = QtGui.QColor("#fff")
painter = QtGui.QPainter(self)
painter.setRenderHints(QtGui.QPainter.Antialiasing)
height = 5
offset = 10
painter.fillRect(self.rect(), white)
busy_rect = QtCore.QRect(0, 0, self.width(), height)
busy_rect.adjust(offset, 0, -offset, 0)
busy_rect.moveCenter(self.rect().center())
painter.fillRect(busy_rect, grey2)
number_of_steps = len(self.labels)
if number_of_steps == 0:
return
step_width = busy_rect.width() / number_of_steps
x = offset + step_width / 2
y = busy_rect.center().y()
radius = 10
font_text = painter.font()
font_icon = QtGui.QFont("Font Awesome 5 Free")
font_icon.setPixelSize(radius)
r = QtCore.QRect(0, 0, 1.5 * radius, 1.5 * radius)
fm = QtGui.QFontMetrics(font_text)
for i, text in enumerate(self.labels, 1):
r.moveCenter(QtCore.QPoint(x, y))
if i <= self.value:
w = (
step_width
if i < self.value
else self._animation.currentValue() * step_width
)
r_busy = QtCore.QRect(0, 0, w, height)
r_busy.moveCenter(busy_rect.center())
if i < number_of_steps:
r_busy.moveLeft(x)
painter.fillRect(r_busy, blue)
pen = QtGui.QPen(green)
pen.setWidth(3)
painter.setPen(pen)
painter.setBrush(green)
painter.drawEllipse(r)
painter.setFont(font_icon)
painter.setPen(white)
painter.drawText(r, QtCore.Qt.AlignCenter, chr(0xF00C))
painter.setPen(green)
else:
is_active = (self.value + 1) == i
pen = QtGui.QPen(grey if is_active else grey2)
pen.setWidth(3)
painter.setPen(pen)
painter.setBrush(white)
painter.drawEllipse(r)
painter.setPen(blue if is_active else QtGui.QColor("black"))
rect = fm.boundingRect(text)
rect.moveCenter(QtCore.QPoint(x, y + 2 * radius))
painter.setFont(font_text)
painter.drawText(rect, QtCore.Qt.AlignCenter, text)
x += step_width
def main():
import sys
app = QtGui.QApplication(sys.argv)
_id = QtGui.QFontDatabase.addApplicationFont(
os.path.join(CURRENT_DIR, "fa-solid-900.ttf")
)
print(QtGui.QFontDatabase.applicationFontFamilies(_id))
progressbar = CustomProgressBar()
progressbar.labels = ["Step One", "Step Two", "Step Three", "Complete"]
button = QtGui.QPushButton("Next Step")
def on_clicked():
progressbar.value = (progressbar.value + 1) % (len(progressbar.labels) + 1)
button.clicked.connect(on_clicked)
w = QtGui.QWidget()
lay = QtGui.QVBoxLayout(w)
lay.addWidget(progressbar)
lay.addWidget(button, alignment=QtCore.Qt.AlignRight)
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Answered By - eyllanesc
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.