1 """

2 Loading progress widget for pyPhotoAlbum

3

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

5 """

6

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

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

9 from PyQt6.QtGui import QPalette, QColor

10

11

12 class 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()