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
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-20 12:55 +0000
1"""
2Loading progress widget for pyPhotoAlbum
4Displays loading progress in the lower-right corner of the window.
5"""
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
12class LoadingWidget(QWidget):
13 """
14 A widget that displays loading progress in the lower-right corner.
16 Features:
17 - Fade in/out animations
18 - Progress bar with percentage
19 - Status message display
20 - Compact, non-intrusive design
21 """
23 def __init__(self, parent=None):
24 super().__init__(parent)
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)
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 """)
59 # Layout
60 layout = QVBoxLayout()
61 layout.setContentsMargins(12, 10, 12, 10)
62 layout.setSpacing(8)
64 # Status label
65 self._status_label = QLabel("Loading...")
66 self._status_label.setAlignment(Qt.AlignmentFlag.AlignLeft)
67 layout.addWidget(self._status_label)
69 # Progress bar with percentage label
70 progress_layout = QHBoxLayout()
71 progress_layout.setSpacing(8)
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)
81 layout.addLayout(progress_layout)
83 self.setLayout(layout)
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)
91 # Initially hidden
92 self.hide()
94 @pyqtProperty(float)
95 def opacity(self):
96 """Get opacity for animation"""
97 return self._opacity
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)
105 def show_loading(self, message: str = "Loading..."):
106 """
107 Show the loading widget with a fade-in animation.
109 Args:
110 message: Initial status message
111 """
112 self.set_status(message)
113 self.set_progress(0)
115 # Position in lower-right corner of parent
116 self._reposition()
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()
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()
133 def set_status(self, message: str):
134 """
135 Update the status message.
137 Args:
138 message: Status message to display
139 """
140 self._status_label.setText(message)
142 def set_progress(self, value: int, maximum: int = 100):
143 """
144 Update the progress bar.
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)
153 def set_indeterminate(self, indeterminate: bool = True):
154 """
155 Set the progress bar to indeterminate mode (busy indicator).
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)
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)
176 def showEvent(self, event):
177 """Handle show event to reposition."""
178 super().showEvent(event)
179 self._reposition()
181 def resizeParent(self):
182 """Call this when parent is resized to reposition the widget."""
183 if self.isVisible():
184 self._reposition()