bec-widgets 1.17.1__py3-none-any.whl → 1.18.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.
Files changed (26) hide show
  1. .gitlab-ci.yml +5 -6
  2. CHANGELOG.md +33 -0
  3. PKG-INFO +1 -4
  4. README.md +15 -19
  5. bec_widgets/cli/generate_cli.py +15 -6
  6. bec_widgets/examples/jupyter_console/jupyter_console_window.py +12 -0
  7. bec_widgets/qt_utils/round_frame.py +7 -4
  8. bec_widgets/qt_utils/side_panel.py +63 -80
  9. bec_widgets/utils/widget_state_manager.py +84 -12
  10. bec_widgets/widgets/containers/layout_manager/layout_manager.py +1 -0
  11. bec_widgets/widgets/dap/lmfit_dialog/lmfit_dialog.py +22 -19
  12. bec_widgets/widgets/plots_next_gen/plot_base.py +571 -0
  13. bec_widgets/widgets/plots_next_gen/setting_menus/__init__.py +0 -0
  14. bec_widgets/widgets/plots_next_gen/setting_menus/axis_settings.py +95 -0
  15. bec_widgets/widgets/plots_next_gen/setting_menus/axis_settings_horizontal.ui +256 -0
  16. bec_widgets/widgets/plots_next_gen/setting_menus/axis_settings_vertical.ui +240 -0
  17. bec_widgets/widgets/plots_next_gen/toolbar_bundles/__init__.py +0 -0
  18. bec_widgets/widgets/plots_next_gen/toolbar_bundles/mouse_interactions.py +88 -0
  19. bec_widgets/widgets/plots_next_gen/toolbar_bundles/plot_export.py +63 -0
  20. bec_widgets/widgets/plots_next_gen/toolbar_bundles/save_state.py +48 -0
  21. {bec_widgets-1.17.1.dist-info → bec_widgets-1.18.0.dist-info}/METADATA +1 -4
  22. {bec_widgets-1.17.1.dist-info → bec_widgets-1.18.0.dist-info}/RECORD +26 -17
  23. pyproject.toml +1 -2
  24. {bec_widgets-1.17.1.dist-info → bec_widgets-1.18.0.dist-info}/WHEEL +0 -0
  25. {bec_widgets-1.17.1.dist-info → bec_widgets-1.18.0.dist-info}/entry_points.txt +0 -0
  26. {bec_widgets-1.17.1.dist-info → bec_widgets-1.18.0.dist-info}/licenses/LICENSE +0 -0
.gitlab-ci.yml CHANGED
@@ -78,9 +78,9 @@ formatter:
78
78
  stage: Formatter
79
79
  needs: []
80
80
  script:
81
- - pip install black isort
82
- - isort --check --diff ./
83
- - black --check --diff --color ./
81
+ - pip install bec_lib[dev]
82
+ - isort --check --diff --line-length=100 --profile=black --multi-line=3 --trailing-comma ./
83
+ - black --check --diff --color --line-length=100 --skip-magic-trailing-comma ./
84
84
  rules:
85
85
  - if: $CI_PROJECT_PATH == "bec/bec_widgets"
86
86
 
@@ -148,7 +148,7 @@ tests:
148
148
  - *clone-repos
149
149
  - *install-os-packages
150
150
  - *install-repos
151
- - pip install -e .[dev,pyqt6]
151
+ - pip install -e .[dev,pyside6]
152
152
  - coverage run --source=./bec_widgets -m pytest -v --junitxml=report.xml --maxfail=2 --random-order --full-trace ./tests/unit_tests
153
153
  - coverage report
154
154
  - coverage xml
@@ -172,7 +172,6 @@ test-matrix:
172
172
  - "3.12"
173
173
  QT_PCKG:
174
174
  - "pyside6"
175
- - "pyqt6"
176
175
 
177
176
  stage: AdditionalTests
178
177
  needs: []
@@ -211,7 +210,7 @@ end-2-end-conda:
211
210
  - cd ../
212
211
  - pip install -e ./ophyd_devices
213
212
 
214
- - pip install -e .[dev,pyqt6]
213
+ - pip install -e .[dev,pyside6]
215
214
  - cd ./tests/end-2-end
216
215
  - pytest -v --start-servers --flush-redis --random-order
217
216
 
CHANGELOG.md CHANGED
@@ -1,6 +1,39 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v1.18.0 (2025-01-30)
5
+
6
+ ### Bug Fixes
7
+
8
+ - **generate_cli**: Widgets can be tagged with RPC=False, then they are excluded from client.py for
9
+ RPC
10
+ ([`48fc63d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/48fc63d83e26889843b09b1eb4792612b53200ec))
11
+
12
+ ### Build System
13
+
14
+ - Pyqt6 support dropped
15
+ ([`a20935e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a20935e8625a9490e6c451a3b4012476e19317e5))
16
+
17
+ ### Continuous Integration
18
+
19
+ - Fix formatter 2024 versions
20
+ ([`4f8e683`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4f8e6835fe2312151dc2b40f0ab9eb50a9173f7c))
21
+
22
+ ### Features
23
+
24
+ - **plot_base_next_gen**: New type of plot base inherited from QWidget
25
+ ([`e7c9729`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e7c97290cd783d19128625567835d7ae9a414989))
26
+
27
+
28
+ ## v1.17.2 (2025-01-28)
29
+
30
+ ### Bug Fixes
31
+
32
+ - **widget_state_manager**: Skip QLabel saving; skip_setting property widget excluded from INI;
33
+ stored=False property excluded from INI
34
+ ([`b2b0450`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b2b0450bcb07c974e5f8002e084b350599c32d39))
35
+
36
+
4
37
  ## v1.17.1 (2025-01-26)
5
38
 
6
39
  ### Bug Fixes
PKG-INFO CHANGED
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec_widgets
3
- Version: 1.17.1
3
+ Version: 1.18.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
@@ -28,8 +28,5 @@ Requires-Dist: pytest-random-order~=1.1; extra == 'dev'
28
28
  Requires-Dist: pytest-timeout~=2.2; extra == 'dev'
29
29
  Requires-Dist: pytest-xvfb~=3.0; extra == 'dev'
30
30
  Requires-Dist: pytest~=8.0; extra == 'dev'
31
- Provides-Extra: pyqt6
32
- Requires-Dist: pyqt6-webengine>=6.7; extra == 'pyqt6'
33
- Requires-Dist: pyqt6>=6.7; extra == 'pyqt6'
34
31
  Provides-Extra: pyside6
35
32
  Requires-Dist: pyside6==6.7.2; extra == 'pyside6'
README.md CHANGED
@@ -1,12 +1,17 @@
1
1
  # BEC Widgets
2
2
 
3
+ **⚠️ Important Notice:**
4
+
5
+ 🚨 **PyQt6 is no longer supported** due to incompatibilities with Qt Designer. Please use **PySide6** instead. 🚨
6
+
3
7
  BEC Widgets is a GUI framework designed for interaction with [BEC (Beamline Experiment Control)](https://gitlab.psi.ch/bec/bec).
8
+
4
9
  ## Installation
5
10
 
6
11
  Use the package manager [pip](https://pip.pypa.io/en/stable/) to install BEC Widgets:
7
12
 
8
13
  ```bash
9
- pip install bec_widgets PyQt6
14
+ pip install bec_widgets[pyside6]
10
15
  ```
11
16
 
12
17
  For development purposes, you can clone the repository and install the package locally in editable mode:
@@ -14,22 +19,12 @@ For development purposes, you can clone the repository and install the package l
14
19
  ```bash
15
20
  git clone https://gitlab.psi.ch/bec/bec-widgets
16
21
  cd bec_widgets
17
- pip install -e .[dev,pyqt6]
22
+ pip install -e .[dev,pyside6]
18
23
  ```
19
24
 
20
- BEC Widgets currently supports both Pyside6 and PyQt6, however, no default distribution is specified. As a result, users must install one of the supported
21
- Python Qt distributions manually.
22
-
23
- To select a specific Python Qt distribution, install the package with an additional tag:
24
-
25
- ```bash
26
- pip install bec_widgets[pyqt6]
27
- ```
28
- or
25
+ BEC Widgets now **only supports PySide6**. Users must manually install PySide6 as no default Qt distribution is
26
+ specified.
29
27
 
30
- ```bash
31
- pip install bec_widgets[pyside6]
32
- ```
33
28
  ## Documentation
34
29
 
35
30
  Documentation of BEC Widgets can be found [here](https://bec-widgets.readthedocs.io/en/latest/). The documentation of the BEC can be found [here](https://bec.readthedocs.io/en/latest/).
@@ -39,7 +34,7 @@ Documentation of BEC Widgets can be found [here](https://bec-widgets.readthedocs
39
34
  All commits should use the Angular commit scheme:
40
35
 
41
36
  > #### <a name="commit-header"></a>Angular Commit Message Header
42
- >
37
+ >
43
38
  > ```
44
39
  > <type>(<scope>): <short summary>
45
40
  > │ │ │
@@ -53,13 +48,13 @@ All commits should use the Angular commit scheme:
53
48
  > │
54
49
  > └─⫸ Commit Type: build|ci|docs|feat|fix|perf|refactor|test
55
50
  > ```
56
- >
51
+ >
57
52
  > The `<type>` and `<summary>` fields are mandatory, the `(<scope>)` field is optional.
58
53
 
59
54
  > ##### Type
60
- >
55
+ >
61
56
  > Must be one of the following:
62
- >
57
+ >
63
58
  > * **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
64
59
  > * **ci**: Changes to our CI configuration files and scripts (examples: CircleCi, SauceLabs)
65
60
  > * **docs**: Documentation only changes
@@ -71,4 +66,5 @@ All commits should use the Angular commit scheme:
71
66
 
72
67
  ## License
73
68
 
74
- [BSD-3-Clause](https://choosealicense.com/licenses/bsd-3-clause/)
69
+ [BSD-3-Clause](https://choosealicense.com/licenses/bsd-3-clause/)
70
+
@@ -43,14 +43,21 @@ from bec_widgets.cli.rpc.rpc_base import RPCBase, rpc_call
43
43
 
44
44
  def generate_client(self, class_container: BECClassContainer):
45
45
  """
46
- Generate the client for the published classes.
46
+ Generate the client for the published classes, skipping any classes
47
+ that have `RPC = False`.
47
48
 
48
49
  Args:
49
50
  class_container: The class container with the classes to generate the client for.
50
51
  """
51
- rpc_top_level_classes = class_container.rpc_top_level_classes
52
+ # Filter out classes that explicitly have RPC=False
53
+ rpc_top_level_classes = [
54
+ cls for cls in class_container.rpc_top_level_classes if getattr(cls, "RPC", True)
55
+ ]
52
56
  rpc_top_level_classes.sort(key=lambda x: x.__name__)
53
- connector_classes = class_container.connector_classes
57
+
58
+ connector_classes = [
59
+ cls for cls in class_container.connector_classes if getattr(cls, "RPC", True)
60
+ ]
54
61
  connector_classes.sort(key=lambda x: x.__name__)
55
62
 
56
63
  self.write_client_enum(rpc_top_level_classes)
@@ -81,13 +88,13 @@ class Widgets(str, enum.Enum):
81
88
 
82
89
  class_name = cls.__name__
83
90
 
84
- # Generate the content
85
- if cls.__name__ == "BECDockArea":
91
+ if class_name == "BECDockArea":
86
92
  self.content += f"""
87
93
  class {class_name}(RPCBase):"""
88
94
  else:
89
95
  self.content += f"""
90
96
  class {class_name}(RPCBase):"""
97
+
91
98
  if not cls.USER_ACCESS:
92
99
  self.content += """...
93
100
  """
@@ -100,8 +107,10 @@ class {class_name}(RPCBase):"""
100
107
  method = method.split(".setter")[0]
101
108
  if obj is None:
102
109
  raise AttributeError(
103
- f"Method {method} not found in class {cls.__name__}. Please check the USER_ACCESS list."
110
+ f"Method {method} not found in class {cls.__name__}. "
111
+ f"Please check the USER_ACCESS list."
104
112
  )
113
+
105
114
  if isinstance(obj, (property, QtProperty)):
106
115
  # for the cli, we can map qt properties to regular properties
107
116
  if is_property_setter:
@@ -20,6 +20,7 @@ from bec_widgets.widgets.containers.dock import BECDockArea
20
20
  from bec_widgets.widgets.containers.figure import BECFigure
21
21
  from bec_widgets.widgets.containers.layout_manager.layout_manager import LayoutManagerWidget
22
22
  from bec_widgets.widgets.editors.jupyter_console.jupyter_console import BECJupyterConsole
23
+ from bec_widgets.widgets.plots_next_gen.plot_base import PlotBase
23
24
 
24
25
 
25
26
  class JupyterConsoleWindow(QWidget): # pragma: no cover:
@@ -62,6 +63,8 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
62
63
  "btn4": self.btn4,
63
64
  "btn5": self.btn5,
64
65
  "btn6": self.btn6,
66
+ "pb": self.pb,
67
+ "pi": self.pi,
65
68
  }
66
69
  )
67
70
 
@@ -92,6 +95,15 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
92
95
  third_tab_layout.addWidget(self.lm)
93
96
  tab_widget.addTab(third_tab, "Layout Manager Widget")
94
97
 
98
+ fourth_tab = QWidget()
99
+ fourth_tab_layout = QVBoxLayout(fourth_tab)
100
+ self.pb = PlotBase()
101
+ self.pi = self.pb.plot_item
102
+ fourth_tab_layout.addWidget(self.pb)
103
+ tab_widget.addTab(fourth_tab, "PltoBase")
104
+
105
+ tab_widget.setCurrentIndex(3)
106
+
95
107
  group_box = QGroupBox("Jupyter Console", splitter)
96
108
  group_box_layout = QVBoxLayout(group_box)
97
109
  self.console = BECJupyterConsole(inprocess=True)
@@ -29,6 +29,7 @@ class RoundedFrame(BECWidget, QFrame):
29
29
  self._radius = radius
30
30
 
31
31
  # Apply rounded frame styling
32
+ self.setProperty("skip_settings", True)
32
33
  self.setObjectName("roundedFrame")
33
34
  self.update_style()
34
35
 
@@ -113,10 +114,12 @@ class RoundedFrame(BECWidget, QFrame):
113
114
 
114
115
  # Apply axis label and tick colors
115
116
  plot_item = self.content_widget.getPlotItem()
116
- plot_item.getAxis("left").setPen(pg.mkPen(color=axis_color))
117
- plot_item.getAxis("bottom").setPen(pg.mkPen(color=axis_color))
118
- plot_item.getAxis("left").setTextPen(pg.mkPen(color=label_color))
119
- plot_item.getAxis("bottom").setTextPen(pg.mkPen(color=label_color))
117
+ for axis in ["left", "right", "top", "bottom"]:
118
+ plot_item.getAxis(axis).setPen(pg.mkPen(color=axis_color))
119
+ plot_item.getAxis(axis).setTextPen(pg.mkPen(color=label_color))
120
+
121
+ # Change title color
122
+ plot_item.titleLabel.setText(plot_item.titleLabel.text, color=label_color)
120
123
 
121
124
  # Apply border style via stylesheet
122
125
  self.content_widget.setStyleSheet(
@@ -5,18 +5,18 @@ from qtpy.QtCore import Property, QEasingCurve, QPropertyAnimation
5
5
  from qtpy.QtGui import QAction
6
6
  from qtpy.QtWidgets import (
7
7
  QApplication,
8
+ QFrame,
8
9
  QHBoxLayout,
9
10
  QLabel,
10
11
  QMainWindow,
12
+ QScrollArea,
11
13
  QSizePolicy,
12
- QSpacerItem,
13
14
  QStackedWidget,
14
15
  QVBoxLayout,
15
16
  QWidget,
16
17
  )
17
18
 
18
19
  from bec_widgets.qt_utils.toolbar import MaterialIconAction, ModularToolBar
19
- from bec_widgets.widgets.plots.waveform.waveform_widget import BECWaveformWidget
20
20
 
21
21
 
22
22
  class SidePanel(QWidget):
@@ -34,11 +34,13 @@ class SidePanel(QWidget):
34
34
  ):
35
35
  super().__init__(parent=parent)
36
36
 
37
+ self.setProperty("skip_settings", True)
38
+ self.setObjectName("SidePanel")
39
+
37
40
  self._orientation = orientation
38
41
  self._panel_max_width = panel_max_width
39
42
  self._animation_duration = animation_duration
40
43
  self._animations_enabled = animations_enabled
41
- self._orientation = orientation
42
44
 
43
45
  self._panel_width = 0
44
46
  self._panel_height = 0
@@ -68,6 +70,7 @@ class SidePanel(QWidget):
68
70
  self.stack_widget = QStackedWidget()
69
71
  self.stack_widget.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
70
72
  self.stack_widget.setMinimumWidth(5)
73
+ self.stack_widget.setMaximumWidth(self._panel_max_width)
71
74
 
72
75
  if self._orientation == "left":
73
76
  self.main_layout.addWidget(self.toolbar)
@@ -77,7 +80,10 @@ class SidePanel(QWidget):
77
80
  self.main_layout.addWidget(self.toolbar)
78
81
 
79
82
  self.container.layout.addWidget(self.stack_widget)
80
- self.stack_widget.setMaximumWidth(self._panel_max_width)
83
+
84
+ self.menu_anim = QPropertyAnimation(self, b"panel_width")
85
+ self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
86
+ self.panel_width = 0 # start hidden
81
87
 
82
88
  else:
83
89
  self.main_layout = QVBoxLayout(self)
@@ -94,6 +100,7 @@ class SidePanel(QWidget):
94
100
  self.stack_widget = QStackedWidget()
95
101
  self.stack_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
96
102
  self.stack_widget.setMinimumHeight(5)
103
+ self.stack_widget.setMaximumHeight(self._panel_max_width)
97
104
 
98
105
  if self._orientation == "top":
99
106
  self.main_layout.addWidget(self.toolbar)
@@ -103,74 +110,46 @@ class SidePanel(QWidget):
103
110
  self.main_layout.addWidget(self.toolbar)
104
111
 
105
112
  self.container.layout.addWidget(self.stack_widget)
106
- self.stack_widget.setMaximumHeight(self._panel_max_width)
107
113
 
108
- if self._orientation in ("left", "right"):
109
- self.menu_anim = QPropertyAnimation(self, b"panel_width")
110
- else:
111
114
  self.menu_anim = QPropertyAnimation(self, b"panel_height")
115
+ self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
116
+ self.panel_height = 0 # start hidden
112
117
 
113
118
  self.menu_anim.setDuration(self._animation_duration)
114
119
  self.menu_anim.setEasingCurve(QEasingCurve.InOutQuad)
115
120
 
116
- if self._orientation in ("left", "right"):
117
- self.panel_width = 0
118
- else:
119
- self.panel_height = 0
120
-
121
121
  @Property(int)
122
122
  def panel_width(self):
123
- """
124
- Get the panel width.
125
- """
123
+ """Get the panel width."""
126
124
  return self._panel_width
127
125
 
128
126
  @panel_width.setter
129
127
  def panel_width(self, width: int):
130
- """
131
- Set the panel width.
132
-
133
- Args:
134
- width(int): The width of the panel.
135
- """
128
+ """Set the panel width."""
136
129
  self._panel_width = width
137
130
  if self._orientation in ("left", "right"):
138
131
  self.stack_widget.setFixedWidth(width)
139
132
 
140
133
  @Property(int)
141
134
  def panel_height(self):
142
- """
143
- Get the panel height.
144
- """
135
+ """Get the panel height."""
145
136
  return self._panel_height
146
137
 
147
138
  @panel_height.setter
148
139
  def panel_height(self, height: int):
149
- """
150
- Set the panel height.
151
-
152
- Args:
153
- height(int): The height of the panel.
154
- """
140
+ """Set the panel height."""
155
141
  self._panel_height = height
156
142
  if self._orientation in ("top", "bottom"):
157
143
  self.stack_widget.setFixedHeight(height)
158
144
 
159
145
  @Property(int)
160
146
  def panel_max_width(self):
161
- """
162
- Get the maximum width of the panel.
163
- """
147
+ """Get the maximum width of the panel."""
164
148
  return self._panel_max_width
165
149
 
166
150
  @panel_max_width.setter
167
151
  def panel_max_width(self, size: int):
168
- """
169
- Set the maximum width of the panel.
170
-
171
- Args:
172
- size(int): The maximum width of the panel.
173
- """
152
+ """Set the maximum width of the panel."""
174
153
  self._panel_max_width = size
175
154
  if self._orientation in ("left", "right"):
176
155
  self.stack_widget.setMaximumWidth(self._panel_max_width)
@@ -179,45 +158,28 @@ class SidePanel(QWidget):
179
158
 
180
159
  @Property(int)
181
160
  def animation_duration(self):
182
- """
183
- Get the duration of the animation.
184
- """
161
+ """Get the duration of the animation."""
185
162
  return self._animation_duration
186
163
 
187
164
  @animation_duration.setter
188
165
  def animation_duration(self, duration: int):
189
- """
190
- Set the duration of the animation.
191
-
192
- Args:
193
- duration(int): The duration of the animation.
194
- """
166
+ """Set the duration of the animation."""
195
167
  self._animation_duration = duration
196
168
  self.menu_anim.setDuration(duration)
197
169
 
198
170
  @Property(bool)
199
171
  def animations_enabled(self):
200
- """
201
- Get the status of the animations.
202
- """
172
+ """Get the status of the animations."""
203
173
  return self._animations_enabled
204
174
 
205
175
  @animations_enabled.setter
206
176
  def animations_enabled(self, enabled: bool):
207
- """
208
- Set the status of the animations.
209
-
210
- Args:
211
- enabled(bool): The status of the animations.
212
- """
177
+ """Set the status of the animations."""
213
178
  self._animations_enabled = enabled
214
179
 
215
180
  def show_panel(self, idx: int):
216
181
  """
217
182
  Show the side panel with animation and switch to idx.
218
-
219
- Args:
220
- idx(int): The index of the panel to show.
221
183
  """
222
184
  self.stack_widget.setCurrentIndex(idx)
223
185
  self.panel_visible = True
@@ -265,9 +227,6 @@ class SidePanel(QWidget):
265
227
  def switch_to(self, idx: int):
266
228
  """
267
229
  Switch to the specified index without animation.
268
-
269
- Args:
270
- idx(int): The index of the panel to switch to.
271
230
  """
272
231
  if self.current_index != idx:
273
232
  self.stack_widget.setCurrentIndex(idx)
@@ -284,21 +243,35 @@ class SidePanel(QWidget):
284
243
  widget(QWidget): The widget to add to the panel.
285
244
  title(str): The title of the panel.
286
245
  """
246
+ # container_widget: top-level container for the stacked page
287
247
  container_widget = QWidget()
288
248
  container_layout = QVBoxLayout(container_widget)
289
- container_widget.setStyleSheet("background-color: rgba(0,0,0,0);")
249
+ container_layout.setContentsMargins(0, 0, 0, 0)
250
+ container_layout.setSpacing(5)
251
+
290
252
  title_label = QLabel(f"<b>{title}</b>")
291
253
  title_label.setStyleSheet("font-size: 16px;")
292
- spacer = QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)
293
254
  container_layout.addWidget(title_label)
294
- container_layout.addWidget(widget)
295
- container_layout.addItem(spacer)
296
- container_layout.setContentsMargins(5, 5, 5, 5)
297
- container_layout.setSpacing(5)
298
255
 
256
+ # Create a QScrollArea for the actual widget to ensure scrolling if the widget inside is too large
257
+ scroll_area = QScrollArea()
258
+ scroll_area.setFrameShape(QFrame.NoFrame)
259
+ scroll_area.setWidgetResizable(True)
260
+ # Let the scroll area expand in both directions if there's room
261
+ scroll_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
262
+ scroll_area.setWidget(widget)
263
+
264
+ # Put the scroll area in the container layout
265
+ container_layout.addWidget(scroll_area)
266
+
267
+ # Optionally stretch the scroll area to fill vertical space
268
+ container_layout.setStretchFactor(scroll_area, 1)
269
+
270
+ # Add container_widget to the stacked widget
299
271
  index = self.stack_widget.count()
300
272
  self.stack_widget.addWidget(container_widget)
301
273
 
274
+ # Add an action to the toolbar
302
275
  action = MaterialIconAction(icon_name=icon_name, tooltip=tooltip, checkable=True)
303
276
  self.toolbar.add_action(action_id, action, target_widget=self)
304
277
 
@@ -326,6 +299,11 @@ class SidePanel(QWidget):
326
299
  action.action.toggled.connect(on_action_toggled)
327
300
 
328
301
 
302
+ ############################################
303
+ # DEMO APPLICATION
304
+ ############################################
305
+
306
+
329
307
  class ExampleApp(QMainWindow): # pragma: no cover
330
308
  def __init__(self):
331
309
  super().__init__()
@@ -333,20 +311,24 @@ class ExampleApp(QMainWindow): # pragma: no cover
333
311
 
334
312
  central_widget = QWidget()
335
313
  self.setCentralWidget(central_widget)
336
-
337
- self.side_panel = SidePanel(self, orientation="left")
338
-
339
314
  self.layout = QHBoxLayout(central_widget)
340
315
 
316
+ # Create side panel
317
+ self.side_panel = SidePanel(self, orientation="left", panel_max_width=250)
341
318
  self.layout.addWidget(self.side_panel)
319
+
320
+ from bec_widgets.widgets.plots.waveform.waveform_widget import BECWaveformWidget
321
+
342
322
  self.plot = BECWaveformWidget()
343
323
  self.layout.addWidget(self.plot)
324
+
344
325
  self.add_side_menus()
345
326
 
346
327
  def add_side_menus(self):
347
328
  widget1 = QWidget()
348
- widget1_layout = QVBoxLayout(widget1)
349
- widget1_layout.addWidget(QLabel("This is Widget 1"))
329
+ layout1 = QVBoxLayout(widget1)
330
+ for i in range(15):
331
+ layout1.addWidget(QLabel(f"Widget 1 label row {i}"))
350
332
  self.side_panel.add_menu(
351
333
  action_id="widget1",
352
334
  icon_name="counter_1",
@@ -356,8 +338,8 @@ class ExampleApp(QMainWindow): # pragma: no cover
356
338
  )
357
339
 
358
340
  widget2 = QWidget()
359
- widget2_layout = QVBoxLayout(widget2)
360
- widget2_layout.addWidget(QLabel("This is Widget 2"))
341
+ layout2 = QVBoxLayout(widget2)
342
+ layout2.addWidget(QLabel("Short widget 2 content"))
361
343
  self.side_panel.add_menu(
362
344
  action_id="widget2",
363
345
  icon_name="counter_2",
@@ -367,8 +349,9 @@ class ExampleApp(QMainWindow): # pragma: no cover
367
349
  )
368
350
 
369
351
  widget3 = QWidget()
370
- widget3_layout = QVBoxLayout(widget3)
371
- widget3_layout.addWidget(QLabel("This is Widget 3"))
352
+ layout3 = QVBoxLayout(widget3)
353
+ for i in range(10):
354
+ layout3.addWidget(QLabel(f"Line {i} for Widget 3"))
372
355
  self.side_panel.add_menu(
373
356
  action_id="widget3",
374
357
  icon_name="counter_3",
@@ -381,6 +364,6 @@ class ExampleApp(QMainWindow): # pragma: no cover
381
364
  if __name__ == "__main__": # pragma: no cover
382
365
  app = QApplication(sys.argv)
383
366
  window = ExampleApp()
384
- window.resize(800, 600)
367
+ window.resize(1000, 700)
385
368
  window.show()
386
369
  sys.exit(app.exec())