Sideband/sbapp/kivymd/uix/bottomsheet/bottomsheet.py

510 lines
14 KiB
Python
Executable File

"""
Components/BottomSheet
======================
.. seealso::
`Material Design spec, Sheets: bottom <https://material.io/components/sheets-bottom>`_
.. rubric:: Bottom sheets are surfaces containing supplementary content that are anchored to the bottom of the screen.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet.png
:align: center
Two classes are available to you :class:`~MDListBottomSheet` and :class:`~MDGridBottomSheet`
for standard bottom sheets dialogs:
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/grid-list-bottomsheets.png
:align: center
Usage :class:`~MDListBottomSheet`
=================================
.. code-block:: python
from kivy.lang import Builder
from kivymd.toast import toast
from kivymd.uix.bottomsheet import MDListBottomSheet
from kivymd.app import MDApp
KV = '''
MDScreen:
MDTopAppBar:
title: "Example BottomSheet"
pos_hint: {"top": 1}
elevation: 10
MDRaisedButton:
text: "Open list bottom sheet"
on_release: app.show_example_list_bottom_sheet()
pos_hint: {"center_x": .5, "center_y": .5}
'''
class Example(MDApp):
def build(self):
return Builder.load_string(KV)
def callback_for_menu_items(self, *args):
toast(args[0])
def show_example_list_bottom_sheet(self):
bottom_sheet_menu = MDListBottomSheet()
for i in range(1, 11):
bottom_sheet_menu.add_item(
f"Standart Item {i}",
lambda x, y=i: self.callback_for_menu_items(
f"Standart Item {y}"
),
)
bottom_sheet_menu.open()
Example().run()
The :attr:`~MDListBottomSheet.add_item` method of the :class:`~MDListBottomSheet`
class takes the following arguments:
``text`` - element text;
``callback`` - function that will be called when clicking on an item;
There is also an optional argument ``icon``,
which will be used as an icon to the left of the item:
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/icon-list-bottomsheets.png
:align: center
.. rubric:: Using the :class:`~MDGridBottomSheet` class is similar
to using the :class:`~MDListBottomSheet` class:
.. code-block:: python
from kivy.lang import Builder
from kivymd.toast import toast
from kivymd.uix.bottomsheet import MDGridBottomSheet
from kivymd.app import MDApp
KV = '''
MDScreen:
MDTopAppBar:
title: 'Example BottomSheet'
pos_hint: {"top": 1}
elevation: 10
MDRaisedButton:
text: "Open grid bottom sheet"
on_release: app.show_example_grid_bottom_sheet()
pos_hint: {"center_x": .5, "center_y": .5}
'''
class Example(MDApp):
def build(self):
return Builder.load_string(KV)
def callback_for_menu_items(self, *args):
toast(args[0])
def show_example_grid_bottom_sheet(self):
bottom_sheet_menu = MDGridBottomSheet()
data = {
"Facebook": "facebook-box",
"YouTube": "youtube",
"Twitter": "twitter-box",
"Da Cloud": "cloud-upload",
"Camera": "camera",
}
for item in data.items():
bottom_sheet_menu.add_item(
item[0],
lambda x, y=item[0]: self.callback_for_menu_items(y),
icon_src=item[1],
)
bottom_sheet_menu.open()
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/grid-bottomsheet.png
:align: center
.. rubric:: You can use custom content for bottom sheet dialogs:
.. code-block:: python
from kivy.lang import Builder
from kivy.factory import Factory
from kivymd.uix.bottomsheet import MDCustomBottomSheet
from kivymd.app import MDApp
KV = '''
<ItemForCustomBottomSheet@OneLineIconListItem>
on_press: app.custom_sheet.dismiss()
icon: ""
IconLeftWidget:
icon: root.icon
<ContentCustomSheet@BoxLayout>:
orientation: "vertical"
size_hint_y: None
height: "400dp"
MDTopAppBar:
title: 'Custom bottom sheet:'
ScrollView:
MDGridLayout:
cols: 1
adaptive_height: True
ItemForCustomBottomSheet:
icon: "page-previous"
text: "Preview"
ItemForCustomBottomSheet:
icon: "exit-to-app"
text: "Exit"
MDScreen:
MDTopAppBar:
title: 'Example BottomSheet'
pos_hint: {"top": 1}
elevation: 10
MDRaisedButton:
text: "Open custom bottom sheet"
on_release: app.show_example_custom_bottom_sheet()
pos_hint: {"center_x": .5, "center_y": .5}
'''
class Example(MDApp):
custom_sheet = None
def build(self):
return Builder.load_string(KV)
def show_example_custom_bottom_sheet(self):
self.custom_sheet = MDCustomBottomSheet(screen=Factory.ContentCustomSheet())
self.custom_sheet.open()
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-bottomsheet.png
:align: center
.. note:: When you use the :attr:`~MDCustomBottomSheet` class, you must specify
the height of the user-defined content exactly, otherwise ``dp(100)``
heights will be used for your ``ContentCustomSheet`` class:
.. code-block:: kv
<ContentCustomSheet@BoxLayout>:
orientation: "vertical"
size_hint_y: None
height: "400dp"
.. note:: The height of the bottom sheet dialog will never exceed half
the height of the screen!
"""
__all__ = (
"MDGridBottomSheet",
"GridBottomSheetItem",
"MDListBottomSheet",
"MDCustomBottomSheet",
"MDBottomSheet",
)
import os
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.properties import (
BooleanProperty,
ColorProperty,
NumericProperty,
ObjectProperty,
OptionProperty,
StringProperty,
)
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.modalview import ModalView
from kivy.uix.scrollview import ScrollView
from kivymd import images_path, uix_path
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import BackgroundColorBehavior
from kivymd.uix.label import MDIcon
from kivymd.uix.list import ILeftBody, OneLineIconListItem, OneLineListItem
with open(
os.path.join(uix_path, "bottomsheet", "bottomsheet.kv"),
encoding="utf-8",
) as kv_file:
Builder.load_string(kv_file.read())
class SheetList(ScrollView):
pass
class BsPadding(ButtonBehavior, FloatLayout):
pass
class BottomSheetContent(BackgroundColorBehavior, GridLayout):
pass
class MDBottomSheet(ThemableBehavior, ModalView):
background = f"{images_path}transparent.png"
"""Private attribute."""
duration_opening = NumericProperty(0.15)
"""
The duration of the bottom sheet dialog opening animation.
:attr:`duration_opening` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0.15`.
"""
duration_closing = NumericProperty(0.15)
"""
The duration of the bottom sheet dialog closing animation.
:attr:`duration_closing` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0.15`.
"""
radius = NumericProperty(25)
"""
The value of the rounding of the corners of the dialog.
:attr:`radius` is an :class:`~kivy.properties.NumericProperty`
and defaults to `25`.
"""
radius_from = OptionProperty(
None,
options=[
"top_left",
"top_right",
"top",
"bottom_right",
"bottom_left",
"bottom",
],
allownone=True,
)
"""
Sets which corners to cut from the dialog. Available options are:
(`"top_left"`, `"top_right"`, `"top"`, `"bottom_right"`, `"bottom_left"`, `"bottom"`).
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-radius-from.png
:align: center
:attr:`radius_from` is an :class:`~kivy.properties.OptionProperty`
and defaults to `None`.
"""
animation = BooleanProperty(False)
"""
Whether to use animation for opening and closing of the bottomsheet or not.
:attr:`animation` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
bg_color = ColorProperty(None)
"""
Dialog background color in ``rgba`` format.
:attr:`bg_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[]`.
"""
value_transparent = ColorProperty([0, 0, 0, 0.8])
"""
Background transparency value when opening a dialog.
:attr:`value_transparent` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0.8]`.
"""
_upper_padding = ObjectProperty()
_gl_content = ObjectProperty()
_position_content = NumericProperty()
def open(self, *args):
super().open(*args)
def add_widget(self, widget, index=0, canvas=None):
super().add_widget(widget, index, canvas)
def dismiss(self, *args, **kwargs):
def dismiss(*args):
self.dispatch("on_pre_dismiss")
self._gl_content.clear_widgets()
self._real_remove_widget()
self.dispatch("on_dismiss")
if self.animation:
a = Animation(height=0, d=self.duration_closing)
a.bind(on_complete=dismiss)
a.start(self._gl_content)
else:
dismiss()
def resize_content_layout(self, content, layout, interval=0):
if not layout.ids.get("box_sheet_list"):
_layout = layout
else:
_layout = layout.ids.box_sheet_list
if _layout.height > Window.height / 2:
height = Window.height / 2
else:
height = _layout.height
if self.animation:
Animation(height=height, d=self.duration_opening).start(_layout)
Animation(height=height, d=self.duration_opening).start(content)
else:
layout.height = height
content.height = height
class ListBottomSheetIconLeft(ILeftBody, MDIcon):
pass
class MDCustomBottomSheet(MDBottomSheet):
screen = ObjectProperty()
"""
Custom content.
:attr:`screen` is an :class:`~kivy.properties.ObjectProperty`
and defaults to `None`.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._gl_content.add_widget(self.screen)
Clock.schedule_once(
lambda x: self.resize_content_layout(self._gl_content, self.screen),
0,
)
class MDListBottomSheet(MDBottomSheet):
sheet_list = ObjectProperty()
"""
:attr:`sheet_list` is an :class:`~kivy.properties.ObjectProperty`
and defaults to `None`.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.sheet_list = SheetList(size_hint_y=None)
self._gl_content.add_widget(self.sheet_list)
Clock.schedule_once(
lambda x: self.resize_content_layout(
self._gl_content, self.sheet_list
),
0,
)
def add_item(self, text, callback, icon=None):
"""
:arg text: element text;
:arg callback: function that will be called when clicking on an item;
:arg icon: which will be used as an icon to the left of the item;
"""
if icon:
item = OneLineIconListItem(text=text, on_release=callback)
item.add_widget(ListBottomSheetIconLeft(icon=icon))
else:
item = OneLineListItem(text=text, on_release=callback)
item.bind(on_release=lambda x: self.dismiss())
self.sheet_list.ids.box_sheet_list.add_widget(item)
class GridBottomSheetItem(ButtonBehavior, BoxLayout):
source = StringProperty()
"""
Icon path if you use a local image or icon name
if you use icon names from a file ``kivymd/icon_definitions.py``.
:attr:`source` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
caption = StringProperty()
"""
Item text.
:attr:`caption` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
icon_size = NumericProperty("24sp")
"""
Icon size.
:attr:`caption` is an :class:`~kivy.properties.StringProperty`
and defaults to `'24sp'`.
"""
class MDGridBottomSheet(MDBottomSheet):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.sheet_list = SheetList(size_hint_y=None)
self.sheet_list.ids.box_sheet_list.cols = 3
self.sheet_list.ids.box_sheet_list.padding = (dp(16), 0, dp(16), dp(96))
self._gl_content.add_widget(self.sheet_list)
Clock.schedule_once(
lambda x: self.resize_content_layout(
self._gl_content, self.sheet_list
),
0,
)
def add_item(self, text, callback, icon_src):
"""
:arg text: element text;
:arg callback: function that will be called when clicking on an item;
:arg icon_src: icon item;
"""
def tap_on_item(instance):
callback(instance)
self.dismiss()
item = GridBottomSheetItem(
caption=text, on_release=tap_on_item, source=icon_src
)
if len(self._gl_content.children) % 3 == 0:
self._gl_content.height += dp(96)
self.sheet_list.ids.box_sheet_list.add_widget(item)