mirror of
https://github.com/markqvist/Sideband.git
synced 2024-12-11 17:04:26 -05:00
1164 lines
36 KiB
Python
1164 lines
36 KiB
Python
"""
|
||
Components/BottomSheet
|
||
======================
|
||
|
||
.. seealso::
|
||
|
||
`Material Design spec, Sheets: bottom <https://m3.material.io/components/bottom-sheets/overview>`_
|
||
|
||
.. 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
|
||
|
||
Usage
|
||
=====
|
||
|
||
.. code-block:: kv
|
||
|
||
MDScreen:
|
||
|
||
[ Content screen ]
|
||
|
||
MDBottomSheet:
|
||
|
||
The bottom sheet has two types:
|
||
|
||
- Standard_
|
||
- Modal_
|
||
|
||
.. Standard:
|
||
Standard
|
||
--------
|
||
|
||
`Standard bottom sheets <https://m3.material.io/components/bottom-sheets/guidelines#aa1caae4-2d86-4c8c-af09-548a6f666b8a>`_
|
||
co-exist with the screen’s main UI region and allow for simultaneously viewing
|
||
and interacting with both regions, especially when the main UI region is
|
||
frequently scrolled or panned.
|
||
|
||
Use a standard bottom sheet to display content that complements the screen’s
|
||
primary content, such as an audio player in a music app.
|
||
|
||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-standard.png
|
||
:align: center
|
||
|
||
Standard bottom sheets are elevated above the main UI region so their
|
||
visibility is not affected by panning or scrolling.
|
||
|
||
Standard bottom sheet example
|
||
-----------------------------
|
||
|
||
.. tabs::
|
||
|
||
.. tab:: Declarative KV style
|
||
|
||
.. code-block:: python
|
||
|
||
from kivy.lang import Builder
|
||
|
||
from kivymd.app import MDApp
|
||
|
||
KV = '''
|
||
MDScreen:
|
||
|
||
MDBoxLayout:
|
||
orientation: "vertical"
|
||
padding: "12dp"
|
||
adaptive_height: True
|
||
pos_hint: {"top": 1}
|
||
|
||
MDSmartTile:
|
||
id: smart_tile
|
||
source: "https://picsum.photos/id/70/3011/2000"
|
||
radius: 16
|
||
box_radius: [0, 0, 16, 16]
|
||
size_hint_y: None
|
||
height: "240dp"
|
||
on_release:
|
||
bottom_sheet.open() \\
|
||
if bottom_sheet.state == "close" else \\
|
||
bottom_sheet.dismiss()
|
||
|
||
MDLabel:
|
||
bold: True
|
||
color: 1, 1, 1, 1
|
||
text:
|
||
"Tap to open the bottom sheet" \\
|
||
if bottom_sheet.state == "close" else \\
|
||
"Tap to close the bottom sheet"
|
||
|
||
MDBottomSheet:
|
||
id: bottom_sheet
|
||
type: "standard"
|
||
bg_color: "grey"
|
||
default_opening_height: smart_tile.y - dp(12)
|
||
size_hint_y: None
|
||
height: root.height - (smart_tile.height + dp(24))
|
||
'''
|
||
|
||
|
||
class Example(MDApp):
|
||
def build(self):
|
||
self.theme_cls.theme_style = "Dark"
|
||
return Builder.load_string(KV)
|
||
|
||
|
||
Example().run()
|
||
|
||
.. tab:: Declarative python style
|
||
|
||
.. code-block:: python
|
||
|
||
from kivy.clock import Clock
|
||
from kivy.metrics import dp
|
||
|
||
from kivymd.app import MDApp
|
||
from kivymd.uix.bottomsheet import MDBottomSheet
|
||
from kivymd.uix.boxlayout import MDBoxLayout
|
||
from kivymd.uix.imagelist import MDSmartTile
|
||
from kivymd.uix.label import MDLabel
|
||
from kivymd.uix.screen import MDScreen
|
||
|
||
|
||
class Example(MDApp):
|
||
def build(self):
|
||
self.theme_cls.theme_style = "Dark"
|
||
return MDScreen(
|
||
MDBoxLayout(
|
||
MDSmartTile(
|
||
MDLabel(
|
||
id="tile_label",
|
||
text="Tap to open the bottom sheet",
|
||
bold=True,
|
||
color=(1, 1, 1, 1),
|
||
),
|
||
id="smart_tile",
|
||
source="https://picsum.photos/id/70/3011/2000",
|
||
radius=16,
|
||
box_radius=[0, 0, 16, 16],
|
||
size_hint_y=None,
|
||
height="240dp",
|
||
),
|
||
id="box",
|
||
orientation="vertical",
|
||
padding="12dp",
|
||
pos_hint={"top": 1},
|
||
adaptive_height=True,
|
||
),
|
||
MDBottomSheet(
|
||
id="bottom_sheet",
|
||
size_hint_y=None,
|
||
type="standard",
|
||
bg_color="grey",
|
||
),
|
||
)
|
||
|
||
def open_bottom_sheet(self, *args):
|
||
bottom_sheet = self.root.ids.bottom_sheet
|
||
smart_tile = self.root.ids.box.ids.smart_tile
|
||
tile_label = smart_tile.ids.tile_label
|
||
bottom_sheet.open() if bottom_sheet.state == "close" else bottom_sheet.dismiss()
|
||
tile_label.text = (
|
||
"Tap to open the bottom sheet"
|
||
if bottom_sheet.state == "close"
|
||
else "Tap to close the bottom sheet"
|
||
)
|
||
|
||
def on_start(self):
|
||
def on_start(*args):
|
||
bottom_sheet = self.root.ids.bottom_sheet
|
||
smart_tile = self.root.ids.box.ids.smart_tile
|
||
bottom_sheet.default_opening_height = smart_tile.y - dp(12)
|
||
bottom_sheet.height = self.root.height - (
|
||
smart_tile.height + dp(24)
|
||
)
|
||
smart_tile.bind(on_release=lambda x: self.open_bottom_sheet())
|
||
|
||
Clock.schedule_once(on_start, 1.2)
|
||
|
||
|
||
Example().run()
|
||
|
||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-standard-example.gif
|
||
:align: center
|
||
|
||
.. Modal:
|
||
Modal
|
||
-----
|
||
|
||
Like dialogs, `modal bottom sheets <https://m3.material.io/components/bottom-sheets/guidelines#1cb775b6-6d2b-4d50-96ad-1862727e986b>`_
|
||
appear in front of app content, disabling all other app functionality when
|
||
they appear, and remaining on screen until confirmed, dismissed, or a required
|
||
action has been taken.
|
||
|
||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-modal.png
|
||
:align: center
|
||
|
||
Modal bottom sheet example
|
||
--------------------------
|
||
|
||
.. tabs::
|
||
|
||
.. tab:: Declarative KV style
|
||
|
||
.. code-block:: python
|
||
|
||
from kivy.lang import Builder
|
||
|
||
from kivymd.app import MDApp
|
||
|
||
KV = '''
|
||
MDScreen:
|
||
|
||
MDBoxLayout:
|
||
orientation: "vertical"
|
||
padding: "12dp"
|
||
adaptive_height: True
|
||
pos_hint: {"top": 1}
|
||
|
||
MDSmartTile:
|
||
id: smart_tile
|
||
source: "https://picsum.photos/id/70/3011/2000"
|
||
radius: 16
|
||
box_radius: [0, 0, 16, 16]
|
||
size_hint_y: None
|
||
height: "240dp"
|
||
on_release: bottom_sheet.open()
|
||
|
||
MDLabel:
|
||
bold: True
|
||
color: 1, 1, 1, 1
|
||
text: "Tap to open the modal bottom sheet"
|
||
|
||
MDBottomSheet:
|
||
id: bottom_sheet
|
||
bg_color: "grey"
|
||
default_opening_height: smart_tile.y - dp(12)
|
||
size_hint_y: None
|
||
height: root.height - (smart_tile.height + dp(24))
|
||
'''
|
||
|
||
|
||
class Example(MDApp):
|
||
def build(self):
|
||
self.theme_cls.theme_style = "Dark"
|
||
return Builder.load_string(KV)
|
||
|
||
|
||
Example().run()
|
||
|
||
.. tab:: Declarative python style
|
||
|
||
.. code-block:: python
|
||
|
||
from kivy.clock import Clock
|
||
from kivy.metrics import dp
|
||
|
||
from kivymd.app import MDApp
|
||
from kivymd.uix.bottomsheet import MDBottomSheet
|
||
from kivymd.uix.boxlayout import MDBoxLayout
|
||
from kivymd.uix.imagelist import MDSmartTile
|
||
from kivymd.uix.label import MDLabel
|
||
from kivymd.uix.screen import MDScreen
|
||
|
||
|
||
class Example(MDApp):
|
||
def build(self):
|
||
self.theme_cls.theme_style = "Dark"
|
||
return MDScreen(
|
||
MDBoxLayout(
|
||
MDSmartTile(
|
||
MDLabel(
|
||
id="tile_label",
|
||
text="Tap to open the modal bottom sheet",
|
||
bold=True,
|
||
color=(1, 1, 1, 1),
|
||
),
|
||
id="smart_tile",
|
||
source="https://picsum.photos/id/70/3011/2000",
|
||
radius=16,
|
||
box_radius=[0, 0, 16, 16],
|
||
size_hint_y=None,
|
||
height="240dp",
|
||
),
|
||
id="box",
|
||
orientation="vertical",
|
||
padding="12dp",
|
||
pos_hint={"top": 1},
|
||
adaptive_height=True,
|
||
),
|
||
MDBottomSheet(
|
||
id="bottom_sheet",
|
||
size_hint_y=None,
|
||
bg_color="grey",
|
||
),
|
||
)
|
||
|
||
def open_bottom_sheet(self, *args):
|
||
bottom_sheet = self.root.ids.bottom_sheet
|
||
bottom_sheet.open()
|
||
|
||
def on_start(self):
|
||
def on_start(*args):
|
||
bottom_sheet = self.root.ids.bottom_sheet
|
||
smart_tile = self.root.ids.box.ids.smart_tile
|
||
bottom_sheet.default_opening_height = smart_tile.y - dp(12)
|
||
bottom_sheet.height = self.root.height - (
|
||
smart_tile.height + dp(24)
|
||
)
|
||
smart_tile.bind(on_release=lambda x: self.open_bottom_sheet())
|
||
|
||
Clock.schedule_once(on_start, 1.2)
|
||
|
||
|
||
Example().run()
|
||
|
||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-modal-example.gif
|
||
:align: center
|
||
|
||
Tapping the scrim dismisses a modal bottom sheet.
|
||
|
||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-modal-tapping.png
|
||
:align: center
|
||
|
||
Custom positioning
|
||
------------------
|
||
|
||
The optional drag handle provides an affordance for custom sheet height,
|
||
or for a quick toggle through preset heights.
|
||
|
||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-drag-handle.png
|
||
:align: center
|
||
|
||
.. code-block:: kv
|
||
|
||
MDBottomSheet:
|
||
|
||
MDBottomSheetDragHandle:
|
||
|
||
By default, when you drag and then release the drag handle, the bottom sheet
|
||
will be closed or expand to the full screen, depending on whether you released
|
||
the drag handle closer to the top or to the bottom of the screen:
|
||
|
||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-drag-handle.gif
|
||
:align: center
|
||
|
||
In order to manually adjust the height of the bottom sheet with the drag handle,
|
||
set the `auto_positioning` parameter to `False`:
|
||
|
||
.. code-block:: kv
|
||
|
||
MDBottomSheet:
|
||
auto_positioning: False
|
||
|
||
MDBottomSheetDragHandle:
|
||
|
||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-drag-handle-auto-positioning.gif
|
||
:align: center
|
||
|
||
Add elements to :class:`~MDBottomSheetDragHandleTitle` class
|
||
------------------------------------------------------------
|
||
|
||
.. code-block:: kv
|
||
|
||
MDBottomSheet:
|
||
|
||
MDBottomSheetDragHandle:
|
||
|
||
MDBottomSheetDragHandleTitle:
|
||
text: "MDBottomSheet"
|
||
adaptive_height: True
|
||
font_style: "H6"
|
||
pos_hint: {"center_y": .5}
|
||
|
||
MDBottomSheetDragHandleButton:
|
||
icon: "close"
|
||
|
||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-drag-handle-elements.png
|
||
:align: center
|
||
|
||
Add custom content to :class:`~MDBottomSheet` class
|
||
---------------------------------------------------
|
||
|
||
To add custom content to the bottom sheet, use the
|
||
:class:`~MDBottomSheetContent` class:
|
||
|
||
.. code-block:: kv
|
||
|
||
MDBottomSheet:
|
||
bg_color: "darkgrey"
|
||
type: "standard"
|
||
max_opening_height: self.height
|
||
default_opening_height: self.max_opening_height
|
||
adaptive_height: True
|
||
|
||
MDBottomSheetDragHandle:
|
||
drag_handle_color: "grey"
|
||
|
||
MDBottomSheetContent:
|
||
padding: "16dp"
|
||
|
||
MDLabel:
|
||
text: "Content"
|
||
halign: "center"
|
||
font_style: "H5"
|
||
adaptive_height: True
|
||
|
||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-content.png
|
||
:align: center
|
||
|
||
A practical example with standard bottom sheet
|
||
----------------------------------------------
|
||
|
||
(A double tap on the map to open the bottom sheet)
|
||
|
||
.. code-block:: python
|
||
|
||
from kivy.lang import Builder
|
||
from kivy.properties import StringProperty, ObjectProperty, BooleanProperty
|
||
from kivy_garden.mapview import MapView
|
||
|
||
from kivymd.app import MDApp
|
||
from kivymd.uix.behaviors import TouchBehavior
|
||
from kivymd.uix.boxlayout import MDBoxLayout
|
||
from kivymd.utils import asynckivy
|
||
|
||
KV = '''
|
||
#:import MapSource kivy_garden.mapview.MapSource
|
||
#:import asynckivy kivymd.utils.asynckivy
|
||
|
||
|
||
<TypeMapElement>
|
||
orientation: "vertical"
|
||
adaptive_height: True
|
||
spacing: "8dp"
|
||
|
||
MDIconButton:
|
||
id: icon
|
||
icon: root.icon
|
||
md_bg_color: "#EDF1F9" if not root.selected else app.theme_cls.primary_color
|
||
pos_hint: {"center_x": .5}
|
||
theme_icon_color: "Custom"
|
||
icon_color: "white" if root.selected else "black"
|
||
on_release: app.set_active_element(root, root.title.lower())
|
||
|
||
MDLabel:
|
||
font_size: "14sp"
|
||
text: root.title
|
||
pos_hint: {"center_x": .5}
|
||
halign: "center"
|
||
adaptive_height: True
|
||
|
||
|
||
MDScreen:
|
||
|
||
CustomMapView:
|
||
bottom_sheet: bottom_sheet
|
||
map_source: MapSource(url=app.map_sources[app.current_map])
|
||
lat: 46.5124
|
||
lon: 47.9812
|
||
zoom: 12
|
||
|
||
MDBottomSheet:
|
||
id: bottom_sheet
|
||
elevation: 2
|
||
shadow_softness: 6
|
||
bg_color: "white"
|
||
type: "standard"
|
||
max_opening_height: self.height
|
||
default_opening_height: self.max_opening_height
|
||
adaptive_height: True
|
||
on_open: asynckivy.start(app.generate_content())
|
||
|
||
MDBottomSheetDragHandle:
|
||
drag_handle_color: "grey"
|
||
|
||
MDBottomSheetDragHandleTitle:
|
||
text: "Select type map"
|
||
adaptive_height: True
|
||
bold: True
|
||
pos_hint: {"center_y": .5}
|
||
|
||
MDBottomSheetDragHandleButton:
|
||
icon: "close"
|
||
_no_ripple_effect: True
|
||
on_release: bottom_sheet.dismiss()
|
||
|
||
MDBottomSheetContent:
|
||
id: content_container
|
||
padding: 0, 0, 0, "16dp"
|
||
'''
|
||
|
||
|
||
class TypeMapElement(MDBoxLayout):
|
||
selected = BooleanProperty(False)
|
||
icon = StringProperty()
|
||
title = StringProperty()
|
||
|
||
|
||
class CustomMapView(MapView, TouchBehavior):
|
||
bottom_sheet = ObjectProperty()
|
||
|
||
def on_double_tap(self, touch, *args):
|
||
if self.bottom_sheet:
|
||
self.bottom_sheet.open()
|
||
|
||
|
||
class Example(MDApp):
|
||
map_sources = {
|
||
"street": "https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}",
|
||
"sputnik": "https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}",
|
||
"hybrid": "https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}",
|
||
}
|
||
current_map = StringProperty("street")
|
||
|
||
async def generate_content(self):
|
||
icons = {
|
||
"street": "google-street-view",
|
||
"sputnik": "space-station",
|
||
"hybrid": "map-legend",
|
||
}
|
||
if not self.root.ids.content_container.children:
|
||
for i, title in enumerate(self.map_sources.keys()):
|
||
await asynckivy.sleep(0)
|
||
self.root.ids.content_container.add_widget(
|
||
TypeMapElement(
|
||
title=title.capitalize(),
|
||
icon=icons[title],
|
||
selected=not i,
|
||
)
|
||
)
|
||
|
||
def set_active_element(self, instance, type_map):
|
||
for element in self.root.ids.content_container.children:
|
||
if instance == element:
|
||
element.selected = True
|
||
self.current_map = type_map
|
||
else:
|
||
element.selected = False
|
||
|
||
def build(self):
|
||
return Builder.load_string(KV)
|
||
|
||
|
||
Example().run()
|
||
|
||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-sheet-real-example.gif
|
||
:align: center
|
||
|
||
"""
|
||
|
||
__all__ = (
|
||
"MDCustomBottomSheet",
|
||
"MDGridBottomSheet",
|
||
"MDListBottomSheet",
|
||
"MDBottomSheet",
|
||
"MDBottomSheetContent",
|
||
"MDBottomSheetDragHandle",
|
||
"MDBottomSheetDragHandleTitle",
|
||
"MDBottomSheetDragHandleButton",
|
||
)
|
||
|
||
import os
|
||
|
||
from kivy import Logger
|
||
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.screenmanager import Screen
|
||
|
||
from kivymd import uix_path
|
||
from kivymd.uix.behaviors import CommonElevationBehavior, TouchBehavior
|
||
from kivymd.uix.boxlayout import MDBoxLayout
|
||
from kivymd.uix.button import MDIconButton
|
||
from kivymd.uix.label import MDLabel
|
||
from kivymd.uix.screen import MDScreen
|
||
from kivymd.uix.widget import MDWidget
|
||
|
||
with open(
|
||
os.path.join(uix_path, "bottomsheet", "bottomsheet.kv"),
|
||
encoding="utf-8",
|
||
) as kv_file:
|
||
Builder.load_string(kv_file.read())
|
||
|
||
|
||
class BottomSheetDragHandle(MDWidget):
|
||
pass
|
||
|
||
|
||
class BottomSheetDragHandleContainer(MDBoxLayout):
|
||
pass
|
||
|
||
|
||
class BottomSheetScrimLayer(MDWidget):
|
||
"""
|
||
Implements a transparency layer to shade the parent widget
|
||
on which the bottom sheet is displayed.
|
||
"""
|
||
|
||
|
||
class MDBottomSheetContent(MDBoxLayout):
|
||
"""
|
||
Implements a container for custom content for the :class:`~MDBottomSheet`
|
||
class
|
||
|
||
For more information, see in the
|
||
:class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation.
|
||
|
||
.. versionadded:: 1.2.0
|
||
"""
|
||
|
||
|
||
class MDBottomSheetDragHandleButton(MDIconButton):
|
||
"""
|
||
Implements a close button (or other functionality) for the
|
||
:class:`~MDBottomSheetDragHandle` container.
|
||
|
||
For more information, see in the
|
||
:class:`~kivymd.uix.button.MDIconButton` class documentation.
|
||
|
||
.. versionadded:: 1.2.0
|
||
"""
|
||
|
||
|
||
class MDBottomSheetDragHandleTitle(MDLabel):
|
||
"""
|
||
Implements a header for the :class:`~MDBottomSheetDragHandle` container.
|
||
|
||
For more information, see in the
|
||
:class:`~kivymd.uix.label.MDLabel` class documentation.
|
||
|
||
.. versionadded:: 1.2.0
|
||
"""
|
||
|
||
|
||
class MDBottomSheetDragHandle(MDBoxLayout):
|
||
"""
|
||
Implements a container that can place the header of the bottom sheet
|
||
and the close button. Also implements the event of dragging the
|
||
bottom sheet on the parent screen.
|
||
|
||
For more information, see in the
|
||
:class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation.
|
||
|
||
.. versionadded:: 1.2.0
|
||
"""
|
||
|
||
drag_handle_color = ColorProperty(None)
|
||
"""
|
||
Color of drag handle element in (r, g, b, a) or string format.
|
||
|
||
.. code-block:: kv
|
||
|
||
MDBottomSheet:
|
||
|
||
MDBottomSheetDragHandle:
|
||
drag_handle_color: "white"
|
||
|
||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-sheet-drag-handle-color.png
|
||
:align: center
|
||
|
||
:attr:`drag_handle_color` is an :class:`~kivy.properties.ColorProperty`
|
||
and defaults to `None`.
|
||
"""
|
||
|
||
def add_widget(self, widget, *args, **kwargs):
|
||
if isinstance(
|
||
widget,
|
||
(MDBottomSheetDragHandleTitle, MDBottomSheetDragHandleButton),
|
||
):
|
||
self.ids.header_container.add_widget(widget)
|
||
elif isinstance(
|
||
widget,
|
||
(BottomSheetDragHandleContainer, BottomSheetDragHandle),
|
||
):
|
||
return super().add_widget(widget)
|
||
|
||
|
||
class MDBottomSheet(MDBoxLayout, CommonElevationBehavior, TouchBehavior):
|
||
"""
|
||
Bottom sheet class.
|
||
|
||
For more information, see in the
|
||
:class:`~kivymd.uix.boxlayout.MDBoxLayout` and
|
||
:class:`~kivymd.uix.behaviors.touch_behavior.CommonElevationBehavior` and
|
||
:class:`~kivymd.uix.behaviors.touch_behavior.TouchBehavior`
|
||
classes documentation.
|
||
|
||
:Events:
|
||
`on_open`
|
||
Event when opening the bottom sheet.
|
||
`on_close`
|
||
Event when closing the bottom sheet.
|
||
`on_progress`
|
||
Bottom sheet opening/closing progress event.
|
||
"""
|
||
|
||
auto_dismiss = BooleanProperty(True)
|
||
"""
|
||
This property determines if the view is automatically
|
||
dismissed when the user clicks outside it.
|
||
|
||
.. versionadded:: 1.2.0
|
||
|
||
:attr:`auto_dismiss` is a :class:`~kivy.properties.BooleanProperty`
|
||
and defaults to `True`.
|
||
"""
|
||
|
||
type = OptionProperty("modal", options=["modal", "standard"])
|
||
"""
|
||
Type sheet. There are two types of bottom sheets: standard and modal.
|
||
Available options are: `'modal'`, `'standard'`.
|
||
|
||
.. versionadded:: 1.2.0
|
||
|
||
:attr:`type` is an :class:`~kivy.properties.OptionProperty`
|
||
and defaults to `'modal`.
|
||
"""
|
||
|
||
auto_positioning = BooleanProperty(True)
|
||
"""
|
||
Close or expand the bottom menu automatically when you release the
|
||
drag handle.
|
||
|
||
.. versionadded:: 1.2.0
|
||
|
||
:attr:`auto_positioning` is an :class:`~kivy.properties.BooleanProperty`
|
||
and defaults to `True`.
|
||
"""
|
||
|
||
max_opening_height = NumericProperty(None, allownone=True)
|
||
"""
|
||
The maximum height a that the bottom sheet can be opened using the
|
||
drag handle.
|
||
|
||
.. versionadded:: 1.2.0
|
||
|
||
.. code-block:: kv
|
||
|
||
MDBottomSheet:
|
||
max_opening_height: "300dp"
|
||
|
||
MDBottomSheetDragHandle:
|
||
|
||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-max-opening-height.gif
|
||
:align: center
|
||
|
||
:attr:`max_opening_height` is an :class:`~kivy.properties.BooleanProperty`
|
||
and defaults to `None`.
|
||
"""
|
||
|
||
opening_transition = StringProperty("out_cubic")
|
||
"""
|
||
The name of the animation transition type to use when animating to
|
||
the :attr:`state` `'open'`.
|
||
|
||
.. versionadded:: 1.2.0
|
||
|
||
: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` 'close'.
|
||
|
||
.. versionadded:: 1.2.0
|
||
|
||
:attr:`closing_transition` is a :class:`~kivy.properties.StringProperty`
|
||
and defaults to `'out_sine'`.
|
||
"""
|
||
|
||
default_opening_height = NumericProperty(dp(200))
|
||
"""
|
||
Default opening height of the bottom sheet.
|
||
|
||
.. versionadded:: 1.2.0
|
||
|
||
:attr:`default_opening_height` is an :class:`~kivy.properties.NumericProperty`
|
||
and defaults to `dp(100)`.
|
||
"""
|
||
|
||
duration_opening = NumericProperty(0.15)
|
||
"""
|
||
The duration of the bottom sheet 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`.
|
||
"""
|
||
|
||
animation = BooleanProperty(True)
|
||
"""
|
||
Whether to use animation for opening and closing of the bottom sheet
|
||
or not.
|
||
|
||
:attr:`animation` is an :class:`~kivy.properties.BooleanProperty`
|
||
and defaults to `True`.
|
||
"""
|
||
|
||
state = OptionProperty("close", options=["close", "open"])
|
||
"""
|
||
Menu state. Available options are: `'close'`, `'open'`.
|
||
|
||
.. versionadded:: 1.2.0
|
||
|
||
:attr:`state` is an :class:`~kivy.properties.OptionProperty`
|
||
and defaults to `'close'`.
|
||
"""
|
||
|
||
scrim_layer_color = ColorProperty([0, 0, 0, 1])
|
||
"""
|
||
Color for scrim in (r, g, b, a) or string format.
|
||
|
||
.. versionadded:: 1.2.0
|
||
|
||
:attr:`scrim_layer_color` is a :class:`~kivy.properties.ColorProperty`
|
||
and defaults to `[0, 0, 0, 1]`.
|
||
"""
|
||
|
||
bg_color = ColorProperty(None)
|
||
"""
|
||
Background color of bottom sheet in (r, g, b, a) or string format.
|
||
|
||
:attr:`bg_color` is an :class:`~kivy.properties.ColorProperty`
|
||
and defaults to `None`.
|
||
"""
|
||
|
||
radius_from = OptionProperty(
|
||
None,
|
||
options=[
|
||
"top_left",
|
||
"top_right",
|
||
"top",
|
||
"bottom_right",
|
||
"bottom_left",
|
||
"bottom",
|
||
],
|
||
allownone=True,
|
||
deprecated=True,
|
||
)
|
||
"""
|
||
Sets which corners to cut from the dialog. Available options are:
|
||
`"top_left"`, `"top_right"`, `"top"`, `"bottom_right"`, `"bottom_left"`,
|
||
`"bottom"`.
|
||
|
||
.. deprecated:: 1.2.0
|
||
Use :attr:`radius` instead.
|
||
|
||
:attr:`radius_from` is an :class:`~kivy.properties.OptionProperty`
|
||
and defaults to `None`.
|
||
"""
|
||
|
||
value_transparent = ColorProperty([0, 0, 0, 0.8], deprecated=True)
|
||
"""
|
||
Background color in (r, g, b, a) or string format transparency value when
|
||
opening a dialog.
|
||
|
||
.. deprecated:: 1.2.0
|
||
|
||
:attr:`value_transparent` is an :class:`~kivy.properties.ColorProperty`
|
||
and defaults to `[0, 0, 0, 0.8]`.
|
||
"""
|
||
|
||
_diff_between_touch_height_sheet = 0
|
||
_alpha_channel_value = 0
|
||
# Menu state:
|
||
# - value 'down' - menu is captured;
|
||
# - value 'none' - menu is not captured;
|
||
_state = OptionProperty("none", options=["none", "down"])
|
||
# There was a touch to the bottom sheet.
|
||
_touch_sheet = False
|
||
# kivymd.uix.bottomsheet.bottomsheet.BottomSheetScrimLayer object.
|
||
_scrim_layer = ObjectProperty(None, allownone=True)
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
self.y = -Window.height # start bottom sheet position
|
||
Clock.schedule_once(self.check_parent)
|
||
Clock.schedule_once(self.check_max_opening_height)
|
||
Clock.schedule_once(self.add_scrim_layer)
|
||
self.register_event_type("on_open")
|
||
self.register_event_type("on_close")
|
||
self.register_event_type("on_progress")
|
||
|
||
def on_progress(self, *args) -> None:
|
||
"""Bottom sheet opening/closing progress event."""
|
||
|
||
def on_open(self, *args) -> None:
|
||
"""Event when opening the bottom sheet."""
|
||
|
||
def on_close(self, *args) -> None:
|
||
"""Event when closing the bottom sheet."""
|
||
|
||
def on_long_touch(self, touch, *args):
|
||
if self.ids.drag_handle_container.collide_point(touch.x, touch.y):
|
||
self._state = "down"
|
||
|
||
def on_touch_down(self, touch):
|
||
if self.type == "standard":
|
||
super().on_touch_down(touch)
|
||
|
||
if self.collide_point(touch.x, touch.y):
|
||
self._touch_sheet = not self._touch_sheet
|
||
if self.type == "standard":
|
||
return True
|
||
elif self.type == "modal":
|
||
return super().on_touch_down(touch)
|
||
|
||
def on_touch_up(self, touch):
|
||
self._diff_between_touch_height_sheet = 0
|
||
self._alpha_channel_value = 0
|
||
|
||
if self.collide_point(touch.x, touch.y):
|
||
self._touch_sheet = not self._touch_sheet
|
||
if self.auto_positioning:
|
||
if self._state == "down":
|
||
self._set_state(touch.y)
|
||
else:
|
||
if self._state == "down":
|
||
self._touch_sheet = not self._touch_sheet
|
||
self._set_state(touch.y)
|
||
|
||
def on_touch_move(self, touch):
|
||
if self._state == "down":
|
||
if not self._diff_between_touch_height_sheet:
|
||
self._diff_between_touch_height_sheet = (
|
||
abs(self.y) if self.y else self.height
|
||
) - touch.y
|
||
|
||
# FIXME: the behavior of the drag handle looks strange:
|
||
# sometimes the bottom sheet is dragged as needed, and sometimes
|
||
# it's position does not correspond to the cursor coordinates.
|
||
y = -(
|
||
(self.height - touch.y)
|
||
- 0 # self._diff_between_touch_height_sheet
|
||
)
|
||
|
||
if y > 0:
|
||
self.y = 0
|
||
return
|
||
if self.max_opening_height and touch.y > self.max_opening_height:
|
||
self.y = -(self.height - self.max_opening_height)
|
||
return
|
||
|
||
self.y = y
|
||
|
||
if self._scrim_layer and self.type == "modal":
|
||
if not self._alpha_channel_value:
|
||
self._alpha_channel_value = (
|
||
self._scrim_layer.md_bg_color[-1] - touch.psy
|
||
)
|
||
|
||
self._scrim_layer.md_bg_color = self._scrim_layer.md_bg_color[
|
||
:-1
|
||
] + [touch.psy + self._alpha_channel_value]
|
||
|
||
#
|
||
# if self.radius == [0.0, 0.0, 0.0, 0.0]:
|
||
# self.radius = [16, 16, 0, 0]
|
||
|
||
return super().on_touch_move(touch)
|
||
|
||
def on_type(self, *args) -> None:
|
||
self.add_scrim_layer()
|
||
|
||
def add_scrim_layer(self, *args) -> None:
|
||
"""
|
||
Adds a scrim layer to the parent widget on which the bottom sheet
|
||
will be displayed.
|
||
"""
|
||
|
||
if not self._scrim_layer and self.type == "modal":
|
||
self._scrim_layer = BottomSheetScrimLayer()
|
||
self.parent.add_widget(self._scrim_layer, index=1)
|
||
self._scrim_layer.bind(on_touch_down=self._on_touch_down_layer)
|
||
if self._scrim_layer and self.type == "standard":
|
||
self.parent.remove_widget(self._scrim_layer)
|
||
self._scrim_layer = None
|
||
|
||
def check_max_opening_height(self, *args) -> None:
|
||
if (
|
||
self.max_opening_height
|
||
and self.max_opening_height < self.default_opening_height
|
||
):
|
||
raise ValueError(
|
||
"The value of `max_opening_height` cannot be less "
|
||
"than the value of `default_opening_height`"
|
||
)
|
||
|
||
def check_parent(self, *args) -> None:
|
||
"""
|
||
Checks the type of parent widget to which the bottom sheet
|
||
will be added.
|
||
"""
|
||
|
||
if not issubclass(self.parent.__class__, Screen):
|
||
raise TypeError(
|
||
f"The bottom sheet can only be added to the {Screen} "
|
||
f"or {MDScreen} widgets."
|
||
)
|
||
|
||
def dismiss(self, *args) -> None:
|
||
"""Dismiss of bottom sheet."""
|
||
|
||
anim = Animation(
|
||
y=-self.height,
|
||
d=self.duration_closing if self.animation else 0,
|
||
t=self.closing_transition,
|
||
)
|
||
anim.bind(
|
||
on_complete=lambda x, y: self.dispatch("on_close"),
|
||
on_progress=lambda x, y, z: self.dispatch("on_progress", z),
|
||
)
|
||
anim.start(self)
|
||
|
||
# Animation(
|
||
# radius=[16, 16, 0, 0],
|
||
# d=self.duration_closing if self.animation else 0,
|
||
# ).start(self)
|
||
|
||
if self.type == "modal":
|
||
Animation(
|
||
md_bg_color=self.scrim_layer_color[:-1] + [0],
|
||
d=self.duration_closing if self.animation else 0,
|
||
).start(self._scrim_layer)
|
||
|
||
self.state = "close"
|
||
|
||
def expand(self) -> None:
|
||
"""Expand of bottom sheet."""
|
||
|
||
Animation(
|
||
y=0
|
||
if not self.max_opening_height
|
||
else -(self.height - self.default_opening_height),
|
||
d=self.duration_opening if self.animation else 0,
|
||
t=self.opening_transition,
|
||
).start(self)
|
||
|
||
# Animation(
|
||
# radius=[0, 0, 0, 0],
|
||
# d=self.duration_opening if self.animation else 0,
|
||
# ).start(self)
|
||
|
||
def open(self, *args) -> None:
|
||
"""Opening of bottom sheet."""
|
||
|
||
anim = Animation(
|
||
y=-(self.height - self.default_opening_height),
|
||
d=self.duration_opening if self.animation else 0,
|
||
t=self.opening_transition,
|
||
)
|
||
anim.bind(
|
||
on_complete=lambda x, y: self.dispatch("on_open"),
|
||
on_progress=lambda x, y, z: self.dispatch("on_progress", z),
|
||
)
|
||
anim.start(self)
|
||
|
||
if self.type == "modal":
|
||
alpha_channel_value = 100 / self.parent.height
|
||
Animation(
|
||
md_bg_color=self.scrim_layer_color[:-1] + [alpha_channel_value],
|
||
d=self.duration_opening if self.animation else 0,
|
||
).start(self._scrim_layer)
|
||
|
||
self.state = "open"
|
||
|
||
def clear_content(self) -> None:
|
||
"""Removes custom content from the bottom sheet."""
|
||
|
||
self.ids.container.clear_widgets()
|
||
|
||
def add_widget(self, widget, *args, **kwargs):
|
||
if isinstance(widget, MDBottomSheetDragHandle):
|
||
self.ids.drag_handle_container.add_widget(widget)
|
||
return
|
||
elif isinstance(widget, MDBottomSheetContent):
|
||
self.ids.container.add_widget(widget)
|
||
return
|
||
return super().add_widget(widget)
|
||
|
||
def _set_state(self, y):
|
||
self._state = "none"
|
||
if y < self.height / 2:
|
||
self.dismiss()
|
||
elif y > self.height / 2:
|
||
self.expand()
|
||
|
||
def _on_touch_down_layer(self, instance, touch):
|
||
if instance.collide_point(touch.x, touch.y):
|
||
if self._touch_sheet:
|
||
return True
|
||
|
||
if self.state == "open" and not self.auto_dismiss:
|
||
return True
|
||
elif self.state == "open" and self.auto_dismiss:
|
||
self.dismiss()
|
||
return True
|
||
|
||
|
||
class MDCustomBottomSheet(MDBottomSheet):
|
||
"""
|
||
.. deprecated:: 1.2.0
|
||
Use :class:`~kivymd.uix.bottomsheet.bottomsheet.MDBottomSheet`
|
||
class instead.
|
||
"""
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
Logger.warning(
|
||
"KivyMD: "
|
||
"The `MDCustomBottomSheet` class has been deprecated. "
|
||
"Use the `MDBottomSheet` class instead."
|
||
)
|
||
|
||
|
||
class MDListBottomSheet(MDBottomSheet):
|
||
"""
|
||
.. deprecated:: 1.2.0
|
||
Use :class:`~kivymd.uix.bottomsheet.bottomsheet.MDBottomSheet`
|
||
class instead.
|
||
"""
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
Logger.warning(
|
||
"KivyMD: "
|
||
"The `MDListBottomSheet` class has been deprecated. "
|
||
"Use the `MDBottomSheet` class instead."
|
||
)
|
||
|
||
|
||
class MDGridBottomSheet(MDBottomSheet):
|
||
"""
|
||
.. deprecated:: 1.2.0
|
||
Use :class:`~kivymd.uix.bottomsheet.bottomsheet.MDBottomSheet`
|
||
class instead.
|
||
"""
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
Logger.warning(
|
||
"KivyMD: "
|
||
"The `MDGridBottomSheet` class has been deprecated. "
|
||
"Use the `MDBottomSheet` class instead."
|
||
)
|