0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-14 03:31:50 +02:00

feat(collapsible_panel_manager): panel manager to handle collapsing and expanding widgets from the main widget added

This commit is contained in:
2024-12-10 23:32:44 +01:00
parent b467b29f77
commit a434d3ee57
3 changed files with 743 additions and 3 deletions

View File

@ -0,0 +1,380 @@
from __future__ import annotations
import sys
from typing import Literal
import pyqtgraph as pg
from qtpy.QtCore import Property, QEasingCurve, QObject, QPropertyAnimation
from qtpy.QtWidgets import (
QApplication,
QHBoxLayout,
QMainWindow,
QPushButton,
QSizePolicy,
QVBoxLayout,
QWidget,
)
from typeguard import typechecked
from bec_widgets.widgets.containers.layout_manager.layout_manager import LayoutManagerWidget
class DimensionAnimator(QObject):
"""
Helper class to animate the size of a panel widget.
"""
def __init__(self, panel_widget: QWidget, direction: str):
super().__init__()
self.panel_widget = panel_widget
self.direction = direction
self._size = 0
@Property(int)
def panel_width(self):
"""
Returns the current width of the panel widget.
"""
return self._size
@panel_width.setter
def panel_width(self, val: int):
"""
Set the width of the panel widget.
Args:
val(int): The width to set.
"""
self._size = val
self.panel_widget.setFixedWidth(val)
@Property(int)
def panel_height(self):
"""
Returns the current height of the panel widget.
"""
return self._size
@panel_height.setter
def panel_height(self, val: int):
"""
Set the height of the panel widget.
Args:
val(int): The height to set.
"""
self._size = val
self.panel_widget.setFixedHeight(val)
class CollapsiblePanelManager(QObject):
"""
Manager class to handle collapsible panels from a main widget using LayoutManagerWidget.
"""
def __init__(self, layout_manager: LayoutManagerWidget, reference_widget: QWidget, parent=None):
super().__init__(parent)
self.layout_manager = layout_manager
self.reference_widget = reference_widget
self.animations = {}
self.panels = {}
self.direction_settings = {
"left": {"property": b"maximumWidth", "default_size": 200},
"right": {"property": b"maximumWidth", "default_size": 200},
"top": {"property": b"maximumHeight", "default_size": 150},
"bottom": {"property": b"maximumHeight", "default_size": 150},
}
def add_panel(
self,
direction: Literal["left", "right", "top", "bottom"],
panel_widget: QWidget,
target_size: int | None = None,
duration: int = 300,
):
"""
Add a panel widget to the layout manager.
Args:
direction(Literal["left", "right", "top", "bottom"]): Direction of the panel.
panel_widget(QWidget): The panel widget to add.
target_size(int, optional): The target size of the panel. Defaults to None.
duration(int): The duration of the animation in milliseconds. Defaults to 300.
"""
if direction not in self.direction_settings:
raise ValueError("Direction must be one of 'left', 'right', 'top', 'bottom'.")
if target_size is None:
target_size = self.direction_settings[direction]["default_size"]
self.layout_manager.add_widget_relative(
widget=panel_widget, reference_widget=self.reference_widget, position=direction
)
panel_widget.setVisible(False)
# Set initial constraints as flexible
if direction in ["left", "right"]:
panel_widget.setMaximumWidth(0)
panel_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
else:
panel_widget.setMaximumHeight(0)
panel_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.panels[direction] = {
"widget": panel_widget,
"direction": direction,
"target_size": target_size,
"duration": duration,
"animator": None,
}
def toggle_panel(
self,
direction: Literal["left", "right", "top", "bottom"],
target_size: int | None = None,
duration: int | None = None,
easing_curve: QEasingCurve = QEasingCurve.InOutQuad,
ensure_max: bool = False,
scale: float | None = None,
animation: bool = True,
):
"""
Toggle the specified panel.
Parameters:
direction (Literal["left", "right", "top", "bottom"]): Direction of the panel to toggle.
target_size (int, optional): Override target size for this toggle.
duration (int, optional): Override the animation duration.
easing_curve (QEasingCurve): Animation easing curve.
ensure_max (bool): If True, animate as a fixed-size panel.
scale (float, optional): If provided, calculate target_size from main widget size.
animation (bool): If False, no animation is performed; panel instantly toggles.
"""
if direction not in self.panels:
raise ValueError(f"No panel found in direction '{direction}'.")
panel_info = self.panels[direction]
panel_widget = panel_info["widget"]
dir_settings = self.direction_settings[direction]
# Determine final target size
if scale is not None:
main_rect = self.reference_widget.geometry()
if direction in ["left", "right"]:
computed_target = int(main_rect.width() * scale)
else:
computed_target = int(main_rect.height() * scale)
final_target_size = computed_target
else:
if target_size is None:
final_target_size = panel_info["target_size"]
else:
final_target_size = target_size
if duration is None:
duration = panel_info["duration"]
expanding_property = dir_settings["property"]
currently_visible = panel_widget.isVisible()
if ensure_max:
if panel_info["animator"] is None:
panel_info["animator"] = DimensionAnimator(panel_widget, direction)
animator = panel_info["animator"]
if direction in ["left", "right"]:
prop_name = b"panel_width"
else:
prop_name = b"panel_height"
else:
animator = None
prop_name = expanding_property
if currently_visible:
# Hide the panel
if ensure_max:
start_value = final_target_size
end_value = 0
finish_callback = lambda w=panel_widget, d=direction: self._after_hide_reset(w, d)
else:
start_value = (
panel_widget.width()
if direction in ["left", "right"]
else panel_widget.height()
)
end_value = 0
finish_callback = lambda w=panel_widget: w.setVisible(False)
else:
# Show the panel
start_value = 0
end_value = final_target_size
finish_callback = None
if ensure_max:
# Fix panel exactly
if direction in ["left", "right"]:
panel_widget.setMinimumWidth(0)
panel_widget.setMaximumWidth(final_target_size)
panel_widget.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
else:
panel_widget.setMinimumHeight(0)
panel_widget.setMaximumHeight(final_target_size)
panel_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
else:
# Flexible mode
if direction in ["left", "right"]:
panel_widget.setMinimumWidth(0)
panel_widget.setMaximumWidth(final_target_size)
panel_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
else:
panel_widget.setMinimumHeight(0)
panel_widget.setMaximumHeight(final_target_size)
panel_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
panel_widget.setVisible(True)
if not animation:
# No animation: instantly set final state
if end_value == 0:
# Hiding
if ensure_max:
# Reset after hide
self._after_hide_reset(panel_widget, direction)
else:
panel_widget.setVisible(False)
else:
# Showing
if ensure_max:
# Already set fixed size
if direction in ["left", "right"]:
panel_widget.setFixedWidth(end_value)
else:
panel_widget.setFixedHeight(end_value)
else:
# Just set maximum dimension
if direction in ["left", "right"]:
panel_widget.setMaximumWidth(end_value)
else:
panel_widget.setMaximumHeight(end_value)
return
# With animation
animation = QPropertyAnimation(animator if ensure_max else panel_widget, prop_name)
animation.setDuration(duration)
animation.setStartValue(start_value)
animation.setEndValue(end_value)
animation.setEasingCurve(easing_curve)
if end_value == 0 and finish_callback:
animation.finished.connect(finish_callback)
elif end_value == 0 and not finish_callback:
animation.finished.connect(lambda w=panel_widget: w.setVisible(False))
animation.start()
self.animations[panel_widget] = animation
@typechecked
def _after_hide_reset(
self, panel_widget: QWidget, direction: Literal["left", "right", "top", "bottom"]
):
"""
Reset the panel widget after hiding it in ensure_max mode.
Args:
panel_widget(QWidget): The panel widget to reset.
direction(Literal["left", "right", "top", "bottom"]): The direction of the panel.
"""
# Called after hiding a panel in ensure_max mode
panel_widget.setVisible(False)
if direction in ["left", "right"]:
panel_widget.setMinimumWidth(0)
panel_widget.setMaximumWidth(0)
panel_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
else:
panel_widget.setMinimumHeight(0)
panel_widget.setMaximumHeight(16777215)
panel_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
####################################################################################################
# The following code is for the GUI control panel to interact with the CollapsiblePanelManager.
# It is not covered by any tests as it serves only as an example for the CollapsiblePanelManager class.
####################################################################################################
class MainWindow(QMainWindow): # pragma: no cover
def __init__(self):
super().__init__()
self.setWindowTitle("Panels with ensure_max, scale, and animation toggle")
self.resize(800, 600)
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
main_layout.setContentsMargins(10, 10, 10, 10)
main_layout.setSpacing(10)
# Buttons
buttons_layout = QHBoxLayout()
self.btn_left = QPushButton("Toggle Left (ensure_max=True)")
self.btn_top = QPushButton("Toggle Top (scale=0.5, no animation)")
self.btn_right = QPushButton("Toggle Right (ensure_max=True, scale=0.3)")
self.btn_bottom = QPushButton("Toggle Bottom (no animation)")
buttons_layout.addWidget(self.btn_left)
buttons_layout.addWidget(self.btn_top)
buttons_layout.addWidget(self.btn_right)
buttons_layout.addWidget(self.btn_bottom)
main_layout.addLayout(buttons_layout)
self.layout_manager = LayoutManagerWidget()
main_layout.addWidget(self.layout_manager)
# Main widget
self.main_plot = pg.PlotWidget()
self.main_plot.plot([1, 2, 3, 4], [4, 3, 2, 1])
self.layout_manager.add_widget(self.main_plot, 0, 0)
self.panel_manager = CollapsiblePanelManager(self.layout_manager, self.main_plot)
# Panels
self.left_panel = pg.PlotWidget()
self.left_panel.plot([1, 2, 3], [3, 2, 1])
self.panel_manager.add_panel("left", self.left_panel, target_size=200)
self.right_panel = pg.PlotWidget()
self.right_panel.plot([10, 20, 30], [1, 10, 1])
self.panel_manager.add_panel("right", self.right_panel, target_size=200)
self.top_panel = pg.PlotWidget()
self.top_panel.plot([1, 2, 3], [1, 2, 3])
self.panel_manager.add_panel("top", self.top_panel, target_size=150)
self.bottom_panel = pg.PlotWidget()
self.bottom_panel.plot([2, 4, 6], [10, 5, 10])
self.panel_manager.add_panel("bottom", self.bottom_panel, target_size=150)
# Connect buttons
# Left with ensure_max
self.btn_left.clicked.connect(
lambda: self.panel_manager.toggle_panel("left", ensure_max=True)
)
# Top with scale=0.5 and no animation
self.btn_top.clicked.connect(
lambda: self.panel_manager.toggle_panel("top", scale=0.5, animation=False)
)
# Right with ensure_max, scale=0.3
self.btn_right.clicked.connect(
lambda: self.panel_manager.toggle_panel("right", ensure_max=True, scale=0.3)
)
# Bottom no animation
self.btn_bottom.clicked.connect(
lambda: self.panel_manager.toggle_panel("bottom", target_size=100, animation=False)
)
if __name__ == "__main__": # pragma: no cover
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec())

View File

@ -408,9 +408,9 @@ class LayoutManagerWidget(QWidget):
def remove(
self,
row: Optional[int] = None,
col: Optional[int] = None,
coordinates: Optional[Tuple[int, int]] = None,
row: int | None = None,
col: int | None = None,
coordinates: Tuple[int, int] | None = None,
) -> None:
"""
Remove a widget from the layout. Can be removed by widget ID or by coordinates.

View File

@ -0,0 +1,360 @@
import pytest
from qtpy.QtCore import QEasingCurve
from qtpy.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
from bec_widgets.qt_utils.collapsible_panel_manager import (
CollapsiblePanelManager,
DimensionAnimator,
)
from bec_widgets.widgets.containers.layout_manager.layout_manager import LayoutManagerWidget
# NOTE the following fixtures has to be done with using .show() method, otherwise qt .isVisible() check will not work!
@pytest.fixture
def reference_widget(qtbot):
widget = QWidget()
layout = QVBoxLayout(widget)
btn = QPushButton("Reference")
layout.addWidget(btn)
qtbot.addWidget(widget)
widget.show()
return widget
@pytest.fixture
def layout_manager(qtbot, reference_widget):
manager = LayoutManagerWidget()
qtbot.addWidget(manager)
manager.show()
manager.add_widget(reference_widget, row=0, col=0)
return manager
@pytest.fixture
def panel_manager(layout_manager, reference_widget):
manager = CollapsiblePanelManager(layout_manager, reference_widget)
return manager
@pytest.fixture
def test_panel_widget():
widget = QWidget()
return widget
def test_dimension_animator_width_setting(test_panel_widget):
animator = DimensionAnimator(test_panel_widget, "left")
animator.panel_width = 100
assert animator.panel_width == 100
assert test_panel_widget.width() == 100
def test_dimension_animator_height_setting(qtbot, test_panel_widget):
animator = DimensionAnimator(test_panel_widget, "top")
animator.panel_height = 150
assert animator.panel_height == 150
assert test_panel_widget.height() == 150
def test_add_panel(panel_manager, test_panel_widget):
panel_manager.add_panel("left", test_panel_widget, target_size=200)
# After adding, panel should be hidden and have max width set to 0 (if left/right)
assert panel_manager.panels["left"]["widget"] == test_panel_widget
assert not test_panel_widget.isVisible()
assert test_panel_widget.maximumWidth() == 0
def test_add_panel_no_target_size(panel_manager, test_panel_widget):
# Using default target size for direction "top"
panel_manager.add_panel("top", test_panel_widget)
assert panel_manager.panels["top"]["target_size"] == 150
assert not test_panel_widget.isVisible()
def test_add_panel_invalid_direction(panel_manager, test_panel_widget):
with pytest.raises(ValueError) as exc_info:
panel_manager.add_panel("invalid", test_panel_widget)
assert "Direction must be one of 'left', 'right', 'top', 'bottom'." in str(exc_info.value)
def test_toggle_panel_show(panel_manager, test_panel_widget):
panel_manager.add_panel("left", test_panel_widget, target_size=200)
# Initially hidden
assert not test_panel_widget.isVisible()
panel_manager.toggle_panel("left", animation=False)
# After toggle, panel should become visible
assert test_panel_widget.isVisible()
def test_toggle_panel_hide(panel_manager, test_panel_widget):
panel_manager.add_panel("left", test_panel_widget, target_size=200)
panel_manager.toggle_panel("left", animation=False)
# Now panel is visible
assert test_panel_widget.isVisible()
# Toggle again to hide
panel_manager.toggle_panel("left", animation=False)
# Should be invisible after second toggle
assert not test_panel_widget.isVisible()
def test_toggle_panel_scale(panel_manager, test_panel_widget, reference_widget):
reference_widget.resize(800, 600) # Set a known size
panel_manager.add_panel("right", test_panel_widget)
# Toggling with scale=0.25 on a right panel should set final width ~ 800 * 0.25 = 200
panel_manager.toggle_panel("right", scale=0.25, animation=False)
assert test_panel_widget.isVisible()
assert test_panel_widget.maximumWidth() == 200
def test_toggle_panel_ensure_max(panel_manager, test_panel_widget):
panel_manager.add_panel("bottom", test_panel_widget, target_size=150)
# Ensure fixed height after show
panel_manager.toggle_panel("bottom", ensure_max=True, animation=False)
assert test_panel_widget.isVisible()
assert test_panel_widget.maximumHeight() == 150
# Hide again
panel_manager.toggle_panel("bottom", ensure_max=True, animation=False)
assert not test_panel_widget.isVisible()
# After hide, reset to flexible height
assert test_panel_widget.maximumHeight() == 16777215
def test_toggle_panel_easing_curve(panel_manager, test_panel_widget):
panel_manager.add_panel("top", test_panel_widget, target_size=100, duration=500)
# Just ensure no errors raised when using different easing curves
panel_manager.toggle_panel("top", easing_curve=QEasingCurve.OutBounce, animation=True)
# Hard to test animation directly, but we can check if animation object is stored
assert panel_manager.animations.get(test_panel_widget) is not None
def test_toggle_nonexistent_panel(panel_manager):
with pytest.raises(ValueError) as exc_info:
panel_manager.toggle_panel("invalid")
assert "No panel found in direction 'invalid'." in str(exc_info.value)
def test_toggle_panel_without_animation(panel_manager, test_panel_widget):
panel_manager.add_panel("left", test_panel_widget, target_size=200)
panel_manager.toggle_panel("left", animation=False)
# Visible and max width set
assert test_panel_widget.isVisible()
assert test_panel_widget.maximumWidth() == 200
# Toggle again without animation to hide instantly
panel_manager.toggle_panel("left", animation=False)
assert not test_panel_widget.isVisible()
def test_after_hide_reset(panel_manager, test_panel_widget):
# Test internal method by simulating scenario
panel_manager.add_panel("left", test_panel_widget, target_size=200)
# Show panel with ensure_max
panel_manager.toggle_panel("left", ensure_max=True, animation=False)
assert test_panel_widget.isVisible()
# Hide panel with ensure_max
panel_manager.toggle_panel("left", ensure_max=True, animation=False)
assert not test_panel_widget.isVisible()
# After hide reset should restore flexible sizing
assert test_panel_widget.minimumWidth() == 0
assert test_panel_widget.maximumWidth() == 0
def test_toggle_panel_repeated(panel_manager, test_panel_widget):
# Repeated toggles should show/hide correctly
panel_manager.add_panel("right", test_panel_widget, target_size=200)
panel_manager.toggle_panel("right", animation=False)
assert test_panel_widget.isVisible()
panel_manager.toggle_panel("right", animation=False)
assert not test_panel_widget.isVisible()
panel_manager.toggle_panel("right", animation=False)
assert test_panel_widget.isVisible()
def test_toggle_panel_with_custom_duration(panel_manager, test_panel_widget):
panel_manager.add_panel("bottom", test_panel_widget, target_size=150, duration=1000)
# Toggle with overriding duration
panel_manager.toggle_panel("bottom", duration=2000, animation=True)
animation = panel_manager.animations.get(test_panel_widget)
assert animation is not None
assert animation.duration() == 2000
def test_toggle_panel_ensure_max_scale(panel_manager, test_panel_widget, reference_widget):
reference_widget.resize(1000, 800)
panel_manager.add_panel("top", test_panel_widget)
# With scale=0.5 on top panel, target size = 800 * 0.5 = 400
panel_manager.toggle_panel("top", ensure_max=True, scale=0.5, animation=False)
assert test_panel_widget.isVisible()
assert test_panel_widget.maximumHeight() == 400
def test_no_animation_mode(panel_manager, test_panel_widget):
# When animation=False, panel should jump instantly to final state
panel_manager.add_panel("left", test_panel_widget, target_size=200)
panel_manager.toggle_panel("left", animation=False)
assert test_panel_widget.isVisible()
# Check again for no animation hide
panel_manager.toggle_panel("left", animation=False)
assert not test_panel_widget.isVisible()
def test_toggle_panel_nondefault_easing(panel_manager, test_panel_widget):
panel_manager.add_panel("right", test_panel_widget, target_size=200)
panel_manager.toggle_panel("right", easing_curve=QEasingCurve.InCurve, animation=True)
animation = panel_manager.animations.get(test_panel_widget)
assert animation is not None
# Just ensuring no exceptions and property is set
assert animation.easingCurve() == QEasingCurve.InCurve
def test_toggle_panel_ensure_max_no_animation(panel_manager, test_panel_widget):
panel_manager.add_panel("bottom", test_panel_widget, target_size=150)
# Ensure max with no animation
panel_manager.toggle_panel("bottom", ensure_max=True, animation=False)
assert test_panel_widget.isVisible()
assert test_panel_widget.maximumHeight() == 150
# Toggle off ensure max with no animation
panel_manager.toggle_panel("bottom", ensure_max=True, animation=False)
assert not test_panel_widget.isVisible()
assert test_panel_widget.maximumHeight() == 16777215
def test_toggle_panel_new_target_size(panel_manager, test_panel_widget):
panel_manager.add_panel("left", test_panel_widget, target_size=200)
# Toggle with different target_size on the fly
panel_manager.toggle_panel("left", target_size=300, animation=False)
assert test_panel_widget.isVisible()
assert test_panel_widget.maximumWidth() == 300
# Hide panel
panel_manager.toggle_panel("left", animation=False)
assert not test_panel_widget.isVisible()
def test_toggle_panel_new_duration(panel_manager, test_panel_widget):
panel_manager.add_panel("left", test_panel_widget, target_size=200, duration=300)
panel_manager.toggle_panel("left", duration=1000, animation=True)
animation = panel_manager.animations.get(test_panel_widget)
assert animation.duration() == 1000
def test_toggle_panel_wrong_direction(panel_manager):
with pytest.raises(ValueError) as exc:
panel_manager.toggle_panel("unknown_direction")
assert "No panel found in direction 'unknown_direction'." in str(exc.value)
def test_toggle_panel_no_panels(panel_manager):
# Attempting to toggle a panel that was never added
with pytest.raises(ValueError) as exc:
panel_manager.toggle_panel("top")
assert "No panel found in direction 'top'." in str(exc.value)
def test_multiple_panels_interaction(panel_manager):
widget_left = QWidget()
widget_right = QWidget()
panel_manager.add_panel("left", widget_left, target_size=200)
panel_manager.add_panel("right", widget_right, target_size=300)
# Toggle left on
panel_manager.toggle_panel("left", animation=False)
assert widget_left.isVisible()
# Toggle right on
panel_manager.toggle_panel("right", animation=False)
assert widget_right.isVisible()
# Hide left
panel_manager.toggle_panel("left", animation=False)
assert not widget_left.isVisible()
assert widget_right.isVisible()
# Hide right
panel_manager.toggle_panel("right", animation=False)
assert not widget_right.isVisible()
def test_panel_manager_custom_easing(panel_manager, test_panel_widget):
panel_manager.add_panel("top", test_panel_widget, target_size=150)
panel_manager.toggle_panel("top", easing_curve=QEasingCurve.InQuad, animation=True)
animation = panel_manager.animations.get(test_panel_widget)
assert animation is not None
assert animation.easingCurve() == QEasingCurve.InQuad
def test_toggle_panel_scale_no_animation(panel_manager, test_panel_widget, reference_widget):
reference_widget.resize(400, 300)
panel_manager.add_panel("bottom", test_panel_widget)
# scale=0.5 for bottom means target_size=300*0.5=150
panel_manager.toggle_panel("bottom", scale=0.5, animation=False)
assert test_panel_widget.isVisible()
assert test_panel_widget.maximumHeight() == 150
# Hide again
panel_manager.toggle_panel("bottom", animation=False)
assert not test_panel_widget.isVisible()
def test_after_hide_reset_properties(panel_manager, test_panel_widget):
panel_manager.add_panel("left", test_panel_widget, target_size=200)
panel_manager.toggle_panel("left", ensure_max=True, animation=False)
panel_manager.toggle_panel("left", ensure_max=True, animation=False)
# After hide reset, properties should revert to flexible sizing
assert not test_panel_widget.isVisible()
assert test_panel_widget.minimumWidth() == 0
# If the direction is left, we also check maximumWidth after hiding
assert test_panel_widget.maximumWidth() == 0
def test_toggle_panel_no_animation_show_only(panel_manager, test_panel_widget):
# Show panel only, no animation
panel_manager.add_panel("right", test_panel_widget, target_size=100)
panel_manager.toggle_panel("right", animation=False)
# Check visible and dimension
assert test_panel_widget.isVisible()
assert test_panel_widget.maximumWidth() == 100
def test_toggle_panel_no_animation_hide_only(panel_manager, test_panel_widget):
# Show first
panel_manager.add_panel("left", test_panel_widget, target_size=100)
panel_manager.toggle_panel("left", animation=False)
assert test_panel_widget.isVisible()
# Now hide without animation
panel_manager.toggle_panel("left", animation=False)
assert not test_panel_widget.isVisible()
def test_toggle_panel_easing_inout(panel_manager, test_panel_widget):
panel_manager.add_panel("top", test_panel_widget, target_size=120)
panel_manager.toggle_panel("top", easing_curve=QEasingCurve.InOutQuad, animation=True)
animation = panel_manager.animations.get(test_panel_widget)
assert animation is not None
assert animation.easingCurve() == QEasingCurve.InOutQuad
def test_toggle_panel_ensure_max_width(panel_manager, test_panel_widget):
panel_manager.add_panel("right", test_panel_widget, target_size=200)
panel_manager.toggle_panel("right", ensure_max=True, animation=False)
assert test_panel_widget.isVisible()
assert test_panel_widget.maximumWidth() == 200
def test_toggle_panel_invalid_direction_twice(panel_manager, test_panel_widget):
# Add a valid panel
panel_manager.add_panel("left", test_panel_widget, target_size=200)
# Try toggling invalid direction again
with pytest.raises(ValueError) as exc_info:
panel_manager.toggle_panel("invalid_direction")
assert "No panel found in direction 'invalid_direction'." in str(exc_info.value)
def test_ensure_max_hiding_animation(panel_manager, test_panel_widget):
# Test that ensure_max mode sets a DimensionAnimator and uses it
panel_manager.add_panel("top", test_panel_widget, target_size=150)
panel_manager.toggle_panel("top", ensure_max=True, animation=True)
assert test_panel_widget.isVisible()
# Hide with animation
panel_manager.toggle_panel("top", ensure_max=True, animation=True)
anim = panel_manager.animations.get(test_panel_widget)
assert anim is not None