bec-widgets 1.9.1__py3-none-any.whl → 1.11.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 CHANGED
@@ -1,6 +1,28 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v1.11.0 (2024-12-11)
5
+
6
+ ### Features
7
+
8
+ - **collapsible_panel_manager**: Panel manager to handle collapsing and expanding widgets from the
9
+ main widget added
10
+ ([`a434d3e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a434d3ee574081356c32c096d2fd61f641e04542))
11
+
12
+ ### Testing
13
+
14
+ - **collapsible_panel_manager**: Fixture changed to not use .show()
15
+ ([`ff654b5`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ff654b56ae98388a2b707c040d51220be6cbce13))
16
+
17
+
18
+ ## v1.10.0 (2024-12-10)
19
+
20
+ ### Features
21
+
22
+ - **layout_manager**: Grid layout manager widget
23
+ ([`17a63e3`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/17a63e3b639ecf6b41c379717d81339b04ef10f8))
24
+
25
+
4
26
  ## v1.9.1 (2024-12-10)
5
27
 
6
28
  ### Bug Fixes
@@ -189,22 +211,3 @@ Depending on the test, auto-updates are enabled or not.
189
211
 
190
212
  - **crosshair**: Label of coordinates of TextItem displays numbers in general format
191
213
  ([`11e5937`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/11e5937ae0f3c1413acd4e66878a692ebe4ef7d0))
192
-
193
- - **crosshair**: Label of coordinates of TextItem is updated according to the current theme of qapp
194
- ([`4f31ea6`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4f31ea655cf6190e141e6a2720a2d6da517a2b5b))
195
-
196
- - **crosshair**: Log is separately scaled for backend logic and for signal emit
197
- ([`b2eb71a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b2eb71aae0b6a7c82158f2d150ae1e31411cfdeb))
198
-
199
- ### Features
200
-
201
- - **crosshair**: Textitem to display crosshair coordinates
202
- ([`035136d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/035136d5171ec5f4311d15a9aa5bad2bdbc1f6cb))
203
-
204
- ### Testing
205
-
206
- - **crosshair**: Tests extended
207
- ([`64df805`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/64df805a9ed92bb97e580ac3bc0a1bbd2b1cb81e))
208
-
209
-
210
- ## v1.3.3 (2024-11-07)
PKG-INFO CHANGED
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bec_widgets
3
- Version: 1.9.1
3
+ Version: 1.11.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
@@ -7,6 +7,7 @@ from qtpy.QtWidgets import (
7
7
  QApplication,
8
8
  QGroupBox,
9
9
  QHBoxLayout,
10
+ QPushButton,
10
11
  QSplitter,
11
12
  QTabWidget,
12
13
  QVBoxLayout,
@@ -17,6 +18,7 @@ from bec_widgets.utils import BECDispatcher
17
18
  from bec_widgets.utils.colors import apply_theme
18
19
  from bec_widgets.widgets.containers.dock import BECDockArea
19
20
  from bec_widgets.widgets.containers.figure import BECFigure
21
+ from bec_widgets.widgets.containers.layout_manager.layout_manager import LayoutManagerWidget
20
22
  from bec_widgets.widgets.editors.jupyter_console.jupyter_console import BECJupyterConsole
21
23
 
22
24
 
@@ -50,11 +52,16 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
50
52
  "d1": self.d1,
51
53
  "d2": self.d2,
52
54
  "wave": self.wf,
53
- # "bar": self.bar,
54
- # "cm": self.colormap,
55
55
  "im": self.im,
56
56
  "mm": self.mm,
57
57
  "mw": self.mw,
58
+ "lm": self.lm,
59
+ "btn1": self.btn1,
60
+ "btn2": self.btn2,
61
+ "btn3": self.btn3,
62
+ "btn4": self.btn4,
63
+ "btn5": self.btn5,
64
+ "btn6": self.btn6,
58
65
  }
59
66
  )
60
67
 
@@ -79,11 +86,25 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
79
86
  second_tab_layout.addWidget(self.figure)
80
87
  tab_widget.addTab(second_tab, "BEC Figure")
81
88
 
89
+ third_tab = QWidget()
90
+ third_tab_layout = QVBoxLayout(third_tab)
91
+ self.lm = LayoutManagerWidget()
92
+ third_tab_layout.addWidget(self.lm)
93
+ tab_widget.addTab(third_tab, "Layout Manager Widget")
94
+
82
95
  group_box = QGroupBox("Jupyter Console", splitter)
83
96
  group_box_layout = QVBoxLayout(group_box)
84
97
  self.console = BECJupyterConsole(inprocess=True)
85
98
  group_box_layout.addWidget(self.console)
86
99
 
100
+ # Some buttons for layout testing
101
+ self.btn1 = QPushButton("Button 1")
102
+ self.btn2 = QPushButton("Button 2")
103
+ self.btn3 = QPushButton("Button 3")
104
+ self.btn4 = QPushButton("Button 4")
105
+ self.btn5 = QPushButton("Button 5")
106
+ self.btn6 = QPushButton("Button 6")
107
+
87
108
  # add stuff to figure
88
109
  self._init_figure()
89
110
 
@@ -93,15 +114,7 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
93
114
  self.setWindowTitle("Jupyter Console Window")
94
115
 
95
116
  def _init_figure(self):
96
- self.w1 = self.figure.plot(
97
- x_name="samx",
98
- y_name="bpm4i",
99
- # title="Standard Plot with sync device, custom labels - w1",
100
- # x_label="Motor Position",
101
- # y_label="Intensity (A.U.)",
102
- row=0,
103
- col=0,
104
- )
117
+ self.w1 = self.figure.plot(x_name="samx", y_name="bpm4i", row=0, col=0)
105
118
  self.w1.set(
106
119
  title="Standard Plot with sync device, custom labels - w1",
107
120
  x_label="Motor Position",
@@ -169,14 +182,6 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
169
182
  self.wf = self.d2.add_widget("BECFigure", row=0, col=0)
170
183
 
171
184
  self.mw = self.wf.multi_waveform(monitor="waveform") # , config=config)
172
- # self.wf.plot(x_name="samx", y_name="bpm3a")
173
- # self.wf.plot(x_name="samx", y_name="bpm4i", dap="GaussianModel")
174
- # self.bar = self.d2.add_widget("RingProgressBar", row=0, col=1)
175
- # self.bar.set_diameter(200)
176
-
177
- # self.d3 = self.dock.add_dock(name="dock_3", position="bottom")
178
- # self.colormap = pg.GradientWidget()
179
- # self.d3.add_widget(self.colormap, row=0, col=0)
180
185
 
181
186
  self.dock.save_state()
182
187
 
@@ -0,0 +1,380 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from typing import Literal
5
+
6
+ import pyqtgraph as pg
7
+ from qtpy.QtCore import Property, QEasingCurve, QObject, QPropertyAnimation
8
+ from qtpy.QtWidgets import (
9
+ QApplication,
10
+ QHBoxLayout,
11
+ QMainWindow,
12
+ QPushButton,
13
+ QSizePolicy,
14
+ QVBoxLayout,
15
+ QWidget,
16
+ )
17
+ from typeguard import typechecked
18
+
19
+ from bec_widgets.widgets.containers.layout_manager.layout_manager import LayoutManagerWidget
20
+
21
+
22
+ class DimensionAnimator(QObject):
23
+ """
24
+ Helper class to animate the size of a panel widget.
25
+ """
26
+
27
+ def __init__(self, panel_widget: QWidget, direction: str):
28
+ super().__init__()
29
+ self.panel_widget = panel_widget
30
+ self.direction = direction
31
+ self._size = 0
32
+
33
+ @Property(int)
34
+ def panel_width(self):
35
+ """
36
+ Returns the current width of the panel widget.
37
+ """
38
+ return self._size
39
+
40
+ @panel_width.setter
41
+ def panel_width(self, val: int):
42
+ """
43
+ Set the width of the panel widget.
44
+
45
+ Args:
46
+ val(int): The width to set.
47
+ """
48
+ self._size = val
49
+ self.panel_widget.setFixedWidth(val)
50
+
51
+ @Property(int)
52
+ def panel_height(self):
53
+ """
54
+ Returns the current height of the panel widget.
55
+ """
56
+ return self._size
57
+
58
+ @panel_height.setter
59
+ def panel_height(self, val: int):
60
+ """
61
+ Set the height of the panel widget.
62
+
63
+ Args:
64
+ val(int): The height to set.
65
+ """
66
+ self._size = val
67
+ self.panel_widget.setFixedHeight(val)
68
+
69
+
70
+ class CollapsiblePanelManager(QObject):
71
+ """
72
+ Manager class to handle collapsible panels from a main widget using LayoutManagerWidget.
73
+ """
74
+
75
+ def __init__(self, layout_manager: LayoutManagerWidget, reference_widget: QWidget, parent=None):
76
+ super().__init__(parent)
77
+ self.layout_manager = layout_manager
78
+ self.reference_widget = reference_widget
79
+ self.animations = {}
80
+ self.panels = {}
81
+ self.direction_settings = {
82
+ "left": {"property": b"maximumWidth", "default_size": 200},
83
+ "right": {"property": b"maximumWidth", "default_size": 200},
84
+ "top": {"property": b"maximumHeight", "default_size": 150},
85
+ "bottom": {"property": b"maximumHeight", "default_size": 150},
86
+ }
87
+
88
+ def add_panel(
89
+ self,
90
+ direction: Literal["left", "right", "top", "bottom"],
91
+ panel_widget: QWidget,
92
+ target_size: int | None = None,
93
+ duration: int = 300,
94
+ ):
95
+ """
96
+ Add a panel widget to the layout manager.
97
+
98
+ Args:
99
+ direction(Literal["left", "right", "top", "bottom"]): Direction of the panel.
100
+ panel_widget(QWidget): The panel widget to add.
101
+ target_size(int, optional): The target size of the panel. Defaults to None.
102
+ duration(int): The duration of the animation in milliseconds. Defaults to 300.
103
+ """
104
+ if direction not in self.direction_settings:
105
+ raise ValueError("Direction must be one of 'left', 'right', 'top', 'bottom'.")
106
+
107
+ if target_size is None:
108
+ target_size = self.direction_settings[direction]["default_size"]
109
+
110
+ self.layout_manager.add_widget_relative(
111
+ widget=panel_widget, reference_widget=self.reference_widget, position=direction
112
+ )
113
+ panel_widget.setVisible(False)
114
+
115
+ # Set initial constraints as flexible
116
+ if direction in ["left", "right"]:
117
+ panel_widget.setMaximumWidth(0)
118
+ panel_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
119
+ else:
120
+ panel_widget.setMaximumHeight(0)
121
+ panel_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
122
+
123
+ self.panels[direction] = {
124
+ "widget": panel_widget,
125
+ "direction": direction,
126
+ "target_size": target_size,
127
+ "duration": duration,
128
+ "animator": None,
129
+ }
130
+
131
+ def toggle_panel(
132
+ self,
133
+ direction: Literal["left", "right", "top", "bottom"],
134
+ target_size: int | None = None,
135
+ duration: int | None = None,
136
+ easing_curve: QEasingCurve = QEasingCurve.InOutQuad,
137
+ ensure_max: bool = False,
138
+ scale: float | None = None,
139
+ animation: bool = True,
140
+ ):
141
+ """
142
+ Toggle the specified panel.
143
+
144
+ Parameters:
145
+ direction (Literal["left", "right", "top", "bottom"]): Direction of the panel to toggle.
146
+ target_size (int, optional): Override target size for this toggle.
147
+ duration (int, optional): Override the animation duration.
148
+ easing_curve (QEasingCurve): Animation easing curve.
149
+ ensure_max (bool): If True, animate as a fixed-size panel.
150
+ scale (float, optional): If provided, calculate target_size from main widget size.
151
+ animation (bool): If False, no animation is performed; panel instantly toggles.
152
+ """
153
+ if direction not in self.panels:
154
+ raise ValueError(f"No panel found in direction '{direction}'.")
155
+
156
+ panel_info = self.panels[direction]
157
+ panel_widget = panel_info["widget"]
158
+ dir_settings = self.direction_settings[direction]
159
+
160
+ # Determine final target size
161
+ if scale is not None:
162
+ main_rect = self.reference_widget.geometry()
163
+ if direction in ["left", "right"]:
164
+ computed_target = int(main_rect.width() * scale)
165
+ else:
166
+ computed_target = int(main_rect.height() * scale)
167
+ final_target_size = computed_target
168
+ else:
169
+ if target_size is None:
170
+ final_target_size = panel_info["target_size"]
171
+ else:
172
+ final_target_size = target_size
173
+
174
+ if duration is None:
175
+ duration = panel_info["duration"]
176
+
177
+ expanding_property = dir_settings["property"]
178
+ currently_visible = panel_widget.isVisible()
179
+
180
+ if ensure_max:
181
+ if panel_info["animator"] is None:
182
+ panel_info["animator"] = DimensionAnimator(panel_widget, direction)
183
+ animator = panel_info["animator"]
184
+
185
+ if direction in ["left", "right"]:
186
+ prop_name = b"panel_width"
187
+ else:
188
+ prop_name = b"panel_height"
189
+ else:
190
+ animator = None
191
+ prop_name = expanding_property
192
+
193
+ if currently_visible:
194
+ # Hide the panel
195
+ if ensure_max:
196
+ start_value = final_target_size
197
+ end_value = 0
198
+ finish_callback = lambda w=panel_widget, d=direction: self._after_hide_reset(w, d)
199
+ else:
200
+ start_value = (
201
+ panel_widget.width()
202
+ if direction in ["left", "right"]
203
+ else panel_widget.height()
204
+ )
205
+ end_value = 0
206
+ finish_callback = lambda w=panel_widget: w.setVisible(False)
207
+ else:
208
+ # Show the panel
209
+ start_value = 0
210
+ end_value = final_target_size
211
+ finish_callback = None
212
+ if ensure_max:
213
+ # Fix panel exactly
214
+ if direction in ["left", "right"]:
215
+ panel_widget.setMinimumWidth(0)
216
+ panel_widget.setMaximumWidth(final_target_size)
217
+ panel_widget.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
218
+ else:
219
+ panel_widget.setMinimumHeight(0)
220
+ panel_widget.setMaximumHeight(final_target_size)
221
+ panel_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
222
+ else:
223
+ # Flexible mode
224
+ if direction in ["left", "right"]:
225
+ panel_widget.setMinimumWidth(0)
226
+ panel_widget.setMaximumWidth(final_target_size)
227
+ panel_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
228
+ else:
229
+ panel_widget.setMinimumHeight(0)
230
+ panel_widget.setMaximumHeight(final_target_size)
231
+ panel_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
232
+
233
+ panel_widget.setVisible(True)
234
+
235
+ if not animation:
236
+ # No animation: instantly set final state
237
+ if end_value == 0:
238
+ # Hiding
239
+ if ensure_max:
240
+ # Reset after hide
241
+ self._after_hide_reset(panel_widget, direction)
242
+ else:
243
+ panel_widget.setVisible(False)
244
+ else:
245
+ # Showing
246
+ if ensure_max:
247
+ # Already set fixed size
248
+ if direction in ["left", "right"]:
249
+ panel_widget.setFixedWidth(end_value)
250
+ else:
251
+ panel_widget.setFixedHeight(end_value)
252
+ else:
253
+ # Just set maximum dimension
254
+ if direction in ["left", "right"]:
255
+ panel_widget.setMaximumWidth(end_value)
256
+ else:
257
+ panel_widget.setMaximumHeight(end_value)
258
+ return
259
+
260
+ # With animation
261
+ animation = QPropertyAnimation(animator if ensure_max else panel_widget, prop_name)
262
+ animation.setDuration(duration)
263
+ animation.setStartValue(start_value)
264
+ animation.setEndValue(end_value)
265
+ animation.setEasingCurve(easing_curve)
266
+
267
+ if end_value == 0 and finish_callback:
268
+ animation.finished.connect(finish_callback)
269
+ elif end_value == 0 and not finish_callback:
270
+ animation.finished.connect(lambda w=panel_widget: w.setVisible(False))
271
+
272
+ animation.start()
273
+ self.animations[panel_widget] = animation
274
+
275
+ @typechecked
276
+ def _after_hide_reset(
277
+ self, panel_widget: QWidget, direction: Literal["left", "right", "top", "bottom"]
278
+ ):
279
+ """
280
+ Reset the panel widget after hiding it in ensure_max mode.
281
+
282
+ Args:
283
+ panel_widget(QWidget): The panel widget to reset.
284
+ direction(Literal["left", "right", "top", "bottom"]): The direction of the panel.
285
+ """
286
+ # Called after hiding a panel in ensure_max mode
287
+ panel_widget.setVisible(False)
288
+ if direction in ["left", "right"]:
289
+ panel_widget.setMinimumWidth(0)
290
+ panel_widget.setMaximumWidth(0)
291
+ panel_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
292
+ else:
293
+ panel_widget.setMinimumHeight(0)
294
+ panel_widget.setMaximumHeight(16777215)
295
+ panel_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
296
+
297
+
298
+ ####################################################################################################
299
+ # The following code is for the GUI control panel to interact with the CollapsiblePanelManager.
300
+ # It is not covered by any tests as it serves only as an example for the CollapsiblePanelManager class.
301
+ ####################################################################################################
302
+
303
+
304
+ class MainWindow(QMainWindow): # pragma: no cover
305
+ def __init__(self):
306
+ super().__init__()
307
+ self.setWindowTitle("Panels with ensure_max, scale, and animation toggle")
308
+ self.resize(800, 600)
309
+
310
+ central_widget = QWidget()
311
+ self.setCentralWidget(central_widget)
312
+ main_layout = QVBoxLayout(central_widget)
313
+ main_layout.setContentsMargins(10, 10, 10, 10)
314
+ main_layout.setSpacing(10)
315
+
316
+ # Buttons
317
+ buttons_layout = QHBoxLayout()
318
+ self.btn_left = QPushButton("Toggle Left (ensure_max=True)")
319
+ self.btn_top = QPushButton("Toggle Top (scale=0.5, no animation)")
320
+ self.btn_right = QPushButton("Toggle Right (ensure_max=True, scale=0.3)")
321
+ self.btn_bottom = QPushButton("Toggle Bottom (no animation)")
322
+
323
+ buttons_layout.addWidget(self.btn_left)
324
+ buttons_layout.addWidget(self.btn_top)
325
+ buttons_layout.addWidget(self.btn_right)
326
+ buttons_layout.addWidget(self.btn_bottom)
327
+
328
+ main_layout.addLayout(buttons_layout)
329
+
330
+ self.layout_manager = LayoutManagerWidget()
331
+ main_layout.addWidget(self.layout_manager)
332
+
333
+ # Main widget
334
+ self.main_plot = pg.PlotWidget()
335
+ self.main_plot.plot([1, 2, 3, 4], [4, 3, 2, 1])
336
+ self.layout_manager.add_widget(self.main_plot, 0, 0)
337
+
338
+ self.panel_manager = CollapsiblePanelManager(self.layout_manager, self.main_plot)
339
+
340
+ # Panels
341
+ self.left_panel = pg.PlotWidget()
342
+ self.left_panel.plot([1, 2, 3], [3, 2, 1])
343
+ self.panel_manager.add_panel("left", self.left_panel, target_size=200)
344
+
345
+ self.right_panel = pg.PlotWidget()
346
+ self.right_panel.plot([10, 20, 30], [1, 10, 1])
347
+ self.panel_manager.add_panel("right", self.right_panel, target_size=200)
348
+
349
+ self.top_panel = pg.PlotWidget()
350
+ self.top_panel.plot([1, 2, 3], [1, 2, 3])
351
+ self.panel_manager.add_panel("top", self.top_panel, target_size=150)
352
+
353
+ self.bottom_panel = pg.PlotWidget()
354
+ self.bottom_panel.plot([2, 4, 6], [10, 5, 10])
355
+ self.panel_manager.add_panel("bottom", self.bottom_panel, target_size=150)
356
+
357
+ # Connect buttons
358
+ # Left with ensure_max
359
+ self.btn_left.clicked.connect(
360
+ lambda: self.panel_manager.toggle_panel("left", ensure_max=True)
361
+ )
362
+ # Top with scale=0.5 and no animation
363
+ self.btn_top.clicked.connect(
364
+ lambda: self.panel_manager.toggle_panel("top", scale=0.5, animation=False)
365
+ )
366
+ # Right with ensure_max, scale=0.3
367
+ self.btn_right.clicked.connect(
368
+ lambda: self.panel_manager.toggle_panel("right", ensure_max=True, scale=0.3)
369
+ )
370
+ # Bottom no animation
371
+ self.btn_bottom.clicked.connect(
372
+ lambda: self.panel_manager.toggle_panel("bottom", target_size=100, animation=False)
373
+ )
374
+
375
+
376
+ if __name__ == "__main__": # pragma: no cover
377
+ app = QApplication(sys.argv)
378
+ w = MainWindow()
379
+ w.show()
380
+ sys.exit(app.exec())