globalforest/kivymd/uix/expansionpanel.py

396 lines
11 KiB
Python
Raw Normal View History

2020-10-14 00:19:43 -04:00
"""
Components/Expansion Panel
==========================
.. 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
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'
ScrollView:
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=f"{images_path}kivymd.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",
)
from kivy.animation import Animation
from kivy.lang import Builder
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.icon_definitions import md_icons
from kivymd.uix.button import MDIconButton
from kivymd.uix.list import (
IconLeftWidget,
ImageLeftWidget,
IRightBodyTouch,
OneLineAvatarIconListItem,
ThreeLineAvatarIconListItem,
TwoLineAvatarIconListItem,
)
Builder.load_string(
"""
<MDExpansionChevronRight>:
icon: 'chevron-right'
disabled: True
canvas.before:
PushMatrix
Rotate:
angle: self._angle
axis: (0, 0, 1)
origin: self.center
canvas.after:
PopMatrix
<MDExpansionPanel>
size_hint_y: None
#height: dp(68)
"""
)
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 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`.
"""
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,
),
):
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)
)
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:
# 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):
"""
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 is panel.children[1]:
press_current_panel = True
panel.remove_widget(panel.children[0])
chevron = panel.children[0].children[0].children[0]
self.set_chevron_up(chevron)
self.close_panel(panel)
self.dispatch("on_close")
break
if not press_current_panel:
self.set_chevron_down()
def set_chevron_down(self):
"""Sets the chevron down."""
Animation(_angle=-90, d=self.opening_time).start(self.chevron)
self.open_panel()
self.dispatch("on_open")
def set_chevron_up(self, instance_chevron):
"""Sets the chevron up."""
Animation(_angle=0, d=self.closing_time).start(instance_chevron)
def close_panel(self, instance_panel):
"""Method closes the panel."""
Animation(
height=self.panel_cls.height,
d=self.closing_time,
t=self.closing_transition,
).start(instance_panel)
def open_panel(self, *args):
"""Method opens a panel."""
anim = Animation(
height=self.content.height + self.height,
d=self.opening_time,
t=self.opening_transition,
)
anim.bind(on_complete=self._add_content)
anim.start(self)
def add_widget(self, widget, index=0, canvas=None):
if isinstance(
widget,
(
MDExpansionPanelOneLine,
MDExpansionPanelTwoLine,
MDExpansionPanelThreeLine,
),
):
self.height = widget.height
return super().add_widget(widget)
def _add_content(self, *args):
if self.content:
try:
self.add_widget(self.content)
except WidgetException:
pass