bec-widgets 2.16.2__py3-none-any.whl → 2.17.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- CHANGELOG.md +58 -0
- PKG-INFO +3 -3
- bec_widgets/applications/launch_window.py +1 -1
- bec_widgets/cli/client.py +24 -0
- bec_widgets/widgets/containers/main_window/main_window.py +122 -7
- bec_widgets/widgets/progress/bec_progressbar/bec_progressbar.py +135 -21
- bec_widgets/widgets/progress/scan_progressbar/__init__.py +0 -0
- bec_widgets/widgets/progress/scan_progressbar/register_scan_progress_bar.py +17 -0
- bec_widgets/widgets/progress/scan_progressbar/scan_progress_bar.pyproject +1 -0
- bec_widgets/widgets/progress/scan_progressbar/scan_progress_bar_plugin.py +54 -0
- bec_widgets/widgets/progress/scan_progressbar/scan_progressbar.py +320 -0
- bec_widgets/widgets/progress/scan_progressbar/scan_progressbar.ui +141 -0
- bec_widgets/widgets/progress/scan_progressbar/scan_progressbar_one_line.ui +124 -0
- {bec_widgets-2.16.2.dist-info → bec_widgets-2.17.0.dist-info}/METADATA +3 -3
- {bec_widgets-2.16.2.dist-info → bec_widgets-2.17.0.dist-info}/RECORD +19 -12
- pyproject.toml +6 -6
- {bec_widgets-2.16.2.dist-info → bec_widgets-2.17.0.dist-info}/WHEEL +0 -0
- {bec_widgets-2.16.2.dist-info → bec_widgets-2.17.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-2.16.2.dist-info → bec_widgets-2.17.0.dist-info}/licenses/LICENSE +0 -0
CHANGELOG.md
CHANGED
@@ -1,6 +1,64 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
3
|
|
4
|
+
## v2.17.0 (2025-06-22)
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
- **bec_progressbar**: Layout and sizing adjustments
|
9
|
+
([`b02c870`](https://github.com/bec-project/bec_widgets/commit/b02c870dbfecb4bc6921ec4c915dac0e67beb9b4))
|
10
|
+
|
11
|
+
- **launch_window**: Number of remaining connections increase to 2 to include the ScanProgressBar
|
12
|
+
([`3bbb8da`](https://github.com/bec-project/bec_widgets/commit/3bbb8daa24348613f62bde667a446d37dcec8fb0))
|
13
|
+
|
14
|
+
- **main_window**: Labels and sizing of scan progress adopted
|
15
|
+
([`aca6efb`](https://github.com/bec-project/bec_widgets/commit/aca6efb567528eb3c68521a59b4f9903a5616c6f))
|
16
|
+
|
17
|
+
- **scan_progressbar**: Cleanup adjusted
|
18
|
+
([`e8ae972`](https://github.com/bec-project/bec_widgets/commit/e8ae9725fa86b7db52a147ca5a2acc62fa2ccf43))
|
19
|
+
|
20
|
+
- **scan_progressbar**: Mapping of bec progress states to the progressbar enums
|
21
|
+
([`88b42e4`](https://github.com/bec-project/bec_widgets/commit/88b42e49e30a0aa0edc2de4d970408f4be5bde6b))
|
22
|
+
|
23
|
+
### Build System
|
24
|
+
|
25
|
+
- Update min dependency of bec to 3.42.4
|
26
|
+
([`a4274ff`](https://github.com/bec-project/bec_widgets/commit/a4274ff8cd9f3e73a61b2eaf902c172c028d21b0))
|
27
|
+
|
28
|
+
### Features
|
29
|
+
|
30
|
+
- **main_window**: Added scan progress bar to BECMainWindow status bar
|
31
|
+
([`497e394`](https://github.com/bec-project/bec_widgets/commit/497e394deb5cfe36c8fc4f769fef26f109fd1c1f))
|
32
|
+
|
33
|
+
- **main_window**: Timer to show hide scan progress when it is relevant only
|
34
|
+
([`9ff1706`](https://github.com/bec-project/bec_widgets/commit/9ff170660edd9e03f99eccee60b5e20fc1cf5a8d))
|
35
|
+
|
36
|
+
- **progressbar**: Added padding as designer property
|
37
|
+
([`a451625`](https://github.com/bec-project/bec_widgets/commit/a451625a5ab804ca8259f9c9f83c4f9ebbea4a5b))
|
38
|
+
|
39
|
+
- **progressbar**: State setting and dynamic corner radius
|
40
|
+
([`d3a9e09`](https://github.com/bec-project/bec_widgets/commit/d3a9e0903a263d735ecab3a2ad9319c9d5e86092))
|
41
|
+
|
42
|
+
- **scan_progressbar**: Added oneline design for compact applications
|
43
|
+
([`d5ca7b8`](https://github.com/bec-project/bec_widgets/commit/d5ca7b84337cf60aa66f961d357ae66994f53c7a))
|
44
|
+
|
45
|
+
- **scan_progressbar**: Added progressbar with hooks to scan progress and device progress
|
46
|
+
([`c4b8538`](https://github.com/bec-project/bec_widgets/commit/c4b85381a41e4742567680864668ee83d498b1d1))
|
47
|
+
|
48
|
+
### Refactoring
|
49
|
+
|
50
|
+
- **progressbar**: Change slot / property to safeslot / safeproperty
|
51
|
+
([`92d0ffe`](https://github.com/bec-project/bec_widgets/commit/92d0ffee65babc718fafd60131d0a4f291e5ca2b))
|
52
|
+
|
53
|
+
### Testing
|
54
|
+
|
55
|
+
- **scan progress**: Add test for queue update logic
|
56
|
+
([`b2a46e2`](https://github.com/bec-project/bec_widgets/commit/b2a46e284d45e97dd9853d1a3c8e95de7e530267))
|
57
|
+
|
58
|
+
- **scan_progress**: Tests extended
|
59
|
+
([`6c04eac`](https://github.com/bec-project/bec_widgets/commit/6c04eac18c887526b333f58fc1118c3b4029abd8))
|
60
|
+
|
61
|
+
|
4
62
|
## v2.16.2 (2025-06-20)
|
5
63
|
|
6
64
|
### Bug Fixes
|
PKG-INFO
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: bec_widgets
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.17.0
|
4
4
|
Summary: BEC Widgets
|
5
5
|
Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec_widgets/issues
|
6
6
|
Project-URL: Homepage, https://gitlab.psi.ch/bec/bec_widgets
|
@@ -9,8 +9,8 @@ Classifier: Development Status :: 3 - Alpha
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
10
10
|
Classifier: Topic :: Scientific/Engineering
|
11
11
|
Requires-Python: >=3.10
|
12
|
-
Requires-Dist: bec-ipython-client<=4.0,>=3.
|
13
|
-
Requires-Dist: bec-lib<=4.0,>=3.
|
12
|
+
Requires-Dist: bec-ipython-client<=4.0,>=3.42.4
|
13
|
+
Requires-Dist: bec-lib<=4.0,>=3.42.4
|
14
14
|
Requires-Dist: bec-qthemes>=0.7,~=0.7
|
15
15
|
Requires-Dist: black~=25.0
|
16
16
|
Requires-Dist: isort>=5.13.2,~=5.13
|
@@ -542,7 +542,7 @@ class LaunchWindow(BECMainWindow):
|
|
542
542
|
remaining_connections = [
|
543
543
|
connection for connection in connections.values() if connection.parent_id != self.gui_id
|
544
544
|
]
|
545
|
-
return len(remaining_connections) <=
|
545
|
+
return len(remaining_connections) <= 2
|
546
546
|
|
547
547
|
def _turn_off_the_lights(self, connections: dict):
|
548
548
|
"""
|
bec_widgets/cli/client.py
CHANGED
@@ -474,6 +474,20 @@ class BECProgressBar(RPCBase):
|
|
474
474
|
>>> progressbar.label_template = "$value / $percentage %"
|
475
475
|
"""
|
476
476
|
|
477
|
+
@property
|
478
|
+
@rpc_call
|
479
|
+
def state(self):
|
480
|
+
"""
|
481
|
+
None
|
482
|
+
"""
|
483
|
+
|
484
|
+
@state.setter
|
485
|
+
@rpc_call
|
486
|
+
def state(self):
|
487
|
+
"""
|
488
|
+
None
|
489
|
+
"""
|
490
|
+
|
477
491
|
@rpc_call
|
478
492
|
def _get_label(self) -> str:
|
479
493
|
"""
|
@@ -3245,6 +3259,16 @@ class ScanControl(RPCBase):
|
|
3245
3259
|
"""
|
3246
3260
|
|
3247
3261
|
|
3262
|
+
class ScanProgressBar(RPCBase):
|
3263
|
+
"""Widget to display a progress bar that is hooked up to the scan progress of a scan."""
|
3264
|
+
|
3265
|
+
@rpc_call
|
3266
|
+
def remove(self):
|
3267
|
+
"""
|
3268
|
+
Cleanup the BECConnector
|
3269
|
+
"""
|
3270
|
+
|
3271
|
+
|
3248
3272
|
class ScatterCurve(RPCBase):
|
3249
3273
|
"""Scatter curve item for the scatter waveform widget."""
|
3250
3274
|
|
@@ -1,9 +1,28 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import os
|
2
4
|
|
3
5
|
from bec_lib.endpoints import MessageEndpoints
|
4
|
-
from qtpy.QtCore import
|
6
|
+
from qtpy.QtCore import (
|
7
|
+
QAbstractAnimation,
|
8
|
+
QEasingCurve,
|
9
|
+
QEvent,
|
10
|
+
QPropertyAnimation,
|
11
|
+
QSize,
|
12
|
+
Qt,
|
13
|
+
QTimer,
|
14
|
+
)
|
5
15
|
from qtpy.QtGui import QAction, QActionGroup, QIcon
|
6
|
-
from qtpy.QtWidgets import
|
16
|
+
from qtpy.QtWidgets import (
|
17
|
+
QApplication,
|
18
|
+
QFrame,
|
19
|
+
QHBoxLayout,
|
20
|
+
QLabel,
|
21
|
+
QMainWindow,
|
22
|
+
QStyle,
|
23
|
+
QVBoxLayout,
|
24
|
+
QWidget,
|
25
|
+
)
|
7
26
|
|
8
27
|
import bec_widgets
|
9
28
|
from bec_widgets.utils import UILoader
|
@@ -13,6 +32,7 @@ from bec_widgets.utils.error_popups import SafeSlot
|
|
13
32
|
from bec_widgets.utils.widget_io import WidgetHierarchy
|
14
33
|
from bec_widgets.widgets.containers.main_window.addons.scroll_label import ScrollLabel
|
15
34
|
from bec_widgets.widgets.containers.main_window.addons.web_links import BECWebLinksMixin
|
35
|
+
from bec_widgets.widgets.progress.scan_progressbar.scan_progressbar import ScanProgressBar
|
16
36
|
|
17
37
|
MODULE_PATH = os.path.dirname(bec_widgets.__file__)
|
18
38
|
|
@@ -20,6 +40,8 @@ MODULE_PATH = os.path.dirname(bec_widgets.__file__)
|
|
20
40
|
class BECMainWindow(BECWidget, QMainWindow):
|
21
41
|
RPC = False
|
22
42
|
PLUGIN = False
|
43
|
+
SCAN_PROGRESS_WIDTH = 100 # px
|
44
|
+
STATUS_BAR_WIDGETS_EXPIRE_TIME = 60_000 # milliseconds
|
23
45
|
|
24
46
|
def __init__(
|
25
47
|
self,
|
@@ -33,6 +55,7 @@ class BECMainWindow(BECWidget, QMainWindow):
|
|
33
55
|
super().__init__(parent=parent, gui_id=gui_id, **kwargs)
|
34
56
|
|
35
57
|
self.app = QApplication.instance()
|
58
|
+
self.status_bar = self.statusBar()
|
36
59
|
self.setWindowTitle(window_title)
|
37
60
|
self._init_ui()
|
38
61
|
self._connect_to_theme_change()
|
@@ -61,14 +84,13 @@ class BECMainWindow(BECWidget, QMainWindow):
|
|
61
84
|
"""
|
62
85
|
Prepare the BEC specific widgets in the status bar.
|
63
86
|
"""
|
64
|
-
status_bar = self.statusBar()
|
65
87
|
|
66
88
|
# Left: App‑ID label
|
67
89
|
self._app_id_label = QLabel()
|
68
90
|
self._app_id_label.setAlignment(
|
69
91
|
Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter
|
70
92
|
)
|
71
|
-
status_bar.addWidget(self._app_id_label)
|
93
|
+
self.status_bar.addWidget(self._app_id_label)
|
72
94
|
|
73
95
|
# Add a separator after the app ID label
|
74
96
|
self._add_separator()
|
@@ -78,16 +100,100 @@ class BECMainWindow(BECWidget, QMainWindow):
|
|
78
100
|
self._client_info_label.setAlignment(
|
79
101
|
Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter
|
80
102
|
)
|
81
|
-
status_bar.addWidget(self._client_info_label, 1)
|
103
|
+
self.status_bar.addWidget(self._client_info_label, 1)
|
82
104
|
|
83
105
|
# Timer to automatically clear client messages once they expire
|
84
106
|
self._client_info_expire_timer = QTimer(self)
|
85
107
|
self._client_info_expire_timer.setSingleShot(True)
|
86
108
|
self._client_info_expire_timer.timeout.connect(lambda: self._client_info_label.setText(""))
|
87
109
|
|
88
|
-
|
110
|
+
# Add scan_progress bar with display logic
|
111
|
+
self._add_scan_progress_bar()
|
112
|
+
|
113
|
+
################################################################################
|
114
|
+
# Progress‑bar helpers
|
115
|
+
def _add_scan_progress_bar(self):
|
116
|
+
|
117
|
+
# --- Progress bar -------------------------------------------------
|
118
|
+
# Scan progress bar minimalistic design setup
|
119
|
+
self._scan_progress_bar = ScanProgressBar(self, one_line_design=True)
|
120
|
+
self._scan_progress_bar.show_elapsed_time = False
|
121
|
+
self._scan_progress_bar.show_remaining_time = False
|
122
|
+
self._scan_progress_bar.show_source_label = False
|
123
|
+
self._scan_progress_bar.progressbar.label_template = ""
|
124
|
+
self._scan_progress_bar.progressbar.setFixedHeight(8)
|
125
|
+
self._scan_progress_bar.progressbar.setFixedWidth(80)
|
126
|
+
|
127
|
+
# Bundle the progress bar with a separator
|
128
|
+
separator = self._add_separator(separate_object=True)
|
129
|
+
self._scan_progress_bar_with_separator = QWidget()
|
130
|
+
self._scan_progress_bar_with_separator.layout = QHBoxLayout(
|
131
|
+
self._scan_progress_bar_with_separator
|
132
|
+
)
|
133
|
+
self._scan_progress_bar_with_separator.layout.setContentsMargins(0, 0, 0, 0)
|
134
|
+
self._scan_progress_bar_with_separator.layout.setSpacing(0)
|
135
|
+
self._scan_progress_bar_with_separator.layout.addWidget(separator)
|
136
|
+
self._scan_progress_bar_with_separator.layout.addWidget(self._scan_progress_bar)
|
137
|
+
|
138
|
+
# Set Size
|
139
|
+
self._scan_progress_bar_target_width = self.SCAN_PROGRESS_WIDTH
|
140
|
+
self._scan_progress_bar_with_separator.setMaximumWidth(self._scan_progress_bar_target_width)
|
141
|
+
|
142
|
+
self.status_bar.addWidget(self._scan_progress_bar_with_separator)
|
143
|
+
|
144
|
+
# Visibility logic
|
145
|
+
self._scan_progress_bar_with_separator.hide()
|
146
|
+
self._scan_progress_bar_with_separator.setMaximumWidth(0)
|
147
|
+
|
148
|
+
# Timer for hiding logic
|
149
|
+
self._scan_progress_hide_timer = QTimer(self)
|
150
|
+
self._scan_progress_hide_timer.setSingleShot(True)
|
151
|
+
self._scan_progress_hide_timer.setInterval(self.STATUS_BAR_WIDGETS_EXPIRE_TIME)
|
152
|
+
self._scan_progress_hide_timer.timeout.connect(self._animate_hide_scan_progress_bar)
|
153
|
+
|
154
|
+
# Show / hide behaviour
|
155
|
+
self._scan_progress_bar.progress_started.connect(self._show_scan_progress_bar)
|
156
|
+
self._scan_progress_bar.progress_finished.connect(self._delay_hide_scan_progress_bar)
|
157
|
+
|
158
|
+
def _show_scan_progress_bar(self):
|
159
|
+
if self._scan_progress_hide_timer.isActive():
|
160
|
+
self._scan_progress_hide_timer.stop()
|
161
|
+
if self._scan_progress_bar_with_separator.isVisible():
|
162
|
+
return
|
163
|
+
|
164
|
+
# Make visible and reset width
|
165
|
+
self._scan_progress_bar_with_separator.show()
|
166
|
+
self._scan_progress_bar_with_separator.setMaximumWidth(0)
|
167
|
+
|
168
|
+
self._show_container_anim = QPropertyAnimation(
|
169
|
+
self._scan_progress_bar_with_separator, b"maximumWidth", self
|
170
|
+
)
|
171
|
+
self._show_container_anim.setDuration(300)
|
172
|
+
self._show_container_anim.setStartValue(0)
|
173
|
+
self._show_container_anim.setEndValue(self._scan_progress_bar_target_width)
|
174
|
+
self._show_container_anim.setEasingCurve(QEasingCurve.OutCubic)
|
175
|
+
self._show_container_anim.start()
|
176
|
+
|
177
|
+
def _delay_hide_scan_progress_bar(self):
|
178
|
+
"""Start the countdown to hide the scan progress bar."""
|
179
|
+
if hasattr(self, "_scan_progress_hide_timer"):
|
180
|
+
self._scan_progress_hide_timer.start()
|
181
|
+
|
182
|
+
def _animate_hide_scan_progress_bar(self):
|
183
|
+
"""Shrink container to the right, then hide."""
|
184
|
+
self._hide_container_anim = QPropertyAnimation(
|
185
|
+
self._scan_progress_bar_with_separator, b"maximumWidth", self
|
186
|
+
)
|
187
|
+
self._hide_container_anim.setDuration(300)
|
188
|
+
self._hide_container_anim.setStartValue(self._scan_progress_bar_with_separator.width())
|
189
|
+
self._hide_container_anim.setEndValue(0)
|
190
|
+
self._hide_container_anim.setEasingCurve(QEasingCurve.InCubic)
|
191
|
+
self._hide_container_anim.finished.connect(self._scan_progress_bar_with_separator.hide)
|
192
|
+
self._hide_container_anim.start()
|
193
|
+
|
194
|
+
def _add_separator(self, separate_object: bool = False) -> QWidget | None:
|
89
195
|
"""
|
90
|
-
Add a vertically centred separator to the status bar.
|
196
|
+
Add a vertically centred separator to the status bar or just return it as a separate object.
|
91
197
|
"""
|
92
198
|
status_bar = self.statusBar()
|
93
199
|
|
@@ -106,6 +212,8 @@ class BECMainWindow(BECWidget, QMainWindow):
|
|
106
212
|
vbox.addStretch()
|
107
213
|
wrapper.setFixedWidth(line.sizeHint().width())
|
108
214
|
|
215
|
+
if separate_object:
|
216
|
+
return wrapper
|
109
217
|
status_bar.addWidget(wrapper)
|
110
218
|
|
111
219
|
def _init_bec_icon(self):
|
@@ -279,10 +387,16 @@ class BECMainWindow(BECWidget, QMainWindow):
|
|
279
387
|
child.close()
|
280
388
|
child.deleteLater()
|
281
389
|
|
390
|
+
# Timer cleanup
|
282
391
|
if hasattr(self, "_client_info_expire_timer") and self._client_info_expire_timer.isActive():
|
283
392
|
self._client_info_expire_timer.stop()
|
393
|
+
if hasattr(self, "_scan_progress_hide_timer") and self._scan_progress_hide_timer.isActive():
|
394
|
+
self._scan_progress_hide_timer.stop()
|
395
|
+
|
284
396
|
# Status bar widgets cleanup
|
285
397
|
self._client_info_label.cleanup()
|
398
|
+
self._scan_progress_bar.close()
|
399
|
+
self._scan_progress_bar.deleteLater()
|
286
400
|
super().cleanup()
|
287
401
|
|
288
402
|
|
@@ -296,4 +410,5 @@ if __name__ == "__main__":
|
|
296
410
|
app = QApplication(sys.argv)
|
297
411
|
main_window = UILaunchWindow()
|
298
412
|
main_window.show()
|
413
|
+
main_window.resize(800, 600)
|
299
414
|
sys.exit(app.exec())
|
@@ -1,12 +1,46 @@
|
|
1
1
|
import sys
|
2
|
+
from enum import Enum
|
2
3
|
from string import Template
|
3
4
|
|
4
|
-
from qtpy.QtCore import
|
5
|
+
from qtpy.QtCore import QEasingCurve, QPropertyAnimation, QRectF, Qt, QTimer
|
5
6
|
from qtpy.QtGui import QColor, QPainter, QPainterPath
|
7
|
+
|
8
|
+
|
9
|
+
class ProgressState(Enum):
|
10
|
+
NORMAL = "normal"
|
11
|
+
PAUSED = "paused"
|
12
|
+
INTERRUPTED = "interrupted"
|
13
|
+
COMPLETED = "completed"
|
14
|
+
|
15
|
+
@classmethod
|
16
|
+
def from_bec_status(cls, status: str) -> "ProgressState":
|
17
|
+
"""
|
18
|
+
Map a BEC status string (open, paused, aborted, halted, closed)
|
19
|
+
to the corresponding ProgressState.
|
20
|
+
Any unknown status falls back to NORMAL.
|
21
|
+
"""
|
22
|
+
mapping = {
|
23
|
+
"open": cls.NORMAL,
|
24
|
+
"paused": cls.PAUSED,
|
25
|
+
"aborted": cls.INTERRUPTED,
|
26
|
+
"halted": cls.PAUSED,
|
27
|
+
"closed": cls.COMPLETED,
|
28
|
+
}
|
29
|
+
return mapping.get(status.lower(), cls.NORMAL)
|
30
|
+
|
31
|
+
|
32
|
+
PROGRESS_STATE_COLORS = {
|
33
|
+
ProgressState.NORMAL: QColor("#2979ff"), # blue – normal progress
|
34
|
+
ProgressState.PAUSED: QColor("#ffca28"), # orange/amber – paused
|
35
|
+
ProgressState.INTERRUPTED: QColor("#ff5252"), # red – interrupted
|
36
|
+
ProgressState.COMPLETED: QColor("#00e676"), # green – finished
|
37
|
+
}
|
38
|
+
|
6
39
|
from qtpy.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget
|
7
40
|
|
8
41
|
from bec_widgets.utils.bec_widget import BECWidget
|
9
42
|
from bec_widgets.utils.colors import get_accent_colors
|
43
|
+
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
|
10
44
|
|
11
45
|
|
12
46
|
class BECProgressBar(BECWidget, QWidget):
|
@@ -21,6 +55,8 @@ class BECProgressBar(BECWidget, QWidget):
|
|
21
55
|
"set_minimum",
|
22
56
|
"label_template",
|
23
57
|
"label_template.setter",
|
58
|
+
"state",
|
59
|
+
"state.setter",
|
24
60
|
"_get_label",
|
25
61
|
]
|
26
62
|
ICON_NAME = "page_control"
|
@@ -48,27 +84,38 @@ class BECProgressBar(BECWidget, QWidget):
|
|
48
84
|
|
49
85
|
self._completed_color = accent_colors.success
|
50
86
|
self._border_color = QColor(50, 50, 50)
|
87
|
+
# Corner‑rounding: base radius in pixels (auto‑reduced if bar is small)
|
88
|
+
self._corner_radius = 10
|
89
|
+
|
90
|
+
# Progress‑bar state handling
|
91
|
+
self._state = ProgressState.NORMAL
|
92
|
+
self._state_colors = dict(PROGRESS_STATE_COLORS)
|
51
93
|
|
52
94
|
# layout settings
|
95
|
+
self._padding_left_right = 10
|
53
96
|
self._value_animation = QPropertyAnimation(self, b"_progressbar_value")
|
54
97
|
self._value_animation.setDuration(200)
|
55
98
|
self._value_animation.setEasingCurve(QEasingCurve.Type.OutCubic)
|
56
99
|
|
57
100
|
# label on top of the progress bar
|
58
101
|
self.center_label = QLabel(self)
|
59
|
-
self.center_label.setAlignment(Qt.
|
102
|
+
self.center_label.setAlignment(Qt.AlignHCenter)
|
60
103
|
self.center_label.setStyleSheet("color: white;")
|
61
104
|
self.center_label.setMinimumSize(0, 0)
|
62
105
|
|
63
106
|
layout = QVBoxLayout(self)
|
64
|
-
layout.setContentsMargins(
|
107
|
+
layout.setContentsMargins(10, 0, 10, 0)
|
65
108
|
layout.setSpacing(0)
|
66
109
|
layout.addWidget(self.center_label)
|
110
|
+
layout.setAlignment(self.center_label, Qt.AlignCenter)
|
67
111
|
self.setLayout(layout)
|
68
112
|
|
69
113
|
self.update()
|
114
|
+
self._adjust_label_width()
|
70
115
|
|
71
|
-
@
|
116
|
+
@SafeProperty(
|
117
|
+
str, doc="The template for the center label. Use $value, $maximum, and $percentage."
|
118
|
+
)
|
72
119
|
def label_template(self):
|
73
120
|
"""
|
74
121
|
The template for the center label. Use $value, $maximum, and $percentage to insert the values.
|
@@ -83,10 +130,11 @@ class BECProgressBar(BECWidget, QWidget):
|
|
83
130
|
@label_template.setter
|
84
131
|
def label_template(self, template):
|
85
132
|
self._label_template = template
|
133
|
+
self._adjust_label_width()
|
86
134
|
self.set_value(self._user_value)
|
87
135
|
self.update()
|
88
136
|
|
89
|
-
@
|
137
|
+
@SafeProperty(float, designable=False)
|
90
138
|
def _progressbar_value(self):
|
91
139
|
"""
|
92
140
|
The current value of the progress bar.
|
@@ -106,8 +154,20 @@ class BECProgressBar(BECWidget, QWidget):
|
|
106
154
|
percentage=int((self.map_value(self._user_value) / self._maximum) * 100),
|
107
155
|
)
|
108
156
|
|
109
|
-
|
110
|
-
|
157
|
+
def _adjust_label_width(self):
|
158
|
+
"""
|
159
|
+
Reserve enough horizontal space for the center label so the widget
|
160
|
+
doesn't resize as the text grows during progress.
|
161
|
+
"""
|
162
|
+
template = Template(self._label_template)
|
163
|
+
sample_text = template.safe_substitute(
|
164
|
+
value=self._user_maximum, maximum=self._user_maximum, percentage=100
|
165
|
+
)
|
166
|
+
width = self.center_label.fontMetrics().horizontalAdvance(sample_text)
|
167
|
+
self.center_label.setFixedWidth(width)
|
168
|
+
|
169
|
+
@SafeSlot(float)
|
170
|
+
@SafeSlot(int)
|
111
171
|
def set_value(self, value):
|
112
172
|
"""
|
113
173
|
Set the value of the progress bar.
|
@@ -122,35 +182,88 @@ class BECProgressBar(BECWidget, QWidget):
|
|
122
182
|
self._target_value = self.map_value(value)
|
123
183
|
self._user_value = value
|
124
184
|
self.center_label.setText(self._update_template())
|
185
|
+
# Update state automatically unless paused or interrupted
|
186
|
+
if self._state not in (ProgressState.PAUSED, ProgressState.INTERRUPTED):
|
187
|
+
self._state = (
|
188
|
+
ProgressState.COMPLETED
|
189
|
+
if self._user_value >= self._user_maximum
|
190
|
+
else ProgressState.NORMAL
|
191
|
+
)
|
125
192
|
self.animate_progress()
|
126
193
|
|
194
|
+
@SafeProperty(object, doc="Current visual state of the progress bar.")
|
195
|
+
def state(self):
|
196
|
+
return self._state
|
197
|
+
|
198
|
+
@state.setter
|
199
|
+
def state(self, state):
|
200
|
+
"""
|
201
|
+
Set the visual state of the progress bar.
|
202
|
+
|
203
|
+
Args:
|
204
|
+
state(ProgressState | str): The state to set. Can be one of the
|
205
|
+
"""
|
206
|
+
if isinstance(state, str):
|
207
|
+
state = ProgressState(state.lower())
|
208
|
+
if not isinstance(state, ProgressState):
|
209
|
+
raise ValueError("state must be a ProgressState or its value")
|
210
|
+
self._state = state
|
211
|
+
self.update()
|
212
|
+
|
213
|
+
@SafeProperty(float, doc="Base corner radius in pixels (auto‑scaled down on small bars).")
|
214
|
+
def corner_radius(self) -> float:
|
215
|
+
return self._corner_radius
|
216
|
+
|
217
|
+
@corner_radius.setter
|
218
|
+
def corner_radius(self, radius: float):
|
219
|
+
self._corner_radius = max(0.0, radius)
|
220
|
+
self.update()
|
221
|
+
|
222
|
+
@SafeProperty(float)
|
223
|
+
def padding_left_right(self) -> float:
|
224
|
+
return self._padding_left_right
|
225
|
+
|
226
|
+
@padding_left_right.setter
|
227
|
+
def padding_left_right(self, padding: float):
|
228
|
+
self._padding_left_right = padding
|
229
|
+
self.update()
|
230
|
+
|
127
231
|
def paintEvent(self, event):
|
128
232
|
painter = QPainter(self)
|
129
233
|
painter.setRenderHint(QPainter.Antialiasing)
|
130
|
-
rect = self.rect().adjusted(
|
234
|
+
rect = self.rect().adjusted(self._padding_left_right, 0, -self._padding_left_right, -1)
|
235
|
+
|
236
|
+
# Corner radius adapts to widget height so it never exceeds half the bar’s thickness
|
237
|
+
radius = min(self._corner_radius, rect.height() / 2)
|
131
238
|
|
132
239
|
# Draw background
|
133
240
|
painter.setBrush(self._background_color)
|
134
241
|
painter.setPen(Qt.NoPen)
|
135
|
-
painter.drawRoundedRect(rect,
|
242
|
+
painter.drawRoundedRect(rect, radius, radius) # Rounded corners
|
136
243
|
|
137
244
|
# Draw border
|
138
245
|
painter.setBrush(Qt.NoBrush)
|
139
246
|
painter.setPen(self._border_color)
|
140
|
-
painter.drawRoundedRect(rect,
|
141
|
-
|
142
|
-
# Determine progress
|
143
|
-
if self.
|
144
|
-
current_color = self.
|
247
|
+
painter.drawRoundedRect(rect, radius, radius)
|
248
|
+
|
249
|
+
# Determine progress colour based on current state
|
250
|
+
if self._state == ProgressState.PAUSED:
|
251
|
+
current_color = self._state_colors[ProgressState.PAUSED]
|
252
|
+
elif self._state == ProgressState.INTERRUPTED:
|
253
|
+
current_color = self._state_colors[ProgressState.INTERRUPTED]
|
254
|
+
elif self._state == ProgressState.COMPLETED or self._value >= self._maximum:
|
255
|
+
current_color = self._state_colors[ProgressState.COMPLETED]
|
145
256
|
else:
|
146
|
-
current_color = self.
|
257
|
+
current_color = self._state_colors[ProgressState.NORMAL]
|
147
258
|
|
148
259
|
# Set clipping region to preserve the background's rounded corners
|
149
260
|
progress_rect = rect.adjusted(
|
150
261
|
0, 0, int(-rect.width() + (self._value / self._maximum) * rect.width()), 0
|
151
262
|
)
|
152
263
|
clip_path = QPainterPath()
|
153
|
-
clip_path.addRoundedRect(
|
264
|
+
clip_path.addRoundedRect(
|
265
|
+
QRectF(rect), radius, radius
|
266
|
+
) # Clip to the background's rounded corners
|
154
267
|
painter.setClipPath(clip_path)
|
155
268
|
|
156
269
|
# Draw progress bar
|
@@ -168,7 +281,7 @@ class BECProgressBar(BECWidget, QWidget):
|
|
168
281
|
self._value_animation.setEndValue(self._target_value)
|
169
282
|
self._value_animation.start()
|
170
283
|
|
171
|
-
@
|
284
|
+
@SafeProperty(float)
|
172
285
|
def maximum(self):
|
173
286
|
"""
|
174
287
|
The maximum value of the progress bar.
|
@@ -182,7 +295,7 @@ class BECProgressBar(BECWidget, QWidget):
|
|
182
295
|
"""
|
183
296
|
self.set_maximum(maximum)
|
184
297
|
|
185
|
-
@
|
298
|
+
@SafeProperty(float)
|
186
299
|
def minimum(self):
|
187
300
|
"""
|
188
301
|
The minimum value of the progress bar.
|
@@ -193,7 +306,7 @@ class BECProgressBar(BECWidget, QWidget):
|
|
193
306
|
def minimum(self, minimum: float):
|
194
307
|
self.set_minimum(minimum)
|
195
308
|
|
196
|
-
@
|
309
|
+
@SafeProperty(float)
|
197
310
|
def initial_value(self):
|
198
311
|
"""
|
199
312
|
The initial value of the progress bar.
|
@@ -204,7 +317,7 @@ class BECProgressBar(BECWidget, QWidget):
|
|
204
317
|
def initial_value(self, value: float):
|
205
318
|
self.set_value(value)
|
206
319
|
|
207
|
-
@
|
320
|
+
@SafeSlot(float)
|
208
321
|
def set_maximum(self, maximum: float):
|
209
322
|
"""
|
210
323
|
Set the maximum value of the progress bar.
|
@@ -213,10 +326,11 @@ class BECProgressBar(BECWidget, QWidget):
|
|
213
326
|
maximum (float): The maximum value.
|
214
327
|
"""
|
215
328
|
self._user_maximum = maximum
|
329
|
+
self._adjust_label_width()
|
216
330
|
self.set_value(self._user_value) # Update the value to fit the new range
|
217
331
|
self.update()
|
218
332
|
|
219
|
-
@
|
333
|
+
@SafeSlot(float)
|
220
334
|
def set_minimum(self, minimum: float):
|
221
335
|
"""
|
222
336
|
Set the minimum value of the progress bar.
|
File without changes
|
@@ -0,0 +1,17 @@
|
|
1
|
+
def main(): # pragma: no cover
|
2
|
+
from qtpy import PYSIDE6
|
3
|
+
|
4
|
+
if not PYSIDE6:
|
5
|
+
print("PYSIDE6 is not available in the environment. Cannot patch designer.")
|
6
|
+
return
|
7
|
+
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
8
|
+
|
9
|
+
from bec_widgets.widgets.progress.scan_progressbar.scan_progress_bar_plugin import (
|
10
|
+
ScanProgressBarPlugin,
|
11
|
+
)
|
12
|
+
|
13
|
+
QPyDesignerCustomWidgetCollection.addCustomWidget(ScanProgressBarPlugin())
|
14
|
+
|
15
|
+
|
16
|
+
if __name__ == "__main__": # pragma: no cover
|
17
|
+
main()
|
@@ -0,0 +1 @@
|
|
1
|
+
{'files': ['scan_progressbar.py']}
|