Coverage for pyPhotoAlbum/loading_widget.py: 100%

78 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-20 12:55 +0000

1""" 

2Loading progress widget for pyPhotoAlbum 

3 

4Displays loading progress in the lower-right corner of the window. 

5""" 

6 

7from PyQt6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QProgressBar 

8from PyQt6.QtCore import Qt, QPropertyAnimation, QEasingCurve, pyqtProperty # type: ignore[attr-defined] 

9from PyQt6.QtGui import QPalette, QColor 

10 

11 

12class LoadingWidget(QWidget): 

13 """ 

14 A widget that displays loading progress in the lower-right corner. 

15 

16 Features: 

17 - Fade in/out animations 

18 - Progress bar with percentage 

19 - Status message display 

20 - Compact, non-intrusive design 

21 """ 

22 

23 def __init__(self, parent=None): 

24 super().__init__(parent) 

25 

26 # Widget configuration 

27 self.setWindowFlags(Qt.WindowType.ToolTip | Qt.WindowType.FramelessWindowHint) 

28 self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, False) 

29 self.setFixedSize(280, 80) 

30 

31 # Styling 

32 self.setStyleSheet(""" 

33 QWidget { 

34 background-color: rgba(50, 50, 50, 230); 

35 border-radius: 8px; 

36 border: 1px solid rgba(100, 100, 100, 180); 

37 } 

38 QLabel { 

39 color: white; 

40 background-color: transparent; 

41 font-size: 11pt; 

42 } 

43 QProgressBar { 

44 border: 1px solid rgba(80, 80, 80, 180); 

45 border-radius: 4px; 

46 background-color: rgba(30, 30, 30, 200); 

47 text-align: center; 

48 color: white; 

49 font-size: 10pt; 

50 } 

51 QProgressBar::chunk { 

52 background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, 

53 stop:0 rgba(70, 130, 180, 220), 

54 stop:1 rgba(100, 160, 210, 220)); 

55 border-radius: 3px; 

56 } 

57 """) 

58 

59 # Layout 

60 layout = QVBoxLayout() 

61 layout.setContentsMargins(12, 10, 12, 10) 

62 layout.setSpacing(8) 

63 

64 # Status label 

65 self._status_label = QLabel("Loading...") 

66 self._status_label.setAlignment(Qt.AlignmentFlag.AlignLeft) 

67 layout.addWidget(self._status_label) 

68 

69 # Progress bar with percentage label 

70 progress_layout = QHBoxLayout() 

71 progress_layout.setSpacing(8) 

72 

73 self._progress_bar = QProgressBar() 

74 self._progress_bar.setMinimum(0) 

75 self._progress_bar.setMaximum(100) 

76 self._progress_bar.setValue(0) 

77 self._progress_bar.setTextVisible(True) 

78 self._progress_bar.setFormat("%p%") 

79 progress_layout.addWidget(self._progress_bar, 1) 

80 

81 layout.addLayout(progress_layout) 

82 

83 self.setLayout(layout) 

84 

85 # Animation for fade in/out 

86 self._opacity = 1.0 

87 self._fade_animation = QPropertyAnimation(self, b"opacity") 

88 self._fade_animation.setDuration(300) 

89 self._fade_animation.setEasingCurve(QEasingCurve.Type.InOutQuad) 

90 

91 # Initially hidden 

92 self.hide() 

93 

94 @pyqtProperty(float) 

95 def opacity(self): 

96 """Get opacity for animation""" 

97 return self._opacity 

98 

99 @opacity.setter # type: ignore[no-redef] 

100 def opacity(self, value: float) -> None: 

101 """Set opacity for animation""" 

102 self._opacity = value 

103 self.setWindowOpacity(value) 

104 

105 def show_loading(self, message: str = "Loading..."): 

106 """ 

107 Show the loading widget with a fade-in animation. 

108 

109 Args: 

110 message: Initial status message 

111 """ 

112 self.set_status(message) 

113 self.set_progress(0) 

114 

115 # Position in lower-right corner of parent 

116 self._reposition() 

117 

118 # Fade in 

119 self.show() 

120 self._fade_animation.stop() 

121 self._fade_animation.setStartValue(0.0) 

122 self._fade_animation.setEndValue(1.0) 

123 self._fade_animation.start() 

124 

125 def hide_loading(self): 

126 """Hide the loading widget with a fade-out animation.""" 

127 self._fade_animation.stop() 

128 self._fade_animation.setStartValue(1.0) 

129 self._fade_animation.setEndValue(0.0) 

130 self._fade_animation.finished.connect(self.hide) 

131 self._fade_animation.start() 

132 

133 def set_status(self, message: str): 

134 """ 

135 Update the status message. 

136 

137 Args: 

138 message: Status message to display 

139 """ 

140 self._status_label.setText(message) 

141 

142 def set_progress(self, value: int, maximum: int = 100): 

143 """ 

144 Update the progress bar. 

145 

146 Args: 

147 value: Current progress value 

148 maximum: Maximum progress value (default: 100) 

149 """ 

150 self._progress_bar.setMaximum(maximum) 

151 self._progress_bar.setValue(value) 

152 

153 def set_indeterminate(self, indeterminate: bool = True): 

154 """ 

155 Set the progress bar to indeterminate mode (busy indicator). 

156 

157 Args: 

158 indeterminate: True for indeterminate, False for normal progress 

159 """ 

160 if indeterminate: 

161 self._progress_bar.setMinimum(0) 

162 self._progress_bar.setMaximum(0) 

163 else: 

164 self._progress_bar.setMinimum(0) 

165 self._progress_bar.setMaximum(100) 

166 

167 def _reposition(self): 

168 """Position the widget in the lower-right corner of the parent.""" 

169 if self.parent(): 

170 parent_rect = self.parent().rect() 

171 margin = 20 

172 x = parent_rect.width() - self.width() - margin 

173 y = parent_rect.height() - self.height() - margin 

174 self.move(x, y) 

175 

176 def showEvent(self, event): 

177 """Handle show event to reposition.""" 

178 super().showEvent(event) 

179 self._reposition() 

180 

181 def resizeParent(self): 

182 """Call this when parent is resized to reposition the widget.""" 

183 if self.isVisible(): 

184 self._reposition()