QCollapsible for Collapsible Section Control (#37)

* Update changelog to ingnore virtual environment

* wip

* wip

* Working animation

* WIP Implement tests

* All tests are passing

* convert to camalCase

* Change function name to match functionality

* convert pyside to qtcompat

* move animation utils to main module

* remove seperators

* protect util functions

* add example

* remove seperators from test file

* suggestions

* Passing tests and ability to initialize expansion

* Ensure that the test will be passed in any screen resolution

* replace quick functions with parameters

* Update src/superqt/collapsible/_collapsible.py

Fix initial text

Co-authored-by: Talley Lambert <talley.lambert@gmail.com>

* Update src/superqt/collapsible/_collapsible.py

Remote WindowFlags to prevent compatiblity issue.

Co-authored-by: Talley Lambert <talley.lambert@gmail.com>

* merge internal expand and collapse into one function

* Update src/superqt/collapsible/_collapsible.py

* Update tests/test_collapsible.py

Co-authored-by: Talley Lambert <talley.lambert@gmail.com>
This commit is contained in:
Mustafa Al Ibrahim
2021-11-20 17:27:26 +03:00
committed by GitHub
parent 8001022e18
commit 789b98f892
7 changed files with 194 additions and 0 deletions

1
.gitignore vendored
View File

@@ -9,6 +9,7 @@ __pycache__/
# Distribution / packaging
.Python
env/
.venv/
build/
develop-eggs/
dist/

15
examples/collapsible.py Normal file
View File

@@ -0,0 +1,15 @@
"""Example for QCollapsible"""
from PySide2.QtWidgets import QLabel
from superqt import QCollapsible
from superqt.qtcompat.QtWidgets import QApplication, QPushButton, QLabel
app = QApplication([])
collapsible = QCollapsible("Advanced analysis")
collapsible.addWidget(QLabel("This is the inside of the collapsible frame"))
for i in range(10):
collapsible.addWidget(QPushButton(f"Content button {i + 1}"))
collapsible.expand(animate=False)
collapsible.show()
app.exec_()

View File

@@ -6,6 +6,7 @@ except ImportError:
from ._eliding_label import QElidingLabel
from .collapsible import QCollapsible
from .combobox import QEnumComboBox
from .sliders import (
QDoubleRangeSlider,
@@ -33,4 +34,5 @@ __all__ = [
"QMessageHandler",
"QRangeSlider",
"QEnumComboBox",
"QCollapsible",
]

View File

@@ -0,0 +1,3 @@
from ._collapsible import QCollapsible
__all__ = ["QCollapsible"]

View File

@@ -0,0 +1,107 @@
"""A collapsible widget to hide and unhide child widgets"""
from typing import Optional
from ..qtcompat.QtCore import QAbstractAnimation, QEasingCurve, QPropertyAnimation, Qt, QMargins
from ..qtcompat.QtWidgets import QFrame, QPushButton, QVBoxLayout, QWidget
class QCollapsible(QFrame):
"""
A collapsible widget to hide and unhide child widgets.
Based on https://stackoverflow.com/a/68141638
"""
_EXPANDED = ""
_COLLAPSED = ""
def __init__(self, title: str = "", parent: Optional[QWidget] = None):
super().__init__(parent)
self._locked = False
self._toggle_btn = QPushButton(self._COLLAPSED + title)
self._toggle_btn.setCheckable(True)
self._toggle_btn.setChecked(False)
self._toggle_btn.setStyleSheet("text-align: left; background: transparent;")
self._toggle_btn.clicked.connect(self._toggle)
# frame layout
self.setLayout(QVBoxLayout())
self.layout().setAlignment(Qt.AlignmentFlag.AlignTop)
self.layout().addWidget(self._toggle_btn)
# Create animators
self._animation = QPropertyAnimation(self)
self._animation.setPropertyName(b"maximumHeight")
self._animation.setStartValue(0)
self.setDuration(300)
self.setEasingCurve(QEasingCurve.Type.InOutCubic)
# default content widget
_content = QWidget()
_content.setLayout(QVBoxLayout())
_content.setMaximumHeight(0)
_content.layout().setContentsMargins(QMargins(5,0,0,0))
self.setContent(_content)
def setText(self, text: str):
current = self._toggle_btn.text()[: len(self._EXPANDED)]
self._toggle_btn.setText(current + text)
def setContent(self, content: QWidget):
self._content = content
self.layout().addWidget(self._content)
self._animation.setTargetObject(content)
def content(self) -> QWidget:
return self._content
def setDuration(self, msecs: int):
self._animation.setDuration(msecs)
def setEasingCurve(self, easing: QEasingCurve):
self._animation.setEasingCurve(easing)
def addWidget(self, widget: QWidget):
self._content.layout().addWidget(widget)
def removeWidget(self, widget: QWidget):
self._content.layout().removeWidget(widget)
def expand(self, animate: bool = True):
self._expand_collapse(QAbstractAnimation.Direction.Forward, animate)
def collapse(self, animate: bool = True):
self._expand_collapse(QAbstractAnimation.Direction.Backward, animate)
def expanded(self):
return self._toggle_btn.isChecked()
def _expand_collapse(self, direction: QAbstractAnimation.Direction, animate:bool = True):
if self._locked is True:
return
forward = direction == QAbstractAnimation.Direction.Forward
text = self._EXPANDED if forward else self._COLLAPSED
self._toggle_btn.setChecked(forward)
self._toggle_btn.setText(text + self._toggle_btn.text()[len(self._EXPANDED) :])
if animate:
self._animation.setDirection(direction)
self._animation.setEndValue(self._content.sizeHint().height() + 10)
self._animation.start()
else:
height = 0 if forward==False else (self._content.sizeHint().height() + 10)
self._content.setMaximumHeight(height)
def _toggle(self):
if self._locked is True:
self._toggle_btn.setChecked(not self._toggle_btn.isChecked)
self.expand() if self._toggle_btn.isChecked() else self.collapse()
def setLocked(self, locked: bool = True):
self._locked = locked
def locked(self) -> bool:
return self._locked

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

66
tests/test_collapsible.py Normal file
View File

@@ -0,0 +1,66 @@
"""A test module for testing collapsible"""
from superqt import QCollapsible
from superqt.qtcompat.QtCore import QEasingCurve
from superqt.qtcompat.QtWidgets import QPushButton
def test_checked_initialization(qtbot):
"""Test simple collapsible"""
wdg1 = QCollapsible("Advanced analysis")
wdg1.expand(False)
assert wdg1.expanded() is True
assert wdg1._content.maximumHeight() > 0
wdg2 = QCollapsible("Advanced analysis")
wdg1.collapse(False)
assert wdg2.expanded() is False
assert wdg2._content.maximumHeight() == 0
def test_content_hide_show(qtbot):
"""Test collapsible with content"""
# Create child component
collapsible = QCollapsible("Advanced analysis")
for i in range(10):
collapsible.addWidget(QPushButton(f"Content button {i + 1}"))
collapsible.collapse(False)
assert collapsible.expanded() is False
assert collapsible._content.maximumHeight() == 0
collapsible.expand(False)
assert collapsible.expanded() is True
assert collapsible._content.maximumHeight() > 0
def test_locking(qtbot):
"""Test locking collapsible"""
wdg1 = QCollapsible()
assert wdg1.locked() is False
wdg1.setLocked(True)
assert wdg1.locked() is True
# Simulate button press
wdg1._toggle_btn.setChecked(True)
wdg1._toggle()
assert wdg1.expanded() is False
def test_changing_animation_settings(qtbot):
"""Quick test for changing animation settings"""
wdg = QCollapsible()
wdg.setDuration(600)
wdg.setEasingCurve(QEasingCurve.Type.InElastic)
assert wdg._animation.easingCurve() == QEasingCurve.Type.InElastic
assert wdg._animation.duration() == 600
def test_changing_content(qtbot):
"""Test changing the content"""
content = QPushButton()
wdg = QCollapsible()
wdg.setContent(content)
assert wdg._content == content