sciwidgets 0.1.2__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.
@@ -0,0 +1,124 @@
1
+ """Demo for the labels subpackage: ``python -m sciwidgets.labels``."""
2
+
3
+ from PySide6.QtCore import Qt
4
+
5
+ from sciwidgets.labels import math_label
6
+ from sciwidgets.labels.math_label import MathLabel
7
+
8
+
9
+ def _demo_main() -> int:
10
+ """Run this module as a standalone demo application.
11
+
12
+ Shows LaTeX vs mathtext rendering, alignment and stretching, colors and
13
+ the disabled state (toggled from a context menu), and a live editor for
14
+ trying arbitrary expressions.
15
+
16
+ Returns:
17
+ int: Qt application exit code.
18
+ """
19
+ from PySide6.QtWidgets import (
20
+ QApplication, QDoubleSpinBox, QGridLayout, QGroupBox, QHBoxLayout,
21
+ QLabel, QLineEdit, QVBoxLayout, QWidget,
22
+ )
23
+
24
+ app = QApplication([])
25
+ win = QWidget()
26
+ win.setWindowTitle("MathLabel demo")
27
+ root = QVBoxLayout(win)
28
+
29
+ status = "on" if math_label._latex_enabled else "off (mathtext fallback)"
30
+ root.addWidget(QLabel(f"External LaTeX: {status}"))
31
+
32
+ # --- 1. LaTeX vs mathtext -------------------------------------------
33
+ box = QGroupBox("use_tex=True vs use_tex=False")
34
+ grid = QGridLayout(box)
35
+ samples = [
36
+ r"T_1",
37
+ r"E = mc^2",
38
+ r"\Omega_{\mathrm{R}} = \frac{d\,E}{\hbar}",
39
+ r"\mathrm{Population}\ \Delta N",
40
+ ]
41
+ for row, s in enumerate(samples):
42
+ grid.addWidget(MathLabel(s, font_size=16, parent=box), row, 0)
43
+ grid.addWidget(MathLabel(s, font_size=16, use_tex=False, parent=box), row, 1)
44
+ root.addWidget(box)
45
+
46
+ # --- 2. Alignment and stretching ------------------------------------
47
+ box = QGroupBox("Alignment (aspect kept) and keep_aspect_ratio=False")
48
+ row = QHBoxLayout(box)
49
+ for align in (Qt.AlignLeft, Qt.AlignCenter, Qt.AlignRight):
50
+ lbl = MathLabel(r"\eta", font_size=14, alignment=align, parent=box)
51
+ lbl.setMinimumSize(90, 50)
52
+ lbl.setAutoFillBackground(True) # makes the label's area visible
53
+ lbl.setStyleSheet("background: #ffffff;")
54
+ row.addWidget(lbl)
55
+ stretched = MathLabel(r"\omega_0", font_size=14, keep_aspect_ratio=False, parent=box)
56
+ stretched.setMinimumSize(90, 50)
57
+ stretched.setAutoFillBackground(True)
58
+ stretched.setStyleSheet("background: #ffffff;")
59
+ row.addWidget(stretched)
60
+ root.addWidget(box)
61
+
62
+ # --- 3. Colors, disabled state, context menu ------------------------
63
+ box = QGroupBox("Colors (right-click the first label)")
64
+ row = QHBoxLayout(box)
65
+ controller = MathLabel(r"\mathrm{Menu}", font_size=16, parent=box)
66
+ colored = [
67
+ MathLabel(r"^4_2\mathrm{He}", font_size=16, text_color="red", parent=box),
68
+ MathLabel(r"A'", font_size=16, text_color="green", parent=box),
69
+ MathLabel(r"w", font_size=16, text_color="blue", parent=box),
70
+ ]
71
+ controller.set_menu_items([
72
+ {"id": "enable", "text": "Colors enabled", "checkable": True, "checked": True},
73
+ {"id": "sep"},
74
+ {"id": "hello", "text": "Print hello"},
75
+ ])
76
+
77
+ def on_action(act_id: str, checked: bool) -> None:
78
+ print(f"action={act_id!r} checked={checked}")
79
+ if act_id == "enable":
80
+ for lbl in colored:
81
+ lbl.setEnabled(checked)
82
+
83
+ controller.rightClickRequested.connect(on_action)
84
+ row.addWidget(controller)
85
+ for lbl in colored:
86
+ row.addWidget(lbl)
87
+ root.addWidget(box)
88
+
89
+ # --- 4. Live editor ---------------------------------------------------
90
+ box = QGroupBox("Live editor (press Enter to render)")
91
+ col = QVBoxLayout(box)
92
+ controls = QHBoxLayout()
93
+ editor = QLineEdit(r"\frac{\partial E}{\partial z} = -\frac{1}{c}\frac{\partial E}{\partial t}")
94
+ size = QDoubleSpinBox()
95
+ size.setRange(6.0, 48.0)
96
+ size.setValue(18.0)
97
+ controls.addWidget(editor, 1)
98
+ controls.addWidget(size)
99
+ preview = MathLabel(font_size=size.value(), parent=box)
100
+ error = QLabel()
101
+ error.setStyleSheet("color: #b00020;")
102
+ col.addLayout(controls)
103
+ col.addWidget(preview)
104
+ col.addWidget(error)
105
+
106
+ def render() -> None:
107
+ try:
108
+ preview.set_text(editor.text(), font_size=size.value())
109
+ error.clear()
110
+ except Exception as exc: # bad LaTeX/mathtext raises here
111
+ error.setText(f"{type(exc).__name__}: {str(exc).splitlines()[0]}")
112
+
113
+ editor.returnPressed.connect(render)
114
+ size.valueChanged.connect(lambda _: render())
115
+ render()
116
+ root.addWidget(box)
117
+
118
+ win.resize(520, 560)
119
+ win.show()
120
+ return app.exec()
121
+
122
+
123
+ if __name__ == "__main__":
124
+ raise SystemExit(_demo_main())
@@ -0,0 +1,505 @@
1
+ """Math label widget rendered through Matplotlib (LaTeX or mathtext)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import io
6
+ import shutil
7
+ from typing import Optional, List, Dict, Any
8
+
9
+ from matplotlib.figure import Figure
10
+
11
+ from PySide6.QtCore import QSize, QRectF, QByteArray, Signal, Qt, QEvent
12
+ from PySide6.QtGui import QPainter, QPalette, QColor
13
+ from PySide6.QtWidgets import QLabel, QWidget, QMenu
14
+ from PySide6.QtSvg import QSvgRenderer
15
+
16
+ _latex_enabled: bool = shutil.which("latex") is not None
17
+
18
+
19
+ def set_latex_enabled(enabled: bool) -> None:
20
+ """Globally allow or forbid external LaTeX rendering for SvgLabel.
21
+
22
+ Affects labels created afterwards; existing labels keep their setting.
23
+ """
24
+ global _latex_enabled
25
+ _latex_enabled = bool(enabled)
26
+
27
+
28
+ class MathLabel(QLabel):
29
+ """Display math text rendered by Matplotlib.
30
+
31
+ This widget behaves similarly to :class:`QLabel`, but its text is always
32
+ interpreted in math mode and rendered through Matplotlib (external LaTeX
33
+ when enabled, otherwise mathtext). Internally the result is converted to
34
+ SVG and drawn with :class:`QSvgRenderer`, so it scales cleanly.
35
+
36
+ The rendering pipeline is:
37
+
38
+ text -> Matplotlib (optionally external LaTeX) -> SVG bytes -> QSvgRenderer
39
+
40
+ Notes:
41
+ - The rendered SVG is rescaled when the widget size changes.
42
+ - By default, scaling preserves the SVG aspect ratio.
43
+ - The natural SVG size is cached and used as the widget's minimum size.
44
+ - The label is regenerated when the enabled state changes so disabled
45
+ text appearance stays in sync with the palette.
46
+ - If LaTeX rendering is enabled and available, Matplotlib may use the
47
+ system LaTeX installation for text rendering.
48
+
49
+ Signals:
50
+ rightClickRequested: Emitted when a context-menu action is triggered.
51
+ The signal arguments are the action id and the resulting checked
52
+ state.
53
+ """
54
+
55
+ rightClickRequested = Signal(str, bool)
56
+
57
+ def __init__(
58
+ self,
59
+ text: str = "",
60
+ font_size: float = 18.0,
61
+ text_color: str | QColor = "black",
62
+ pad_inches: float = 0.06,
63
+ alignment: Qt.Alignment = Qt.AlignCenter,
64
+ fix_size: bool = False,
65
+ keep_aspect_ratio: bool = True,
66
+ use_tex: bool = True,
67
+ parent: Optional[QWidget] = None,
68
+ ) -> None:
69
+ """Initialize the SVG label.
70
+
71
+ Args:
72
+ text: Initial text to render. When non-empty, it is wrapped in
73
+ ``$...$`` before rendering so it is interpreted in math mode.
74
+ font_size: Font size in points used by Matplotlib.
75
+ text_color: Text color used for rendering.
76
+ pad_inches: Extra padding, in inches, added around the rendered text
77
+ when the SVG is saved with a tight bounding box.
78
+ alignment: Alignment of the rendered SVG inside the widget.
79
+ fix_size: If ``True``, the widget size is fixed to the SVG's natural
80
+ size after rendering. Otherwise, the natural size is applied as
81
+ the minimum size.
82
+ keep_aspect_ratio: If ``True``, scale the SVG uniformly to preserve
83
+ aspect ratio. If ``False``, stretch it to fill the widget.
84
+ use_tex: If ``True``, allow Matplotlib to use external LaTeX
85
+ rendering, provided it is also enabled globally (see
86
+ :func:`set_latex_enabled`; on by default when ``latex`` is
87
+ found on the system). Otherwise Matplotlib's mathtext is used.
88
+ parent: Parent widget.
89
+ """
90
+ super().__init__(parent)
91
+
92
+ self._editable = False
93
+ self._menu_items = None
94
+ self._renderer = QSvgRenderer(self)
95
+ self._keep_aspect_ratio = keep_aspect_ratio
96
+ self._natural_size: QSize = QSize()
97
+
98
+ # Store current text settings
99
+ self._text: str = f"${text}$" if text else ""
100
+ self._font_size: float = font_size
101
+ self._text_color = QColor(text_color)
102
+ self._use_tex: bool = _latex_enabled and use_tex
103
+ self._pad_inches: float = pad_inches
104
+ self._alignment: Qt.Alignment = alignment
105
+ self._fix_size: bool = fix_size
106
+
107
+ # We do not use QLabel's text painting
108
+ super().setText("")
109
+
110
+ if text:
111
+ self._update_from_text()
112
+
113
+ # ---------- Public API ----------
114
+ def set_menu_items(self, items: List[Dict[str, Any]]) -> None:
115
+ """Set the context-menu item specifications for the label.
116
+
117
+ Args:
118
+ items: List of menu item specification dictionaries. Each dictionary
119
+ may define keys such as ``id``, ``text``, ``enabled``,
120
+ ``checkable``, and ``checked``. A separator is represented by
121
+ ``{"id": "sep"}``.
122
+ """
123
+ self._editable = True
124
+ self._menu_items = items
125
+
126
+ def set_menu_item_checked(self, act_id, checked: bool) -> bool:
127
+ """Update the checked state of a context-menu item.
128
+
129
+ Args:
130
+ act_id: Identifier of the target menu item.
131
+ checked: Checked state to store for the item.
132
+
133
+ Returns:
134
+ bool: ``True`` if the item was found and updated, otherwise
135
+ ``False``.
136
+ """
137
+ if not self._menu_items:
138
+ return False
139
+
140
+ for item in self._menu_items:
141
+ if item.get("id") == "sep":
142
+ continue
143
+ if item.get("id") == act_id:
144
+ # ensure it's checkable (optional; remove if you want strict behavior)
145
+ item["checkable"] = bool(item.get("checkable", True))
146
+ item["checked"] = bool(checked)
147
+ return True
148
+
149
+ return False
150
+
151
+ def set_editable(self, editable: bool) -> None:
152
+ """Enable or disable the custom context menu.
153
+
154
+ Args:
155
+ editable: If ``True``, the widget accepts the configured context menu.
156
+ If ``False``, right clicks do not open the custom menu.
157
+ """
158
+ self._editable = editable
159
+
160
+ def set_menu_item_enabled(self, act_id, enabled: bool) -> bool:
161
+ """Update the enabled state of a context-menu item.
162
+
163
+ Args:
164
+ act_id: Identifier of the target menu item.
165
+ enabled: Enabled state to store for the item.
166
+
167
+ Returns:
168
+ bool: ``True`` if the item was found and updated, otherwise
169
+ ``False``.
170
+ """
171
+ if not self._menu_items:
172
+ return False
173
+
174
+ for item in self._menu_items:
175
+ if item.get("id") == "sep":
176
+ continue
177
+ if item.get("id") == act_id:
178
+ item["enabled"] = bool(enabled)
179
+ return True
180
+
181
+ return False
182
+
183
+ def set_text(
184
+ self,
185
+ text: str,
186
+ font_size: Optional[float] = None,
187
+ pad_inches: Optional[float] = None,
188
+ ) -> None:
189
+ """Update the label text and optional rendering settings.
190
+
191
+ Args:
192
+ text: New text to display. The text is wrapped in ``$...$`` and
193
+ rendered in math mode, so plain words should be written using
194
+ LaTeX text commands such as ``\\mathrm{...}`` when needed.
195
+ font_size: New font size in points. If ``None``, the current value is
196
+ kept.
197
+ pad_inches: New padding value in inches. If ``None``, the current
198
+ value is kept.
199
+ """
200
+ self._text = f"${text}$" if text else ""
201
+ if font_size is not None:
202
+ self._font_size = font_size
203
+ if pad_inches is not None:
204
+ self._pad_inches = pad_inches
205
+
206
+ self._update_from_text()
207
+
208
+ def setAlignment(self, alignment: Qt.Alignment) -> None:
209
+ """Set how the SVG is positioned inside the widget rectangle.
210
+
211
+ Args:
212
+ alignment: Qt alignment flags controlling horizontal and vertical
213
+ positioning of the rendered SVG.
214
+ """
215
+ self._alignment = alignment
216
+ self.update()
217
+
218
+ def alignment(self) -> Qt.Alignment:
219
+ """Return the alignment used for rendering.
220
+
221
+ Returns:
222
+ Qt.Alignment: Current alignment flags for the rendered SVG.
223
+ """
224
+ return self._alignment
225
+
226
+ # ---------- Internal helpers ----------
227
+ def _update_from_text(self) -> None:
228
+ """Regenerate and load the SVG from the current text settings.
229
+
230
+ The SVG is rebuilt using the current text, font size, padding, and
231
+ effective text color. If loading succeeds, the widget's cached natural
232
+ size, layout geometry, and display are refreshed.
233
+ """
234
+ svg_bytes = self._create_text_svg(
235
+ text=self._text,
236
+ font_size=self._font_size,
237
+ pad_inches=self._pad_inches,
238
+ color=self._current_text_color(),
239
+ )
240
+
241
+ if self._renderer.load(QByteArray(svg_bytes)):
242
+ self._update_natural_size()
243
+ self.updateGeometry()
244
+ self.update()
245
+
246
+ def _create_text_svg(
247
+ self,
248
+ text: str,
249
+ font_size: float,
250
+ pad_inches: float = 0.06,
251
+ color: str | tuple[float, float, float, float] = "black",
252
+ ) -> bytes:
253
+ """Render text to SVG bytes using Matplotlib.
254
+
255
+ Uses a standalone :class:`matplotlib.figure.Figure` rather than
256
+ ``pyplot``, so no global Matplotlib state or GUI backend is touched.
257
+
258
+ Args:
259
+ text: Text passed to Matplotlib for rendering. This may be plain
260
+ text, mathtext, or a LaTeX-style string depending on how it was
261
+ prepared before calling this method.
262
+ font_size: Font size in points.
263
+ pad_inches: Padding in inches passed to Matplotlib when saving with
264
+ ``bbox_inches="tight"``.
265
+ color: Text color passed to Matplotlib, as a color name or an
266
+ RGBA tuple of floats in ``[0, 1]``.
267
+
268
+ Returns:
269
+ bytes: Raw SVG data.
270
+ """
271
+ fig = Figure()
272
+ fig.patch.set_alpha(0.0) # Transparent background
273
+
274
+ # Centered text
275
+ fig.text(
276
+ 0.5,
277
+ 0.5,
278
+ text,
279
+ fontsize=font_size,
280
+ ha="center",
281
+ va="center",
282
+ usetex=self._use_tex,
283
+ color=color,
284
+ )
285
+
286
+ buf = io.BytesIO()
287
+ fig.savefig(
288
+ buf,
289
+ format="svg",
290
+ bbox_inches="tight",
291
+ pad_inches=pad_inches,
292
+ transparent=True,
293
+ )
294
+ return buf.getvalue()
295
+
296
+ def _update_natural_size(self) -> None:
297
+ """Update the cached natural size from the loaded SVG.
298
+
299
+ If the renderer reports a valid default size, that size is cached and
300
+ applied either as the widget's fixed size or minimum size, depending on
301
+ ``self._fix_size``. If the SVG size is invalid, the cached size is
302
+ cleared.
303
+ """
304
+ size = self._renderer.defaultSize()
305
+ if size.isValid():
306
+ self._natural_size = size
307
+ if self._fix_size:
308
+ self.setFixedSize(size)
309
+ else:
310
+ self.setMinimumSize(size)
311
+ else:
312
+ self._natural_size = QSize()
313
+
314
+ def _current_text_color(self):
315
+ """Return the effective text color for the current widget state.
316
+
317
+ When the widget is disabled, the base text color keeps its RGB values
318
+ but adopts the alpha channel of the disabled ``WindowText`` palette
319
+ color.
320
+
321
+ Returns:
322
+ tuple[float, float, float, float]: RGBA color components in the
323
+ normalized format returned by :meth:`QColor.getRgbF`.
324
+ """
325
+ base = QColor(self._text_color)
326
+
327
+ if not self.isEnabled():
328
+ ref = self.palette().color(QPalette.Disabled, QPalette.WindowText)
329
+ base.setAlphaF(ref.alphaF())
330
+
331
+ return base.getRgbF()
332
+
333
+ # ---------- QWidget overrides ----------
334
+ def changeEvent(self, event) -> None:
335
+ """Handle widget state changes.
336
+
337
+ When the enabled or disabled state changes, the SVG is regenerated so
338
+ the text appearance matches the current palette state.
339
+
340
+ Args:
341
+ event: Qt change event delivered to the widget.
342
+ """
343
+ super().changeEvent(event)
344
+ if event.type() == QEvent.EnabledChange:
345
+ self._update_from_text()
346
+
347
+ def contextMenuEvent(self, event):
348
+ """Show the configured context menu and emit the selected action.
349
+
350
+ The menu is built from the stored menu item specifications. For
351
+ checkable actions, the method updates the stored checked state and emits
352
+ :attr:`rightClickRequested` with the action id and the resulting checked
353
+ state.
354
+
355
+ Args:
356
+ event: Qt context-menu event.
357
+ """
358
+ parent = self.parentWidget()
359
+ if parent is not None:
360
+ parent.contextMenuEvent(event)
361
+ event.accept()
362
+ if not self._editable:
363
+ return
364
+ if not self._menu_items:
365
+ return
366
+
367
+ menu = QMenu(self)
368
+
369
+ # map id -> underlying dict so we can update checked state
370
+ item_by_id = {}
371
+
372
+ for item in self._menu_items:
373
+ act_id = item.get("id")
374
+ if act_id == "sep":
375
+ menu.addSeparator()
376
+ continue
377
+
378
+ text = str(item.get("text", act_id if act_id is not None else "None"))
379
+ enabled = bool(item.get("enabled", True))
380
+ checkable = bool(item.get("checkable", False))
381
+ checked = bool(item.get("checked", False))
382
+
383
+ act = menu.addAction(text)
384
+ act.setEnabled(enabled)
385
+ act.setCheckable(checkable)
386
+ if checkable:
387
+ act.setChecked(checked)
388
+ act.setData(act_id)
389
+ item_by_id[act_id] = item
390
+
391
+ action = menu.exec(event.globalPos())
392
+ if action is None:
393
+ return
394
+
395
+ act_id = action.data()
396
+ if act_id is None:
397
+ act_id = action.text()
398
+
399
+ # Determine (and ensure) the new checked state
400
+ new_checked = False
401
+ if action.isCheckable():
402
+ old_checked = bool(item_by_id.get(act_id, {}).get("checked", action.isChecked()))
403
+ new_checked = bool(action.isChecked())
404
+
405
+ # Safety: if Qt didn't toggle for some reason, toggle ourselves once
406
+ if new_checked == old_checked:
407
+ new_checked = not old_checked
408
+ action.setChecked(new_checked)
409
+
410
+ # Persist back to spec
411
+ if act_id in item_by_id:
412
+ item_by_id[act_id]["checked"] = new_checked
413
+
414
+ self.rightClickRequested.emit(act_id, new_checked)
415
+
416
+ def sizeHint(self) -> QSize:
417
+ """Return the preferred size for layouts.
418
+
419
+ Returns:
420
+ QSize: The cached natural SVG size when available, otherwise the
421
+ renderer default size, otherwise the base :class:`QLabel` size hint.
422
+ """
423
+ if self._natural_size.isValid():
424
+ return self._natural_size
425
+ default = self._renderer.defaultSize()
426
+ if default.isValid():
427
+ return default
428
+ return super().sizeHint()
429
+
430
+ def minimumSizeHint(self) -> QSize:
431
+ """Return the minimum size hint for layouts.
432
+
433
+ Returns:
434
+ QSize: The cached natural SVG size when available, otherwise the
435
+ renderer default size, otherwise the base :class:`QLabel` minimum
436
+ size hint.
437
+ """
438
+ if self._natural_size.isValid():
439
+ return self._natural_size
440
+ size = self._renderer.defaultSize()
441
+ if size.isValid():
442
+ return size
443
+ return super().minimumSizeHint()
444
+
445
+ def paintEvent(self, event) -> None:
446
+ """Paint the rendered SVG into the widget.
447
+
448
+ If aspect-ratio preservation is enabled, the SVG is scaled uniformly and
449
+ positioned according to the current alignment. Otherwise, it is stretched
450
+ to fill the widget rectangle.
451
+
452
+ Args:
453
+ event: Qt paint event.
454
+ """
455
+ if not self._renderer.isValid():
456
+ return super().paintEvent(event)
457
+
458
+ painter = QPainter(self)
459
+ widget_rect = self.rect()
460
+
461
+ if not self._keep_aspect_ratio:
462
+ self._renderer.render(painter, widget_rect)
463
+ return
464
+
465
+ svg_size = self._renderer.defaultSize()
466
+ if not svg_size.isValid():
467
+ self._renderer.render(painter, widget_rect)
468
+ return
469
+
470
+ svg_w = svg_size.width()
471
+ svg_h = svg_size.height()
472
+ if svg_w <= 0 or svg_h <= 0:
473
+ self._renderer.render(painter, widget_rect)
474
+ return
475
+
476
+ # Uniform scale factor
477
+ scale = min(
478
+ widget_rect.width() / svg_w,
479
+ widget_rect.height() / svg_h,
480
+ )
481
+
482
+ target_w = svg_w * scale
483
+ target_h = svg_h * scale
484
+
485
+ align = self._alignment
486
+
487
+ # horizontal
488
+ if align & Qt.AlignLeft:
489
+ x = widget_rect.left()
490
+ elif align & Qt.AlignRight:
491
+ x = widget_rect.right() - target_w
492
+ else: # center (default)
493
+ x = widget_rect.left() + (widget_rect.width() - target_w) / 2.0
494
+
495
+ # vertical
496
+ if align & Qt.AlignTop:
497
+ y = widget_rect.top()
498
+ elif align & Qt.AlignBottom:
499
+ y = widget_rect.bottom() - target_h
500
+ else: # center (default)
501
+ y = widget_rect.top() + (widget_rect.height() - target_h) / 2.0
502
+
503
+ target_rect = QRectF(x, y, target_w, target_h)
504
+ self._renderer.render(painter, target_rect)
505
+
@@ -0,0 +1,18 @@
1
+ """Parameter input widgets: line edits, toggles, combo boxes, and sliders."""
2
+
3
+ from .base import ParameterWidgetBase
4
+ from .combo_box import ParameterComboBox
5
+ from .line_edit import ParameterLineEdit
6
+ from .numeric_line_edit import NumericLineEdit
7
+ from .sliders import * # noqa: F401,F403
8
+ from .sliders import __all__ as _sliders_all
9
+ from .toggle import ParameterToggle
10
+
11
+ __all__ = [
12
+ "NumericLineEdit",
13
+ "ParameterComboBox",
14
+ "ParameterLineEdit",
15
+ "ParameterToggle",
16
+ "ParameterWidgetBase",
17
+ *_sliders_all,
18
+ ]