Coverage for main/components/circular_progress_bar.py: 100%

43 statements  

« prev     ^ index     » next       coverage.py v7.15.0, created at 2026-07-03 15:51 +0000

1from PyQt6.QtCore import Qt, QRectF 

2from PyQt6.QtGui import QColor, QPainter, QPen, QFont 

3from PyQt6.QtWidgets import QWidget 

4 

5 

6class CircularProgress(QWidget): 

7 """ 

8 A customizable **circular progress bar widget** for PyQt6 applications. 

9 

10 This widget visually represents a percentage-based progress value (0–100) 

11 as a circular arc with a numeric label at its center. It is lightweight, 

12 anti-aliased, and supports dynamic color changes. 

13 

14 --- 

15 **Features** 

16 - Smooth circular progress rendering with anti-aliasing 

17 - Adjustable color via `setColor()` 

18 - Automatically scales with parent widget size 

19 - Displays numeric percentage text in the center 

20 --- 

21 

22 **Parameters** 

23 - `color (str | QColor)`: Initial color for the progress arc. 

24 Accepts either a hex string (e.g., "#3498DB") or a `QColor` instance. 

25 

26 **Example** 

27 ```python 

28 progress = CircularProgress("#00B894") 

29 progress.setValue(75) 

30 ``` 

31 """ 

32 

33 def __init__(self, color: str = "#3498DB"): 

34 """ 

35 Initialize the circular progress widget. 

36 

37 Args: 

38 color (str): Hex color string for the progress arc (default: "#3498DB"). 

39 """ 

40 super().__init__() 

41 self.value = 0 # Current progress value (0–100) 

42 self.color = QColor(color) # Active progress color 

43 

44 self.setMaximumSize(300, 300) 

45 

46 def setValue(self, val: int): 

47 """ 

48 Update the progress value and refresh the widget. 

49 

50 Args: 

51 val (int): New progress value (0–100). Values outside this range are clamped. 

52 

53 Notes: 

54 This method triggers a repaint using `update()`. 

55 """ 

56 self.value = max(0, min(100, int(val))) 

57 self.update() 

58 

59 def setColor(self, color: str | QColor): 

60 """ 

61 Change the color of the progress arc. 

62 

63 Args: 

64 color (str | QColor): The new color for the progress indicator. 

65 Can be a hex string or a `QColor` instance. 

66 

67 Example: 

68 ```python 

69 progress.setColor("#E74C3C") # Set to red 

70 ``` 

71 """ 

72 if isinstance(color, str): 

73 self.color = QColor(color) 

74 else: 

75 self.color = color 

76 self.update() 

77 

78 def sizeHint(self): 

79 """ 

80 Suggest an appropriate default size for the widget. 

81 

82 Returns: 

83 QSize: The preferred size of the widget, matching its parent if available. 

84 """ 

85 return self.parentWidget().size() if self.parentWidget() else super().sizeHint() 

86 

87 def paintEvent(self, event): 

88 """ 

89 Handle the paint event to draw the circular progress indicator. 

90 

91 The widget draws: 

92 1. A **light gray background circle** 

93 2. A **colored progress arc** proportional to the current value 

94 3. A **percentage label** centered within the circle 

95 

96 Args: 

97 event (QPaintEvent): The paint event object automatically provided by Qt. 

98 """ 

99 width = self.width() 

100 height = self.height() 

101 size = min(width, height) # Ensure square aspect ratio 

102 

103 # Center the circle in the available space 

104 x_offset = int((width - size) / 2) 

105 y_offset = int((height - size) / 2) 

106 

107 painter = QPainter(self) 

108 painter.setRenderHint(QPainter.RenderHint.Antialiasing) 

109 

110 # --- Draw background circle --- 

111 painter.setPen(Qt.PenStyle.NoPen) 

112 painter.setBrush(QColor("#f0f0f0")) # Light gray background 

113 painter.drawEllipse(x_offset, y_offset, int(size), int(size)) 

114 

115 # --- Draw progress arc --- 

116 pen_width = max(5, int(size / 12)) # Adaptive thickness 

117 pen = QPen(self.color) 

118 pen.setWidth(pen_width) 

119 pen.setCapStyle(Qt.PenCapStyle.RoundCap) 

120 painter.setPen(pen) 

121 painter.setBrush(Qt.BrushStyle.NoBrush) 

122 

123 # Define drawing area for the arc 

124 rect = QRectF( 

125 x_offset + pen_width / 2, 

126 y_offset + pen_width / 2, 

127 size - pen_width, 

128 size - pen_width 

129 ) 

130 angle_span = int(360 * self.value / 100) 

131 painter.drawArc(rect, -90 * 16, -angle_span * 16) # Start from top center 

132 

133 # --- Draw percentage text --- 

134 font_size = max(5, int(size / 9)) 

135 painter.setPen(QColor("#2C3E50")) # Dark blue-gray text 

136 painter.setFont(QFont("Arial", font_size, QFont.Weight.Bold)) 

137 painter.drawText(rect, Qt.AlignmentFlag.AlignCenter, f"{self.value}%")