from PyQt5.QtCore import QParallelAnimationGroup, Qt, QPropertyAnimation, QAbstractAnimation from PyQt5.QtWidgets import QWidget, QFrame, QToolButton, QGridLayout, QSizePolicy, QLayout from rare.utils.misc import icon # https://newbedev.com/how-to-make-an-expandable-collapsable-section-widget-in-qt class CollabsibleWidget(QWidget): def __init__( self, child_layout: QLayout = None, title: str = "", animation_duration: int = 200, parent=None ): """ References: # Adapted from c++ version https://stackoverflow.com/questions/32476006/how-to-make-an-expandable-collapsable-section-widget-in-qt """ super(CollabsibleWidget, self).__init__(parent=parent) self.animationDuration = animation_duration self.toggleAnimation = QParallelAnimationGroup() self.contentArea = QWidget() self.headerLine = QFrame() self.toggleButton = QToolButton() self.mainLayout = QGridLayout() toggleButton = self.toggleButton toggleButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) toggleButton.setIcon(icon("fa.arrow-right")) toggleButton.setText(str(title)) toggleButton.setCheckable(True) toggleButton.setChecked(False) headerLine = self.headerLine headerLine.setFrameShape(QFrame.StyledPanel) headerLine.setFrameShadow(QFrame.Plain) headerLine.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.contentArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) # start out collapsed self.contentArea.setMaximumHeight(0) self.contentArea.setMinimumHeight(0) # let the entire widget grow and shrink with its content toggleAnimation = self.toggleAnimation toggleAnimation.addAnimation(QPropertyAnimation(self, b"minimumHeight")) toggleAnimation.addAnimation(QPropertyAnimation(self, b"maximumHeight")) toggleAnimation.addAnimation(QPropertyAnimation(self.contentArea, b"maximumHeight")) # don't waste space mainLayout = self.mainLayout mainLayout.setVerticalSpacing(0) mainLayout.setContentsMargins(0, 0, 0, 0) row = 0 mainLayout.addWidget(self.toggleButton, row, 0, 1, 1, Qt.AlignLeft) mainLayout.addWidget(self.headerLine, row, 2, 1, 1) row += 1 mainLayout.addWidget(self.contentArea, row, 0, 1, 3) self.setLayout(self.mainLayout) def start_animation(checked): arrow_type = icon("fa.arrow-down") if checked else icon("fa.arrow-right") direction = QAbstractAnimation.Forward if checked else QAbstractAnimation.Backward toggleButton.setIcon(arrow_type) self.toggleAnimation.setDirection(direction) self.toggleAnimation.start() self.toggleButton.clicked.connect(start_animation) if child_layout: self.setContentLayout(child_layout) def setContentLayout(self, content_layout: QLayout): # Not sure if this is equivalent to self.contentArea.destroy() self.contentArea.destroy() self.contentArea.setLayout(content_layout) collapsedHeight = self.sizeHint().height() - self.contentArea.maximumHeight() contentHeight = content_layout.sizeHint().height() for i in range(self.toggleAnimation.animationCount() - 1): spoilerAnimation = self.toggleAnimation.animationAt(i) spoilerAnimation.setDuration(self.animationDuration) spoilerAnimation.setStartValue(collapsedHeight) spoilerAnimation.setEndValue(collapsedHeight + contentHeight) contentAnimation = self.toggleAnimation.animationAt(self.toggleAnimation.animationCount() - 1) contentAnimation.setDuration(self.animationDuration) contentAnimation.setStartValue(0) contentAnimation.setEndValue(contentHeight)