Sideband/sbapp/kivymd/uix/card/card.py

1112 lines
33 KiB
Python
Raw Normal View History

2022-07-07 16:16:10 -04:00
"""
Components/Card
===============
.. seealso::
`Material Design spec, Cards <https://material.io/components/cards>`_ and
`Material Design 3 spec, Cards <https://m3.material.io/components/cards/specs>`_
.. rubric:: Cards contain content and actions about a single subject.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/cards.png
:align: center
`KivyMD` provides the following card classes for use:
- MDCard_
- MDCardSwipe_
.. note:: :class:`~MDCard` inherited from
:class:`~kivy.uix.boxlayout.BoxLayout`. You can use all parameters and
attributes of the :class:`~kivy.uix.boxlayout.BoxLayout` class in the
:class:`~MDCard` class.
.. MDCard:
MDCard
------
.. warning:: Starting from the KivyMD 1.1.0 library version, it is necessary
to manually inherit the card class from one of the ``Elevation`` classes
from ``kivymd/uix/behaviors/elevation.py`` module to draw the card shadow.
.. code-block:: python
from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
from kivymd.uix.card import MDCard
class MD3Card(MDCard, RoundedRectangularElevationBehavior):
'''Implements a material design v3 card.'''
It actually allows for better control over the providers that implement the
rendering of the shadows.
.. note:: You can read more information about the classes that implement the
rendering of shadows on this
`documentation page <https://kivymd.readthedocs.io/en/latest/behaviors/elevation/>`_.
2022-07-07 16:16:10 -04:00
An example of the implementation of a card in the style of material design version 3
------------------------------------------------------------------------------------
2022-10-02 11:16:59 -04:00
.. tabs::
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. tab:: Declarative KV and imperative python styles
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. code-block:: python
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
from kivy.lang import Builder
from kivy.properties import StringProperty
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
from kivymd.app import MDApp
from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
2022-10-02 11:16:59 -04:00
from kivymd.uix.card import MDCard
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
KV = '''
<MD3Card>
padding: 4
size_hint: None, None
size: "200dp", "100dp"
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDRelativeLayout:
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDIconButton:
icon: "dots-vertical"
pos_hint: {"top": 1, "right": 1}
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDLabel:
id: label
text: root.text
adaptive_size: True
color: "grey"
pos: "12dp", "12dp"
bold: True
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDScreen:
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDBoxLayout:
id: box
adaptive_size: True
spacing: "56dp"
pos_hint: {"center_x": .5, "center_y": .5}
'''
class MD3Card(MDCard, RoundedRectangularElevationBehavior):
2022-10-02 11:16:59 -04:00
'''Implements a material design v3 card.'''
text = StringProperty()
class Example(MDApp):
def build(self):
self.theme_cls.material_style = "M3"
return Builder.load_string(KV)
def on_start(self):
styles = {
"elevated": "#f6eeee", "filled": "#f4dedc", "outlined": "#f8f5f4"
}
for style in styles.keys():
self.root.ids.box.add_widget(
MD3Card(
line_color=(0.2, 0.2, 0.2, 0.8),
style=style,
text=style.capitalize(),
md_bg_color=styles[style],
)
)
Example().run()
.. tab:: Declarative python styles
.. code-block:: python
from kivymd.app import MDApp
from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
2022-10-02 11:16:59 -04:00
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDIconButton
from kivymd.uix.card import MDCard
from kivymd.uix.label import MDLabel
from kivymd.uix.relativelayout import MDRelativeLayout
from kivymd.uix.screen import MDScreen
class MD3Card(MDCard, RoundedRectangularElevationBehavior):
2022-10-02 11:16:59 -04:00
'''Implements a material design v3 card.'''
class Example(MDApp):
def build(self):
self.theme_cls.material_style = "M3"
return (
MDScreen(
MDBoxLayout(
id="box",
adaptive_size=True,
spacing="56dp",
pos_hint={"center_x": 0.5, "center_y": 0.5},
)
)
2022-07-07 16:16:10 -04:00
)
2022-10-02 11:16:59 -04:00
def on_start(self):
styles = {
"elevated": "#f6eeee", "filled": "#f4dedc", "outlined": "#f8f5f4"
}
for style in styles.keys():
self.root.ids.box.add_widget(
MD3Card(
MDRelativeLayout(
MDIconButton(
icon="dots-vertical",
pos_hint={"top": 1, "right": 1}
),
MDLabel(
text=style.capitalize(),
adaptive_size=True,
color="grey",
pos=("12dp", "12dp"),
bold=True,
2022-10-02 11:16:59 -04:00
),
),
line_color=(0.2, 0.2, 0.2, 0.8),
style=style,
padding="4dp",
size_hint=(None, None),
size=("200dp", "100dp"),
md_bg_color=styles[style],
)
)
Example().run()
2022-07-07 16:16:10 -04:00
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/cards-m3.png
:align: center
.. MDCardSwipe:
MDCardSwipe
-----------
To create a card with `swipe-to-delete` behavior, you must create a new class
that inherits from the :class:`~MDCardSwipe` class:
.. code-block:: kv
2022-10-02 11:16:59 -04:00
<SwipeToDeleteItem>
2022-07-07 16:16:10 -04:00
size_hint_y: None
height: content.height
MDCardSwipeLayerBox:
MDCardSwipeFrontBox:
OneLineListItem:
id: content
text: root.text
_no_ripple_effect: True
.. code-block:: python
class SwipeToDeleteItem(MDCardSwipe):
text = StringProperty()
2022-10-02 11:16:59 -04:00
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sceleton-mdcard-swiper.png
2022-07-07 16:16:10 -04:00
:align: center
End full code
-------------
2022-10-02 11:16:59 -04:00
.. tabs::
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. tab:: Declarative KV and imperative python styles
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. code-block:: python
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
from kivy.lang import Builder
from kivy.properties import StringProperty
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
from kivymd.app import MDApp
from kivymd.uix.card import MDCardSwipe
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
KV = '''
<SwipeToDeleteItem>
size_hint_y: None
height: content.height
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDCardSwipeLayerBox:
# Content under the card.
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDCardSwipeFrontBox:
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
# Content of card.
OneLineListItem:
id: content
text: root.text
_no_ripple_effect: True
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDScreen:
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDBoxLayout:
orientation: "vertical"
spacing: "10dp"
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDTopAppBar:
elevation: 10
2022-10-02 11:16:59 -04:00
title: "MDCardSwipe"
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDScrollView:
scroll_timeout : 100
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDList:
id: md_list
padding: 0
'''
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
class SwipeToDeleteItem(MDCardSwipe):
'''Card with `swipe-to-delete` behavior.'''
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
text = StringProperty()
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
def on_start(self):
'''Creates a list of cards.'''
for i in range(20):
self.screen.ids.md_list.add_widget(
2022-10-02 11:16:59 -04:00
SwipeToDeleteItem(text=f"One-line item {i}")
)
Example().run()
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. tab:: Declarative python styles
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. code-block:: python
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.card import (
MDCardSwipe, MDCardSwipeLayerBox, MDCardSwipeFrontBox
)
from kivymd.uix.list import MDList, OneLineListItem
from kivymd.uix.screen import MDScreen
from kivymd.uix.scrollview import MDScrollView
from kivymd.uix.toolbar import MDTopAppBar
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return (
MDScreen(
MDBoxLayout(
MDTopAppBar(
elevation=10,
2022-10-02 11:16:59 -04:00
title="MDCardSwipe",
),
MDScrollView(
MDList(
id="md_list",
),
id="scroll",
scroll_timeout=100,
),
id="box",
orientation="vertical",
spacing="10dp",
2022-10-02 11:16:59 -04:00
),
)
)
def on_start(self):
'''Creates a list of cards.'''
for i in range(20):
self.root.ids.box.ids.scroll.ids.md_list.add_widget(
MDCardSwipe(
MDCardSwipeLayerBox(),
MDCardSwipeFrontBox(
OneLineListItem(
id="content",
text=f"One-line item {i}",
_no_ripple_effect=True,
)
),
size_hint_y=None,
height="52dp",
)
)
Example().run()
2022-07-07 16:16:10 -04:00
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/list-mdcard-swipe.gif
:align: center
Binding a swipe to one of the sides of the screen
-------------------------------------------------
.. code-block:: kv
2022-10-02 11:16:59 -04:00
<SwipeToDeleteItem>
2022-07-07 16:16:10 -04:00
# By default, the parameter is "left"
anchor: "right"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/mdcard-swipe-anchor-right.gif
:align: center
.. Note:: You cannot use the left and right swipe at the same time.
Swipe behavior
--------------
.. code-block:: kv
2022-10-02 11:16:59 -04:00
<SwipeToDeleteItem>
2022-07-07 16:16:10 -04:00
# By default, the parameter is "hand"
type_swipe: "hand"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hand-mdcard-swipe.gif
:align: center
.. code-block:: kv
<SwipeToDeleteItem>:
type_swipe: "auto"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/auto-mdcard-swipe.gif
:align: center
Removing an item using the ``type_swipe = "auto"`` parameter
------------------------------------------------------------
The map provides the :attr:`MDCardSwipe.on_swipe_complete` event.
You can use this event to remove items from a list:
2022-10-02 11:16:59 -04:00
.. tabs::
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. tab:: Declarative KV styles
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. code-block:: kv
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
<SwipeToDeleteItem>:
on_swipe_complete: app.on_swipe_complete(root)
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. tab:: Declarative python styles
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. code-block:: kv
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. code-block:: python
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDCardSwipe(
...
on_swipe_complete=self.on_swipe_complete,
)
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. tabs::
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. tab:: Imperative python styles
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. code-block:: python
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
def on_swipe_complete(self, instance):
self.screen.ids.md_list.remove_widget(instance)
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. tab:: Decralative python styles
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. code-block:: python
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
def on_swipe_complete(self, instance):
self.root.ids.box.ids.scroll.ids.md_list.remove_widget(instance)
2022-07-07 16:16:10 -04:00
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/autodelete-mdcard-swipe.gif
:align: center
Add content to the bottom layer of the card
-------------------------------------------
To add content to the bottom layer of the card,
use the :class:`~MDCardSwipeLayerBox` class.
.. code-block:: kv
<SwipeToDeleteItem>:
MDCardSwipeLayerBox:
padding: "8dp"
MDIconButton:
icon: "trash-can"
pos_hint: {"center_y": .5}
on_release: app.remove_item(root)
End full code
-------------
2022-10-02 11:16:59 -04:00
.. tabs::
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. tab:: Declarative KV styles
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. code-block:: python
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
from kivy.lang import Builder
from kivy.properties import StringProperty
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
from kivymd.app import MDApp
from kivymd.uix.card import MDCardSwipe
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
KV = '''
<SwipeToDeleteItem>:
size_hint_y: None
height: content.height
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDCardSwipeLayerBox:
padding: "8dp"
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDIconButton:
icon: "trash-can"
pos_hint: {"center_y": .5}
on_release: app.remove_item(root)
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDCardSwipeFrontBox:
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
OneLineListItem:
id: content
text: root.text
_no_ripple_effect: True
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDScreen:
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDBoxLayout:
orientation: "vertical"
spacing: "10dp"
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDTopAppBar:
elevation: 10
2022-10-02 11:16:59 -04:00
title: "MDCardSwipe"
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDScrollView:
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDList:
id: md_list
padding: 0
'''
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
class SwipeToDeleteItem(MDCardSwipe):
text = StringProperty()
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
class Example(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
self.screen = Builder.load_string(KV)
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
def build(self):
return self.screen
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
def remove_item(self, instance):
self.screen.ids.md_list.remove_widget(instance)
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
def on_start(self):
for i in range(20):
self.screen.ids.md_list.add_widget(
SwipeToDeleteItem(text=f"One-line item {i}")
)
Example().run()
.. tab:: Decralative python styles
.. code-block:: python
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDIconButton
from kivymd.uix.card import (
MDCardSwipe, MDCardSwipeLayerBox, MDCardSwipeFrontBox
)
from kivymd.uix.list import MDList, OneLineListItem
from kivymd.uix.screen import MDScreen
from kivymd.uix.scrollview import MDScrollView
from kivymd.uix.toolbar import MDTopAppBar
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return (
MDScreen(
MDBoxLayout(
MDTopAppBar(
elevation=10,
2022-10-02 11:16:59 -04:00
title="MDCardSwipe",
),
MDScrollView(
MDList(
id="md_list",
),
id="scroll",
scroll_timeout=100,
),
id="box",
orientation="vertical",
spacing="10dp",
2022-10-02 11:16:59 -04:00
),
)
)
def on_start(self):
'''Creates a list of cards.'''
for i in range(20):
self.root.ids.box.ids.scroll.ids.md_list.add_widget(
MDCardSwipe(
MDCardSwipeLayerBox(
MDIconButton(
icon="trash-can",
pos_hint={"center_y": 0.5},
on_release=self.remove_item,
),
),
MDCardSwipeFrontBox(
OneLineListItem(
id="content",
text=f"One-line item {i}",
_no_ripple_effect=True,
)
),
size_hint_y=None,
height="52dp",
)
)
def remove_item(self, instance):
self.root.ids.box.ids.scroll.ids.md_list.remove_widget(
instance.parent.parent
)
Example().run()
2022-07-07 16:16:10 -04:00
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/handdelete-mdcard-swipe.gif
:align: center
Focus behavior
--------------
.. code-block:: kv
MDCard:
focus_behavior: True
2022-10-02 11:16:59 -04:00
.. tabs::
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. tab:: Declarative KV styles
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. code-block:: python
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
from kivy.lang import Builder
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
from kivymd.app import MDApp
from kivymd.uix.behaviors import FakeRectangularElevationBehavior
from kivymd.uix.card import MDCard
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
KV = '''
MDScreen:
2022-07-07 16:16:10 -04:00
ElevationCard:
2022-10-02 11:16:59 -04:00
size_hint: .7, .4
focus_behavior: True
pos_hint: {"center_x": .5, "center_y": .5}
md_bg_color: "darkgrey"
unfocus_color: "darkgrey"
focus_color: "grey"
'''
2022-07-07 16:16:10 -04:00
class ElevationCard(MDCard, FakeRectangularElevationBehavior):
pass
2022-10-02 11:16:59 -04:00
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
return Builder.load_string(KV)
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
Example().run()
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. tab:: Declarative python styles
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. code-block:: python
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
from kivymd.app import MDApp
from kivymd.uix.behaviors import FakeRectangularElevationBehavior
2022-10-02 11:16:59 -04:00
from kivymd.uix.card import MDCard
from kivymd.uix.screen import MDScreen
2022-07-07 16:16:10 -04:00
class ElevationCard(MDCard, FakeRectangularElevationBehavior):
pass
2022-10-02 11:16:59 -04:00
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
return (
MDScreen(
ElevationCard(
2022-10-02 11:16:59 -04:00
size_hint=(0.7, 0.4),
focus_behavior=True,
pos_hint={"center_x": 0.5, "center_y": 0.5},
md_bg_color="darkgrey",
unfocus_color="darkgrey",
focus_color="grey",
),
)
)
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
Example().run()
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-focus.gif
:align: center
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
Ripple behavior
---------------
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. code-block:: kv
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
MDCard:
ripple_behavior: True
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-behavior.gif
:align: center
2022-07-07 16:16:10 -04:00
"""
__all__ = (
"MDCard",
"MDCardSwipe",
"MDCardSwipeFrontBox",
"MDCardSwipeLayerBox",
"MDSeparator",
)
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 (
BooleanProperty,
ColorProperty,
NumericProperty,
OptionProperty,
StringProperty,
VariableListProperty,
)
from kivy.uix.boxlayout import BoxLayout
from kivymd import uix_path
from kivymd.color_definitions import colors
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import (
BackgroundColorBehavior,
2022-10-02 11:16:59 -04:00
DeclarativeBehavior,
2022-07-07 16:16:10 -04:00
RectangularRippleBehavior,
)
2022-10-02 11:16:59 -04:00
from kivymd.uix.behaviors.focus_behavior import FocusBehavior
2022-07-07 16:16:10 -04:00
from kivymd.uix.boxlayout import MDBoxLayout
2022-10-02 11:16:59 -04:00
from kivymd.uix.relativelayout import MDRelativeLayout
2022-07-07 16:16:10 -04:00
with open(
os.path.join(uix_path, "card", "card.kv"), encoding="utf-8"
) as kv_file:
Builder.load_string(kv_file.read())
class MDSeparator(ThemableBehavior, MDBoxLayout):
"""A separator line."""
color = ColorProperty(None)
"""
2022-10-02 11:16:59 -04:00
Separator color.
2022-07-07 16:16:10 -04:00
:attr:`color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.on_orientation()
def on_orientation(self, *args) -> None:
self.size_hint = (
(1, None) if self.orientation == "horizontal" else (None, 1)
)
if self.orientation == "horizontal":
self.height = dp(1)
else:
self.width = dp(1)
class MDCard(
2022-10-02 11:16:59 -04:00
DeclarativeBehavior,
2022-07-07 16:16:10 -04:00
ThemableBehavior,
BackgroundColorBehavior,
RectangularRippleBehavior,
FocusBehavior,
BoxLayout,
):
focus_behavior = BooleanProperty(False)
"""
Using focus when hovering over a card.
:attr:`focus_behavior` is a :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
ripple_behavior = BooleanProperty(False)
"""
Use ripple effect for card.
:attr:`ripple_behavior` is a :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
elevation = NumericProperty(None, allownone=True)
"""
Elevation value.
:attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
and defaults to 1.
"""
2022-07-07 16:16:10 -04:00
radius = VariableListProperty([dp(6), dp(6), dp(6), dp(6)])
"""
Card radius by default.
.. versionadded:: 1.0.0
:attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
and defaults to `[dp(6), dp(6), dp(6), dp(6)]`.
"""
style = OptionProperty(None, options=("filled", "elevated", "outlined"))
"""
Card type.
.. versionadded:: 1.0.0
Available options are: 'filled', 'elevated', 'outlined'.
:attr:`style` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'elevated'`.
"""
_bg_color_map = (
colors["Light"]["CardsDialogs"],
colors["Dark"]["CardsDialogs"],
2022-07-07 16:16:10 -04:00
[1.0, 1.0, 1.0, 0.0],
)
2022-10-02 11:16:59 -04:00
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.theme_cls.bind(theme_style=self.update_md_bg_color)
self.theme_cls.bind(material_style=self.set_style)
2022-07-07 16:16:10 -04:00
Clock.schedule_once(self.set_style)
Clock.schedule_once(
lambda x: self.on_ripple_behavior(0, self.ripple_behavior)
)
self.update_md_bg_color(self, self.theme_cls.theme_style)
def update_md_bg_color(self, instance_card, theme_style: str) -> None:
if self.md_bg_color in self._bg_color_map:
self.md_bg_color = colors[theme_style]["CardsDialogs"]
2022-07-07 16:16:10 -04:00
def set_style(self, *args) -> None:
self.set_radius()
self.set_elevation()
self.set_line_color()
2022-10-02 11:16:59 -04:00
def set_line_color(self) -> None:
2022-07-07 16:16:10 -04:00
if self.theme_cls.material_style == "M3":
if self.style == "elevated" or self.style == "filled":
self.line_color = [0, 0, 0, 0]
2022-10-02 11:16:59 -04:00
def set_elevation(self) -> None:
2022-07-07 16:16:10 -04:00
if self.theme_cls.material_style == "M3":
if self.style == "outlined" or self.style == "filled":
self.elevation = 0
elif self.style == "elevated":
self.elevation = 1
2022-07-07 16:16:10 -04:00
def set_radius(self) -> None:
if (
self.radius == [dp(6), dp(6), dp(6), dp(6)]
and self.theme_cls.material_style == "M3"
):
self.radius = [dp(16), dp(16), dp(16), dp(16)]
elif (
self.radius == [dp(16), dp(16), dp(16), dp(16)]
and self.theme_cls.material_style == "M2"
):
self.radius = [dp(6), dp(6), dp(6), dp(6)]
def on_ripple_behavior(
self, interval: Union[int, float], value_behavior: bool
) -> None:
self._no_ripple_effect = False if value_behavior else True
2022-10-02 11:16:59 -04:00
class MDCardSwipe(MDRelativeLayout):
2022-07-07 16:16:10 -04:00
"""
:Events:
:attr:`on_swipe_complete`
Called when a swipe of card is completed.
"""
open_progress = NumericProperty(0.0)
"""
Percent of visible part of side panel. The percent is specified as a
floating point number in the range 0-1. 0.0 if panel is closed and 1.0 if
panel is opened.
:attr:`open_progress` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.0`.
"""
opening_transition = StringProperty("out_cubic")
"""
The name of the animation transition type to use when animating to
the :attr:`state` `'opened'`.
:attr:`opening_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'out_cubic'`.
"""
closing_transition = StringProperty("out_sine")
"""
The name of the animation transition type to use when animating to
the :attr:`state` 'closed'.
:attr:`closing_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'out_sine'`.
"""
2022-10-02 11:16:59 -04:00
closing_interval = NumericProperty(0)
"""
Interval for closing the front layer.
.. versionadded:: 1.1.0
:attr:`closing_interval` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0`.
"""
2022-07-07 16:16:10 -04:00
anchor = OptionProperty("left", options=("left", "right"))
"""
Anchoring screen edge for card. Available options are: `'left'`, `'right'`.
:attr:`anchor` is a :class:`~kivy.properties.OptionProperty`
and defaults to `left`.
"""
swipe_distance = NumericProperty(50)
"""
The distance of the swipe with which the movement of navigation drawer
begins.
:attr:`swipe_distance` is a :class:`~kivy.properties.NumericProperty`
and defaults to `50`.
"""
opening_time = NumericProperty(0.2)
"""
The time taken for the card to slide to the :attr:`state` `'open'`.
:attr:`opening_time` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.2`.
"""
state = OptionProperty("closed", options=("closed", "opened"))
"""
Detailed state. Sets before :attr:`state`. Bind to :attr:`state` instead
of :attr:`status`. Available options are: `'closed'`, `'opened'`.
:attr:`status` is a :class:`~kivy.properties.OptionProperty`
and defaults to `'closed'`.
"""
max_swipe_x = NumericProperty(0.3)
"""
If, after the events of :attr:`~on_touch_up` card position exceeds this
value - will automatically execute the method :attr:`~open_card`,
and if not - will automatically be :attr:`~close_card` method.
:attr:`max_swipe_x` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.3`.
"""
max_opened_x = NumericProperty("100dp")
"""
The value of the position the card shifts to when :attr:`~type_swipe`
s set to `'hand'`.
:attr:`max_opened_x` is a :class:`~kivy.properties.NumericProperty`
and defaults to `100dp`.
"""
type_swipe = OptionProperty("hand", options=("auto", "hand"))
"""
Type of card opening when swipe. Shift the card to the edge or to
a set position :attr:`~max_opened_x`. Available options are:
`'auto'`, `'hand'`.
:attr:`type_swipe` is a :class:`~kivy.properties.OptionProperty`
and defaults to `auto`.
"""
_opens_process = False
_to_closed = True
2022-10-02 11:16:59 -04:00
_distance = 0
2022-07-07 16:16:10 -04:00
2022-10-02 11:16:59 -04:00
def __init__(self, *args, **kwargs):
2022-07-07 16:16:10 -04:00
self.register_event_type("on_swipe_complete")
2022-10-02 11:16:59 -04:00
super().__init__(*args, **kwargs)
2022-07-07 16:16:10 -04:00
def add_widget(self, widget, index=0, canvas=None):
if isinstance(widget, (MDCardSwipeFrontBox, MDCardSwipeLayerBox)):
return super().add_widget(widget)
def on_swipe_complete(self, *args):
"""Called when a swipe of card is completed."""
def on_anchor(
self, instance_swipe_to_delete_item, anchor_value: str
) -> None:
if anchor_value == "right":
self.open_progress = 1.0
else:
self.open_progress = 0.0
def on_open_progress(
self, instance_swipe_to_delete_item, progress_value: float
) -> None:
2022-10-02 11:16:59 -04:00
def on_open_progress(*args):
if self.anchor == "left":
self.children[0].x = self.width * progress_value
else:
self.children[0].x = self.width * progress_value - self.width
Clock.schedule_once(on_open_progress)
2022-07-07 16:16:10 -04:00
def on_touch_move(self, touch):
if self.collide_point(touch.x, touch.y):
2022-10-02 11:16:59 -04:00
self._distance += touch.dx
expr = False
if self.anchor == "left" and touch.dx >= 0:
expr = abs(self._distance) < self.swipe_distance
elif self.anchor == "right" and touch.dx < 0:
expr = abs(self._distance) > self.swipe_distance
2022-07-07 16:16:10 -04:00
if expr and not self._opens_process:
self._opens_process = True
self._to_closed = False
if self._opens_process:
self.open_progress = max(
min(self.open_progress + touch.dx / self.width, 2.5), 0
)
return super().on_touch_move(touch)
def on_touch_up(self, touch):
2022-10-02 11:16:59 -04:00
self._distance = 0
2022-07-07 16:16:10 -04:00
if self.collide_point(touch.x, touch.y):
if not self._to_closed:
self._opens_process = False
self.complete_swipe()
return super().on_touch_up(touch)
def on_touch_down(self, touch):
if self.collide_point(touch.x, touch.y):
if self.state == "opened":
self._to_closed = True
2022-10-02 11:16:59 -04:00
Clock.schedule_once(self.close_card, self.closing_interval)
2022-07-07 16:16:10 -04:00
return super().on_touch_down(touch)
def complete_swipe(self) -> None:
expr = (
self.open_progress <= self.max_swipe_x
if self.anchor == "left"
else self.open_progress >= self.max_swipe_x
)
if expr:
2022-10-02 11:16:59 -04:00
Clock.schedule_once(self.close_card, self.closing_interval)
2022-07-07 16:16:10 -04:00
else:
self.open_card()
def open_card(self) -> None:
if self.type_swipe == "hand":
swipe_x = (
self.max_opened_x
if self.anchor == "left"
else -self.max_opened_x
)
else:
swipe_x = self.width if self.anchor == "left" else 0
anim = Animation(
x=swipe_x, t=self.opening_transition, d=self.opening_time
)
anim.bind(on_complete=self._on_swipe_complete)
anim.start(self.children[0])
self.state = "opened"
2022-10-02 11:16:59 -04:00
def close_card(self, *args) -> None:
2022-07-07 16:16:10 -04:00
anim = Animation(x=0, t=self.closing_transition, d=self.opening_time)
anim.bind(on_complete=self._reset_open_progress)
anim.start(self.children[0])
self.state = "closed"
def _on_swipe_complete(self, *args):
self.dispatch("on_swipe_complete")
def _reset_open_progress(self, *args):
self.open_progress = 0.0 if self.anchor == "left" else 1.0
self._to_closed = False
self.dispatch("on_swipe_complete")
class MDCardSwipeFrontBox(MDCard):
pass
class MDCardSwipeLayerBox(MDBoxLayout):
pass