464 lines
13 KiB
Python
Executable File
464 lines
13 KiB
Python
Executable File
"""
|
|
Components/ExpansionPanel
|
|
=========================
|
|
|
|
.. seealso::
|
|
|
|
`Material Design spec, Expansion panel <https://material.io/archive/guidelines/components/expansion-panels.html#>`_
|
|
|
|
.. rubric:: Expansion panels contain creation flows and allow lightweight editing of an element.
|
|
|
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/expansion-panel.png
|
|
:align: center
|
|
|
|
Usage
|
|
-----
|
|
|
|
.. code-block:: python
|
|
|
|
self.add_widget(
|
|
MDExpansionPanel(
|
|
icon="logo.png", # panel icon
|
|
content=Content(), # panel content
|
|
panel_cls=MDExpansionPanelOneLine(text="Secondary text"), # panel class
|
|
)
|
|
)
|
|
|
|
To use :class:`~MDExpansionPanel` you must pass one of the following classes
|
|
to the :attr:`~MDExpansionPanel.panel_cls` parameter:
|
|
|
|
- :class:`~MDExpansionPanelOneLine`
|
|
- :class:`~MDExpansionPanelTwoLine`
|
|
- :class:`~MDExpansionPanelThreeLine`
|
|
|
|
These classes are inherited from the following classes:
|
|
|
|
- :class:`~kivymd.uix.list.OneLineAvatarIconListItem`
|
|
- :class:`~kivymd.uix.list.TwoLineAvatarIconListItem`
|
|
- :class:`~kivymd.uix.list.ThreeLineAvatarIconListItem`
|
|
|
|
.. code-block:: python
|
|
|
|
self.root.ids.box.add_widget(
|
|
MDExpansionPanel(
|
|
icon="logo.png",
|
|
content=Content(),
|
|
panel_cls=MDExpansionPanelThreeLine(
|
|
text="Text",
|
|
secondary_text="Secondary text",
|
|
tertiary_text="Tertiary text",
|
|
)
|
|
)
|
|
)
|
|
|
|
Example
|
|
-------
|
|
|
|
.. code-block:: python
|
|
|
|
import os
|
|
|
|
from kivy.lang import Builder
|
|
|
|
from kivymd.app import MDApp
|
|
from kivymd.uix.boxlayout import MDBoxLayout
|
|
from kivymd.uix.expansionpanel import MDExpansionPanel, MDExpansionPanelThreeLine
|
|
from kivymd import images_path
|
|
|
|
KV = '''
|
|
<Content>
|
|
adaptive_height: True
|
|
|
|
TwoLineIconListItem:
|
|
text: "(050)-123-45-67"
|
|
secondary_text: "Mobile"
|
|
|
|
IconLeftWidget:
|
|
icon: 'phone'
|
|
|
|
|
|
MDScrollView:
|
|
|
|
MDGridLayout:
|
|
id: box
|
|
cols: 1
|
|
adaptive_height: True
|
|
'''
|
|
|
|
|
|
class Content(MDBoxLayout):
|
|
'''Custom content.'''
|
|
|
|
|
|
class Test(MDApp):
|
|
def build(self):
|
|
return Builder.load_string(KV)
|
|
|
|
def on_start(self):
|
|
for i in range(10):
|
|
self.root.ids.box.add_widget(
|
|
MDExpansionPanel(
|
|
icon=os.path.join(images_path, "logo", "kivymd-icon-128.png"),
|
|
content=Content(),
|
|
panel_cls=MDExpansionPanelThreeLine(
|
|
text="Text",
|
|
secondary_text="Secondary text",
|
|
tertiary_text="Tertiary text",
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
Test().run()
|
|
|
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/expansion-panel.gif
|
|
:align: center
|
|
|
|
Two events are available for :class:`~MDExpansionPanel`
|
|
-------------------------------------------------------
|
|
|
|
- :attr:`~MDExpansionPanel.on_open`
|
|
- :attr:`~MDExpansionPanel.on_close`
|
|
|
|
.. code-block:: kv
|
|
|
|
MDExpansionPanel:
|
|
on_open: app.on_panel_open(args)
|
|
on_close: app.on_panel_close(args)
|
|
|
|
The user function takes one argument - the object of the panel:
|
|
|
|
.. code-block:: python
|
|
|
|
def on_panel_open(self, instance_panel):
|
|
print(instance_panel)
|
|
|
|
.. seealso:: `See Expansion panel example <https://github.com/kivymd/KivyMD/wiki/Components-Expansion-Panel>`_
|
|
|
|
`Expansion panel and MDCard <https://github.com/kivymd/KivyMD/wiki/Components-Expansion-Panel-and-MDCard>`_
|
|
"""
|
|
|
|
__all__ = (
|
|
"MDExpansionPanel",
|
|
"MDExpansionPanelOneLine",
|
|
"MDExpansionPanelTwoLine",
|
|
"MDExpansionPanelThreeLine",
|
|
"MDExpansionPanelLabel",
|
|
)
|
|
|
|
import os
|
|
from typing import Union
|
|
|
|
from kivy.animation import Animation
|
|
from kivy.clock import Clock
|
|
from kivy.lang import Builder
|
|
from kivy.metrics import dp
|
|
from kivy.properties import NumericProperty, ObjectProperty, StringProperty
|
|
from kivy.uix.relativelayout import RelativeLayout
|
|
from kivy.uix.widget import WidgetException
|
|
|
|
import kivymd.material_resources as m_res
|
|
from kivymd import uix_path
|
|
from kivymd.icon_definitions import md_icons
|
|
from kivymd.uix.button import MDIconButton
|
|
from kivymd.uix.list import (
|
|
IconLeftWidget,
|
|
ImageLeftWidget,
|
|
IRightBodyTouch,
|
|
OneLineAvatarIconListItem,
|
|
ThreeLineAvatarIconListItem,
|
|
TwoLineAvatarIconListItem,
|
|
TwoLineListItem,
|
|
)
|
|
|
|
with open(
|
|
os.path.join(uix_path, "expansionpanel", "expansionpanel.kv"),
|
|
encoding="utf-8",
|
|
) as kv_file:
|
|
Builder.load_string(kv_file.read())
|
|
|
|
|
|
class MDExpansionChevronRight(IRightBodyTouch, MDIconButton):
|
|
"""Chevron icon on the right panel."""
|
|
|
|
_angle = NumericProperty(0)
|
|
|
|
|
|
class MDExpansionPanelOneLine(OneLineAvatarIconListItem):
|
|
"""Single line panel."""
|
|
|
|
|
|
class MDExpansionPanelTwoLine(TwoLineAvatarIconListItem):
|
|
"""Two-line panel."""
|
|
|
|
|
|
class MDExpansionPanelThreeLine(ThreeLineAvatarIconListItem):
|
|
"""Three-line panel."""
|
|
|
|
|
|
class MDExpansionPanelLabel(TwoLineListItem):
|
|
"""
|
|
Label panel.
|
|
|
|
..warning:: This class is created for use in the
|
|
:class:`~kivymd.uix.stepper.MDStepperVertical` and
|
|
:class:`~kivymd.uix.stepper.MDStepper` classes, and has not
|
|
been tested for use outside of these classes.
|
|
"""
|
|
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
Clock.schedule_once(self.set_paddings)
|
|
|
|
def set_paddings(self, interval: Union[int, float]) -> None:
|
|
self._txt_bot_pad = dp(36)
|
|
self._txt_left_pad = dp(0)
|
|
|
|
|
|
class MDExpansionPanel(RelativeLayout):
|
|
"""
|
|
:Events:
|
|
:attr:`on_open`
|
|
Called when a panel is opened.
|
|
:attr:`on_close`
|
|
Called when a panel is closed.
|
|
"""
|
|
|
|
content = ObjectProperty()
|
|
"""
|
|
Content of panel. Must be `Kivy` widget.
|
|
|
|
:attr:`content` is an :class:`~kivy.properties.ObjectProperty`
|
|
and defaults to `None`.
|
|
"""
|
|
|
|
icon = StringProperty()
|
|
"""
|
|
Icon of panel.
|
|
|
|
Icon Should be either be a path to an image or
|
|
a logo name in :class:`~kivymd.icon_definitions.md_icons`
|
|
|
|
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
|
|
and defaults to `''`.
|
|
"""
|
|
|
|
opening_transition = StringProperty("out_cubic")
|
|
"""
|
|
The name of the animation transition type to use when animating to
|
|
the :attr:`state` `'open'`.
|
|
|
|
:attr:`opening_transition` is a :class:`~kivy.properties.StringProperty`
|
|
and defaults to `'out_cubic'`.
|
|
"""
|
|
|
|
opening_time = NumericProperty(0.2)
|
|
"""
|
|
The time taken for the panel to slide to the :attr:`state` `'open'`.
|
|
|
|
:attr:`opening_time` is a :class:`~kivy.properties.NumericProperty`
|
|
and defaults to `0.2`.
|
|
"""
|
|
|
|
closing_transition = StringProperty("out_sine")
|
|
"""
|
|
The name of the animation transition type to use when animating to
|
|
the :attr:`state` 'close'.
|
|
|
|
:attr:`closing_transition` is a :class:`~kivy.properties.StringProperty`
|
|
and defaults to `'out_sine'`.
|
|
"""
|
|
|
|
closing_time = NumericProperty(0.2)
|
|
"""
|
|
The time taken for the panel to slide to the :attr:`state` `'close'`.
|
|
|
|
:attr:`closing_time` is a :class:`~kivy.properties.NumericProperty`
|
|
and defaults to `0.2`.
|
|
"""
|
|
|
|
panel_cls = ObjectProperty()
|
|
"""
|
|
Panel object. The object must be one of the classes
|
|
:class:`~MDExpansionPanelOneLine`, :class:`~MDExpansionPanelTwoLine` or
|
|
:class:`~MDExpansionPanelThreeLine`.
|
|
|
|
:attr:`panel_cls` is a :class:`~kivy.properties.ObjectProperty`
|
|
and defaults to `None`.
|
|
"""
|
|
|
|
_state = StringProperty("close")
|
|
_anim_playing = False
|
|
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.register_event_type("on_open")
|
|
self.register_event_type("on_close")
|
|
|
|
if self.panel_cls and isinstance(
|
|
self.panel_cls,
|
|
(
|
|
MDExpansionPanelOneLine,
|
|
MDExpansionPanelTwoLine,
|
|
MDExpansionPanelThreeLine,
|
|
MDExpansionPanelLabel,
|
|
),
|
|
):
|
|
self.panel_cls.pos_hint = {"top": 1}
|
|
self.panel_cls._no_ripple_effect = True
|
|
self.panel_cls.bind(
|
|
on_release=lambda x: self.check_open_panel(self.panel_cls)
|
|
)
|
|
if not isinstance(self.panel_cls, MDExpansionPanelLabel):
|
|
self.chevron = MDExpansionChevronRight()
|
|
self.panel_cls.add_widget(self.chevron)
|
|
if self.icon:
|
|
if self.icon in md_icons.keys():
|
|
self.panel_cls.add_widget(
|
|
IconLeftWidget(
|
|
icon=self.icon,
|
|
pos_hint={"center_y": 0.5},
|
|
)
|
|
)
|
|
else:
|
|
self.panel_cls.add_widget(
|
|
ImageLeftWidget(
|
|
source=self.icon, pos_hint={"center_y": 0.5}
|
|
)
|
|
)
|
|
else:
|
|
self.panel_cls.remove_widget(
|
|
self.panel_cls.ids._left_container
|
|
)
|
|
self.panel_cls._txt_left_pad = 0
|
|
else:
|
|
# if no icon
|
|
self.panel_cls._txt_left_pad = m_res.HORIZ_MARGINS
|
|
self.add_widget(self.panel_cls)
|
|
else:
|
|
raise ValueError(
|
|
"KivyMD: `panel_cls` object must be must be one of the "
|
|
"objects from the list\n"
|
|
"[MDExpansionPanelOneLine, MDExpansionPanelTwoLine, "
|
|
"MDExpansionPanelThreeLine]"
|
|
)
|
|
|
|
def on_open(self, *args):
|
|
"""Called when a panel is opened."""
|
|
|
|
def on_close(self, *args):
|
|
"""Called when a panel is closed."""
|
|
|
|
def check_open_panel(
|
|
self,
|
|
instance_panel: [
|
|
MDExpansionPanelThreeLine,
|
|
MDExpansionPanelTwoLine,
|
|
MDExpansionPanelThreeLine,
|
|
MDExpansionPanelLabel,
|
|
],
|
|
) -> None:
|
|
"""
|
|
Called when you click on the panel. Called methods to open or close
|
|
a panel.
|
|
"""
|
|
|
|
press_current_panel = False
|
|
for panel in self.parent.children:
|
|
if isinstance(panel, MDExpansionPanel):
|
|
if len(panel.children) == 2:
|
|
if instance_panel is panel.children[1]:
|
|
press_current_panel = True
|
|
panel.remove_widget(panel.children[0])
|
|
if not isinstance(self.panel_cls, MDExpansionPanelLabel):
|
|
chevron = panel.children[0].children[0].children[0]
|
|
self.set_chevron_up(chevron)
|
|
self.close_panel(panel, press_current_panel)
|
|
self.dispatch("on_close")
|
|
break
|
|
if not press_current_panel:
|
|
self.set_chevron_down()
|
|
|
|
def set_chevron_down(self) -> None:
|
|
"""Sets the chevron down."""
|
|
|
|
if not isinstance(self.panel_cls, MDExpansionPanelLabel):
|
|
Animation(_angle=-90, d=self.opening_time).start(self.chevron)
|
|
self.open_panel()
|
|
self.dispatch("on_open")
|
|
|
|
def set_chevron_up(self, instance_chevron: MDExpansionChevronRight) -> None:
|
|
"""Sets the chevron up."""
|
|
|
|
if not isinstance(self.panel_cls, MDExpansionPanelLabel):
|
|
Animation(_angle=0, d=self.closing_time).start(instance_chevron)
|
|
|
|
def close_panel(
|
|
self, instance_expansion_panel, press_current_panel: bool
|
|
) -> None:
|
|
"""Method closes the panel."""
|
|
|
|
if self._anim_playing:
|
|
return
|
|
|
|
if press_current_panel:
|
|
self._anim_playing = True
|
|
|
|
self._state = "close"
|
|
|
|
anim = Animation(
|
|
height=self.panel_cls.height,
|
|
d=self.closing_time,
|
|
t=self.closing_transition,
|
|
)
|
|
anim.bind(on_complete=self._disable_anim)
|
|
anim.start(instance_expansion_panel)
|
|
|
|
def open_panel(self, *args) -> None:
|
|
"""Method opens a panel."""
|
|
|
|
if self._anim_playing:
|
|
return
|
|
|
|
self._anim_playing = True
|
|
self._state = "open"
|
|
|
|
anim = Animation(
|
|
height=self.content.height + self.height,
|
|
d=self.opening_time,
|
|
t=self.opening_transition,
|
|
)
|
|
anim.bind(on_complete=self._add_content)
|
|
anim.bind(on_complete=self._disable_anim)
|
|
anim.start(self)
|
|
|
|
def get_state(self) -> str:
|
|
"""Returns the state of panel. Can be `close` or `open` ."""
|
|
|
|
return self._state
|
|
|
|
def add_widget(self, widget, index=0, canvas=None):
|
|
if isinstance(
|
|
widget,
|
|
(
|
|
MDExpansionPanelOneLine,
|
|
MDExpansionPanelTwoLine,
|
|
MDExpansionPanelThreeLine,
|
|
MDExpansionPanelLabel,
|
|
),
|
|
):
|
|
self.height = widget.height
|
|
return super().add_widget(widget)
|
|
|
|
def _disable_anim(self, *args):
|
|
self._anim_playing = False
|
|
|
|
def _add_content(self, *args):
|
|
if self.content:
|
|
try:
|
|
if isinstance(self.panel_cls, MDExpansionPanelLabel):
|
|
self.content.y = dp(36)
|
|
self.add_widget(self.content)
|
|
except WidgetException:
|
|
pass
|