From 5206528feccaf192f3d5872ac785470562b493f9 Mon Sep 17 00:00:00 2001 From: David Perl Date: Mon, 17 Feb 2025 15:38:05 +0100 Subject: [PATCH] feat: add expandable/collapsible frame --- bec_widgets/qt_utils/expandable_frame.py | 64 ++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 bec_widgets/qt_utils/expandable_frame.py diff --git a/bec_widgets/qt_utils/expandable_frame.py b/bec_widgets/qt_utils/expandable_frame.py new file mode 100644 index 00000000..b9dc9487 --- /dev/null +++ b/bec_widgets/qt_utils/expandable_frame.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +from bec_qthemes import material_icon +from PySide6.QtWidgets import QHBoxLayout, QSizePolicy, QToolButton +from qtpy.QtWidgets import QFrame, QLabel, QLayout, QVBoxLayout, QWidget + +from bec_widgets.qt_utils.error_popups import SafeProperty, SafeSlot + + +class ExpandableGroupFrame(QFrame): + + EXPANDED_ICON_NAME: str = "collapse_all" + COLLAPSED_ICON_NAME: str = "expand_all" + + def __init__(self, title: str, parent: QWidget | None = None, expanded: bool = True) -> None: + super().__init__(parent=parent) + self._expanded = expanded + + self.setFrameStyle(QFrame.Shape.StyledPanel | QFrame.Shadow.Plain) + self.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum) + self._layout = QVBoxLayout() + self._layout.setContentsMargins(0, 0, 0, 0) + self.setLayout(self._layout) + self._title_layout = QHBoxLayout() + self._layout.addLayout(self._title_layout) + self._expansion_button = QToolButton() + self._update_icon() + self._title = QLabel(f"{title}") + self._title_layout.addWidget(self._expansion_button) + self._title_layout.addWidget(self._title) + + self._contents = QWidget() + self._layout.addWidget(self._contents) + + self._expansion_button.clicked.connect(self.switch_expanded_state) + self.expanded = self._expanded # type: ignore + + def set_layout(self, layout: QLayout) -> None: + self._contents.setLayout(layout) + self._contents.layout().setContentsMargins(0, 0, 0, 0) # type: ignore + + @SafeSlot() + def switch_expanded_state(self): + self.expanded = not self.expanded # type: ignore + self._update_icon() + + @SafeProperty(bool) + def expanded(self): # type: ignore + return self._expanded + + @expanded.setter + def expanded(self, expanded: bool): + self._expanded = expanded + self._contents.setVisible(expanded) + self.updateGeometry() + + def _update_icon(self): + self._expansion_button.setIcon( + material_icon(icon_name=self.EXPANDED_ICON_NAME, size=(10, 10), convert_to_pixmap=False) + if self.expanded + else material_icon( + icon_name=self.COLLAPSED_ICON_NAME, size=(10, 10), convert_to_pixmap=False + ) + )