from PyQt6.QtWidgets import QGroupBox, QWidget, QVBoxLayout from PyQt6.QtCore import QPropertyAnimation class CollapsibleGroupBox(QGroupBox): def __init__(self, title="", parent=None): super().__init__(title, parent) self.setCheckable(True) self.setChecked(True) self.animation = QPropertyAnimation(self, b"maximumHeight") self.animation.setDuration(200) # Connect the toggled signal to our animation function self.toggled.connect(self._toggle_animation) self._full_height = 0 def set_content_layout(self, layout): # We need a wrapper widget to hold the layout self.content_widget = QWidget() self.content_widget.setStyleSheet("background-color: transparent;") self.content_widget.setLayout(layout) main_layout = QVBoxLayout() main_layout.setContentsMargins(0, 0, 0, 0) main_layout.addWidget(self.content_widget) self.setLayout(main_layout) def _toggle_animation(self, checked): if not hasattr(self, 'content_widget'): return if checked: # Expand: show content first, then animate self.content_widget.setVisible(True) target_height = self.sizeHint().height() self.animation.stop() self.animation.setStartValue(self.height()) self.animation.setEndValue(target_height) self.animation.finished.connect(self._on_expand_finished) self.animation.start() else: # Collapse self.animation.stop() self.animation.setStartValue(self.height()) self.animation.setEndValue(32) self.animation.finished.connect(self._on_collapse_finished) self.animation.start() def _on_expand_finished(self): # Remove height constraint so content can grow dynamically self.setMaximumHeight(16777215) try: self.animation.finished.disconnect(self._on_expand_finished) except TypeError: pass def _on_collapse_finished(self): if not self.isChecked(): self.content_widget.setVisible(False) try: self.animation.finished.disconnect(self._on_collapse_finished) except TypeError: pass