Sideband/sbapp/kivymd/uix/navigationdrawer/navigationdrawer.py

1271 lines
36 KiB
Python
Raw Normal View History

2022-07-07 16:16:10 -04:00
"""
Components/NavigationDrawer
===========================
.. seealso::
`Material Design 2 spec, Navigation drawer <https://material.io/components/navigation-drawer>`_ and
`Material Design 3 spec, Navigation drawer <https://m3.material.io/components/navigation-drawer/overview>`_
.. rubric:: Navigation drawers provide access to destinations in your app.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer.png
:align: center
When using the class :class:`~MDNavigationDrawer` skeleton of your `KV` markup
should look like this:
Anatomy
-------
.. code-block:: kv
Root:
MDNavigationLayout:
ScreenManager:
Screen_1:
Screen_2:
MDNavigationDrawer:
# This custom rule should implement what will be appear in your
# MDNavigationDrawer.
ContentNavigationDrawer:
A simple example
----------------
.. code-block:: python
from kivy.lang import Builder
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.app import MDApp
KV = '''
MDScreen:
MDNavigationLayout:
ScreenManager:
MDScreen:
MDTopAppBar:
title: "Navigation Drawer"
elevation: 10
pos_hint: {"top": 1}
md_bg_color: "#e7e4c0"
specific_text_color: "#4a4939"
left_action_items:
[['menu', lambda x: nav_drawer.set_state("open")]]
MDNavigationDrawer:
id: nav_drawer
md_bg_color: "#f7f4e7"
ContentNavigationDrawer:
'''
class ContentNavigationDrawer(MDBoxLayout):
pass
class TestNavigationDrawer(MDApp):
def build(self):
return Builder.load_string(KV)
TestNavigationDrawer().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer.gif
:align: center
.. Note:: :class:`~MDNavigationDrawer` is an empty
:class:`~kivymd.uix.card.MDCard` panel.
Custom content for navigation drawer
------------------------------------
Let's extend the ``ContentNavigationDrawer`` class from the above example and
create content for our :class:`~MDNavigationDrawer` panel:
.. code-block:: kv
# Menu item in the DrawerList list.
<ItemDrawer>
theme_text_color: "Custom"
on_release: self.parent.set_color_item(self)
IconLeftWidget:
id: icon
icon: root.icon
theme_text_color: "Custom"
text_color: root.text_color
.. code-block:: python
class ItemDrawer(OneLineIconListItem):
icon = StringProperty()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/drawer-item.png
:align: center
Top of ``ContentNavigationDrawer`` and ``DrawerList`` for menu items:
.. code-block:: kv
<ContentNavigationDrawer>
orientation: "vertical"
padding: "8dp"
spacing: "8dp"
AnchorLayout:
anchor_x: "left"
size_hint_y: None
height: avatar.height
Image:
id: avatar
size_hint: None, None
size: "56dp", "56dp"
source: "kivymd.png"
MDLabel:
text: "KivyMD library"
font_style: "Button"
size_hint_y: None
height: self.texture_size[1]
MDLabel:
text: "kivydevelopment@gmail.com"
font_style: "Caption"
size_hint_y: None
height: self.texture_size[1]
ScrollView:
DrawerList:
id: md_list
.. code-block:: python
class ContentNavigationDrawer(BoxLayout):
pass
class DrawerList(ThemableBehavior, MDList):
def set_color_item(self, instance_item):
'''Called when tap on a menu item.'''
# Set the color of the icon and text for the menu item.
for item in self.children:
if item.text_color == self.theme_cls.primary_color:
item.text_color = self.theme_cls.text_color
break
instance_item.text_color = self.theme_cls.primary_color
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/drawer-top.png
:align: center
Create a menu list for ``ContentNavigationDrawer``:
.. code-block:: python
def on_start(self):
icons_item = {
"folder": "My files",
"account-multiple": "Shared with me",
"star": "Starred",
"history": "Recent",
"checkbox-marked": "Shared with me",
"upload": "Upload",
}
for icon_name in icons_item.keys():
self.root.ids.content_drawer.ids.md_list.add_widget(
ItemDrawer(icon=icon_name, text=icons_item[icon_name])
)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/drawer-work.gif
:align: center
Standard content for the navigation bar
---------------------------------------
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
<DrawerClickableItem@MDNavigationDrawerItem>
focus_color: "#e7e4c0"
unfocus_color: "#f7f4e7"
text_color: "#4a4939"
icon_color: "#4a4939"
ripple_color: "#c5bdd2"
selected_color: "#0c6c4d"
<DrawerLabelItem@MDNavigationDrawerItem>
bg_color: "#f7f4e7"
text_color: "#4a4939"
icon_color: "#4a4939"
_no_ripple_effect: True
MDScreen:
MDNavigationLayout:
ScreenManager:
MDScreen:
MDTopAppBar:
title: "Navigation Drawer"
elevation: 10
pos_hint: {"top": 1}
md_bg_color: "#e7e4c0"
specific_text_color: "#4a4939"
left_action_items:
[ \
[ \
'menu', lambda x: \
nav_drawer.set_state("open") \
if nav_drawer.state == "close" else \
nav_drawer.set_state("close") \
] \
]
MDNavigationDrawer:
id: nav_drawer
radius: (0, 16, 16, 0) if self.anchor == "left" else (16, 0, 0, 16)
md_bg_color: "#f7f4e7"
MDNavigationDrawerMenu:
MDNavigationDrawerHeader:
title: "Header title"
title_color: "#4a4939"
text: "Header text"
title_color: "#4a4939"
spacing: "4dp"
padding: "12dp", 0, 0, "56dp"
MDNavigationDrawerLabel:
text: "Mail"
DrawerClickableItem:
icon: "gmail"
right_text: "+99"
text_right_color: "#4a4939"
text: "Inbox"
DrawerClickableItem:
icon: "send"
text: "Outbox"
MDNavigationDrawerDivider:
MDNavigationDrawerLabel:
text: "Labels"
DrawerLabelItem:
icon: "information-outline"
text: "Label"
DrawerLabelItem:
icon: "information-outline"
text: "Label"
'''
class TestNavigationDrawer(MDApp):
def build(self):
self.theme_cls.primary_palette = "Indigo"
return Builder.load_string(KV)
TestNavigationDrawer().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-standatd-content.gif
:align: center
Switching screens in the ``ScreenManager`` and using the common ``MDTopAppBar``
-----------------------------------------------------------------------------
.. code-block:: python
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
KV = '''
<ContentNavigationDrawer>
ScrollView:
MDList:
OneLineListItem:
text: "Screen 1"
on_press:
root.nav_drawer.set_state("close")
root.screen_manager.current = "scr 1"
OneLineListItem:
text: "Screen 2"
on_press:
root.nav_drawer.set_state("close")
root.screen_manager.current = "scr 2"
MDScreen:
MDTopAppBar:
id: toolbar
pos_hint: {"top": 1}
elevation: 10
title: "MDNavigationDrawer"
left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]]
MDNavigationLayout:
x: toolbar.height
ScreenManager:
id: screen_manager
MDScreen:
name: "scr 1"
MDLabel:
text: "Screen 1"
halign: "center"
MDScreen:
name: "scr 2"
MDLabel:
text: "Screen 2"
halign: "center"
MDNavigationDrawer:
id: nav_drawer
ContentNavigationDrawer:
screen_manager: screen_manager
nav_drawer: nav_drawer
'''
class ContentNavigationDrawer(MDBoxLayout):
screen_manager = ObjectProperty()
nav_drawer = ObjectProperty()
class TestNavigationDrawer(MDApp):
def build(self):
return Builder.load_string(KV)
TestNavigationDrawer().run()
"""
__all__ = (
"MDNavigationLayout",
"MDNavigationDrawer",
"MDNavigationDrawerItem",
"MDNavigationDrawerMenu",
"MDNavigationDrawerHeader",
"MDNavigationDrawerLabel",
"MDNavigationDrawerDivider",
)
import os
from typing import Union
from kivy.animation import Animation, AnimationTransition
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
from kivy.lang import Builder
from kivy.properties import (
AliasProperty,
BooleanProperty,
ColorProperty,
NumericProperty,
ObjectProperty,
OptionProperty,
StringProperty,
VariableListProperty,
)
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager
from kivy.uix.scrollview import ScrollView
from kivymd import uix_path
from kivymd.uix.behaviors import FakeRectangularElevationBehavior, FocusBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.card import MDCard
from kivymd.uix.list import MDList, OneLineAvatarIconListItem
from kivymd.uix.toolbar import MDTopAppBar
with open(
os.path.join(uix_path, "navigationdrawer", "navigationdrawer.kv"),
encoding="utf-8",
) as kv_file:
Builder.load_string(kv_file.read())
class NavigationDrawerContentError(Exception):
pass
class MDNavigationLayout(FloatLayout):
_scrim_color = ObjectProperty(None)
_scrim_rectangle = ObjectProperty(None)
_screen_manager = ObjectProperty(None)
_navigation_drawer = ObjectProperty(None)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(width=self.update_pos)
def update_pos(self, instance_navigation_drawer, pos_x: float) -> None:
drawer = self._navigation_drawer
manager = self._screen_manager
if not drawer or not manager:
return
if drawer.type == "standard":
manager.size_hint_x = None
if drawer.anchor == "left":
manager.x = drawer.width + drawer.x
manager.width = self.width - manager.x
else:
manager.x = 0
manager.width = drawer.x
elif drawer.type == "modal":
manager.size_hint_x = None
manager.x = 0
if drawer.anchor == "left":
manager.width = self.width - manager.x
else:
manager.width = self.width
def add_scrim(self, instance_manager: ScreenManager) -> None:
with instance_manager.canvas.after:
self._scrim_color = Color(rgba=[0, 0, 0, 0])
self._scrim_rectangle = Rectangle(
pos=instance_manager.pos, size=instance_manager.size
)
instance_manager.bind(
pos=self.update_scrim_rectangle,
size=self.update_scrim_rectangle,
)
def update_scrim_rectangle(
self, instance_manager: ScreenManager, size: list
) -> None:
self._scrim_rectangle.pos = self.pos
self._scrim_rectangle.size = self.size
def add_widget(self, widget, index=0, canvas=None):
"""
Only two layouts are allowed:
:class:`~kivy.uix.screenmanager.ScreenManager` and
:class:`~MDNavigationDrawer`.
"""
if not isinstance(
widget, (MDNavigationDrawer, ScreenManager, MDTopAppBar)
):
raise NavigationDrawerContentError(
"The MDNavigationLayout must contain "
"only `MDNavigationDrawer` and `ScreenManager`"
)
if isinstance(widget, ScreenManager):
self._screen_manager = widget
self.add_scrim(widget)
if isinstance(widget, MDNavigationDrawer):
self._navigation_drawer = widget
widget.bind(
x=self.update_pos, width=self.update_pos, anchor=self.update_pos
)
if len(self.children) > 3:
raise NavigationDrawerContentError(
"The MDNavigationLayout must contain "
"only `MDNavigationDrawer` and `ScreenManager`"
)
return super().add_widget(widget)
class MDNavigationDrawerLabel(MDBoxLayout):
"""
Implements a label for a menu for :class:`~MDNavigationDrawer` class.
.. versionadded:: 1.0.0
.. code-block:: kv
MDNavigationDrawer:
MDNavigationDrawerMenu:
MDNavigationDrawerLabel:
text: "Mail"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-label.png
:align: center
"""
text = StringProperty()
"""
Text label.
:attr:`text` is a :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
padding = VariableListProperty(["20dp", 0, 0, "8dp"])
"""
Padding between layout box and children: [padding_left, padding_top,
padding_right, padding_bottom].
Padding also accepts a two argument form [padding_horizontal,
padding_vertical] and a one argument form [padding].
:attr:`padding` is a :class:`~kivy.properties.VariableListProperty`
and defaults to `['20dp', 0, 0, '8dp']`.
"""
class MDNavigationDrawerDivider(MDBoxLayout):
"""
Implements a divider for a menu for :class:`~MDNavigationDrawer` class.
.. versionadded:: 1.0.0
.. code-block:: kv
MDNavigationDrawer:
MDNavigationDrawerMenu:
MDNavigationDrawerLabel:
text: "Mail"
MDNavigationDrawerDivider:
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-divider.png
:align: center
"""
padding = VariableListProperty(["20dp", "12dp", 0, "12dp"])
"""
Padding between layout box and children: [padding_left, padding_top,
padding_right, padding_bottom].
Padding also accepts a two argument form [padding_horizontal,
padding_vertical] and a one argument form [padding].
:attr:`padding` is a :class:`~kivy.properties.VariableListProperty`
and defaults to `['20dp', '12dp', 0, '12dp']`.
"""
color = ColorProperty(None)
"""
Divider color in ``rgba`` format.
:attr:`color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
class MDNavigationDrawerHeader(MDBoxLayout):
"""
Implements a header for a menu for :class:`~MDNavigationDrawer` class.
.. versionadded:: 1.0.0
.. code-block:: kv
MDNavigationDrawer:
MDNavigationDrawerMenu:
MDNavigationDrawerHeader:
title: "Header title"
text: "Header text"
spacing: "4dp"
padding: "12dp", 0, 0, "56dp"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-header.png
:align: center
"""
source = StringProperty()
"""
Image logo path.
.. code-block:: kv
MDNavigationDrawer:
MDNavigationDrawerMenu:
MDNavigationDrawerHeader:
title: "Header title"
text: "Header text"
source: "logo.png"
spacing: "4dp"
padding: "12dp", 0, 0, "56dp"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-header-source.png
:align: center
:attr:`source` is a :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
title = StringProperty()
"""
Title shown in the first line.
:attr:`title` is a :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
title_halign = StringProperty("left")
"""
Title halign first line.
:attr:`title_halign` is a :class:`~kivy.properties.StringProperty`
and defaults to `'left'`.
"""
title_color = ColorProperty(None)
"""
Title text color.
:attr:`title_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
title_font_style = StringProperty("H4")
"""
Title shown in the first line.
:attr:`title_font_style` is a :class:`~kivy.properties.StringProperty`
and defaults to `'H4'`.
"""
title_font_size = StringProperty("34sp")
"""
Title shown in the first line.
:attr:`title_font_size` is a :class:`~kivy.properties.StringProperty`
and defaults to `'34sp'`.
"""
text = StringProperty()
"""
Text shown in the second line.
:attr:`text` is a :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
text_halign = StringProperty("left")
"""
Text halign first line.
:attr:`text_halign` is a :class:`~kivy.properties.StringProperty`
and defaults to `'left'`.
"""
text_color = ColorProperty(None)
"""
Title text color.
:attr:`text_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
text_font_style = StringProperty("H6")
"""
Title shown in the first line.
:attr:`text_font_style` is a :class:`~kivy.properties.StringProperty`
and defaults to `'H6'`.
"""
text_font_size = StringProperty("20sp")
"""
Title shown in the first line.
:attr:`text_font_size` is a :class:`~kivy.properties.StringProperty`
and defaults to `'20sp'`.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self.check_content)
def check_content(self, interval: Union[int, float]) -> None:
"""Removes widgets that the user has not added to the container."""
if not self.title:
self.ids.label_box.remove_widget(self.ids.title)
if not self.text:
self.ids.label_box.remove_widget(self.ids.text)
if not self.source:
self.remove_widget(self.ids.logo)
class MDNavigationDrawerItem(OneLineAvatarIconListItem, FocusBehavior):
"""
Implements an item for the :class:`~MDNavigationDrawer` menu list.
.. versionadded:: 1.0.0
.. code-block:: kv
MDNavigationDrawer:
MDNavigationDrawerMenu:
MDNavigationDrawerHeader:
title: "Header title"
text: "Header text"
spacing: "4dp"
padding: "12dp", 0, 0, "56dp"
MDNavigationDrawerItem
icon: "gmail"
right_text: "+99"
text: "Inbox"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-item.png
:align: center
"""
selected = BooleanProperty(False)
"""
Is the item selected.
:attr:`selected` is a :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
icon = StringProperty()
"""
Icon item.
:attr:`icon` is a :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
icon_color = ColorProperty(None)
"""
Icon color item.
:attr:`icon_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
selected_color = ColorProperty([0, 0, 0, 1])
"""
The color of the icon and text of the selected item.
:attr:`selected_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 1]`.
"""
right_text = StringProperty()
"""
Right text item.
:attr:`right_text` is a :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
text_right_color = ColorProperty(None)
"""
Right text color item.
:attr:`text_right_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
_text_color = None
_text_right_color = None
# kivymd.uix.navigationdrawer.navigationdrawer.MDNavigationDrawerMenu
_drawer_menu = ObjectProperty()
class MDNavigationDrawerMenu(ScrollView):
"""
Implements a scrollable list for menu items of the
:class:`~MDNavigationDrawer` class.
.. versionadded:: 1.0.0
.. code-block:: kv
MDNavigationDrawer:
MDNavigationDrawerMenu:
# Your menu items.
...
"""
spacing = NumericProperty(0)
"""
Spacing between children, in pixels.
:attr:`spacing` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0`.
"""
def add_widget(self, widget, *args, **kwargs):
if isinstance(widget, MDList):
return super().add_widget(widget, *args, **kwargs)
else:
if isinstance(widget, MDNavigationDrawerItem):
widget._drawer_menu = self
self.ids.menu.add_widget(widget)
def reset_active_color(self, item: MDNavigationDrawerItem) -> None:
for widget in self.ids.menu.children:
if issubclass(widget.__class__, MDNavigationDrawerItem):
if widget != item:
widget.selected = False
else:
widget.selected = True
if (
issubclass(widget.__class__, MDNavigationDrawerItem)
and widget != item
):
if widget._text_color:
widget.text_color = widget._text_color
class MDNavigationDrawer(MDCard, FakeRectangularElevationBehavior):
type = OptionProperty("modal", options=("standard", "modal"))
"""
Type of drawer. Modal type will be on top of screen. Standard type will be
at left or right of screen. Also it automatically disables
:attr:`close_on_click` and :attr:`enable_swiping` to prevent closing
drawer for standard type.
Standard
--------
.. code-block:: kv
MDNavigationDrawer:
type: "standard"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-standard.gif
:align: center
Model
-----
.. code-block:: kv
MDNavigationDrawer:
type: "modal"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-modal.gif
:align: center
:attr:`type` is a :class:`~kivy.properties.OptionProperty`
and defaults to `'modal'`.
"""
anchor = OptionProperty("left", options=("left", "right"))
"""
Anchoring screen edge for drawer. Set it to `'right'` for right-to-left
languages. Available options are: `'left'`, `'right'`.
Left
----
.. code-block:: kv
MDNavigationDrawer:
anchor: "left"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-type-left.png
:align: center
Right
-----
.. code-block:: kv
MDNavigationDrawer:
anchor: "right"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-type-right.png
:align: center
:attr:`anchor` is a :class:`~kivy.properties.OptionProperty`
and defaults to `'left'`.
"""
# FIXME: Doesn't work in Kivy v2.1.0.
scrim_color = ColorProperty([0, 0, 0, 0.5])
"""
Color for scrim. Alpha channel will be multiplied with
:attr:`_scrim_alpha`. Set fourth channel to 0 if you want to disable
scrim.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-scrim-color.png
:align: center
.. code-block:: kv
MDNavigationDrawer:
scrim_color: 0, 0, 0, .8
# scrim_color: 0, 0, 0, .2
:attr:`scrim_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0.5]`.
"""
padding = VariableListProperty([16, 16, 12, 16])
"""
Padding between layout box and children: [padding_left, padding_top,
padding_right, padding_bottom].
Padding also accepts a two argument form [padding_horizontal,
padding_vertical] and a one argument form [padding].
.. versionchanged:: 1.0.0
.. code-block:: kv
MDNavigationDrawer:
padding: 56, 56, 12, 16
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-padding.png
:align: center
:attr:`padding` is a :class:`~kivy.properties.VariableListProperty` and
defaults to '[16, 16, 12, 16]'.
"""
close_on_click = BooleanProperty(True)
"""
Close when click on scrim or keyboard escape. It automatically sets to
False for "standard" type.
:attr:`close_on_click` is a :class:`~kivy.properties.BooleanProperty`
and defaults to `True`.
"""
state = OptionProperty("close", options=("close", "open"))
"""
Indicates if panel closed or opened. Sets after :attr:`status` change.
Available options are: `'close'`, `'open'`.
:attr:`state` is a :class:`~kivy.properties.OptionProperty`
and defaults to `'close'`.
"""
status = OptionProperty(
"closed",
options=(
"closed",
"opening_with_swipe",
"opening_with_animation",
"opened",
"closing_with_swipe",
"closing_with_animation",
),
)
"""
Detailed state. Sets before :attr:`state`. Bind to :attr:`state` instead
of :attr:`status`. Available options are: `'closed'`,
`'opening_with_swipe'`, `'opening_with_animation'`, `'opened'`,
`'closing_with_swipe'`, `'closing_with_animation'`.
:attr:`status` is a :class:`~kivy.properties.OptionProperty`
and defaults to `'closed'`.
"""
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`.
"""
enable_swiping = BooleanProperty(True)
"""
Allow to open or close navigation drawer with swipe. It automatically
sets to False for "standard" type.
:attr:`enable_swiping` is a :class:`~kivy.properties.BooleanProperty`
and defaults to `True`.
"""
swipe_distance = NumericProperty(10)
"""
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 `10`.
"""
swipe_edge_width = NumericProperty(20)
"""
The size of the area in px inside which should start swipe to drag
navigation drawer.
:attr:`swipe_edge_width` is a :class:`~kivy.properties.NumericProperty`
and defaults to `20`.
"""
def _get_scrim_alpha(self):
_scrim_alpha = 0
if self.type == "modal":
_scrim_alpha = self._scrim_alpha_transition(self.open_progress)
if (
isinstance(self.parent, MDNavigationLayout)
and self.parent._scrim_color
):
self.parent._scrim_color.rgba = self.scrim_color[:3] + [
self.scrim_color[3] * _scrim_alpha
]
return _scrim_alpha
_scrim_alpha = AliasProperty(
_get_scrim_alpha,
None,
bind=("_scrim_alpha_transition", "open_progress", "scrim_color"),
)
"""
Multiplier for alpha channel of :attr:`scrim_color`. For internal
usage only.
"""
scrim_alpha_transition = StringProperty("linear")
"""
The name of the animation transition type to use for changing
:attr:`scrim_alpha`.
:attr:`scrim_alpha_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'linear'`.
"""
def _get_scrim_alpha_transition(self):
return getattr(AnimationTransition, self.scrim_alpha_transition)
_scrim_alpha_transition = AliasProperty(
_get_scrim_alpha_transition,
None,
bind=("scrim_alpha_transition",),
cache=True,
)
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`.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(
open_progress=self.update_status,
status=self.update_status,
state=self.update_status,
)
Window.bind(on_keyboard=self._handle_keyboard)
def set_state(self, new_state="toggle", animation=True) -> None:
"""
Change state of the side panel.
New_state can be one of `"toggle"`, `"open"` or `"close"`.
"""
if new_state == "toggle":
new_state = "close" if self.state == "open" else "open"
if new_state == "open":
Animation.cancel_all(self, "open_progress")
self.status = "opening_with_animation"
if animation:
Animation(
open_progress=1.0,
d=self.opening_time * (1 - self.open_progress),
t=self.opening_transition,
).start(self)
else:
self.open_progress = 1
else: # "close"
Animation.cancel_all(self, "open_progress")
self.status = "closing_with_animation"
if animation:
Animation(
open_progress=0.0,
d=self.closing_time * self.open_progress,
t=self.closing_transition,
).start(self)
else:
self.open_progress = 0
def update_status(self, *_) -> None:
status = self.status
if status == "closed":
self.state = "close"
elif status == "opened":
self.state = "open"
elif self.open_progress == 1 and status == "opening_with_animation":
self.status = "opened"
self.state = "open"
elif self.open_progress == 0 and status == "closing_with_animation":
self.status = "closed"
self.state = "close"
elif status in (
"opening_with_swipe",
"opening_with_animation",
"closing_with_swipe",
"closing_with_animation",
):
pass
if self.status == "closed":
self.opacity = 0
else:
self.opacity = 1
def get_dist_from_side(self, x: float) -> float:
if self.anchor == "left":
return 0 if x < 0 else x
return 0 if x > Window.width else Window.width - x
def on_touch_down(self, touch):
if self.status == "closed":
return False
elif self.status == "opened":
for child in self.children[:]:
if child.dispatch("on_touch_down", touch):
return True
if self.type == "standard" and not self.collide_point(
touch.ox, touch.oy
):
return False
return True
def on_touch_move(self, touch):
if self.enable_swiping:
if self.status == "closed":
if (
self.get_dist_from_side(touch.ox) <= self.swipe_edge_width
and abs(touch.x - touch.ox) > self.swipe_distance
):
self.status = "opening_with_swipe"
elif self.status == "opened":
if abs(touch.x - touch.ox) > self.swipe_distance:
self.status = "closing_with_swipe"
if self.status in ("opening_with_swipe", "closing_with_swipe"):
self.open_progress = max(
min(
self.open_progress
+ (touch.dx if self.anchor == "left" else -touch.dx)
/ self.width,
1,
),
0,
)
return True
return super().on_touch_move(touch)
def on_touch_up(self, touch):
if self.status == "opening_with_swipe":
if self.open_progress > 0.5:
self.set_state("open", animation=True)
else:
self.set_state("close", animation=True)
elif self.status == "closing_with_swipe":
if self.open_progress < 0.5:
self.set_state("close", animation=True)
else:
self.set_state("open", animation=True)
elif self.status == "opened":
if self.close_on_click and not self.collide_point(
touch.ox, touch.oy
):
self.set_state("close", animation=True)
elif self.type == "standard" and not self.collide_point(
touch.ox, touch.oy
):
return False
elif self.status == "closed":
return False
return True
def on_radius(self, instance_navigation_drawer, radius_value: list) -> None:
self._radius = radius_value
def on_type(self, instance_navigation_drawer, drawer_type: str) -> None:
if self.type == "standard":
self.enable_swiping = False
self.close_on_click = False
else:
self.enable_swiping = True
self.close_on_click = True
def _handle_keyboard(self, window, key, *largs):
if key == 27 and self.status == "opened" and self.close_on_click:
self.set_state("close")
return True