Skip to content

Commit 6eb0b57

Browse files
committed
Create QFlowProgressBar.py
1 parent b33c36d commit 6eb0b57

File tree

1 file changed

+380
-0
lines changed

1 file changed

+380
-0
lines changed

Custom_Widgets/QFlowProgressBar.py

Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
1+
from qtpy.QtCore import Qt, QRect, Signal, QPointF, Property, QPropertyAnimation, QEasingCurve
2+
from qtpy.QtGui import QPainter, QPaintEvent, QMouseEvent, QColor, QBrush, QPen, QFont, QFontMetrics, QPolygonF
3+
from qtpy.QtWidgets import QWidget
4+
from typing import List
5+
6+
class QFlowProgressBar(QWidget):
7+
onStepClicked = Signal(int) # Define the signal for step clicks
8+
9+
class Styles:
10+
Circular = 0
11+
Flat = 1
12+
Square = 2
13+
Breadcrumb = 3
14+
15+
class Direction:
16+
Up = 0
17+
Down = 1
18+
19+
def __init__(self, strDetailList: List[str] = None, style: int = Styles.Circular, parent: QWidget = None,
20+
finishedNumberColor: QColor = QColor(255, 255, 255), finishedBackgroundColor: QColor = QColor(0, 136, 254),
21+
unfinishedBackgroundColor: QColor = QColor(228, 231, 237), numberFontSize: int = 9, textFontSize: int = 10,
22+
currentStep: int = 0, pointerDirection: Direction = Direction.Up,
23+
animationDuration: int = 1000, easingCurve: QEasingCurve.Type = QEasingCurve.OutQuad, stepsClickable: bool = True):
24+
"""
25+
Construct a QFlowProgressBar.
26+
27+
:param strDetailList: List of detail strings for each step.
28+
:param style: Style of the progress bar (Circular, Flat, Square, Breadcrumb).
29+
:param parent: Parent widget.
30+
:param finishedNumberColor: Color for finished step numbers.
31+
:param finishedBackgroundColor: Background color for finished steps.
32+
:param unfinishedBackgroundColor: Background color for unfinished steps.
33+
:param numberFontSize: Font size for step numbers.
34+
:param textFontSize: Font size for step descriptions.
35+
:param currentStep: The current step in the progress bar.
36+
:param pointerDirection: Pointer direction for flat progressbar.
37+
:param animationDuration: Duration of the animation in milliseconds.
38+
:param easingCurve: Easing curve for the animation.
39+
"""
40+
super().__init__(parent)
41+
self.barStyle = style
42+
self.finishedNumberColor = finishedNumberColor
43+
self.finishedBackgroundColor = finishedBackgroundColor
44+
self.unfinishedBackgroundColor = unfinishedBackgroundColor
45+
self.stepIconRects = []
46+
self.stepMessages = strDetailList if strDetailList else []
47+
self._currentStep = currentStep
48+
49+
self.numberFontSize = numberFontSize
50+
self.textFontSize = textFontSize
51+
52+
self.pointerDirection = pointerDirection
53+
54+
self._finishedProgressLength = 0
55+
self.animationDuration = animationDuration
56+
self.easingCurve = easingCurve
57+
58+
self.adjustSize()
59+
60+
self.animation = QPropertyAnimation(self, b"finishedProgressLength")
61+
self.animation.setDuration(self.animationDuration)
62+
self.animation.setEasingCurve(self.easingCurve)
63+
64+
self.stepsClickable = stepsClickable
65+
66+
def mouseReleaseEvent(self, event: QMouseEvent):
67+
"""
68+
Mouse release event handler.
69+
70+
:param event: QMouseEvent object.
71+
"""
72+
if not self.stepsClickable:
73+
return
74+
point = event.pos()
75+
# print("Mouse click coordinates:", point) # Debug print
76+
for index, rect in enumerate(self.stepIconRects):
77+
if rect.contains(point):
78+
# print("Step", index + 1, "clicked") # Debug print
79+
self.changeCurrentStep(index + 1)
80+
self.onStepClicked.emit(index)
81+
break
82+
83+
def resizeEvent(self, event):
84+
super().resizeEvent(event)
85+
self.changeCurrentStep(self.getCurrentStep())
86+
self.adjustSize()
87+
self.update()
88+
89+
90+
def paintEvent(self, event: QPaintEvent):
91+
"""
92+
Paint event handler to draw the progress bar.
93+
94+
:param event: QPaintEvent object.
95+
"""
96+
painter = QPainter(self)
97+
painter.setRenderHint(QPainter.Antialiasing)
98+
99+
if self.barStyle == self.Styles.Circular or self.barStyle == self.Styles.Square:
100+
self.drawStyle(painter)
101+
elif self.barStyle == self.Styles.Flat:
102+
self.drawFlatStyle(painter)
103+
104+
def drawStyle(self, painter: QPainter):
105+
"""
106+
Draw the progress bar with Circular or Square style.
107+
108+
:param painter: QPainter object.
109+
"""
110+
numberOfSteps = len(self.stepMessages)
111+
progressBarLength = int(self.size().width() * 0.9)
112+
progressBarHeight = int(self.size().height() * 0.1)
113+
totalStepIconLength = int(progressBarLength * 0.88)
114+
iconSize = int((totalStepIconLength / (numberOfSteps - 1)) * 0.15)
115+
iconStep = totalStepIconLength // (numberOfSteps - 1)
116+
iconStartY = 0
117+
118+
iconBorderPen = QPen(QBrush(self.getBackgroundColor()), iconSize * 0.1)
119+
whiteBrush = QBrush(Qt.white)
120+
fontNumber = QFont()
121+
fontText = QFont()
122+
fontNumber.setPointSize(self.numberFontSize)
123+
fontText.setPointSize(self.textFontSize)
124+
textSize = self.getDrawTextSize("Sample", fontText)
125+
126+
# Ensure icon size does not exceed 2/3 of the total height
127+
maxIconSize = int(self.size().height() * 2 / 3)
128+
iconSize = min(iconSize, maxIconSize)
129+
130+
# Ensure icon size plus text height does not exceed total height
131+
totalHeight = self.size().height()
132+
maxTotalHeight = totalHeight - textSize.height()
133+
if iconSize + textSize.height() > totalHeight:
134+
iconSize = maxTotalHeight
135+
136+
# Draw background progress bar
137+
startX = int(self.size().width() * 0.05)
138+
startY = (iconSize / 2) - (progressBarHeight / 2)
139+
140+
backgroundBrush = QBrush(self.getBackgroundColor())
141+
finishedBrush = QBrush(self.getFinishedBackgroundColor())
142+
emptyPen = QPen(Qt.NoPen)
143+
144+
painter.setPen(emptyPen)
145+
painter.setBrush(backgroundBrush)
146+
painter.drawRoundedRect(startX, startY, progressBarLength, progressBarHeight, progressBarHeight, progressBarHeight)
147+
148+
# Draw finished progress
149+
painter.setBrush(finishedBrush)
150+
painter.drawRoundedRect(startX, startY, self.finishedProgressLength, progressBarHeight, progressBarHeight,
151+
progressBarHeight)
152+
153+
painter.save()
154+
painter.translate(startX + progressBarLength * 0.05, iconStartY)
155+
156+
for i in range(numberOfSteps):
157+
if i < self._currentStep:
158+
painter.setBrush(finishedBrush)
159+
else:
160+
painter.setBrush(whiteBrush)
161+
162+
currentXOffset = i * iconStep
163+
painter.setPen(iconBorderPen)
164+
165+
# Store the bounding rectangle of each step icon
166+
iconRect = QRect(currentXOffset, 1, iconSize, iconSize)
167+
clickableRect = QRect(currentXOffset + (iconStep - iconSize) // 2 - (iconSize/2), 1, iconSize, iconSize)
168+
self.stepIconRects.append(clickableRect)
169+
170+
if self.barStyle == self.Styles.Circular:
171+
painter.drawEllipse(iconRect)
172+
else:
173+
painter.drawRect(iconRect)
174+
175+
painter.setFont(fontNumber)
176+
if i < self._currentStep:
177+
painter.setPen(QPen(self.getFinishedNumberColor()))
178+
179+
painter.drawText(currentXOffset, 0, iconSize, iconSize, Qt.AlignCenter, str(i + 1))
180+
181+
displayText = self.stepMessages[i]
182+
textSize = self.getDrawTextSize(displayText, fontText)
183+
184+
textStartX = int((currentXOffset + iconSize * 0.5) - textSize.width() / 2)
185+
textStartY = int(iconSize + 3)
186+
painter.setFont(fontText)
187+
if i < self._currentStep:
188+
painter.setPen(QPen(self.getFinishedBackgroundColor()))
189+
190+
painter.drawText(textStartX, textStartY, textSize.width(), textSize.height(), Qt.AlignCenter, displayText)
191+
192+
painter.restore()
193+
194+
def drawFlatStyle(self, painter: QPainter):
195+
"""
196+
Draw progress bar with Flat style and triangle pointer tip.
197+
198+
:param painter: QPainter object.
199+
"""
200+
numberOfSteps = len(self.stepMessages)
201+
progressBarLength = int(self.size().width() * 0.9)
202+
progressBarHeight = int(self.size().height() * 0.1)
203+
totalStepIconLength = int(progressBarLength * 0.88)
204+
iconSize = int((totalStepIconLength / (numberOfSteps - 1)) * 0.15)
205+
iconStep = totalStepIconLength // (numberOfSteps - 1)
206+
progressBarGap = progressBarHeight * 1.5
207+
208+
iconBorderPen = QPen(QBrush(self.getBackgroundColor()), iconSize * 0.1)
209+
whiteBrush = QBrush(Qt.white)
210+
fontNumber = QFont()
211+
fontNumber.setPointSize(self.numberFontSize)
212+
fontText = QFont()
213+
fontText.setPointSize(self.textFontSize)
214+
textSize = self.getDrawTextSize("Sample", fontText)
215+
216+
# Ensure icon size does not exceed 2/3 of the total height
217+
maxIconSize = int(self.size().height() * 2 / 3)
218+
iconSize = min(iconSize, maxIconSize)
219+
220+
# Ensure icon size plus text height does not exceed total height
221+
totalHeight = self.size().height()
222+
maxTotalHeight = totalHeight - (textSize.height() + progressBarHeight + (progressBarGap * 2))
223+
if (iconSize + textSize.height() + progressBarHeight + (progressBarGap * 2)) > totalHeight:
224+
iconSize = maxTotalHeight
225+
226+
textStartY = int(iconSize + progressBarHeight + progressBarGap)
227+
228+
# Background progress bar
229+
startX = int(self.size().width() * 0.05)
230+
startY = int(self.size().height() * 0.15)
231+
progressBarStartY = startY + iconSize + progressBarGap
232+
233+
backgroundBrush = QBrush(self.getBackgroundColor())
234+
finishedBrush = QBrush(self.getFinishedBackgroundColor())
235+
emptyPen = QPen(Qt.NoPen)
236+
painter.setPen(emptyPen)
237+
painter.setBrush(backgroundBrush)
238+
painter.drawRoundedRect(startX, progressBarStartY, progressBarLength, progressBarHeight, progressBarHeight,
239+
progressBarHeight)
240+
241+
# Draw completed progress
242+
painter.setBrush(finishedBrush)
243+
painter.drawRoundedRect(startX, progressBarStartY, self.finishedProgressLength, progressBarHeight,
244+
progressBarHeight, progressBarHeight)
245+
246+
painter.save()
247+
painter.translate(startX + progressBarLength * 0.05, startY)
248+
249+
for i in range(numberOfSteps):
250+
if i < self._currentStep:
251+
painter.setBrush(finishedBrush)
252+
else:
253+
painter.setBrush(whiteBrush)
254+
255+
currentXOffset = i * iconStep
256+
painter.setPen(iconBorderPen)
257+
258+
iconRect = QRect(currentXOffset, 1, iconSize, iconSize)
259+
clickableRect = QRect(currentXOffset + (iconStep - iconSize) // 2 - (iconSize/2), 1, iconSize, iconSize)
260+
self.stepIconRects.append(clickableRect)
261+
262+
painter.drawEllipse(iconRect)
263+
264+
# Draw triangle pointer tip
265+
pointerTipWidth = progressBarGap
266+
pointerTipHeight = progressBarGap
267+
painter.setPen(Qt.NoPen)
268+
if i < self._currentStep:
269+
painter.setBrush(finishedBrush)
270+
else:
271+
painter.setBrush(backgroundBrush)
272+
273+
if self.pointerDirection == self.Direction.Up:
274+
pointer = QPolygonF([
275+
QPointF(currentXOffset + iconSize / 2 - pointerTipWidth / 2, progressBarStartY - progressBarHeight),
276+
QPointF(currentXOffset + iconSize / 2 + pointerTipWidth / 2, progressBarStartY - progressBarHeight),
277+
QPointF(currentXOffset + iconSize / 2, 1 - pointerTipHeight + progressBarStartY - progressBarHeight)
278+
])
279+
else:
280+
pointer = QPolygonF([QPointF(currentXOffset + iconSize / 2 - pointerTipWidth / 2, 1 + iconSize),
281+
QPointF(currentXOffset + iconSize / 2 + pointerTipWidth / 2, 1 + iconSize),
282+
QPointF(currentXOffset + iconSize / 2, 1 + iconSize + pointerTipHeight)])
283+
284+
painter.drawPolygon(pointer)
285+
286+
painter.setFont(fontNumber)
287+
if i < self._currentStep:
288+
painter.setPen(QPen(self.getFinishedNumberColor()))
289+
290+
painter.drawText(currentXOffset, 0, iconSize, iconSize, Qt.AlignCenter, str(i + 1))
291+
292+
displayText = self.stepMessages[i]
293+
textSize = self.getDrawTextSize(displayText, fontText)
294+
295+
textStartX = int((currentXOffset + iconSize * 0.5) - textSize.width() / 2)
296+
297+
painter.setFont(fontText)
298+
if i < self._currentStep:
299+
painter.setPen(QPen(self.getFinishedBackgroundColor()))
300+
else:
301+
painter.setPen(QPen(Qt.black))
302+
303+
painter.drawText(textStartX, textStartY, textSize.width(), textSize.height(), Qt.AlignCenter, displayText)
304+
305+
painter.restore()
306+
307+
def getBackgroundColor(self) -> QColor:
308+
"""
309+
Get the background color of the progress bar.
310+
311+
:return: QColor object representing the background color.
312+
"""
313+
return self.unfinishedBackgroundColor
314+
315+
def getFinishedBackgroundColor(self) -> QColor:
316+
"""
317+
Get the color for finished progress bar segments.
318+
319+
:return: QColor object representing the finished segment color.
320+
"""
321+
return self.finishedBackgroundColor
322+
323+
def getFinishedNumberColor(self) -> QColor:
324+
"""
325+
Get the color for finished numbers in the progress bar.
326+
327+
:return: QColor object representing the finished number color.
328+
"""
329+
return self.finishedNumberColor
330+
331+
def getCurrentStep(self):
332+
"""
333+
CReturn current step
334+
335+
"""
336+
return self._currentStep
337+
338+
def changeCurrentStep(self, step: int):
339+
"""
340+
Change the current step of the progress bar.
341+
342+
:param step: New step to set.
343+
"""
344+
if step > len(self.stepMessages) or step < 0:
345+
return
346+
347+
self._currentStep = step
348+
self.animation.setStartValue(self.finishedProgressLength)
349+
350+
if step <= 0:
351+
finishedProgressLength = 0
352+
else:
353+
progressBarLength = int(self.size().width() * 0.9)
354+
iconStep = int((progressBarLength * 0.88) / (len(self.stepMessages) - 1))
355+
finishedProgressLength = int(progressBarLength * 0.05 + (step - 1) * iconStep + iconStep / 2)
356+
finishedProgressLength = min(finishedProgressLength, progressBarLength)
357+
358+
self.animation.setEndValue(finishedProgressLength)
359+
self.animation.start()
360+
361+
362+
def getDrawTextSize(self, text: str, font: QFont) -> QRect:
363+
"""
364+
Get the size of the text to be drawn.
365+
366+
:param text: Text string.
367+
:param font: QFont object.
368+
:return: QRect object representing the size of the text.
369+
"""
370+
metrics = QFontMetrics(font)
371+
return metrics.boundingRect(text).adjusted(-2, -2, 2, 2)
372+
373+
def getFinishedProgressLength(self) -> int:
374+
return self._finishedProgressLength
375+
376+
def setFinishedProgressLength(self, length: int):
377+
self._finishedProgressLength = length
378+
self.update()
379+
380+
finishedProgressLength = Property(int, getFinishedProgressLength, setFinishedProgressLength)

0 commit comments

Comments
 (0)