first commit
This commit is contained in:
commit
417e54da96
5696 changed files with 900003 additions and 0 deletions
|
@ -0,0 +1,92 @@
|
|||
__all__ = ("MDAdaptiveWidget",)
|
||||
|
||||
from kivy.properties import BooleanProperty
|
||||
from kivy.uix.floatlayout import FloatLayout
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.screenmanager import Screen
|
||||
|
||||
from kivymd.uix.behaviors import SpecificBackgroundColorBehavior
|
||||
|
||||
|
||||
class MDAdaptiveWidget(SpecificBackgroundColorBehavior):
|
||||
adaptive_height = BooleanProperty(False)
|
||||
"""
|
||||
If `True`, the following properties will be applied to the widget:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
|
||||
:attr:`adaptive_height` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
adaptive_width = BooleanProperty(False)
|
||||
"""
|
||||
If `True`, the following properties will be applied to the widget:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
size_hint_x: None
|
||||
width: self.minimum_width
|
||||
|
||||
:attr:`adaptive_width` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
adaptive_size = BooleanProperty(False)
|
||||
"""
|
||||
If `True`, the following properties will be applied to the widget:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
size_hint: None, None
|
||||
size: self.minimum_size
|
||||
|
||||
:attr:`adaptive_size` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
def on_adaptive_height(self, md_widget, value: bool) -> None:
|
||||
self.size_hint_y = None
|
||||
if issubclass(self.__class__, Label):
|
||||
self.bind(
|
||||
texture_size=lambda *x: self.setter("height")(
|
||||
self, self.texture_size[1]
|
||||
)
|
||||
)
|
||||
else:
|
||||
if not isinstance(self, (FloatLayout, Screen)):
|
||||
self.bind(minimum_height=self.setter("height"))
|
||||
if not self.children:
|
||||
self.height = 0
|
||||
|
||||
def on_adaptive_width(self, md_widget, value: bool) -> None:
|
||||
self.size_hint_x = None
|
||||
if issubclass(self.__class__, Label):
|
||||
self.bind(
|
||||
texture_size=lambda *x: self.setter("width")(
|
||||
self, self.texture_size[0]
|
||||
)
|
||||
)
|
||||
else:
|
||||
if not isinstance(self, (FloatLayout, Screen)):
|
||||
self.bind(minimum_width=self.setter("width"))
|
||||
if not self.children:
|
||||
self.width = 0
|
||||
|
||||
def on_adaptive_size(self, md_widget, value: bool) -> None:
|
||||
self.size_hint = (None, None)
|
||||
if issubclass(self.__class__, Label):
|
||||
self.text_size = (None, None)
|
||||
self.bind(
|
||||
texture_size=lambda *x: self.setter("size")(
|
||||
self, self.texture_size
|
||||
)
|
||||
)
|
||||
else:
|
||||
if not isinstance(self, (FloatLayout, Screen)):
|
||||
self.bind(minimum_size=self.setter("size"))
|
||||
if not self.children:
|
||||
self.size = (0, 0)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,47 @@
|
|||
"""
|
||||
Components/AnchorLayout
|
||||
=======================
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
:class:`~kivy.uix.anchorlayout.AnchorLayout` class equivalent. Simplifies working
|
||||
with some widget properties. For example:
|
||||
|
||||
AnchorLayout
|
||||
------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
AnchorLayout:
|
||||
canvas:
|
||||
Color:
|
||||
rgba: app.theme_cls.primary_color
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
|
||||
MDAnchorLayout
|
||||
--------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDAnchorLayout:
|
||||
md_bg_color: app.theme_cls.primary_color
|
||||
"""
|
||||
|
||||
__all__ = ("MDAnchorLayout",)
|
||||
|
||||
from kivy.uix.anchorlayout import AnchorLayout
|
||||
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.uix import MDAdaptiveWidget
|
||||
from kivymd.uix.behaviors import DeclarativeBehavior
|
||||
|
||||
|
||||
class MDAnchorLayout(
|
||||
DeclarativeBehavior, ThemableBehavior, AnchorLayout, MDAdaptiveWidget
|
||||
):
|
||||
"""
|
||||
Anchor layout class. For more information, see in the
|
||||
:class:`~kivy.uix.anchorlayout.AnchorLayout` class documentation.
|
||||
"""
|
|
@ -0,0 +1 @@
|
|||
from .backdrop import MDBackdrop # NOQA F401
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,50 @@
|
|||
<MDBackdrop>
|
||||
md_bg_color:
|
||||
root.theme_cls.primary_color \
|
||||
if not root.back_layer_color \
|
||||
else root.back_layer_color
|
||||
|
||||
MDBackdropToolbar:
|
||||
id: toolbar
|
||||
type_height: "small"
|
||||
anchor_title: root.anchor_title
|
||||
title: root.title
|
||||
elevation: 0
|
||||
left_action_items: root.left_action_items
|
||||
right_action_items: root.right_action_items
|
||||
pos_hint: {"top": 1}
|
||||
md_bg_color:
|
||||
root.theme_cls.primary_color \
|
||||
if not root.back_layer_color \
|
||||
else root.back_layer_color
|
||||
|
||||
_BackLayer:
|
||||
id: back_layer
|
||||
y: -toolbar.height
|
||||
padding: 0, 0, 0, toolbar.height + dp(10)
|
||||
|
||||
_FrontLayer:
|
||||
id: _front_layer
|
||||
md_bg_color: 0, 0, 0, 0
|
||||
orientation: "vertical"
|
||||
size_hint_y: None
|
||||
height: root.height - toolbar.height
|
||||
padding: root.padding
|
||||
md_bg_color:
|
||||
root.theme_cls.bg_normal \
|
||||
if not root.front_layer_color \
|
||||
else root.front_layer_color
|
||||
radius:
|
||||
[root.radius_left, root.radius_right,
|
||||
0, 0]
|
||||
|
||||
OneLineListItem:
|
||||
id: header_button
|
||||
text: root.header_text
|
||||
divider: None
|
||||
_no_ripple_effect: True
|
||||
on_press: root.open()
|
||||
|
||||
MDBoxLayout:
|
||||
id: front_layer
|
||||
padding: 0, 0, 0, "10dp"
|
|
@ -0,0 +1,548 @@
|
|||
"""
|
||||
Components/Backdrop
|
||||
===================
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, Backdrop <https://material.io/components/backdrop>`_
|
||||
|
||||
.. rubric:: Skeleton layout for using :class:`~MDBackdrop`:
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop.png
|
||||
:align: center
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<Root>
|
||||
|
||||
MDBackdrop:
|
||||
|
||||
MDBackdropBackLayer:
|
||||
|
||||
ContentForBackdropBackLayer:
|
||||
|
||||
MDBackdropFrontLayer:
|
||||
|
||||
ContentForBackdropFrontLayer:
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. tab:: Declarative KV styles
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.uix.screen import MDScreen
|
||||
from kivymd.app import MDApp
|
||||
|
||||
# Your layouts.
|
||||
Builder.load_string(
|
||||
'''
|
||||
#:import os os
|
||||
#:import Window kivy.core.window.Window
|
||||
#:import IconLeftWidget kivymd.uix.list.IconLeftWidget
|
||||
#:import images_path kivymd.images_path
|
||||
|
||||
|
||||
<ItemBackdropFrontLayer@TwoLineAvatarListItem>
|
||||
icon: "android"
|
||||
|
||||
IconLeftWidget:
|
||||
icon: root.icon
|
||||
|
||||
|
||||
<MyBackdropFrontLayer@ItemBackdropFrontLayer>
|
||||
backdrop: None
|
||||
text: "Lower the front layer"
|
||||
secondary_text: " by 50 %"
|
||||
icon: "transfer-down"
|
||||
on_press: root.backdrop.open(-Window.height / 2)
|
||||
pos_hint: {"top": 1}
|
||||
_no_ripple_effect: True
|
||||
|
||||
|
||||
<MyBackdropBackLayer@Image>
|
||||
size_hint: .8, .8
|
||||
source: os.path.join(images_path, "logo", "kivymd-icon-512.png")
|
||||
pos_hint: {"center_x": .5, "center_y": .6}
|
||||
'''
|
||||
)
|
||||
|
||||
# Usage example of MDBackdrop.
|
||||
Builder.load_string(
|
||||
'''
|
||||
<ExampleBackdrop>
|
||||
|
||||
MDBackdrop:
|
||||
id: backdrop
|
||||
left_action_items: [['menu', lambda x: self.open()]]
|
||||
title: "Example Backdrop"
|
||||
radius_left: "25dp"
|
||||
radius_right: "0dp"
|
||||
header_text: "Menu:"
|
||||
|
||||
MDBackdropBackLayer:
|
||||
MyBackdropBackLayer:
|
||||
id: backlayer
|
||||
|
||||
MDBackdropFrontLayer:
|
||||
MyBackdropFrontLayer:
|
||||
backdrop: backdrop
|
||||
'''
|
||||
)
|
||||
|
||||
|
||||
class ExampleBackdrop(MDScreen):
|
||||
pass
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
self.theme_cls.primary_palette = "Orange"
|
||||
return ExampleBackdrop()
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. tab:: Declarative python styles
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import os
|
||||
|
||||
from kivy.core.window import Window
|
||||
from kivy.uix.image import Image
|
||||
|
||||
from kivymd import images_path
|
||||
from kivymd.uix.backdrop import MDBackdrop
|
||||
from kivymd.uix.backdrop.backdrop import (
|
||||
MDBackdropBackLayer, MDBackdropFrontLayer
|
||||
)
|
||||
from kivymd.uix.list import TwoLineAvatarListItem, IconLeftWidget
|
||||
from kivymd.uix.screen import MDScreen
|
||||
from kivymd.app import MDApp
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
self.theme_cls.primary_palette = "Orange"
|
||||
|
||||
return (
|
||||
MDScreen(
|
||||
MDBackdrop(
|
||||
MDBackdropBackLayer(
|
||||
Image(
|
||||
size_hint=(0.8, 0.8),
|
||||
source=os.path.join(images_path, "logo", "kivymd-icon-512.png"),
|
||||
pos_hint={"center_x": 0.5, "center_y": 0.6},
|
||||
)
|
||||
),
|
||||
MDBackdropFrontLayer(
|
||||
TwoLineAvatarListItem(
|
||||
IconLeftWidget(icon="transfer-down"),
|
||||
text="Lower the front layer",
|
||||
secondary_text=" by 50 %",
|
||||
on_press=self.backdrop_open_by_50_percent,
|
||||
pos_hint={"top": 1},
|
||||
_no_ripple_effect=True,
|
||||
),
|
||||
),
|
||||
id="backdrop",
|
||||
title="Example Backdrop",
|
||||
radius_left="25dp",
|
||||
radius_right="0dp",
|
||||
header_text="Menu:",
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def backdrop_open_by_50_percent(self, *args):
|
||||
self.root.ids.backdrop.open(-Window.height / 2)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop.gif
|
||||
:align: center
|
||||
|
||||
.. Note:: `See full example <https://github.com/kivymd/KivyMD/wiki/Components-Backdrop>`_
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"MDBackdropToolbar",
|
||||
"MDBackdropFrontLayer",
|
||||
"MDBackdropBackLayer",
|
||||
"MDBackdrop",
|
||||
)
|
||||
|
||||
import os
|
||||
from typing import Union
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.clock import Clock
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import (
|
||||
BooleanProperty,
|
||||
ColorProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
OptionProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
|
||||
from kivymd import uix_path
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.card import MDCard
|
||||
from kivymd.uix.floatlayout import MDFloatLayout
|
||||
from kivymd.uix.toolbar.toolbar import ActionTopAppBarButton, MDTopAppBar
|
||||
|
||||
with open(
|
||||
os.path.join(uix_path, "backdrop", "backdrop.kv"),
|
||||
encoding="utf-8",
|
||||
) as kv_file:
|
||||
Builder.load_string(kv_file.read())
|
||||
|
||||
|
||||
class MDBackdrop(MDFloatLayout):
|
||||
"""
|
||||
For more information, see in the
|
||||
:class:`~kivymd.uix.floatlayout.MDFloatLayout` class documentation.
|
||||
|
||||
:Events:
|
||||
:attr:`on_open`
|
||||
When the front layer drops.
|
||||
:attr:`on_close`
|
||||
When the front layer rises.
|
||||
"""
|
||||
|
||||
anchor_title = OptionProperty("left", options=["left", "center", "right"])
|
||||
"""
|
||||
Position toolbar title. Only used with `material_style = 'M3'`
|
||||
Available options are: `'left'`, `'center'`, `'right'`.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-anchor-title.png
|
||||
:align: center
|
||||
|
||||
:attr:`anchor_title` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'left'`.
|
||||
"""
|
||||
|
||||
padding = ListProperty([0, 0, 0, 0])
|
||||
"""
|
||||
Padding for contents of the front layer.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-padding.png
|
||||
:align: center
|
||||
|
||||
:attr:`padding` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[0, 0, 0, 0]`.
|
||||
"""
|
||||
|
||||
left_action_items = ListProperty()
|
||||
"""
|
||||
The icons and methods left of the :class:`kivymd.uix.toolbar.MDTopAppBar`
|
||||
in back layer. For more information, see the
|
||||
:class:`kivymd.uix.toolbar.MDTopAppBar` module
|
||||
and :attr:`left_action_items` parameter.
|
||||
|
||||
:attr:`left_action_items` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
right_action_items = ListProperty()
|
||||
"""
|
||||
Works the same way as :attr:`left_action_items`.
|
||||
|
||||
:attr:`right_action_items` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
title = StringProperty()
|
||||
"""
|
||||
See the :class:`kivymd.uix.toolbar.MDTopAppBar.title` parameter.
|
||||
|
||||
:attr:`title` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
back_layer_color = ColorProperty(None)
|
||||
"""
|
||||
Background color of back layer in (r, g, b, a) or string format.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-back-layer-color.png
|
||||
:align: center
|
||||
|
||||
:attr:`back_layer_color` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
front_layer_color = ColorProperty(None)
|
||||
"""
|
||||
Background color of front layer in (r, g, b, a) or string format.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-front-layer-color.png
|
||||
:align: center
|
||||
|
||||
:attr:`front_layer_color` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
radius_left = NumericProperty("16dp")
|
||||
"""
|
||||
The value of the rounding radius of the upper left corner
|
||||
of the front layer.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-radius-left.png
|
||||
:align: center
|
||||
|
||||
:attr:`radius_left` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `16dp`.
|
||||
"""
|
||||
|
||||
radius_right = NumericProperty("16dp")
|
||||
"""
|
||||
The value of the rounding radius of the upper right corner
|
||||
of the front layer.
|
||||
|
||||
:attr:`radius_right` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `16dp`.
|
||||
"""
|
||||
|
||||
header = BooleanProperty(True)
|
||||
"""
|
||||
Whether to use a header above the contents of the front layer.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-header.png
|
||||
:align: center
|
||||
|
||||
:attr:`header` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `True`.
|
||||
"""
|
||||
|
||||
header_text = StringProperty("Header")
|
||||
"""
|
||||
Text of header.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-header-text.png
|
||||
:align: center
|
||||
|
||||
:attr:`header_text` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'Header'`.
|
||||
"""
|
||||
|
||||
close_icon = StringProperty("close")
|
||||
"""
|
||||
The name of the icon that will be installed on the toolbar
|
||||
on the left when opening the front layer.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-close-icon.png
|
||||
:align: center
|
||||
|
||||
:attr:`close_icon` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'close'`.
|
||||
"""
|
||||
|
||||
opening_time = NumericProperty(0.2)
|
||||
"""
|
||||
The time taken for the panel to slide to the :attr:`state` `'open'`.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
:attr:`opening_time` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.2`.
|
||||
"""
|
||||
|
||||
opening_transition = StringProperty("out_quad")
|
||||
"""
|
||||
The name of the animation transition type to use when animating to
|
||||
the :attr:`state` `'open'`.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
:attr:`opening_transition` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'out_quad'`.
|
||||
"""
|
||||
|
||||
closing_time = NumericProperty(0.2)
|
||||
"""
|
||||
The time taken for the panel to slide to the :attr:`state` `'close'`.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
:attr:`closing_time` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.2`.
|
||||
"""
|
||||
|
||||
closing_transition = StringProperty("out_quad")
|
||||
"""
|
||||
The name of the animation transition type to use when animating to
|
||||
the :attr:`state` 'close'.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
:attr:`closing_transition` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'out_quad'`.
|
||||
"""
|
||||
|
||||
_open_icon = ""
|
||||
_front_layer_open = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.register_event_type("on_open")
|
||||
self.register_event_type("on_close")
|
||||
Clock.schedule_once(
|
||||
lambda x: self.on_left_action_items(self, self.left_action_items)
|
||||
)
|
||||
|
||||
def on_open(self) -> None:
|
||||
"""When the front layer drops."""
|
||||
|
||||
def on_close(self) -> None:
|
||||
"""When the front layer rises."""
|
||||
|
||||
def on_left_action_items(self, instance_backdrop, menu: list) -> None:
|
||||
if menu:
|
||||
self.left_action_items = [menu[0]]
|
||||
else:
|
||||
self.left_action_items = [["menu", lambda x: self.open()]]
|
||||
self._open_icon = self.left_action_items[0][0]
|
||||
|
||||
def on_header(self, instance_backdrop, value: bool) -> None:
|
||||
def on_header(*args):
|
||||
if not value:
|
||||
self.ids._front_layer.remove_widget(self.ids.header_button)
|
||||
|
||||
Clock.schedule_once(on_header)
|
||||
|
||||
def open(self, open_up_to: int = 0) -> None:
|
||||
"""
|
||||
Opens the front layer.
|
||||
|
||||
:open_up_to:
|
||||
the height to which the front screen will be lowered;
|
||||
if equal to zero - falls to the bottom of the screen;
|
||||
"""
|
||||
|
||||
self.animate_opacity_icon()
|
||||
if self._front_layer_open:
|
||||
self.close()
|
||||
return
|
||||
|
||||
if open_up_to:
|
||||
if open_up_to < (
|
||||
self.ids.header_button.height - self.ids._front_layer.height
|
||||
):
|
||||
y = self.ids.header_button.height - self.ids._front_layer.height
|
||||
elif open_up_to > 0:
|
||||
y = 0
|
||||
else:
|
||||
y = open_up_to
|
||||
else:
|
||||
y = self.ids.header_button.height - self.ids._front_layer.height
|
||||
|
||||
Animation(y=y, d=self.opening_time, t=self.opening_transition).start(
|
||||
self.ids._front_layer
|
||||
)
|
||||
self._front_layer_open = True
|
||||
self.dispatch("on_open")
|
||||
|
||||
def close(self) -> None:
|
||||
"""Opens the front layer."""
|
||||
|
||||
Animation(y=0, d=self.closing_time, t=self.closing_transition).start(
|
||||
self.ids._front_layer
|
||||
)
|
||||
self._front_layer_open = False
|
||||
self.dispatch("on_close")
|
||||
|
||||
def animate_opacity_icon(
|
||||
self,
|
||||
instance_icon_menu: Union[ActionTopAppBarButton, None] = None,
|
||||
opacity_value: int = 0,
|
||||
call_set_new_icon: bool = True,
|
||||
) -> None:
|
||||
"""Starts the opacity animation of the icon."""
|
||||
|
||||
if not instance_icon_menu:
|
||||
instance_icon_menu = self.ids.toolbar.ids.left_actions.children[0]
|
||||
anim = Animation(
|
||||
opacity=opacity_value,
|
||||
d=self.opening_time,
|
||||
t=self.opening_transition,
|
||||
)
|
||||
if call_set_new_icon:
|
||||
anim.bind(on_complete=self.set_new_icon)
|
||||
anim.start(instance_icon_menu)
|
||||
|
||||
def set_new_icon(
|
||||
self,
|
||||
instance_animation: Animation,
|
||||
instance_icon_menu: ActionTopAppBarButton,
|
||||
) -> None:
|
||||
"""
|
||||
Sets the icon of the button depending on the state of the backdrop.
|
||||
"""
|
||||
|
||||
instance_icon_menu.icon = (
|
||||
self.close_icon
|
||||
if instance_icon_menu.icon == self._open_icon
|
||||
else self._open_icon
|
||||
)
|
||||
self.animate_opacity_icon(instance_icon_menu, 1, False)
|
||||
|
||||
def add_widget(self, widget, index=0, canvas=None):
|
||||
if widget.__class__ in (MDBackdropToolbar, _BackLayer, _FrontLayer):
|
||||
return super().add_widget(widget)
|
||||
else:
|
||||
if widget.__class__ is MDBackdropBackLayer:
|
||||
self.ids.back_layer.add_widget(widget)
|
||||
elif widget.__class__ is MDBackdropFrontLayer:
|
||||
self.ids.front_layer.add_widget(widget)
|
||||
|
||||
|
||||
class MDBackdropToolbar(MDTopAppBar):
|
||||
"""
|
||||
Implements a toolbar for back content.
|
||||
|
||||
For more information, see in the
|
||||
:class:`~kivymd.uix.toolbar.toolbar.MDTopAppBar` classes documentation.
|
||||
"""
|
||||
|
||||
|
||||
class MDBackdropFrontLayer(MDBoxLayout):
|
||||
"""
|
||||
Container for front content.
|
||||
|
||||
For more information, see in the
|
||||
:class:`~kivymd.uix.boxlayout.MDBoxLayout` classes documentation.
|
||||
"""
|
||||
|
||||
|
||||
class MDBackdropBackLayer(MDBoxLayout):
|
||||
"""
|
||||
Container for back content.
|
||||
|
||||
For more information, see in the
|
||||
:class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation.
|
||||
"""
|
||||
|
||||
|
||||
class _BackLayer(BoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class _FrontLayer(MDCard):
|
||||
pass
|
|
@ -0,0 +1 @@
|
|||
from .banner import MDBanner # NOQA F401
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,85 @@
|
|||
#:import Window kivy.core.window.Window
|
||||
|
||||
|
||||
<ThreeLineIconBanner>
|
||||
text: root.text_message[0]
|
||||
secondary_text: root.text_message[1]
|
||||
tertiary_text: root.text_message[2]
|
||||
divider: None
|
||||
_no_ripple_effect: True
|
||||
|
||||
ImageLeftWidget:
|
||||
source: root.icon
|
||||
|
||||
|
||||
<TwoLineIconBanner>
|
||||
text: root.text_message[0]
|
||||
secondary_text: root.text_message[1]
|
||||
divider: None
|
||||
_no_ripple_effect: True
|
||||
|
||||
ImageLeftWidget:
|
||||
source: root.icon
|
||||
|
||||
|
||||
<OneLineIconBanner>
|
||||
text: root.text_message[0]
|
||||
divider: None
|
||||
_no_ripple_effect: True
|
||||
|
||||
ImageLeftWidget:
|
||||
source: root.icon
|
||||
|
||||
|
||||
<ThreeLineBanner>
|
||||
text: root.text_message[0]
|
||||
secondary_text: root.text_message[1]
|
||||
tertiary_text: root.text_message[2]
|
||||
divider: None
|
||||
_no_ripple_effect: True
|
||||
|
||||
|
||||
<TwoLineBanner>
|
||||
text: root.text_message[0]
|
||||
secondary_text: root.text_message[1]
|
||||
divider: None
|
||||
_no_ripple_effect: True
|
||||
|
||||
|
||||
<OneLineBanner>
|
||||
text: root.text_message[0]
|
||||
divider: None
|
||||
_no_ripple_effect: True
|
||||
|
||||
|
||||
<MDBanner>
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
banner_y: 0
|
||||
orientation: "vertical"
|
||||
y: Window.height - self.banner_y
|
||||
|
||||
canvas:
|
||||
Color:
|
||||
rgba: 0, 0, 0, 0
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
|
||||
MDBoxLayout:
|
||||
id: container_message
|
||||
adaptive_height: True
|
||||
|
||||
MDBoxLayout:
|
||||
adaptive_size: True
|
||||
pos_hint: {"right": 1}
|
||||
padding: 0, 0, "8dp", "8dp"
|
||||
spacing: "8dp"
|
||||
|
||||
MDBoxLayout:
|
||||
id: left_action_box
|
||||
adaptive_size: True
|
||||
|
||||
MDBoxLayout:
|
||||
id: right_action_box
|
||||
adaptive_size: True
|
|
@ -0,0 +1,439 @@
|
|||
"""
|
||||
Components/Banner
|
||||
=================
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, Banner <https://material.io/components/banners>`_
|
||||
|
||||
.. rubric:: A banner displays a prominent message and related optional actions.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner.png
|
||||
:align: center
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.factory import Factory
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
Builder.load_string('''
|
||||
<ExampleBanner@Screen>
|
||||
|
||||
MDBanner:
|
||||
id: banner
|
||||
text: ["One line string text example without actions."]
|
||||
# The widget that is under the banner.
|
||||
# It will be shifted down to the height of the banner.
|
||||
over_widget: screen
|
||||
vertical_pad: toolbar.height
|
||||
|
||||
MDTopAppBar:
|
||||
id: toolbar
|
||||
title: "Example Banners"
|
||||
elevation: 4
|
||||
pos_hint: {'top': 1}
|
||||
|
||||
MDBoxLayout:
|
||||
id: screen
|
||||
orientation: "vertical"
|
||||
size_hint_y: None
|
||||
height: Window.height - toolbar.height
|
||||
|
||||
OneLineListItem:
|
||||
text: "Banner without actions"
|
||||
on_release: banner.show()
|
||||
|
||||
Widget:
|
||||
''')
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
return Factory.ExampleBanner()
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-example-1.gif
|
||||
:align: center
|
||||
|
||||
.. rubric:: Banner type.
|
||||
|
||||
By default, the banner is of the type ``'one-line'``:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBanner:
|
||||
text: ["One line string text example without actions."]
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-one-line.png
|
||||
:align: center
|
||||
|
||||
To use a two-line banner, specify the ``'two-line'`` :attr:`MDBanner.type` for the banner
|
||||
and pass the list of two lines to the :attr:`MDBanner.text` parameter:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBanner:
|
||||
type: "two-line"
|
||||
text:
|
||||
["One line string text example without actions.", "This is the second line of the banner message."]
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-two-line.png
|
||||
:align: center
|
||||
|
||||
Similarly, create a three-line banner:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBanner:
|
||||
type: "three-line"
|
||||
text:
|
||||
["One line string text example without actions.", "This is the second line of the banner message.", "and this is the third line of the banner message."]
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-three-line.png
|
||||
:align: center
|
||||
|
||||
To add buttons to any type of banner,
|
||||
use the :attr:`MDBanner.left_action` and :attr:`MDBanner.right_action` parameters,
|
||||
which should take a list ['Button name', function]:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBanner:
|
||||
text: ["One line string text example without actions."]
|
||||
left_action: ["CANCEL", lambda x: None]
|
||||
|
||||
Or two buttons:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBanner:
|
||||
text: ["One line string text example without actions."]
|
||||
left_action: ["CANCEL", lambda x: None]
|
||||
right_action: ["CLOSE", lambda x: None]
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-actions.png
|
||||
:align: center
|
||||
|
||||
If you want to use the icon on the left in the banner,
|
||||
add the prefix `'-icon'` to the banner type:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBanner:
|
||||
type: "one-line-icon"
|
||||
icon: f"{images_path}/kivymd.png"
|
||||
text: ["One line string text example without actions."]
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-icon.png
|
||||
:align: center
|
||||
|
||||
.. Note:: `See full example <https://github.com/kivymd/KivyMD/wiki/Components-Banner>`_
|
||||
"""
|
||||
|
||||
__all__ = ("MDBanner",)
|
||||
|
||||
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 (
|
||||
BoundedNumericProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
ObjectProperty,
|
||||
OptionProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
from kivymd import uix_path
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.button import MDFlatButton
|
||||
from kivymd.uix.card import MDCard
|
||||
from kivymd.uix.list import (
|
||||
OneLineAvatarListItem,
|
||||
OneLineListItem,
|
||||
ThreeLineAvatarListItem,
|
||||
ThreeLineListItem,
|
||||
TwoLineAvatarListItem,
|
||||
TwoLineListItem,
|
||||
)
|
||||
|
||||
with open(
|
||||
os.path.join(uix_path, "banner", "banner.kv"),
|
||||
encoding="utf-8",
|
||||
) as kv_file:
|
||||
Builder.load_string(kv_file.read())
|
||||
|
||||
|
||||
class MDBanner(MDCard):
|
||||
"""
|
||||
Banner class.
|
||||
|
||||
For more information, see in the :class:`~kivymd.uix.card.MDCard`
|
||||
class documentation.
|
||||
"""
|
||||
|
||||
vertical_pad = NumericProperty(dp(68))
|
||||
"""
|
||||
Indent the banner at the top of the screen.
|
||||
|
||||
:attr:`vertical_pad` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `dp(68)`.
|
||||
"""
|
||||
|
||||
opening_transition = StringProperty("in_quad")
|
||||
"""
|
||||
The name of the animation transition.
|
||||
|
||||
:attr:`opening_transition` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'in_quad'`.
|
||||
"""
|
||||
|
||||
icon = StringProperty("data/logo/kivy-icon-128.png")
|
||||
"""
|
||||
Icon banner.
|
||||
|
||||
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'data/logo/kivy-icon-128.png'`.
|
||||
"""
|
||||
|
||||
over_widget = ObjectProperty()
|
||||
"""
|
||||
The widget that is under the banner.
|
||||
It will be shifted down to the height of the banner.
|
||||
|
||||
:attr:`over_widget` is an :class:`~kivy.properties.ObjectProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
text = ListProperty()
|
||||
"""
|
||||
List of lines for banner text.
|
||||
Must contain no more than three lines for a
|
||||
`'one-line'`, `'two-line'` and `'three-line'` banner, respectively.
|
||||
|
||||
:attr:`text` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
left_action = ListProperty()
|
||||
"""
|
||||
The action of banner.
|
||||
|
||||
To add one action, make a list [`'name_action'`, callback]
|
||||
where `'name_action'` is a string that corresponds to an action name and
|
||||
``callback`` is the function called on a touch release event.
|
||||
|
||||
:attr:`left_action` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
right_action = ListProperty()
|
||||
"""
|
||||
Works the same way as :attr:`left_action`.
|
||||
|
||||
:attr:`right_action` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
type = OptionProperty(
|
||||
"one-line",
|
||||
options=[
|
||||
"one-line",
|
||||
"two-line",
|
||||
"three-line",
|
||||
"one-line-icon",
|
||||
"two-line-icon",
|
||||
"three-line-icon",
|
||||
],
|
||||
allownone=True,
|
||||
)
|
||||
"""
|
||||
Banner type. . Available options are: (`"one-line"`, `"two-line"`,
|
||||
`"three-line"`, `"one-line-icon"`, `"two-line-icon"`, `"three-line-icon"`).
|
||||
|
||||
:attr:`type` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'one-line'`.
|
||||
"""
|
||||
|
||||
opening_timeout = BoundedNumericProperty(0.7, min=0.7)
|
||||
"""
|
||||
Time interval after which the banner will be shown.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
:attr:`opening_timeout` is an :class:`~kivy.properties.BoundedNumericProperty`
|
||||
and defaults to `0.7`.
|
||||
"""
|
||||
|
||||
opening_time = NumericProperty(0.15)
|
||||
"""
|
||||
The time taken for the banner to slide to the :attr:`state` `'open'`.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
:attr:`opening_time` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.15`.
|
||||
"""
|
||||
|
||||
closing_time = NumericProperty(0.15)
|
||||
"""
|
||||
The time taken for the banner to slide to the :attr:`state` `'close'`.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
:attr:`closing_time` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.15`.
|
||||
"""
|
||||
|
||||
_type_message = None
|
||||
_progress = False
|
||||
|
||||
def add_actions_buttons(
|
||||
self, instance_box: MDBoxLayout, data: list
|
||||
) -> None:
|
||||
"""
|
||||
Adds buttons to the banner.
|
||||
|
||||
:param data: ['NAME BUTTON', <function>];
|
||||
"""
|
||||
|
||||
if data:
|
||||
name_action_button, function_action_button = data
|
||||
action_button = MDFlatButton(
|
||||
text=f"[b]{name_action_button}[/b]",
|
||||
theme_text_color="Custom",
|
||||
text_color=self.theme_cls.primary_color,
|
||||
on_release=function_action_button,
|
||||
)
|
||||
action_button.markup = True
|
||||
instance_box.add_widget(action_button)
|
||||
|
||||
def show(self) -> None:
|
||||
"""Displays a banner on the screen."""
|
||||
|
||||
def show(interval: Union[int, float]):
|
||||
self.set_type_banner()
|
||||
self.add_actions_buttons(self.ids.left_action_box, self.left_action)
|
||||
self.add_actions_buttons(
|
||||
self.ids.right_action_box, self.right_action
|
||||
)
|
||||
self._add_banner_to_container()
|
||||
Clock.schedule_once(self.animation_display_banner, 0.1)
|
||||
|
||||
if not self._progress:
|
||||
self._progress = True
|
||||
if self.ids.container_message.children:
|
||||
self.hide()
|
||||
Clock.schedule_once(show, self.opening_timeout)
|
||||
|
||||
def hide(self) -> None:
|
||||
"""Hides the banner from the screen."""
|
||||
|
||||
def hide(interval: Union[int, float]):
|
||||
anim = Animation(banner_y=0, d=self.closing_time)
|
||||
anim.bind(on_complete=self._remove_banner)
|
||||
anim.start(self)
|
||||
Animation(
|
||||
y=self.over_widget.y + self.height, d=self.closing_time
|
||||
).start(self.over_widget)
|
||||
|
||||
if not self._progress:
|
||||
self._progress = True
|
||||
Clock.schedule_once(hide, 0.5)
|
||||
|
||||
def set_type_banner(self) -> None:
|
||||
self._type_message = {
|
||||
"three-line-icon": ThreeLineIconBanner,
|
||||
"two-line-icon": TwoLineIconBanner,
|
||||
"one-line-icon": OneLineIconBanner,
|
||||
"three-line": ThreeLineBanner,
|
||||
"two-line": TwoLineBanner,
|
||||
"one-line": OneLineBanner,
|
||||
}[self.type]
|
||||
|
||||
def animation_display_banner(self, interval: Union[int, float]) -> None:
|
||||
Animation(
|
||||
banner_y=self.height + self.vertical_pad,
|
||||
d=self.opening_time,
|
||||
t=self.opening_transition,
|
||||
).start(self)
|
||||
anim = Animation(
|
||||
y=self.over_widget.y - self.height,
|
||||
d=self.opening_time,
|
||||
t=self.opening_transition,
|
||||
)
|
||||
anim.bind(on_complete=self._reset_progress)
|
||||
anim.start(self.over_widget)
|
||||
|
||||
def _remove_banner(self, *args):
|
||||
self.ids.container_message.clear_widgets()
|
||||
self.ids.left_action_box.clear_widgets()
|
||||
self.ids.right_action_box.clear_widgets()
|
||||
self._reset_progress()
|
||||
|
||||
def _reset_progress(self, *args):
|
||||
self._progress = False
|
||||
|
||||
def _add_banner_to_container(self) -> None:
|
||||
self.ids.container_message.add_widget(
|
||||
self._type_message(text_message=self.text, icon=self.icon)
|
||||
)
|
||||
|
||||
|
||||
class BaseBanner(Widget):
|
||||
"""Implements the base banner class."""
|
||||
|
||||
text_message = ListProperty(["", "", ""])
|
||||
"""
|
||||
List of banner strings. First, second and, respectively, third lines.
|
||||
|
||||
:attr:`text_message` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `['', '', '']`.
|
||||
"""
|
||||
|
||||
icon = StringProperty()
|
||||
"""
|
||||
Icon banner.
|
||||
|
||||
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
self.parent.parent.hide()
|
||||
|
||||
|
||||
class ThreeLineIconBanner(ThreeLineAvatarListItem, BaseBanner):
|
||||
pass
|
||||
|
||||
|
||||
class TwoLineIconBanner(TwoLineAvatarListItem, BaseBanner):
|
||||
pass
|
||||
|
||||
|
||||
class OneLineIconBanner(OneLineAvatarListItem, BaseBanner):
|
||||
pass
|
||||
|
||||
|
||||
class ThreeLineBanner(ThreeLineListItem, BaseBanner):
|
||||
pass
|
||||
|
||||
|
||||
class TwoLineBanner(TwoLineListItem, BaseBanner):
|
||||
pass
|
||||
|
||||
|
||||
class OneLineBanner(OneLineListItem, BaseBanner):
|
||||
pass
|
|
@ -0,0 +1,35 @@
|
|||
"""
|
||||
Behaviors
|
||||
=========
|
||||
|
||||
Modules and classes implementing various behaviors for buttons etc.
|
||||
"""
|
||||
|
||||
from .backgroundcolor_behavior import (
|
||||
BackgroundColorBehavior,
|
||||
SpecificBackgroundColorBehavior,
|
||||
)
|
||||
|
||||
# flake8: NOQA
|
||||
from .declarative_behavior import DeclarativeBehavior
|
||||
from .elevation import (
|
||||
CircularElevationBehavior,
|
||||
CommonElevationBehavior,
|
||||
FakeCircularElevationBehavior,
|
||||
FakeRectangularElevationBehavior,
|
||||
RectangularElevationBehavior,
|
||||
RoundedRectangularElevationBehavior,
|
||||
)
|
||||
from .motion_behavior import (
|
||||
MotionDialogBehavior,
|
||||
MotionShackBehavior,
|
||||
MotionDropDownMenuBehavior,
|
||||
)
|
||||
from .magic_behavior import MagicBehavior
|
||||
from .ripple_behavior import CircularRippleBehavior, RectangularRippleBehavior
|
||||
from .rotate_behavior import RotateBehavior
|
||||
from .scale_behavior import ScaleBehavior
|
||||
from .stencil_behavior import StencilBehavior
|
||||
from .touch_behavior import TouchBehavior
|
||||
|
||||
from .hover_behavior import HoverBehavior # isort:skip
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,290 @@
|
|||
"""
|
||||
Behaviors/Background Color
|
||||
==========================
|
||||
|
||||
.. note:: The following classes are intended for in-house use of the library.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
__all__ = ("BackgroundColorBehavior", "SpecificBackgroundColorBehavior")
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import (
|
||||
ColorProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
OptionProperty,
|
||||
ReferenceListProperty,
|
||||
StringProperty,
|
||||
VariableListProperty,
|
||||
)
|
||||
from kivy.utils import get_color_from_hex
|
||||
|
||||
from kivymd.color_definitions import hue, palette, text_colors
|
||||
from kivymd.theming import ThemeManager
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
#:import RelativeLayout kivy.uix.relativelayout.RelativeLayout
|
||||
|
||||
|
||||
<BackgroundColorBehavior>
|
||||
canvas:
|
||||
PushMatrix
|
||||
Rotate:
|
||||
angle: self.angle
|
||||
origin: self._background_origin
|
||||
Color:
|
||||
rgba: self._md_bg_color
|
||||
RoundedRectangle:
|
||||
group: "Background_instruction"
|
||||
size: self.size
|
||||
pos: self.pos if not isinstance(self, RelativeLayout) else (0, 0)
|
||||
# FIXME: Sometimes the radius has the value [], which get a
|
||||
# `GraphicException:
|
||||
# Invalid radius value, must be list of tuples/numerics` error`
|
||||
radius: root.radius if root.radius else [0, 0, 0, 0]
|
||||
source: root.background
|
||||
Color:
|
||||
rgba: self.line_color if self.line_color else (0, 0, 0, 0)
|
||||
# TODO: maybe we should use SmoothLine,
|
||||
# but this should be tested on all widgets.
|
||||
Line:
|
||||
width: root.line_width
|
||||
rounded_rectangle:
|
||||
[ \
|
||||
self.x,
|
||||
self.y, \
|
||||
self.width, \
|
||||
self.height, \
|
||||
*self.radius, \
|
||||
]
|
||||
PopMatrix
|
||||
""",
|
||||
filename="BackgroundColorBehavior.kv",
|
||||
)
|
||||
|
||||
|
||||
class BackgroundColorBehavior:
|
||||
background = StringProperty()
|
||||
"""
|
||||
Background image path.
|
||||
|
||||
:attr:`background` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
radius = VariableListProperty([0], length=4)
|
||||
"""
|
||||
Canvas radius.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Top left corner slice.
|
||||
MDBoxLayout:
|
||||
md_bg_color: app.theme_cls.primary_color
|
||||
radius: [25, 0, 0, 0]
|
||||
|
||||
:attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
|
||||
and defaults to `[0, 0, 0, 0]`.
|
||||
"""
|
||||
|
||||
# FIXME: in this case, we will not be able to animate this property
|
||||
# using the `Animation` class.
|
||||
md_bg_color = ColorProperty([1, 1, 1, 0])
|
||||
"""
|
||||
The background color of the widget (:class:`~kivy.uix.widget.Widget`)
|
||||
that will be inherited from the :attr:`BackgroundColorBehavior` class.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
Widget:
|
||||
canvas:
|
||||
Color:
|
||||
rgba: 0, 1, 1, 1
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
similar to code:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<MyWidget@BackgroundColorBehavior>
|
||||
md_bg_color: 0, 1, 1, 1
|
||||
|
||||
:attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `[1, 1, 1, 0]`.
|
||||
"""
|
||||
|
||||
line_color = ColorProperty([0, 0, 0, 0])
|
||||
"""
|
||||
If a custom value is specified for the `line_color parameter`, the border
|
||||
of the specified color will be used to border the widget:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBoxLayout:
|
||||
size_hint: .5, .2
|
||||
md_bg_color: 0, 1, 1, .5
|
||||
line_color: 0, 0, 1, 1
|
||||
radius: [24, ]
|
||||
|
||||
.. versionadded:: 0.104.2
|
||||
|
||||
:attr:`line_color` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `[0, 0, 0, 0]`.
|
||||
"""
|
||||
|
||||
line_width = NumericProperty(1)
|
||||
"""
|
||||
Border of the specified width will be used to border the widget.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
:attr:`line_width` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
angle = NumericProperty(0)
|
||||
background_origin = ListProperty(None)
|
||||
|
||||
_background_x = NumericProperty(0)
|
||||
_background_y = NumericProperty(0)
|
||||
_background_origin = ReferenceListProperty(_background_x, _background_y)
|
||||
_md_bg_color = ColorProperty([0, 0, 0, 0])
|
||||
_origin_line_color = ColorProperty(None)
|
||||
_origin_md_bg_color = ColorProperty(None)
|
||||
|
||||
def __init__(self, **kwarg):
|
||||
super().__init__(**kwarg)
|
||||
self.bind(
|
||||
pos=self.update_background_origin,
|
||||
disabled=self.restore_color_origin,
|
||||
)
|
||||
|
||||
def restore_color_origin(self, instance_md_widget, value: bool) -> None:
|
||||
"""Called when the values of :attr:`disabled` change."""
|
||||
|
||||
if not value:
|
||||
if self._origin_line_color:
|
||||
self.line_color = self._origin_line_color
|
||||
if self._origin_md_bg_color:
|
||||
self.md_bg_color = self._origin_md_bg_color
|
||||
|
||||
def on_line_color(self, instance_md_widget, value: list | str) -> None:
|
||||
"""Called when the values of :attr:`line_color` change."""
|
||||
|
||||
if not self.disabled:
|
||||
self._origin_line_color = value
|
||||
|
||||
def on_md_bg_color(self, instance_md_widget, color: list | str):
|
||||
"""Called when the values of :attr:`md_bg_color` change."""
|
||||
|
||||
if (
|
||||
hasattr(self, "theme_cls")
|
||||
and self.theme_cls.theme_style_switch_animation
|
||||
):
|
||||
Animation(
|
||||
_md_bg_color=color,
|
||||
d=self.theme_cls.theme_style_switch_animation_duration,
|
||||
t="linear",
|
||||
).start(self)
|
||||
else:
|
||||
self._md_bg_color = color
|
||||
|
||||
if not self.disabled:
|
||||
self._origin_md_bg_color = color
|
||||
|
||||
def update_background_origin(self, instance_md_widget, pos: list) -> None:
|
||||
"""Called when the values of :attr:`pos` change."""
|
||||
|
||||
if self.background_origin:
|
||||
self._background_origin = self.background_origin
|
||||
else:
|
||||
self._background_origin = self.center
|
||||
|
||||
|
||||
class SpecificBackgroundColorBehavior(BackgroundColorBehavior):
|
||||
background_palette = OptionProperty(
|
||||
"Primary", options=["Primary", "Accent", *palette]
|
||||
)
|
||||
"""
|
||||
See :attr:`kivymd.color_definitions.palette`.
|
||||
|
||||
:attr:`background_palette` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'Primary'`.
|
||||
"""
|
||||
|
||||
background_hue = OptionProperty("500", options=hue)
|
||||
"""
|
||||
See :attr:`kivymd.color_definitions.hue`.
|
||||
|
||||
:attr:`background_hue` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'500'`.
|
||||
"""
|
||||
|
||||
specific_text_color = ColorProperty([0, 0, 0, 0.87])
|
||||
"""
|
||||
:attr:`specific_text_color` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `[0, 0, 0, 0.87]`.
|
||||
"""
|
||||
|
||||
specific_secondary_text_color = ColorProperty([0, 0, 0, 0.87])
|
||||
"""
|
||||
:attr:`specific_secondary_text_color`is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `[0, 0, 0, 0.87]`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
if hasattr(self, "theme_cls"):
|
||||
self.theme_cls.bind(
|
||||
primary_palette=self._update_specific_text_color,
|
||||
accent_palette=self._update_specific_text_color,
|
||||
theme_style=self._update_specific_text_color,
|
||||
)
|
||||
self.bind(
|
||||
background_hue=self._update_specific_text_color,
|
||||
background_palette=self._update_specific_text_color,
|
||||
)
|
||||
self._update_specific_text_color(None, None)
|
||||
|
||||
def _update_specific_text_color(
|
||||
self, instance_theme_manager: ThemeManager, theme_style: str
|
||||
) -> None:
|
||||
if hasattr(self, "theme_cls"):
|
||||
palette = {
|
||||
"Primary": self.theme_cls.primary_palette,
|
||||
"Accent": self.theme_cls.accent_palette,
|
||||
}.get(self.background_palette, self.background_palette)
|
||||
else:
|
||||
palette = {"Primary": "Blue", "Accent": "Amber"}.get(
|
||||
self.background_palette, self.background_palette
|
||||
)
|
||||
color = get_color_from_hex(text_colors[palette][self.background_hue])
|
||||
secondary_color = color[:]
|
||||
# Check for black text (need to adjust opacity).
|
||||
if (color[0] + color[1] + color[2]) == 0:
|
||||
color[3] = 0.87
|
||||
secondary_color[3] = 0.54
|
||||
else:
|
||||
secondary_color[3] = 0.7
|
||||
|
||||
if (
|
||||
hasattr(self, "theme_cls")
|
||||
and self.theme_cls.theme_style_switch_animation
|
||||
):
|
||||
Animation(
|
||||
specific_text_color=color,
|
||||
specific_secondary_text_color=secondary_color,
|
||||
d=self.theme_cls.theme_style_switch_animation_duration,
|
||||
t="linear",
|
||||
).start(self)
|
||||
else:
|
||||
self.specific_text_color = color
|
||||
self.specific_secondary_text_color = secondary_color
|
|
@ -0,0 +1,317 @@
|
|||
"""
|
||||
Behaviors/Declarative
|
||||
=====================
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; height: auto;">
|
||||
<iframe
|
||||
src="https://www.youtube.com/embed/_kiaJacLz8o"
|
||||
frameborder="0"
|
||||
allowfullscreen
|
||||
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
As you already know, the Kivy framework provides the best/simplest/modern
|
||||
UI creation tool that allows you to separate the logic of your application
|
||||
from the description of the properties of widgets/GUI components.
|
||||
This tool is named `KV Language <https://kivy.org/doc/stable/guide/lang.html>`_.
|
||||
|
||||
But in addition to creating a user interface using the KV Language Kivy allows
|
||||
you to create user interface elements directly in the Python code.
|
||||
And if you've ever created a user interface in Python code, you know how ugly
|
||||
it looks. Even in the simplest user interface design, which was created using
|
||||
Python code it is impossible to trace the widget tree, because in Python code
|
||||
you build the user interface in an imperative style.
|
||||
|
||||
Imperative style
|
||||
----------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.bottomnavigation import MDBottomNavigation, MDBottomNavigationItem
|
||||
from kivymd.uix.label import MDLabel
|
||||
from kivymd.uix.screen import MDScreen
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
screen = MDScreen()
|
||||
bottom_navigation = MDBottomNavigation(
|
||||
panel_color="#eeeaea",
|
||||
selected_color_background="#97ecf8",
|
||||
text_color_active="white",
|
||||
)
|
||||
|
||||
data = {
|
||||
"screen 1": {"text": "Mail", "icon": "gmail"},
|
||||
"screen 2": {"text": "Discord", "icon": "discord"},
|
||||
"screen 3": {"text": "LinkedIN", "icon": "linkedin"},
|
||||
}
|
||||
for key in data.keys():
|
||||
text = data[key]["text"]
|
||||
navigation_item = MDBottomNavigationItem(
|
||||
name=key, text=text, icon=data[key]["icon"]
|
||||
)
|
||||
navigation_item.add_widget(MDLabel(text=text, halign="center"))
|
||||
bottom_navigation.add_widget(navigation_item)
|
||||
|
||||
screen.add_widget(bottom_navigation)
|
||||
return screen
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-styles-programming.png
|
||||
:align: center
|
||||
|
||||
Take a look at the above code example. This is a very simple UI. But looking
|
||||
at this code, you will not be able to figure the widget tree and understand
|
||||
which UI this code implements. This is named imperative programming style,
|
||||
which is used in Kivy.
|
||||
|
||||
Now let's see how the same code is implemented using the KV language,
|
||||
which uses a declarative style of describing widget properties.
|
||||
|
||||
Declarative style with KV language
|
||||
----------------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(
|
||||
'''
|
||||
MDScreen:
|
||||
|
||||
MDBottomNavigation:
|
||||
panel_color: "#eeeaea"
|
||||
selected_color_background: "#97ecf8"
|
||||
text_color_active: "white"
|
||||
|
||||
MDBottomNavigationItem:
|
||||
name: "screen 1"
|
||||
text: "Mail"
|
||||
icon: "gmail"
|
||||
|
||||
MDLabel:
|
||||
text: "Mail"
|
||||
halign: "center"
|
||||
|
||||
MDBottomNavigationItem:
|
||||
name: "screen 2"
|
||||
text: "Discord"
|
||||
icon: "discord"
|
||||
|
||||
MDLabel:
|
||||
text: "Discord"
|
||||
halign: "center"
|
||||
|
||||
MDBottomNavigationItem:
|
||||
name: "screen 3"
|
||||
text: "LinkedIN"
|
||||
icon: "linkedin"
|
||||
|
||||
MDLabel:
|
||||
text: "LinkedIN"
|
||||
halign: "center"
|
||||
'''
|
||||
)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-styles-programming.png
|
||||
:align: center
|
||||
|
||||
Looking at this code, we can now clearly see the widget tree and their properties.
|
||||
We can quickly navigate through the components of the screen and quickly
|
||||
change/add new properties/widgets. This is named declarative UI creation style.
|
||||
|
||||
But now the KivyMD library allows you to write Python code in a declarative style.
|
||||
Just as it is implemented in Flutter/Jetpack Compose/SwiftUI.
|
||||
|
||||
Declarative style with Python code
|
||||
----------------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.bottomnavigation import MDBottomNavigation, MDBottomNavigationItem
|
||||
from kivymd.uix.label import MDLabel
|
||||
from kivymd.uix.screen import MDScreen
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return (
|
||||
MDScreen(
|
||||
MDBottomNavigation(
|
||||
MDBottomNavigationItem(
|
||||
MDLabel(
|
||||
text="Mail",
|
||||
halign="center",
|
||||
),
|
||||
name="screen 1",
|
||||
text="Mail",
|
||||
icon="gmail",
|
||||
),
|
||||
MDBottomNavigationItem(
|
||||
MDLabel(
|
||||
text="Discord",
|
||||
halign="center",
|
||||
),
|
||||
name="screen 2",
|
||||
text="Discord",
|
||||
icon="discord",
|
||||
),
|
||||
MDBottomNavigationItem(
|
||||
MDLabel(
|
||||
text="LinkedIN",
|
||||
halign="center",
|
||||
),
|
||||
name="screen 3",
|
||||
text="LinkedIN",
|
||||
icon="linkedin",
|
||||
),
|
||||
panel_color="#eeeaea",
|
||||
selected_color_background="#97ecf8",
|
||||
text_color_active="white",
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. note:: The KivyMD library does not support creating Kivy widgets in Python
|
||||
code in a declarative style.
|
||||
|
||||
But you can still use the declarative style of creating Kivy widgets in Python code.
|
||||
To do this, you need to create a new class that will be inherited from the Kivy
|
||||
widget and the :class:`~DeclarativeBehavior` class:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.button import Button
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import DeclarativeBehavior
|
||||
|
||||
|
||||
class DeclarativeStyleBoxLayout(DeclarativeBehavior, BoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return (
|
||||
DeclarativeStyleBoxLayout(
|
||||
Button(),
|
||||
Button(),
|
||||
orientation="vertical",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
Get objects by identifiers
|
||||
--------------------------
|
||||
|
||||
In the declarative style in Python code, the ids parameter of the specified
|
||||
widget will return only the id of the child widget/container, ignoring other ids.
|
||||
Therefore, to get objects by identifiers in declarative style in Python code,
|
||||
you must specify all the container ids in which the widget is nested until you
|
||||
get to the desired id:
|
||||
|
||||
.. code-block::
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.button import MDRaisedButton
|
||||
from kivymd.uix.floatlayout import MDFloatLayout
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return (
|
||||
MDBoxLayout(
|
||||
MDFloatLayout(
|
||||
MDRaisedButton(
|
||||
id="button_1",
|
||||
text="Button 1",
|
||||
pos_hint={"center_x": 0.5, "center_y": 0.5},
|
||||
),
|
||||
id="box_container_1",
|
||||
),
|
||||
MDBoxLayout(
|
||||
MDFloatLayout(
|
||||
MDRaisedButton(
|
||||
id="button_2",
|
||||
text="Button 2",
|
||||
pos_hint={"center_x": 0.5, "center_y": 0.5},
|
||||
),
|
||||
id="float_container",
|
||||
),
|
||||
id="box_container_2",
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def on_start(self):
|
||||
# {
|
||||
# 'box_container_1': <kivymd.uix.floatlayout.MDFloatLayout>,
|
||||
# 'box_container_2': <kivymd.uix.boxlayout.MDBoxLayout object>
|
||||
# }
|
||||
print(self.root.ids)
|
||||
|
||||
# <kivymd.uix.button.button.MDRaisedButton>
|
||||
print(self.root.ids.box_container_2.ids.float_container.ids.button_2)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
Yes, this is not a very good solution, but I think it will be fixed soon.
|
||||
|
||||
.. warning:: Declarative programming style in Python code in the KivyMD library
|
||||
is an experimental feature. Therefore, if you receive errors, do not hesitate
|
||||
to create new issue in the KivyMD repository.
|
||||
"""
|
||||
|
||||
from kivy.properties import StringProperty
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
|
||||
class DeclarativeBehavior:
|
||||
"""
|
||||
Implements the creation and addition of child widgets as declarative
|
||||
programming style.
|
||||
"""
|
||||
|
||||
id = StringProperty()
|
||||
"""
|
||||
Widget ID.
|
||||
|
||||
:attr:`id` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
for child in args:
|
||||
if issubclass(child.__class__, Widget):
|
||||
self.add_widget(child)
|
||||
if hasattr(child, "id") and child.id:
|
||||
self.ids[child.id] = child
|
|
@ -0,0 +1,772 @@
|
|||
"""
|
||||
Behaviors/Elevation
|
||||
===================
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, Elevation <https://material.io/design/environment/elevation.html>`_
|
||||
|
||||
.. rubric:: Elevation is the relative distance between two surfaces along the z-axis.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/elevation-previous.png
|
||||
:align: center
|
||||
|
||||
To create an elevation effect, use the :class:`~CommonElevationBehavior` class.
|
||||
For example, let's create a button with a rectangular elevation effect:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. tab:: Declarative style with KV
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import (
|
||||
RectangularRippleBehavior,
|
||||
BackgroundColorBehavior,
|
||||
CommonElevationBehavior,
|
||||
)
|
||||
|
||||
KV = '''
|
||||
<RectangularElevationButton>
|
||||
size_hint: None, None
|
||||
size: "250dp", "50dp"
|
||||
|
||||
|
||||
MDScreen:
|
||||
|
||||
# With elevation effect
|
||||
RectangularElevationButton:
|
||||
pos_hint: {"center_x": .5, "center_y": .6}
|
||||
elevation: 4
|
||||
shadow_offset: 0, -6
|
||||
shadow_softness: 4
|
||||
|
||||
# Without elevation effect
|
||||
RectangularElevationButton:
|
||||
pos_hint: {"center_x": .5, "center_y": .4}
|
||||
'''
|
||||
|
||||
|
||||
class RectangularElevationButton(
|
||||
RectangularRippleBehavior,
|
||||
CommonElevationBehavior,
|
||||
ButtonBehavior,
|
||||
BackgroundColorBehavior,
|
||||
):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.md_bg_color = "red"
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. tab:: Declarative python style
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import (
|
||||
RectangularRippleBehavior,
|
||||
BackgroundColorBehavior,
|
||||
CommonElevationBehavior,
|
||||
)
|
||||
from kivymd.uix.screen import MDScreen
|
||||
|
||||
|
||||
class RectangularElevationButton(
|
||||
RectangularRippleBehavior,
|
||||
CommonElevationBehavior,
|
||||
ButtonBehavior,
|
||||
BackgroundColorBehavior,
|
||||
):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.md_bg_color = "red"
|
||||
self.size_hint = (None, None)
|
||||
self.size = ("250dp", "50dp")
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return (
|
||||
MDScreen(
|
||||
RectangularElevationButton(
|
||||
pos_hint={"center_x": .5, "center_y": .6},
|
||||
elevation=4,
|
||||
shadow_softness=4,
|
||||
shadow_offset=(0, -6),
|
||||
),
|
||||
RectangularElevationButton(
|
||||
pos_hint={"center_x": .5, "center_y": .4},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-elevation-effect.png
|
||||
:align: center
|
||||
|
||||
.. warning::
|
||||
|
||||
If before the KivyMD 1.1.0 library version you used the elevation property
|
||||
with an average value of `12` for the shadow, then starting with the KivyMD
|
||||
1.1.0 library version, the average value of the elevation property will be
|
||||
somewhere `4`.
|
||||
|
||||
Similarly, create a circular button:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. tab:: Declarative style with KV
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import CircularRippleBehavior, CommonElevationBehavior
|
||||
from kivymd.uix.floatlayout import MDFloatLayout
|
||||
|
||||
KV = '''
|
||||
<CircularElevationButton>
|
||||
size_hint: None, None
|
||||
size: "100dp", "100dp"
|
||||
radius: self.size[0] / 2
|
||||
shadow_radius: self.radius[0]
|
||||
md_bg_color: "red"
|
||||
|
||||
MDIcon:
|
||||
icon: "hand-heart"
|
||||
halign: "center"
|
||||
valign: "center"
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
size: root.size
|
||||
pos: root.pos
|
||||
font_size: root.size[0] * .6
|
||||
theme_text_color: "Custom"
|
||||
text_color: "white"
|
||||
|
||||
|
||||
MDScreen:
|
||||
|
||||
CircularElevationButton:
|
||||
pos_hint: {"center_x": .5, "center_y": .6}
|
||||
elevation: 4
|
||||
shadow_softness: 4
|
||||
'''
|
||||
|
||||
|
||||
class CircularElevationButton(
|
||||
CommonElevationBehavior,
|
||||
CircularRippleBehavior,
|
||||
ButtonBehavior,
|
||||
MDFloatLayout,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. tab:: Declarative python style
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.metrics import dp
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import CircularRippleBehavior, CommonElevationBehavior
|
||||
from kivymd.uix.floatlayout import MDFloatLayout
|
||||
from kivymd.uix.label import MDIcon
|
||||
from kivymd.uix.screen import MDScreen
|
||||
|
||||
|
||||
class CircularElevationButton(
|
||||
CommonElevationBehavior,
|
||||
CircularRippleBehavior,
|
||||
ButtonBehavior,
|
||||
MDFloatLayout,
|
||||
):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.size_hint = (None, None)
|
||||
self.size = (dp(100), dp(100))
|
||||
self.radius = dp(100) / 2
|
||||
self.shadow_radius = dp(100) / 2
|
||||
self.md_bg_color = "red"
|
||||
self.add_widget(
|
||||
MDIcon(
|
||||
icon="hand-heart",
|
||||
halign="center",
|
||||
valign="center",
|
||||
pos_hint={"center_x": .5, "center_y": .5},
|
||||
size=self.size,
|
||||
theme_text_color="Custom",
|
||||
text_color="white",
|
||||
font_size=self.size[0] * 0.6,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return (
|
||||
MDScreen(
|
||||
CircularElevationButton(
|
||||
pos_hint={"center_x": .5, "center_y": .5},
|
||||
elevation=4,
|
||||
shadow_softness=4,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-elevation-effect.png
|
||||
:align: center
|
||||
|
||||
Animating the elevation
|
||||
-----------------------
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. tab:: Declarative style with KV
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import CommonElevationBehavior, RectangularRippleBehavior
|
||||
from kivymd.uix.widget import MDWidget
|
||||
|
||||
KV = '''
|
||||
MDScreen:
|
||||
|
||||
ElevatedWidget:
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
size_hint: None, None
|
||||
size: 100, 100
|
||||
md_bg_color: 0, 0, 1, 1
|
||||
elevation: 2
|
||||
radius: 18
|
||||
'''
|
||||
|
||||
|
||||
class ElevatedWidget(
|
||||
CommonElevationBehavior,
|
||||
RectangularRippleBehavior,
|
||||
ButtonBehavior,
|
||||
MDWidget,
|
||||
):
|
||||
_elev = 0 # previous elevation value
|
||||
|
||||
def on_press(self, *args):
|
||||
if not self._elev:
|
||||
self._elev = self.elevation
|
||||
Animation(elevation=self.elevation + 2, d=0.4).start(self)
|
||||
|
||||
def on_release(self, *args):
|
||||
Animation.cancel_all(self, "elevation")
|
||||
Animation(elevation=self._elev, d=0.1).start(self)
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. tab:: Declarative python style
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import CommonElevationBehavior, RectangularRippleBehavior
|
||||
from kivymd.uix.screen import MDScreen
|
||||
from kivymd.uix.widget import MDWidget
|
||||
|
||||
|
||||
class ElevatedWidget(
|
||||
CommonElevationBehavior,
|
||||
RectangularRippleBehavior,
|
||||
ButtonBehavior,
|
||||
MDWidget,
|
||||
):
|
||||
_elev = 0 # previous elevation value
|
||||
|
||||
def on_press(self, *args):
|
||||
if not self._elev:
|
||||
self._elev = self.elevation
|
||||
Animation(elevation=self.elevation + 2, d=0.4).start(self)
|
||||
|
||||
def on_release(self, *args):
|
||||
Animation.cancel_all(self, "elevation")
|
||||
Animation(elevation=self._elev, d=0.1).start(self)
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return (
|
||||
MDScreen(
|
||||
ElevatedWidget(
|
||||
pos_hint={'center_x': .5, 'center_y': .5},
|
||||
size_hint=(None, None),
|
||||
size=(100, 100),
|
||||
md_bg_color="blue",
|
||||
elevation=2,
|
||||
radius=18,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-elevation-animation-effect.gif
|
||||
:align: center
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
__all__ = (
|
||||
"CommonElevationBehavior",
|
||||
"RectangularElevationBehavior",
|
||||
"CircularElevationBehavior",
|
||||
"RoundedRectangularElevationBehavior",
|
||||
"FakeRectangularElevationBehavior",
|
||||
"FakeCircularElevationBehavior",
|
||||
)
|
||||
|
||||
from kivy import Logger
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import (
|
||||
BoundedNumericProperty,
|
||||
ColorProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
VariableListProperty,
|
||||
)
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<CommonElevationBehavior>
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
Scale:
|
||||
x: self.scale_value_x
|
||||
y: self.scale_value_y
|
||||
z: self.scale_value_x
|
||||
origin:
|
||||
self.center \
|
||||
if not self.scale_value_center else \
|
||||
self.scale_value_center
|
||||
Rotate:
|
||||
angle: self.rotate_value_angle
|
||||
axis: tuple(self.rotate_value_axis)
|
||||
origin: self.center
|
||||
Color:
|
||||
rgba:
|
||||
(0, 0, 0, 0) \
|
||||
if self.disabled or not self.elevation else \
|
||||
root.shadow_color
|
||||
BoxShadow:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
offset: root.shadow_offset
|
||||
spread_radius: -(root.shadow_softness), -(root.shadow_softness)
|
||||
blur_radius: root.elevation * 10
|
||||
border_radius:
|
||||
(root.radius if hasattr(self, "radius") else [0, 0, 0, 0]) \
|
||||
if root.shadow_radius == [0.0, 0.0, 0.0, 0.0] else \
|
||||
root.shadow_radius
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class CommonElevationBehavior(Widget):
|
||||
"""
|
||||
Common base class for rectangular and circular elevation behavior.
|
||||
|
||||
For more information, see in the :class:`~kivy.uix.widget.Widget`
|
||||
class documentation.
|
||||
"""
|
||||
|
||||
elevation = BoundedNumericProperty(0, min=0, errorvalue=0)
|
||||
"""
|
||||
Elevation of the widget.
|
||||
|
||||
:attr:`elevation` is an :class:`~kivy.properties.BoundedNumericProperty`
|
||||
and defaults to `0`.
|
||||
"""
|
||||
|
||||
shadow_radius = VariableListProperty([0], length=4)
|
||||
"""
|
||||
Radius of the corners of the shadow.
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
You don't have to use this parameter.
|
||||
The radius of the elevation effect is calculated automatically one way
|
||||
or another based on the radius of the parent widget, for example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
KV = '''
|
||||
MDScreen:
|
||||
|
||||
MDCard:
|
||||
radius: 12, 46, 12, 46
|
||||
size_hint: .5, .3
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
elevation: 2
|
||||
shadow_softness: 4
|
||||
shadow_offset: (2, -2)
|
||||
'''
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-radius.png
|
||||
:align: center
|
||||
|
||||
:attr:`shadow_radius` is an :class:`~kivy.properties.VariableListProperty`
|
||||
and defaults to `[0, 0, 0, 0]`.
|
||||
"""
|
||||
|
||||
shadow_softness = NumericProperty(0.0)
|
||||
"""
|
||||
Softness of the shadow.
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import BackgroundColorBehavior, CommonElevationBehavior
|
||||
|
||||
KV = '''
|
||||
<RectangularElevationButton>
|
||||
size_hint: None, None
|
||||
size: "250dp", "50dp"
|
||||
|
||||
|
||||
MDScreen:
|
||||
|
||||
RectangularElevationButton:
|
||||
pos_hint: {"center_x": .5, "center_y": .6}
|
||||
elevation: 6
|
||||
shadow_softness: 6
|
||||
|
||||
RectangularElevationButton:
|
||||
pos_hint: {"center_x": .5, "center_y": .4}
|
||||
elevation: 6
|
||||
shadow_softness: 12
|
||||
'''
|
||||
|
||||
|
||||
class RectangularElevationButton(CommonElevationBehavior, BackgroundColorBehavior):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.md_bg_color = "blue"
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-softness.png
|
||||
:align: center
|
||||
|
||||
:attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `12`.
|
||||
"""
|
||||
|
||||
shadow_softness_size = BoundedNumericProperty(2, min=2, deprecated=True)
|
||||
"""
|
||||
The value of the softness of the shadow.
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
.. deprecated:: 1.2.0
|
||||
|
||||
:attr:`shadow_softness_size` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `2`.
|
||||
"""
|
||||
|
||||
shadow_offset = ListProperty((0, 0))
|
||||
"""
|
||||
Offset of the shadow.
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import BackgroundColorBehavior, CommonElevationBehavior
|
||||
|
||||
KV = '''
|
||||
<RectangularElevationButton>
|
||||
size_hint: None, None
|
||||
size: "100dp", "100dp"
|
||||
|
||||
|
||||
MDScreen:
|
||||
|
||||
RectangularElevationButton:
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
elevation: 6
|
||||
shadow_radius: 6
|
||||
shadow_softness: 12
|
||||
shadow_offset: -12, -12
|
||||
'''
|
||||
|
||||
|
||||
class RectangularElevationButton(CommonElevationBehavior, BackgroundColorBehavior):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.md_bg_color = "blue"
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-1.png
|
||||
:align: center
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
RectangularElevationButton:
|
||||
shadow_offset: 12, -12
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-2.png
|
||||
:align: center
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
RectangularElevationButton:
|
||||
shadow_offset: 12, 12
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-3.png
|
||||
:align: center
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
RectangularElevationButton:
|
||||
shadow_offset: -12, 12
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-4.png
|
||||
:align: center
|
||||
|
||||
:attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `(0, 0)`.
|
||||
"""
|
||||
|
||||
shadow_color = ColorProperty([0, 0, 0, 0.6])
|
||||
"""
|
||||
Offset of the shadow.
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
RectangularElevationButton:
|
||||
shadow_color: 0, 0, 1, .8
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-color.png
|
||||
:align: center
|
||||
|
||||
:attr:`shadow_color` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `[0, 0, 0, 0.6]`.
|
||||
"""
|
||||
|
||||
scale_value_x = NumericProperty(1)
|
||||
"""
|
||||
X-axis value.
|
||||
|
||||
.. versionadded:: 1.2.0
|
||||
|
||||
:attr:`scale_value_x` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
scale_value_y = NumericProperty(1)
|
||||
"""
|
||||
Y-axis value.
|
||||
|
||||
.. versionadded:: 1.2.0
|
||||
|
||||
:attr:`scale_value_y` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
scale_value_z = NumericProperty(1)
|
||||
"""
|
||||
Z-axis value.
|
||||
|
||||
.. versionadded:: 1.2.0
|
||||
|
||||
:attr:`scale_value_z` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
scale_value_center = ListProperty()
|
||||
"""
|
||||
Origin of the scale.
|
||||
|
||||
.. versionadded:: 1.2.0
|
||||
|
||||
The format of the origin can be either (x, y) or (x, y, z).
|
||||
|
||||
:attr:`scale_value_center` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
rotate_value_angle = NumericProperty(0)
|
||||
"""
|
||||
Property for getting/setting the angle of the rotation.
|
||||
|
||||
.. versionadded:: 1.2.0
|
||||
|
||||
:attr:`rotate_value_angle` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0`.
|
||||
"""
|
||||
|
||||
rotate_value_axis = ListProperty((0, 0, 1))
|
||||
"""
|
||||
Property for getting/setting the axis of the rotation.
|
||||
|
||||
.. versionadded:: 1.2.0
|
||||
|
||||
:attr:`rotate_value_axis` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `(0, 0, 1)`.
|
||||
"""
|
||||
|
||||
_elevation = 0
|
||||
|
||||
def on_elevation(self, instance, value) -> None:
|
||||
self._elevation = value
|
||||
|
||||
|
||||
class RectangularElevationBehavior(CommonElevationBehavior):
|
||||
"""
|
||||
.. deprecated:: 1.1.0
|
||||
Use :class:`~CommonElevationBehavior` class instead.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
Logger.warning(
|
||||
"KivyMD: "
|
||||
"The `RectangularElevationBehavior` class has been deprecated. "
|
||||
"Use the `CommonElevationBehavior` class instead.`"
|
||||
)
|
||||
|
||||
|
||||
class CircularElevationBehavior(CommonElevationBehavior):
|
||||
"""
|
||||
.. deprecated:: 1.1.0
|
||||
Use :class:`~CommonElevationBehavior` class instead.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
Logger.warning(
|
||||
"KivyMD: "
|
||||
"The `CircularElevationBehavior` class has been deprecated. "
|
||||
"Use the `CommonElevationBehavior` class instead.`"
|
||||
)
|
||||
|
||||
|
||||
class RoundedRectangularElevationBehavior(CommonElevationBehavior):
|
||||
"""
|
||||
.. deprecated:: 1.1.0
|
||||
Use :class:`~CommonElevationBehavior` class instead.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
Logger.warning(
|
||||
"KivyMD: "
|
||||
"The `RoundedRectangularElevationBehavior` class has been "
|
||||
"deprecated. Use the `CommonElevationBehavior` class instead.`"
|
||||
)
|
||||
|
||||
|
||||
class FakeRectangularElevationBehavior(CommonElevationBehavior):
|
||||
"""
|
||||
.. deprecated:: 1.1.0
|
||||
Use :class:`~CommonElevationBehavior` class instead.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
Logger.warning(
|
||||
"KivyMD: "
|
||||
"The `FakeRectangularElevationBehavior` class has been "
|
||||
"deprecated. Use the `CommonElevationBehavior` class instead."
|
||||
)
|
||||
|
||||
|
||||
class FakeCircularElevationBehavior(CommonElevationBehavior):
|
||||
"""
|
||||
.. deprecated:: 1.1.0
|
||||
Use :class:`~CommonElevationBehavior` class instead.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
Logger.warning(
|
||||
"KivyMD: "
|
||||
"The `FakeCircularElevationBehavior` class has been deprecated. "
|
||||
"Use the `CommonElevationBehavior` class instead."
|
||||
)
|
|
@ -0,0 +1,147 @@
|
|||
"""
|
||||
Behaviors/Focus
|
||||
===============
|
||||
|
||||
.. rubric:: Changing the background color when the mouse is on the widget.
|
||||
|
||||
To apply focus behavior, you must create a new class that is inherited from the
|
||||
widget to which you apply the behavior and from the :class:`FocusBehavior` class.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import RectangularElevationBehavior
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.behaviors.focus_behavior import FocusBehavior
|
||||
|
||||
KV = '''
|
||||
MDScreen:
|
||||
md_bg_color: 1, 1, 1, 1
|
||||
|
||||
FocusWidget:
|
||||
size_hint: .5, .3
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
md_bg_color: app.theme_cls.bg_light
|
||||
|
||||
MDLabel:
|
||||
text: "Label"
|
||||
theme_text_color: "Primary"
|
||||
pos_hint: {"center_y": .5}
|
||||
halign: "center"
|
||||
'''
|
||||
|
||||
|
||||
class FocusWidget(MDBoxLayout, RectangularElevationBehavior, FocusBehavior):
|
||||
pass
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/focus-widget.gif
|
||||
:align: center
|
||||
|
||||
Color change at focus/defocus
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
FocusWidget:
|
||||
focus_color: 1, 0, 1, 1
|
||||
unfocus_color: 0, 0, 1, 1
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/focus-defocus-color.gif
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = ("FocusBehavior",)
|
||||
|
||||
from kivy.app import App
|
||||
from kivy.properties import BooleanProperty, ColorProperty
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.uix.behaviors import HoverBehavior
|
||||
|
||||
|
||||
class FocusBehavior(HoverBehavior, ButtonBehavior):
|
||||
"""
|
||||
Focus behavior class.
|
||||
|
||||
For more information, see in the :class:`~kivymd.uix.behavior.HoverBehavior`
|
||||
and :class:`~kivy.uix.button.ButtonBehavior` classes documentation.
|
||||
|
||||
:Events:
|
||||
:attr:`on_enter`
|
||||
Called when mouse enters the bbox of the widget AND the widget is visible
|
||||
:attr:`on_leave`
|
||||
Called when the mouse exits the widget AND the widget is visible
|
||||
"""
|
||||
|
||||
focus_behavior = BooleanProperty(True)
|
||||
"""
|
||||
Using focus when hovering over a widget.
|
||||
|
||||
:attr:`focus_behavior` is a :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
focus_color = ColorProperty(None)
|
||||
"""
|
||||
The color of the widget when the mouse enters the bbox of the widget.
|
||||
|
||||
:attr:`focus_color` is a :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
unfocus_color = ColorProperty(None)
|
||||
"""
|
||||
The color of the widget when the mouse exits the bbox widget.
|
||||
|
||||
:attr:`unfocus_color` is a :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
def on_enter(self):
|
||||
"""Called when mouse enter the bbox of the widget."""
|
||||
|
||||
if (
|
||||
hasattr(self, "md_bg_color") or hasattr(self, "bg_color")
|
||||
) and self.focus_behavior:
|
||||
if hasattr(self, "theme_cls") and not self.focus_color:
|
||||
color = self.theme_cls.bg_normal
|
||||
else:
|
||||
if not self.focus_color:
|
||||
color = App.get_running_app().theme_cls.bg_normal
|
||||
else:
|
||||
color = self.focus_color
|
||||
self._set_bg_color(color)
|
||||
|
||||
def on_leave(self):
|
||||
"""Called when the mouse exit the widget."""
|
||||
|
||||
if (
|
||||
hasattr(self, "md_bg_color") or hasattr(self, "bg_color")
|
||||
) and self.focus_behavior:
|
||||
if hasattr(self, "theme_cls") and not self.unfocus_color:
|
||||
color = self.theme_cls.bg_light
|
||||
else:
|
||||
if not self.unfocus_color:
|
||||
color = App.get_running_app().theme_cls.bg_light
|
||||
else:
|
||||
color = self.unfocus_color
|
||||
self._set_bg_color(color)
|
||||
|
||||
def _set_bg_color(self, color):
|
||||
if hasattr(self, "md_bg_color"):
|
||||
self.md_bg_color = color
|
||||
elif hasattr(self, "bg_color"):
|
||||
self.bg_color = color
|
|
@ -0,0 +1,234 @@
|
|||
"""
|
||||
Behaviors/Hover
|
||||
===============
|
||||
|
||||
.. rubric:: Changing when the mouse is on the widget and the widget is visible.
|
||||
|
||||
To apply hover behavior, you must create a new class that is inherited from the
|
||||
widget to which you apply the behavior and from the :attr:`HoverBehavior` class.
|
||||
|
||||
In `KV file`:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<HoverItem@MDBoxLayout+HoverBehavior>
|
||||
|
||||
In `python file`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class HoverItem(MDBoxLayout, HoverBehavior):
|
||||
'''Custom item implementing hover behavior.'''
|
||||
|
||||
After creating a class, you must define two methods for it:
|
||||
:attr:`HoverBehavior.on_enter` and :attr:`HoverBehavior.on_leave`, which will be automatically called
|
||||
when the mouse cursor is over the widget and when the mouse cursor goes beyond
|
||||
the widget.
|
||||
|
||||
.. note::
|
||||
|
||||
:class:`~HoverBehavior` will by default check to see if the current Widget is visible (i.e. not covered by a modal or popup and not a part of a Relative Layout, MDTab or Carousel that is not currently visible etc) and will only issue events if the widget is visible.
|
||||
|
||||
To get the legacy behavior that the events are always triggered, you can set `detect_visible` on the Widget to `False`.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import HoverBehavior
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
|
||||
KV = '''
|
||||
Screen
|
||||
|
||||
MDBoxLayout:
|
||||
id: box
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
size_hint: .8, .8
|
||||
md_bg_color: app.theme_cls.bg_darkest
|
||||
'''
|
||||
|
||||
|
||||
class HoverItem(MDBoxLayout, HoverBehavior):
|
||||
'''Custom item implementing hover behavior.'''
|
||||
|
||||
def on_enter(self, *args):
|
||||
'''The method will be called when the mouse cursor
|
||||
is within the borders of the current widget.'''
|
||||
|
||||
self.md_bg_color = (1, 1, 1, 1)
|
||||
|
||||
def on_leave(self, *args):
|
||||
'''The method will be called when the mouse cursor goes beyond
|
||||
the borders of the current widget.'''
|
||||
|
||||
self.md_bg_color = self.theme_cls.bg_darkest
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
self.screen = Builder.load_string(KV)
|
||||
for i in range(5):
|
||||
self.screen.ids.box.add_widget(HoverItem())
|
||||
return self.screen
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hover-behavior.gif
|
||||
:width: 250 px
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = ("HoverBehavior",)
|
||||
|
||||
from kivy.core.window import Window
|
||||
from kivy.properties import BooleanProperty, ObjectProperty
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
|
||||
class HoverBehavior(object):
|
||||
"""
|
||||
:Events:
|
||||
:attr:`on_enter`
|
||||
Called when mouse enters the bbox of the widget AND the widget is visible
|
||||
:attr:`on_leave`
|
||||
Called when the mouse exits the widget AND the widget is visible
|
||||
"""
|
||||
|
||||
hovering = BooleanProperty(False)
|
||||
"""
|
||||
`True`, if the mouse cursor is within the borders of the widget.
|
||||
|
||||
Note that this is set and cleared even if the widget is not visible
|
||||
|
||||
:attr:`hover` is a :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
hover_visible = BooleanProperty(False)
|
||||
"""
|
||||
`True` if hovering is True AND is the current widget is visible
|
||||
|
||||
:attr:`hover_visible` is a :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
enter_point = ObjectProperty(allownone=True)
|
||||
"""
|
||||
Holds the last position where the mouse pointer crossed into the Widget
|
||||
if the Widget is visible and is currently in a hovering state
|
||||
|
||||
:attr:`enter_point` is a :class:`~kivy.properties.ObjectProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
detect_visible = BooleanProperty(True)
|
||||
"""
|
||||
Should this widget perform the visibility check?
|
||||
|
||||
:attr:`detect_visible` is a :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `True`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.register_event_type("on_enter")
|
||||
self.register_event_type("on_leave")
|
||||
Window.bind(mouse_pos=self.on_mouse_update)
|
||||
super(HoverBehavior, self).__init__(**kwargs)
|
||||
|
||||
def on_mouse_update(self, *args):
|
||||
# If the Widget currently has no parent, do nothing
|
||||
if not self.get_root_window():
|
||||
return
|
||||
pos = args[1]
|
||||
#
|
||||
# is the pointer in the same position as the widget?
|
||||
# If not - then issue an on_exit event if needed
|
||||
#
|
||||
if not self.collide_point(*self.to_widget(*pos)):
|
||||
self.hovering = False
|
||||
self.enter_point = None
|
||||
if self.hover_visible:
|
||||
self.hover_visible = False
|
||||
self.dispatch("on_leave")
|
||||
return
|
||||
|
||||
#
|
||||
# The pointer is in the same position as the widget
|
||||
#
|
||||
|
||||
if self.hovering:
|
||||
#
|
||||
# nothing to do here. Not - this does not handle the case where
|
||||
# a popup comes over an existing hover event.
|
||||
# This seems reasonable
|
||||
#
|
||||
return
|
||||
|
||||
#
|
||||
# Otherwise - set the hovering attribute
|
||||
#
|
||||
self.hovering = True
|
||||
|
||||
#
|
||||
# We need to traverse the tree to see if the Widget is visible
|
||||
#
|
||||
# This is a two stage process:
|
||||
# - first go up the tree to the root Window.
|
||||
# At each stage - check that the Widget is actually visible
|
||||
# - Second - At the root Window check that there is not another branch
|
||||
# covering the Widget
|
||||
#
|
||||
|
||||
self.hover_visible = True
|
||||
if self.detect_visible:
|
||||
widget: Widget = self
|
||||
while True:
|
||||
# Walk up the Widget tree from the target Widget
|
||||
parent = widget.parent
|
||||
try:
|
||||
# See if the mouse point collides with the parent
|
||||
# using both local and glabal coordinates to cover absoluet and relative layouts
|
||||
pinside = parent.collide_point(
|
||||
*parent.to_widget(*pos)
|
||||
) or parent.collide_point(*pos)
|
||||
except Exception:
|
||||
# The collide_point will error when you reach the root Window
|
||||
break
|
||||
if not pinside:
|
||||
self.hover_visible = False
|
||||
break
|
||||
# Iterate upwards
|
||||
widget = parent
|
||||
|
||||
#
|
||||
# parent = root window
|
||||
# widget = first Widget on the current branch
|
||||
#
|
||||
|
||||
children = parent.children
|
||||
for child in children:
|
||||
# For each top level widget - check if is current branch
|
||||
# If it is - then break.
|
||||
# If not then - since we start at 0 - this widget is visible
|
||||
#
|
||||
# Check to see if it should take the hover
|
||||
#
|
||||
if child == widget:
|
||||
# this means that the current widget is visible
|
||||
break
|
||||
if child.collide_point(*pos):
|
||||
# this means that the current widget is covered by a modal or popup
|
||||
self.hover_visible = False
|
||||
break
|
||||
if self.hover_visible:
|
||||
self.enter_point = pos
|
||||
self.dispatch("on_enter")
|
||||
|
||||
def on_enter(self):
|
||||
"""Called when mouse enters the bbox of the widget AND the widget is visible."""
|
||||
|
||||
def on_leave(self):
|
||||
"""Called when the mouse exits the widget AND the widget is visible."""
|
|
@ -0,0 +1,189 @@
|
|||
"""
|
||||
Behaviors/Magic
|
||||
===============
|
||||
|
||||
.. rubric:: Magical effects for buttons.
|
||||
|
||||
.. warning:: Magic effects do not work correctly with `KivyMD` buttons!
|
||||
|
||||
To apply magic effects, you must create a new class that is inherited from the
|
||||
widget to which you apply the effect and from the :attr:`MagicBehavior` class.
|
||||
|
||||
In `KV file`:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<MagicButton@MagicBehavior+MDRectangleFlatButton>
|
||||
|
||||
In `python file`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MagicButton(MagicBehavior, MDRectangleFlatButton):
|
||||
pass
|
||||
|
||||
.. rubric:: The :attr:`MagicBehavior` class provides five effects:
|
||||
|
||||
- :attr:`MagicBehavior.wobble`
|
||||
- :attr:`MagicBehavior.grow`
|
||||
- :attr:`MagicBehavior.shake`
|
||||
- :attr:`MagicBehavior.twist`
|
||||
- :attr:`MagicBehavior.shrink`
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
KV = '''
|
||||
<MagicButton@MagicBehavior+MDRectangleFlatButton>
|
||||
|
||||
|
||||
MDFloatLayout:
|
||||
|
||||
MagicButton:
|
||||
text: "WOBBLE EFFECT"
|
||||
on_release: self.wobble()
|
||||
pos_hint: {"center_x": .5, "center_y": .3}
|
||||
|
||||
MagicButton:
|
||||
text: "GROW EFFECT"
|
||||
on_release: self.grow()
|
||||
pos_hint: {"center_x": .5, "center_y": .4}
|
||||
|
||||
MagicButton:
|
||||
text: "SHAKE EFFECT"
|
||||
on_release: self.shake()
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
|
||||
MagicButton:
|
||||
text: "TWIST EFFECT"
|
||||
on_release: self.twist()
|
||||
pos_hint: {"center_x": .5, "center_y": .6}
|
||||
|
||||
MagicButton:
|
||||
text: "SHRINK EFFECT"
|
||||
on_release: self.shrink()
|
||||
pos_hint: {"center_x": .5, "center_y": .7}
|
||||
'''
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/magic-button.gif
|
||||
:width: 250 px
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = ("MagicBehavior",)
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import NumericProperty
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<MagicBehavior>
|
||||
translate_x: 0
|
||||
translate_y: 0
|
||||
scale_x: 1
|
||||
scale_y: 1
|
||||
rotate: 0
|
||||
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
Translate:
|
||||
x: self.translate_x or 0
|
||||
y: self.translate_y or 0
|
||||
Rotate:
|
||||
origin: self.center
|
||||
angle: self.rotate or 0
|
||||
Scale:
|
||||
origin: self.center
|
||||
x: self.scale_x or 1
|
||||
y: self.scale_y or 1
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class MagicBehavior:
|
||||
magic_speed = NumericProperty(1)
|
||||
"""
|
||||
Animation playback speed.
|
||||
|
||||
:attr:`magic_speed` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
def grow(self) -> None:
|
||||
"""Grow effect animation."""
|
||||
|
||||
(
|
||||
Animation(
|
||||
scale_x=1.2,
|
||||
scale_y=1.2,
|
||||
t="out_quad",
|
||||
d=0.03 / self.magic_speed,
|
||||
)
|
||||
+ Animation(
|
||||
scale_x=1, scale_y=1, t="out_elastic", d=0.4 / self.magic_speed
|
||||
)
|
||||
).start(self)
|
||||
|
||||
def shake(self) -> None:
|
||||
"""Shake effect animation."""
|
||||
|
||||
(
|
||||
Animation(translate_x=50, t="out_quad", d=0.02 / self.magic_speed)
|
||||
+ Animation(
|
||||
translate_x=0, t="out_elastic", d=0.5 / self.magic_speed
|
||||
)
|
||||
).start(self)
|
||||
|
||||
def wobble(self) -> None:
|
||||
"""Wobble effect animation."""
|
||||
|
||||
(
|
||||
(
|
||||
Animation(scale_y=0.7, t="out_quad", d=0.03 / self.magic_speed)
|
||||
& Animation(
|
||||
scale_x=1.4, t="out_quad", d=0.03 / self.magic_speed
|
||||
)
|
||||
)
|
||||
+ (
|
||||
Animation(scale_y=1, t="out_elastic", d=0.5 / self.magic_speed)
|
||||
& Animation(
|
||||
scale_x=1, t="out_elastic", d=0.4 / self.magic_speed
|
||||
)
|
||||
)
|
||||
).start(self)
|
||||
|
||||
def twist(self) -> None:
|
||||
"""Twist effect animation."""
|
||||
|
||||
(
|
||||
Animation(rotate=25, t="out_quad", d=0.05 / self.magic_speed)
|
||||
+ Animation(rotate=0, t="out_elastic", d=0.5 / self.magic_speed)
|
||||
).start(self)
|
||||
|
||||
def shrink(self) -> None:
|
||||
"""Shrink effect animation."""
|
||||
|
||||
Animation(
|
||||
scale_x=0.95, scale_y=0.95, t="out_quad", d=0.1 / self.magic_speed
|
||||
).start(self)
|
||||
|
||||
def on_touch_up(self, *args):
|
||||
Animation.stop_all(self)
|
||||
return super().on_touch_up(*args)
|
|
@ -0,0 +1,285 @@
|
|||
"""
|
||||
Behaviors/Motion
|
||||
================
|
||||
|
||||
.. rubric:: Use motion to make a UI expressive and easy to use.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/motion.png
|
||||
:align: center
|
||||
|
||||
.. versionadded:: 1.2.0
|
||||
|
||||
Classes of the `Motion` type implement the display behavior of widgets such
|
||||
as dialogs, dropdown menu, snack bars, and so on.
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"MotionBase",
|
||||
"MotionDropDownMenuBehavior",
|
||||
"MotionDialogBehavior",
|
||||
"MotionShackBehavior",
|
||||
)
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.clock import Clock
|
||||
from kivy.core.window import Window
|
||||
from kivy.properties import StringProperty, NumericProperty
|
||||
|
||||
from kivymd.uix.behaviors.stencil_behavior import StencilBehavior
|
||||
|
||||
|
||||
class MotionBase:
|
||||
"""Base class for widget display movement behavior."""
|
||||
|
||||
show_transition = StringProperty("linear")
|
||||
"""
|
||||
The type of transition of the widget opening.
|
||||
|
||||
:attr:`show_transition` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'linear'`.
|
||||
"""
|
||||
|
||||
show_duration = NumericProperty(0.2)
|
||||
"""
|
||||
Duration of widget display transition.
|
||||
|
||||
:attr:`show_duration` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.2`.
|
||||
"""
|
||||
|
||||
hide_transition = StringProperty("linear")
|
||||
"""
|
||||
The type of transition of the widget closing.
|
||||
|
||||
:attr:`hide_transition` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'linear'`.
|
||||
"""
|
||||
|
||||
hide_duration = NumericProperty(0.2)
|
||||
"""
|
||||
Duration of widget closing transition.
|
||||
|
||||
:attr:`hide_duration` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.2`.
|
||||
"""
|
||||
|
||||
|
||||
class MotionDropDownMenuBehavior(MotionBase):
|
||||
"""
|
||||
Base class for the dropdown menu movement behavior.
|
||||
|
||||
For more information, see in the :class:`~MotionBase` class documentation.
|
||||
"""
|
||||
|
||||
show_transition = StringProperty("out_back")
|
||||
"""
|
||||
The type of transition of the widget opening.
|
||||
|
||||
:attr:`show_transition` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'out_back'`.
|
||||
"""
|
||||
|
||||
show_duration = NumericProperty(0.4)
|
||||
"""
|
||||
Duration of widget display transition.
|
||||
|
||||
:attr:`show_duration` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.2`.
|
||||
"""
|
||||
|
||||
hide_transition = StringProperty("out_cubic")
|
||||
"""
|
||||
The type of transition of the widget closing.
|
||||
|
||||
:attr:`hide_transition` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'out_cubic'`.
|
||||
"""
|
||||
|
||||
_scale_x = NumericProperty(None)
|
||||
"""
|
||||
Default X-axis scaling values.
|
||||
|
||||
:attr:`_scale_x` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
_scale_y = NumericProperty(None)
|
||||
"""
|
||||
Default Y-axis scaling values.
|
||||
|
||||
:attr:`_scale_y` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
_opacity = NumericProperty(None)
|
||||
"""
|
||||
Menu transparency values.
|
||||
|
||||
:attr:`_opacity` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.set_scale()
|
||||
# self.set_opacity()
|
||||
|
||||
def set_opacity(self) -> None:
|
||||
self._opacity = 0
|
||||
|
||||
def set_scale(self) -> None:
|
||||
self._scale_x = 0
|
||||
self._scale_y = 0
|
||||
|
||||
def on_dismiss(self) -> None:
|
||||
anim = Animation(
|
||||
_scale_x=0,
|
||||
_scale_y=0,
|
||||
# _opacity=0,
|
||||
duration=self.hide_duration,
|
||||
transition=self.hide_transition,
|
||||
)
|
||||
anim.bind(on_complete=lambda *args: Window.remove_widget(self))
|
||||
anim.start(self)
|
||||
|
||||
def on_open(self, *args):
|
||||
anim = Animation(
|
||||
_scale_y=1,
|
||||
# _opacity=1,
|
||||
duration=self.show_duration,
|
||||
transition=self.show_transition,
|
||||
)
|
||||
anim &= Animation(
|
||||
_scale_x=1,
|
||||
duration=self.show_duration - 0.3,
|
||||
transition="out_quad",
|
||||
)
|
||||
anim.start(self)
|
||||
|
||||
def on__opacity(self, instance, value):
|
||||
self.opacity = value
|
||||
|
||||
def on__scale_x(self, instance, value):
|
||||
self.scale_value_x = value
|
||||
|
||||
def on__scale_y(self, instance, value):
|
||||
self.scale_value_y = value
|
||||
|
||||
|
||||
class MotionDialogBehavior(MotionBase):
|
||||
"""
|
||||
Base class for dialog movement behavior.
|
||||
|
||||
For more information, see in the :class:`~MotionBase` class documentation.
|
||||
"""
|
||||
|
||||
show_duration = NumericProperty(0.1)
|
||||
"""
|
||||
Duration of widget display transition.
|
||||
|
||||
:attr:`show_duration` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.1`.
|
||||
"""
|
||||
|
||||
scale_x = NumericProperty(1.5)
|
||||
"""
|
||||
Default X-axis scaling values.
|
||||
|
||||
:attr:`scale_x` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1.5`.
|
||||
"""
|
||||
|
||||
scale_y = NumericProperty(1.5)
|
||||
"""
|
||||
Default Y-axis scaling values.
|
||||
|
||||
:attr:`scale_y` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1.5`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.set_default_values()
|
||||
|
||||
def set_default_values(self):
|
||||
"""Sets default scaled and transparency values."""
|
||||
|
||||
self.scale_value_x = self.scale_x
|
||||
self.scale_value_y = self.scale_y
|
||||
self.opacity = 0
|
||||
|
||||
def on_dismiss(self, *args):
|
||||
"""Called when a dialog closed."""
|
||||
|
||||
self.set_default_values()
|
||||
|
||||
def on_open(self, *args):
|
||||
"""Called when a dialog opened."""
|
||||
|
||||
Animation(
|
||||
opacity=1,
|
||||
scale_value_x=1,
|
||||
scale_value_y=1,
|
||||
t=self.show_transition,
|
||||
d=self.show_duration,
|
||||
).start(self)
|
||||
|
||||
|
||||
class MotionShackBehavior(StencilBehavior, MotionBase):
|
||||
"""
|
||||
The base class for the behavior of the movement of snack bars.
|
||||
|
||||
For more information, see in the
|
||||
:class:`~MotionBase` class and
|
||||
:class:`~kivy.uix.behaviors.stencil_behavior.StencilBehavior` class
|
||||
documentation.
|
||||
"""
|
||||
|
||||
_interval = 0
|
||||
_height = 0
|
||||
|
||||
def on_dismiss(self, *args):
|
||||
"""Called when a snackbar closed."""
|
||||
|
||||
def remove_snackbar(*args):
|
||||
Window.parent.remove_widget(self)
|
||||
self.height = self._height
|
||||
self.dispatch("on_dismiss")
|
||||
|
||||
Clock.unschedule(self._wait_interval)
|
||||
anim = Animation(
|
||||
opacity=0,
|
||||
height=0,
|
||||
t=self.hide_transition,
|
||||
d=self.hide_duration,
|
||||
)
|
||||
anim.bind(on_complete=remove_snackbar)
|
||||
anim.start(self)
|
||||
|
||||
def on_open(self, *args):
|
||||
"""Called when a snackbar opened."""
|
||||
|
||||
def open(*args):
|
||||
self._height = self.height
|
||||
self.height = 0
|
||||
anim = Animation(
|
||||
opacity=1,
|
||||
height=self._height,
|
||||
t=self.show_transition,
|
||||
d=self.show_duration,
|
||||
)
|
||||
anim.bind(
|
||||
on_complete=lambda *args: Clock.schedule_interval(
|
||||
self._wait_interval, 1
|
||||
)
|
||||
)
|
||||
anim.start(self)
|
||||
|
||||
Clock.schedule_once(open)
|
||||
self.dispatch("on_open")
|
||||
|
||||
def _wait_interval(self, interval):
|
||||
self._interval += interval
|
||||
if self._interval > self.duration:
|
||||
self.dismiss()
|
||||
self._interval = 0
|
|
@ -0,0 +1,538 @@
|
|||
"""
|
||||
Behaviors/Ripple
|
||||
================
|
||||
|
||||
.. rubric:: Classes implements a circular and rectangular ripple effects.
|
||||
|
||||
To create a widget with сircular ripple effect, you must create a new class
|
||||
that inherits from the :class:`~CircularRippleBehavior` class.
|
||||
|
||||
For example, let's create an image button with a circular ripple effect:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
from kivy.uix.image import Image
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import CircularRippleBehavior
|
||||
|
||||
KV = '''
|
||||
MDScreen:
|
||||
|
||||
CircularRippleButton:
|
||||
source: "data/logo/kivy-icon-256.png"
|
||||
size_hint: None, None
|
||||
size: "250dp", "250dp"
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
'''
|
||||
|
||||
|
||||
class CircularRippleButton(CircularRippleBehavior, ButtonBehavior, Image):
|
||||
def __init__(self, **kwargs):
|
||||
self.ripple_scale = 0.85
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-ripple-effect.gif
|
||||
:align: center
|
||||
|
||||
To create a widget with rectangular ripple effect, you must create a new class
|
||||
that inherits from the :class:`~RectangularRippleBehavior` class:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import RectangularRippleBehavior, BackgroundColorBehavior
|
||||
|
||||
KV = '''
|
||||
MDScreen:
|
||||
|
||||
RectangularRippleButton:
|
||||
size_hint: None, None
|
||||
size: "250dp", "50dp"
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
'''
|
||||
|
||||
|
||||
class RectangularRippleButton(
|
||||
RectangularRippleBehavior, ButtonBehavior, BackgroundColorBehavior
|
||||
):
|
||||
md_bg_color = [0, 0, 1, 1]
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-ripple-effect.gif
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"CommonRipple",
|
||||
"RectangularRippleBehavior",
|
||||
"CircularRippleBehavior",
|
||||
)
|
||||
|
||||
from typing import NoReturn
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.graphics import (
|
||||
Color,
|
||||
Ellipse,
|
||||
StencilPop,
|
||||
StencilPush,
|
||||
StencilUnUse,
|
||||
StencilUse,
|
||||
)
|
||||
from kivy.graphics.vertex_instructions import RoundedRectangle
|
||||
from kivy.properties import (
|
||||
BooleanProperty,
|
||||
ColorProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.behaviors import ToggleButtonBehavior
|
||||
|
||||
|
||||
class CommonRipple:
|
||||
"""Base class for ripple effect."""
|
||||
|
||||
ripple_rad_default = NumericProperty(1)
|
||||
"""
|
||||
The starting value of the radius of the ripple effect.
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
CircularRippleButton:
|
||||
ripple_rad_default: 100
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-rad-default.gif
|
||||
:align: center
|
||||
|
||||
:attr:`ripple_rad_default` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
ripple_color = ColorProperty(None)
|
||||
"""
|
||||
Ripple color in (r, g, b, a) format.
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
CircularRippleButton:
|
||||
ripple_color: app.theme_cls.primary_color
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-color.gif
|
||||
:align: center
|
||||
|
||||
:attr:`ripple_color` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
ripple_alpha = NumericProperty(0.5)
|
||||
"""
|
||||
Alpha channel values for ripple effect.
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
CircularRippleButton:
|
||||
ripple_alpha: .9
|
||||
ripple_color: app.theme_cls.primary_color
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-alpha.gif
|
||||
:align: center
|
||||
|
||||
:attr:`ripple_alpha` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.5`.
|
||||
"""
|
||||
|
||||
ripple_scale = NumericProperty(None)
|
||||
"""
|
||||
Ripple effect scale.
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
CircularRippleButton:
|
||||
ripple_scale: .5
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-scale-05.gif
|
||||
:align: center
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
CircularRippleButton:
|
||||
ripple_scale: 1
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-scale-1.gif
|
||||
:align: center
|
||||
|
||||
:attr:`ripple_scale` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
ripple_duration_in_fast = NumericProperty(0.3)
|
||||
"""
|
||||
Ripple duration when touching to widget.
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
CircularRippleButton:
|
||||
ripple_duration_in_fast: .1
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-duration-in-fast.gif
|
||||
:align: center
|
||||
|
||||
:attr:`ripple_duration_in_fast` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.3`.
|
||||
"""
|
||||
|
||||
ripple_duration_in_slow = NumericProperty(2)
|
||||
"""
|
||||
Ripple duration when long touching to widget.
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
CircularRippleButton:
|
||||
ripple_duration_in_slow: 5
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-duration-in-slow.gif
|
||||
:align: center
|
||||
|
||||
:attr:`ripple_duration_in_slow` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `2`.
|
||||
"""
|
||||
|
||||
ripple_duration_out = NumericProperty(0.3)
|
||||
"""
|
||||
The duration of the disappearance of the wave effect.
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
CircularRippleButton:
|
||||
ripple_duration_out: 5
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-duration-out.gif
|
||||
:align: center
|
||||
|
||||
:attr:`ripple_duration_out` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.3`.
|
||||
"""
|
||||
|
||||
ripple_canvas_after = BooleanProperty(True)
|
||||
"""
|
||||
The ripple effect is drawn above/below the content.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDIconButton:
|
||||
ripple_canvas_after: True
|
||||
icon: "android"
|
||||
ripple_alpha: .8
|
||||
ripple_color: app.theme_cls.primary_color
|
||||
icon_size: "100sp"
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-canvas-after-true.gif
|
||||
:align: center
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDIconButton:
|
||||
ripple_canvas_after: False
|
||||
icon: "android"
|
||||
ripple_alpha: .8
|
||||
ripple_color: app.theme_cls.primary_color
|
||||
icon_size: "100sp"
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-canvas-after-false.gif
|
||||
:align: center
|
||||
|
||||
:attr:`ripple_canvas_after` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `True`.
|
||||
"""
|
||||
|
||||
ripple_func_in = StringProperty("out_quad")
|
||||
"""
|
||||
Type of animation for ripple in effect.
|
||||
|
||||
:attr:`ripple_func_in` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'out_quad'`.
|
||||
"""
|
||||
|
||||
ripple_func_out = StringProperty("out_quad")
|
||||
"""
|
||||
Type of animation for ripple out effect.
|
||||
|
||||
:attr:`ripple_func_out` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'ripple_func_out'`.
|
||||
"""
|
||||
|
||||
_ripple_rad = NumericProperty()
|
||||
_doing_ripple = BooleanProperty(False)
|
||||
_finishing_ripple = BooleanProperty(False)
|
||||
_fading_out = BooleanProperty(False)
|
||||
_no_ripple_effect = BooleanProperty(False)
|
||||
_round_rad = ListProperty([0, 0, 0, 0])
|
||||
|
||||
def lay_canvas_instructions(self) -> NoReturn:
|
||||
raise NotImplementedError
|
||||
|
||||
def start_ripple(self) -> None:
|
||||
if not self._doing_ripple:
|
||||
self._doing_ripple = True
|
||||
anim = Animation(
|
||||
_ripple_rad=self.finish_rad,
|
||||
t="linear",
|
||||
duration=self.ripple_duration_in_slow,
|
||||
)
|
||||
anim.bind(on_complete=self.fade_out)
|
||||
anim.start(self)
|
||||
|
||||
def finish_ripple(self) -> None:
|
||||
if self._doing_ripple and not self._finishing_ripple:
|
||||
self._finishing_ripple = True
|
||||
self._doing_ripple = False
|
||||
Animation.cancel_all(self, "_ripple_rad")
|
||||
anim = Animation(
|
||||
_ripple_rad=self.finish_rad,
|
||||
t=self.ripple_func_in,
|
||||
duration=self.ripple_duration_in_fast,
|
||||
)
|
||||
anim.bind(on_complete=self.fade_out)
|
||||
anim.start(self)
|
||||
|
||||
def fade_out(self, *args) -> None:
|
||||
rc = self.ripple_color
|
||||
if not self._fading_out:
|
||||
self._fading_out = True
|
||||
Animation.cancel_all(self, "ripple_color")
|
||||
anim = Animation(
|
||||
ripple_color=[rc[0], rc[1], rc[2], 0.0],
|
||||
t=self.ripple_func_out,
|
||||
duration=self.ripple_duration_out,
|
||||
)
|
||||
anim.bind(on_complete=self.anim_complete)
|
||||
anim.start(self)
|
||||
|
||||
def anim_complete(self, *args) -> None:
|
||||
self._doing_ripple = False
|
||||
self._finishing_ripple = False
|
||||
self._fading_out = False
|
||||
|
||||
if not self.ripple_canvas_after:
|
||||
canvas = self.canvas.before
|
||||
else:
|
||||
canvas = self.canvas.after
|
||||
|
||||
canvas.remove_group("circular_ripple_behavior")
|
||||
canvas.remove_group("rectangular_ripple_behavior")
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
# FIXME: in fact, the output of the super method is extra.
|
||||
# But without this, the list (`ScrollView`) placed in the `MDCard`
|
||||
# widget will not scroll.
|
||||
super().on_touch_down(touch)
|
||||
if touch.is_mouse_scrolling:
|
||||
return False
|
||||
if not self.collide_point(touch.x, touch.y):
|
||||
return False
|
||||
if not self.disabled:
|
||||
self.call_ripple_animation_methods(touch)
|
||||
# FIXME: this check is needed for the `MDTabsLabel` object.
|
||||
# With the normal `return True`, events for tabs from the `MDTabs`
|
||||
# class are not processed.
|
||||
# There may be problems with other widgets.
|
||||
# Status: requires check.
|
||||
if isinstance(self, ToggleButtonBehavior):
|
||||
return super().on_touch_down(touch)
|
||||
else:
|
||||
return True
|
||||
|
||||
def call_ripple_animation_methods(self, touch) -> None:
|
||||
if self._doing_ripple:
|
||||
Animation.cancel_all(
|
||||
self, "_ripple_rad", "ripple_color", "rect_color"
|
||||
)
|
||||
self.anim_complete()
|
||||
self._ripple_rad = self.ripple_rad_default
|
||||
self.ripple_pos = (touch.x, touch.y)
|
||||
|
||||
if self.ripple_color:
|
||||
pass
|
||||
elif hasattr(self, "theme_cls"):
|
||||
self.ripple_color = self.theme_cls.ripple_color
|
||||
else:
|
||||
# If no theme, set Gray 300.
|
||||
self.ripple_color = [
|
||||
0.8784313725490196,
|
||||
0.8784313725490196,
|
||||
0.8784313725490196,
|
||||
self.ripple_alpha,
|
||||
]
|
||||
self.ripple_color[3] = self.ripple_alpha
|
||||
self.lay_canvas_instructions()
|
||||
self.finish_rad = max(self.width, self.height) * self.ripple_scale
|
||||
self.start_ripple()
|
||||
|
||||
def on_touch_move(self, touch, *args):
|
||||
if not self.collide_point(touch.x, touch.y):
|
||||
if not self._finishing_ripple and self._doing_ripple:
|
||||
self.finish_ripple()
|
||||
return super().on_touch_move(touch, *args)
|
||||
|
||||
def on_touch_up(self, touch):
|
||||
if self.collide_point(touch.x, touch.y) and self._doing_ripple:
|
||||
self.finish_ripple()
|
||||
return super().on_touch_up(touch)
|
||||
|
||||
def _set_ellipse(self, instance, value):
|
||||
self.ellipse.size = (self._ripple_rad, self._ripple_rad)
|
||||
|
||||
# Adjust ellipse pos here
|
||||
|
||||
def _set_color(self, instance, value):
|
||||
self.col_instruction.a = value[3]
|
||||
|
||||
|
||||
class RectangularRippleBehavior(CommonRipple):
|
||||
"""
|
||||
Class implements a rectangular ripple effect.
|
||||
|
||||
For more information, see in the :class:`~kivymd.uix.behavior.CommonRipple`
|
||||
class documentation.
|
||||
"""
|
||||
|
||||
ripple_scale = NumericProperty(2.75)
|
||||
"""
|
||||
See :class:`~CommonRipple.ripple_scale`.
|
||||
|
||||
:attr:`ripple_scale` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `2.75`.
|
||||
"""
|
||||
|
||||
def lay_canvas_instructions(self) -> None:
|
||||
if self._no_ripple_effect:
|
||||
return
|
||||
|
||||
with self.canvas.after if self.ripple_canvas_after else self.canvas.before:
|
||||
if hasattr(self, "radius"):
|
||||
if isinstance(self.radius, (float, int)):
|
||||
self.radius = [
|
||||
self.radius,
|
||||
]
|
||||
self._round_rad = self.radius
|
||||
StencilPush(group="rectangular_ripple_behavior")
|
||||
RoundedRectangle(
|
||||
pos=self.pos,
|
||||
size=self.size,
|
||||
radius=self._round_rad,
|
||||
group="rectangular_ripple_behavior",
|
||||
)
|
||||
StencilUse(group="rectangular_ripple_behavior")
|
||||
self.col_instruction = Color(
|
||||
rgba=self.ripple_color, group="rectangular_ripple_behavior"
|
||||
)
|
||||
self.ellipse = Ellipse(
|
||||
size=(self._ripple_rad, self._ripple_rad),
|
||||
pos=(
|
||||
self.ripple_pos[0] - self._ripple_rad / 2.0,
|
||||
self.ripple_pos[1] - self._ripple_rad / 2.0,
|
||||
),
|
||||
group="rectangular_ripple_behavior",
|
||||
)
|
||||
StencilUnUse(group="rectangular_ripple_behavior")
|
||||
RoundedRectangle(
|
||||
pos=self.pos,
|
||||
size=self.size,
|
||||
radius=self._round_rad,
|
||||
group="rectangular_ripple_behavior",
|
||||
)
|
||||
StencilPop(group="rectangular_ripple_behavior")
|
||||
self.bind(ripple_color=self._set_color, _ripple_rad=self._set_ellipse)
|
||||
|
||||
def _set_ellipse(self, instance, value):
|
||||
super()._set_ellipse(instance, value)
|
||||
self.ellipse.pos = (
|
||||
self.ripple_pos[0] - self._ripple_rad / 2.0,
|
||||
self.ripple_pos[1] - self._ripple_rad / 2.0,
|
||||
)
|
||||
|
||||
|
||||
class CircularRippleBehavior(CommonRipple):
|
||||
"""
|
||||
Class implements a circular ripple effect.
|
||||
|
||||
For more information, see in the :class:`~kivymd.uix.behavior.CommonRipple`
|
||||
class documentation.
|
||||
"""
|
||||
|
||||
ripple_scale = NumericProperty(1)
|
||||
"""
|
||||
See :class:`~CommonRipple.ripple_scale`.
|
||||
|
||||
:attr:`ripple_scale` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
def lay_canvas_instructions(self) -> None:
|
||||
if self._no_ripple_effect:
|
||||
return
|
||||
|
||||
with self.canvas.after if self.ripple_canvas_after else self.canvas.before:
|
||||
StencilPush(group="circular_ripple_behavior")
|
||||
self.stencil = Ellipse(
|
||||
size=(
|
||||
self.width * self.ripple_scale,
|
||||
self.height * self.ripple_scale,
|
||||
),
|
||||
pos=(
|
||||
self.center_x - (self.width * self.ripple_scale) / 2,
|
||||
self.center_y - (self.height * self.ripple_scale) / 2,
|
||||
),
|
||||
group="circular_ripple_behavior",
|
||||
)
|
||||
StencilUse(group="circular_ripple_behavior")
|
||||
self.col_instruction = Color(rgba=self.ripple_color)
|
||||
self.ellipse = Ellipse(
|
||||
size=(self._ripple_rad, self._ripple_rad),
|
||||
pos=(
|
||||
self.center_x - self._ripple_rad / 2.0,
|
||||
self.center_y - self._ripple_rad / 2.0,
|
||||
),
|
||||
group="circular_ripple_behavior",
|
||||
)
|
||||
StencilUnUse(group="circular_ripple_behavior")
|
||||
Ellipse(
|
||||
pos=self.pos, size=self.size, group="circular_ripple_behavior"
|
||||
)
|
||||
StencilPop(group="circular_ripple_behavior")
|
||||
self.bind(
|
||||
ripple_color=self._set_color, _ripple_rad=self._set_ellipse
|
||||
)
|
||||
|
||||
def _set_ellipse(self, instance, value):
|
||||
super()._set_ellipse(instance, value)
|
||||
if self.ellipse.size[0] > self.width * 0.6 and not self._fading_out:
|
||||
self.fade_out()
|
||||
self.ellipse.pos = (
|
||||
self.center_x - self._ripple_rad / 2.0,
|
||||
self.center_y - self._ripple_rad / 2.0,
|
||||
)
|
|
@ -0,0 +1,137 @@
|
|||
"""
|
||||
Behaviors/Rotate
|
||||
================
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
Base class for controlling the rotate of the widget.
|
||||
|
||||
.. note:: See `kivy.graphics.Rotate
|
||||
<https://kivy.org/doc/stable/api-kivy.graphics.html#kivy.graphics.Rotate>`_
|
||||
for more information.
|
||||
|
||||
Kivy
|
||||
----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.lang import Builder
|
||||
from kivy.app import App
|
||||
from kivy.properties import NumericProperty
|
||||
from kivy.uix.button import Button
|
||||
|
||||
KV = '''
|
||||
Screen:
|
||||
|
||||
RotateButton:
|
||||
size_hint: .5, .5
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
on_release: app.change_rotate(self)
|
||||
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
Rotate:
|
||||
angle: self.rotate_value_angle
|
||||
axis: 0, 0, 1
|
||||
origin: self.center
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
'''
|
||||
|
||||
|
||||
class RotateButton(Button):
|
||||
rotate_value_angle = NumericProperty(0)
|
||||
|
||||
|
||||
class Test(App):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def change_rotate(self, instance_button: Button) -> None:
|
||||
Animation(rotate_value_angle=45, d=0.3).start(instance_button)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
KivyMD
|
||||
------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import RotateBehavior
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
|
||||
KV = '''
|
||||
MDScreen:
|
||||
|
||||
RotateBox:
|
||||
size_hint: .5, .5
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
on_release: app.change_rotate(self)
|
||||
md_bg_color: "red"
|
||||
'''
|
||||
|
||||
|
||||
class RotateBox(ButtonBehavior, RotateBehavior, MDBoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def change_rotate(self, instance_button: RotateBox) -> None:
|
||||
Animation(rotate_value_angle=45, d=0.3).start(instance_button)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. warning:: Do not use `RotateBehavior` class with classes that inherited`
|
||||
from `CommonElevationBehavior` class. `CommonElevationBehavior` classes
|
||||
by default contains attributes for rotate widget.
|
||||
"""
|
||||
|
||||
__all__ = ("RotateBehavior",)
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import ListProperty, NumericProperty
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<RotateBehavior>
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
Rotate:
|
||||
angle: self.rotate_value_angle
|
||||
axis: tuple(self.rotate_value_axis)
|
||||
origin: self.center
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class RotateBehavior:
|
||||
"""Base class for controlling the rotate of the widget."""
|
||||
|
||||
rotate_value_angle = NumericProperty(0)
|
||||
"""
|
||||
Property for getting/setting the angle of the rotation.
|
||||
|
||||
:attr:`rotate_value_angle` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0`.
|
||||
"""
|
||||
|
||||
rotate_value_axis = ListProperty((0, 0, 1))
|
||||
"""
|
||||
Property for getting/setting the axis of the rotation.
|
||||
|
||||
:attr:`rotate_value_axis` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `(0, 0, 1)`.
|
||||
"""
|
|
@ -0,0 +1,175 @@
|
|||
"""
|
||||
Behaviors/Scale
|
||||
===============
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
Base class for controlling the scale of the widget.
|
||||
|
||||
.. note:: See `kivy.graphics.Rotate
|
||||
<https://kivy.org/doc/stable/api-kivy.graphics.html#kivy.graphics.Scale>`_
|
||||
for more information.
|
||||
|
||||
Kivy
|
||||
----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import NumericProperty
|
||||
from kivy.uix.button import Button
|
||||
from kivy.app import App
|
||||
|
||||
|
||||
KV = '''
|
||||
Screen:
|
||||
|
||||
ScaleButton:
|
||||
size_hint: .5, .5
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
on_release: app.change_scale(self)
|
||||
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
Scale:
|
||||
x: self.scale_value_x
|
||||
y: self.scale_value_y
|
||||
z: self.scale_value_x
|
||||
origin: self.center
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
'''
|
||||
|
||||
|
||||
class ScaleButton(Button):
|
||||
scale_value_x = NumericProperty(1)
|
||||
scale_value_y = NumericProperty(1)
|
||||
scale_value_z = NumericProperty(1)
|
||||
|
||||
|
||||
class Test(App):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def change_scale(self, instance_button: Button) -> None:
|
||||
Animation(
|
||||
scale_value_x=0.5,
|
||||
scale_value_y=0.5,
|
||||
scale_value_z=0.5,
|
||||
d=0.3,
|
||||
).start(instance_button)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
KivyMD
|
||||
------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import ScaleBehavior
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
|
||||
KV = '''
|
||||
MDScreen:
|
||||
|
||||
ScaleBox:
|
||||
size_hint: .5, .5
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
on_release: app.change_scale(self)
|
||||
md_bg_color: "red"
|
||||
'''
|
||||
|
||||
|
||||
class ScaleBox(ButtonBehavior, ScaleBehavior, MDBoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def change_scale(self, instance_button: ScaleBox) -> None:
|
||||
Animation(
|
||||
scale_value_x=0.5,
|
||||
scale_value_y=0.5,
|
||||
scale_value_z=0.5,
|
||||
d=0.3,
|
||||
).start(instance_button)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. warning:: Do not use `ScaleBehavior` class with classes that inherited`
|
||||
from `CommonElevationBehavior` class. `CommonElevationBehavior` classes
|
||||
by default contains attributes for scale widget.
|
||||
"""
|
||||
|
||||
__all__ = ("ScaleBehavior",)
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import ListProperty, NumericProperty
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<ScaleBehavior>
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
Scale:
|
||||
x: self.scale_value_x
|
||||
y: self.scale_value_y
|
||||
z: self.scale_value_z
|
||||
origin:
|
||||
self.center \
|
||||
if not self.scale_value_center else \
|
||||
self.scale_value_center
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class ScaleBehavior:
|
||||
"""Base class for controlling the scale of the widget."""
|
||||
|
||||
scale_value_x = NumericProperty(1)
|
||||
"""
|
||||
X-axis value.
|
||||
|
||||
:attr:`scale_value_x` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
scale_value_y = NumericProperty(1)
|
||||
"""
|
||||
Y-axis value.
|
||||
|
||||
:attr:`scale_value_y` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
scale_value_z = NumericProperty(1)
|
||||
"""
|
||||
Z-axis value.
|
||||
|
||||
:attr:`scale_value_z` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
scale_value_center = ListProperty()
|
||||
"""
|
||||
Origin of the scale.
|
||||
|
||||
.. versionadded:: 1.2.0
|
||||
|
||||
The format of the origin can be either (x, y) or (x, y, z).
|
||||
|
||||
:attr:`scale_value_center` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
|
@ -0,0 +1,134 @@
|
|||
"""
|
||||
Behaviors/Stencil
|
||||
=================
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
Base class for controlling the stencil instructions of the widget.
|
||||
|
||||
.. note:: See `Stencil instructions
|
||||
<https://kivy.org/doc/stable/api-kivy.graphics.stencil_instructions.html>`_
|
||||
for more information.
|
||||
|
||||
Kivy
|
||||
----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.app import App
|
||||
|
||||
KV = '''
|
||||
Carousel:
|
||||
|
||||
Button:
|
||||
size_hint: .9, .8
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
|
||||
canvas.before:
|
||||
StencilPush
|
||||
RoundedRectangle:
|
||||
pos: root.pos
|
||||
size: root.size
|
||||
StencilUse
|
||||
canvas.after:
|
||||
StencilUnUse
|
||||
RoundedRectangle:
|
||||
pos: root.pos
|
||||
size: root.size
|
||||
StencilPop
|
||||
'''
|
||||
|
||||
|
||||
class Test(App):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
KivyMD
|
||||
------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import StencilBehavior
|
||||
from kivymd.uix.fitimage import FitImage
|
||||
|
||||
KV = '''
|
||||
#:import os os
|
||||
#:import images_path kivymd.images_path
|
||||
|
||||
|
||||
MDCarousel:
|
||||
|
||||
StencilImage:
|
||||
size_hint: .9, .8
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
source: os.path.join(images_path, "logo", "kivymd-icon-512.png")
|
||||
'''
|
||||
|
||||
|
||||
class StencilImage(FitImage, StencilBehavior):
|
||||
pass
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Test().run()
|
||||
"""
|
||||
|
||||
__all__ = ("StencilBehavior",)
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import VariableListProperty
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<StencilBehavior>
|
||||
canvas.before:
|
||||
StencilPush
|
||||
RoundedRectangle:
|
||||
pos: root.pos
|
||||
size: root.size
|
||||
# FIXME: Sometimes the radius has the value [], which get a
|
||||
# `GraphicException: Invalid radius value, must be list of tuples/numerics` error
|
||||
radius: root.radius if root.radius else [0, 0, 0, 0]
|
||||
StencilUse
|
||||
canvas.after:
|
||||
StencilUnUse
|
||||
RoundedRectangle:
|
||||
pos: root.pos
|
||||
size: root.size
|
||||
# FIXME: Sometimes the radius has the value [], which get a
|
||||
# `GraphicException: Invalid radius value, must be list of tuples/numerics` error
|
||||
radius: root.radius if root.radius else [0, 0, 0, 0]
|
||||
StencilPop
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class StencilBehavior:
|
||||
"""Base class for controlling the stencil instructions of the widget."""
|
||||
|
||||
radius = VariableListProperty([0], length=4)
|
||||
"""
|
||||
Canvas radius.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Top left corner slice.
|
||||
MDWidget:
|
||||
radius: [25, 0, 0, 0]
|
||||
|
||||
:attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
|
||||
and defaults to `[0, 0, 0, 0]`.
|
||||
"""
|
|
@ -0,0 +1,252 @@
|
|||
"""
|
||||
Behaviors/ToggleButton
|
||||
======================
|
||||
|
||||
This behavior must always be inherited after the button's Widget class since it
|
||||
works with the inherited properties of the button class.
|
||||
|
||||
example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyToggleButtonWidget(MDFlatButton, MDToggleButton):
|
||||
# [...]
|
||||
pass
|
||||
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. tab:: Declarative KV style
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors.toggle_behavior import MDToggleButton
|
||||
from kivymd.uix.button import MDFlatButton
|
||||
|
||||
KV = '''
|
||||
MDScreen:
|
||||
|
||||
MDBoxLayout:
|
||||
adaptive_size: True
|
||||
spacing: "12dp"
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
|
||||
MyToggleButton:
|
||||
text: "Show ads"
|
||||
group: "x"
|
||||
|
||||
MyToggleButton:
|
||||
text: "Do not show ads"
|
||||
group: "x"
|
||||
|
||||
MyToggleButton:
|
||||
text: "Does not matter"
|
||||
group: "x"
|
||||
'''
|
||||
|
||||
|
||||
class MyToggleButton(MDFlatButton, MDToggleButton):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.background_down = self.theme_cls.primary_color
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
self.theme_cls.primary_palette = "Orange"
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. tab:: Declarative python style
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors.toggle_behavior import MDToggleButton
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.button import MDFlatButton
|
||||
from kivymd.uix.screen import MDScreen
|
||||
|
||||
|
||||
class MyToggleButton(MDFlatButton, MDToggleButton):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.background_down = self.theme_cls.primary_color
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
self.theme_cls.primary_palette = "Orange"
|
||||
return (
|
||||
MDScreen(
|
||||
MDBoxLayout(
|
||||
MyToggleButton(
|
||||
text="Show ads",
|
||||
group="x",
|
||||
),
|
||||
MyToggleButton(
|
||||
text="Do not show ads",
|
||||
group="x",
|
||||
),
|
||||
MyToggleButton(
|
||||
text="Does not matter",
|
||||
group="x",
|
||||
),
|
||||
adaptive_size=True,
|
||||
spacing="12dp",
|
||||
pos_hint={"center_x": .5, "center_y": .5},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toggle-button-1.gif
|
||||
:align: center
|
||||
|
||||
You can inherit the ``MyToggleButton`` class only from the following classes
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
- :class:`~kivymd.uix.button.MDRaisedButton`
|
||||
- :class:`~kivymd.uix.button.MDFlatButton`
|
||||
- :class:`~kivymd.uix.button.MDRectangleFlatButton`
|
||||
- :class:`~kivymd.uix.button.MDRectangleFlatIconButton`
|
||||
- :class:`~kivymd.uix.button.MDRoundFlatButton`
|
||||
- :class:`~kivymd.uix.button.MDRoundFlatIconButton`
|
||||
- :class:`~kivymd.uix.button.MDFillRoundFlatButton`
|
||||
- :class:`~kivymd.uix.button.MDFillRoundFlatIconButton`
|
||||
"""
|
||||
|
||||
__all__ = ("MDToggleButton",)
|
||||
|
||||
from kivy.properties import BooleanProperty, ColorProperty
|
||||
from kivy.uix.behaviors import ToggleButtonBehavior
|
||||
|
||||
from kivymd.uix.button import (
|
||||
ButtonContentsIconText,
|
||||
MDFillRoundFlatButton,
|
||||
MDFillRoundFlatIconButton,
|
||||
MDFlatButton,
|
||||
MDRaisedButton,
|
||||
MDRectangleFlatButton,
|
||||
MDRectangleFlatIconButton,
|
||||
MDRoundFlatButton,
|
||||
MDRoundFlatIconButton,
|
||||
)
|
||||
|
||||
|
||||
class MDToggleButton(ToggleButtonBehavior):
|
||||
background_normal = ColorProperty(None)
|
||||
"""
|
||||
Color of the button in ``rgba`` format for the 'normal' state.
|
||||
|
||||
:attr:`background_normal` is a :class:`~kivy.properties.ColorProperty`
|
||||
and is defaults to `None`.
|
||||
"""
|
||||
|
||||
background_down = ColorProperty(None)
|
||||
"""
|
||||
Color of the button in ``rgba`` format for the 'down' state.
|
||||
|
||||
:attr:`background_down` is a :class:`~kivy.properties.ColorProperty`
|
||||
and is defaults to `None`.
|
||||
"""
|
||||
|
||||
font_color_normal = ColorProperty(None)
|
||||
"""
|
||||
Color of the font's button in ``rgba`` format for the 'normal' state.
|
||||
|
||||
:attr:`font_color_normal` is a :class:`~kivy.properties.ColorProperty`
|
||||
and is defaults to `None`.
|
||||
"""
|
||||
|
||||
font_color_down = ColorProperty([1, 1, 1, 1])
|
||||
"""
|
||||
Color of the font's button in ``rgba`` format for the 'down' state.
|
||||
|
||||
:attr:`font_color_down` is a :class:`~kivy.properties.ColorProperty`
|
||||
and is defaults to `[1, 1, 1, 1]`.
|
||||
"""
|
||||
|
||||
__is_filled = BooleanProperty(False)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
classinfo = (
|
||||
MDRaisedButton,
|
||||
MDFlatButton,
|
||||
MDRectangleFlatButton,
|
||||
MDRectangleFlatIconButton,
|
||||
MDRoundFlatButton,
|
||||
MDRoundFlatIconButton,
|
||||
MDFillRoundFlatButton,
|
||||
MDFillRoundFlatIconButton,
|
||||
)
|
||||
# Do the object inherited from the "supported" buttons?
|
||||
if not issubclass(self.__class__, classinfo):
|
||||
raise ValueError(
|
||||
f"Class {self.__class__} must be inherited from one of the "
|
||||
f"classes in the list {classinfo}"
|
||||
)
|
||||
if (
|
||||
not self.background_normal
|
||||
): # This means that if the value == [] or None will return True.
|
||||
# If the object inherits from buttons with background:
|
||||
if isinstance(
|
||||
self,
|
||||
(
|
||||
MDRaisedButton,
|
||||
MDFillRoundFlatButton,
|
||||
MDFillRoundFlatIconButton,
|
||||
),
|
||||
):
|
||||
self.__is_filled = True
|
||||
self.background_normal = self.theme_cls.primary_color
|
||||
# If not background_normal must be the same as the inherited one.
|
||||
else:
|
||||
self.background_normal = (
|
||||
self.md_bg_color[:] if self.md_bg_color else (0, 0, 0, 0)
|
||||
)
|
||||
# If no background_down is setter.
|
||||
if (
|
||||
not self.background_down
|
||||
): # This means that if the value == [] or None will return True.
|
||||
self.background_down = (
|
||||
self.theme_cls.primary_dark
|
||||
) # get the primary_color dark from theme_cls
|
||||
if not self.font_color_normal:
|
||||
self.font_color_normal = self.theme_cls.primary_color
|
||||
# Alternative to bind the function to the property.
|
||||
# self.bind(state=self._update_bg)
|
||||
self.fbind("state", self._update_bg)
|
||||
|
||||
def _update_bg(self, ins, val):
|
||||
"""Updates the color of the background."""
|
||||
|
||||
if val == "down":
|
||||
self.md_bg_color = self.background_down
|
||||
if (
|
||||
self.__is_filled is False
|
||||
): # If the background is transparent, and the button it toggled,
|
||||
# the font color must be withe [1, 1, 1, 1].
|
||||
self.text_color = self.font_color_down
|
||||
if self.group:
|
||||
self._release_group(self)
|
||||
else:
|
||||
self.md_bg_color = self.background_normal
|
||||
if (
|
||||
self.__is_filled is False
|
||||
): # If the background is transparent, the font color must be the
|
||||
# primary color.
|
||||
self.text_color = self.font_color_normal
|
||||
|
||||
if issubclass(self.__class__, ButtonContentsIconText):
|
||||
self.icon_color = self.text_color
|
|
@ -0,0 +1,100 @@
|
|||
"""
|
||||
Behaviors/Touch
|
||||
===============
|
||||
|
||||
.. rubric:: Provides easy access to events.
|
||||
|
||||
The following events are available:
|
||||
|
||||
- on_long_touch
|
||||
- on_double_tap
|
||||
- on_triple_tap
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import TouchBehavior
|
||||
from kivymd.uix.button import MDRaisedButton
|
||||
|
||||
KV = '''
|
||||
MDScreen:
|
||||
|
||||
MyButton:
|
||||
text: "PRESS ME"
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
'''
|
||||
|
||||
|
||||
class MyButton(MDRaisedButton, TouchBehavior):
|
||||
def on_long_touch(self, *args):
|
||||
print("<on_long_touch> event")
|
||||
|
||||
def on_double_tap(self, *args):
|
||||
print("<on_double_tap> event")
|
||||
|
||||
def on_triple_tap(self, *args):
|
||||
print("<on_triple_tap> event")
|
||||
|
||||
|
||||
class MainApp(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
MainApp().run()
|
||||
"""
|
||||
|
||||
__all__ = ("TouchBehavior",)
|
||||
|
||||
from functools import partial
|
||||
|
||||
from kivy.clock import Clock
|
||||
from kivy.properties import NumericProperty
|
||||
|
||||
|
||||
class TouchBehavior:
|
||||
duration_long_touch = NumericProperty(0.4)
|
||||
"""
|
||||
Time for a long touch.
|
||||
|
||||
:attr:`duration_long_touch` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.4`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.bind(
|
||||
on_touch_down=self.create_clock, on_touch_up=self.delete_clock
|
||||
)
|
||||
|
||||
def create_clock(self, widget, touch, *args):
|
||||
if self.collide_point(touch.x, touch.y):
|
||||
if "event" not in touch.ud:
|
||||
callback = partial(self.on_long_touch, touch)
|
||||
Clock.schedule_once(callback, self.duration_long_touch)
|
||||
touch.ud["event"] = callback
|
||||
|
||||
if touch.is_double_tap:
|
||||
self.on_double_tap(touch, *args)
|
||||
if touch.is_triple_tap:
|
||||
self.on_triple_tap(touch, *args)
|
||||
|
||||
def delete_clock(self, widget, touch, *args):
|
||||
if self.collide_point(touch.x, touch.y):
|
||||
if "event" in touch.ud:
|
||||
Clock.unschedule(touch.ud["event"])
|
||||
del touch.ud["event"]
|
||||
|
||||
def on_long_touch(self, touch, *args):
|
||||
"""Called when the widget is pressed for a long time."""
|
||||
|
||||
def on_double_tap(self, touch, *args):
|
||||
"""Called by double clicking on the widget."""
|
||||
|
||||
def on_triple_tap(self, touch, *args):
|
||||
"""Called by triple clicking on the widget."""
|
|
@ -0,0 +1,2 @@
|
|||
# NOQA F401
|
||||
from .bottomnavigation import MDBottomNavigation, MDBottomNavigationItem
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,118 @@
|
|||
#:import STANDARD_INCREMENT kivymd.material_resources.STANDARD_INCREMENT
|
||||
|
||||
|
||||
<MDBottomNavigation>
|
||||
orientation: "vertical"
|
||||
height:
|
||||
STANDARD_INCREMENT if app.theme_cls.material_style == "M2" else "80dp"
|
||||
|
||||
ScreenManager:
|
||||
id: tab_manager
|
||||
transition: root.transition(duration=root.transition_duration)
|
||||
on_current:
|
||||
root.dispatch( \
|
||||
"on_switch_tabs", \
|
||||
root._get_switchig_tab(self.current), \
|
||||
self.current \
|
||||
)
|
||||
|
||||
MDBottomNavigationBar:
|
||||
id: bottom_panel
|
||||
size_hint_y: None
|
||||
radius: root.radius
|
||||
height:
|
||||
STANDARD_INCREMENT \
|
||||
if app.theme_cls.material_style == "M2" else \
|
||||
"80dp"
|
||||
md_bg_color:
|
||||
root.theme_cls.bg_dark \
|
||||
if not root.panel_color \
|
||||
else root.panel_color
|
||||
|
||||
MDBoxLayout:
|
||||
id: tab_bar
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
size_hint: None, None
|
||||
height:
|
||||
STANDARD_INCREMENT \
|
||||
if app.theme_cls.material_style == "M2" else \
|
||||
"80dp"
|
||||
|
||||
|
||||
<MDBottomNavigationHeader>
|
||||
md_bg_color: root.panel_color
|
||||
on_press: self.tab.dispatch("on_tab_press")
|
||||
on_release: self.tab.dispatch("on_tab_release")
|
||||
on_touch_down: self.tab.dispatch("on_tab_touch_down", *args)
|
||||
on_touch_move: self.tab.dispatch("on_tab_touch_move", *args)
|
||||
on_touch_up: self.tab.dispatch("on_tab_touch_up", *args)
|
||||
width:
|
||||
root.panel.width / len(root.panel.ids.tab_manager.screens) \
|
||||
if len(root.panel.ids.tab_manager.screens) != 0 \
|
||||
else root.panel.width
|
||||
padding:
|
||||
0, "12dp", 0, "12dp" if app.theme_cls.material_style == "M2" else "16dp"
|
||||
|
||||
RelativeLayout:
|
||||
id: item_container
|
||||
|
||||
MDIcon:
|
||||
id: _label_icon
|
||||
icon: root.tab.icon
|
||||
height: self.height
|
||||
badge_icon: root.tab.badge_icon
|
||||
theme_text_color: "Custom"
|
||||
text_color: root._text_color_normal
|
||||
opposite_colors: root.opposite_colors
|
||||
pos: [self.pos[0], self.pos[1]]
|
||||
font_size: "24dp"
|
||||
y: item_container.height - self.height
|
||||
pos_hint:
|
||||
{"center_x": .5, "center_y": .5} \
|
||||
if not root.panel.use_text else \
|
||||
{"center_x": .5, "top": 1}
|
||||
on_icon:
|
||||
if self.icon not in md_icons.keys(): \
|
||||
self.size_hint = (None, None); \
|
||||
self.width = self.font_size; \
|
||||
self.height = self.font_size
|
||||
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba:
|
||||
( \
|
||||
( \
|
||||
app.theme_cls.disabled_hint_text_color \
|
||||
if not root.selected_color_background else \
|
||||
root.selected_color_background \
|
||||
) \
|
||||
if root.active else \
|
||||
(0, 0, 0, 0) \
|
||||
) \
|
||||
if app.theme_cls.material_style == "M3" else \
|
||||
(0, 0, 0, 0)
|
||||
RoundedRectangle:
|
||||
radius: [16,]
|
||||
size: root._selected_region_width, dp(32)
|
||||
pos:
|
||||
self.center_x - root._selected_region_width / 2, \
|
||||
self.center_y - (dp(16))
|
||||
|
||||
MDLabel:
|
||||
id: _label
|
||||
text: root.tab.text
|
||||
size_hint_x: None
|
||||
text_size: None, root.height
|
||||
adaptive_height: True
|
||||
theme_text_color: "Custom"
|
||||
text_color: root._text_color_normal
|
||||
opposite_colors: root.opposite_colors
|
||||
font_size: root._label_font_size
|
||||
pos_hint: {"center_x": .5}
|
||||
y: -dp(4) if app.theme_cls.material_style == "M2" else 0
|
||||
font_style:
|
||||
"Button" if app.theme_cls.material_style == "M2" else "Body2"
|
||||
|
||||
|
||||
<MDTab>
|
||||
md_bg_color: root.theme_cls.bg_normal
|
|
@ -0,0 +1,880 @@
|
|||
"""
|
||||
Components/BottomNavigation
|
||||
===========================
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design 2 spec, Bottom navigation <https://material.io/components/bottom-navigation>`_ and
|
||||
`Material Design 3 spec, Bottom navigation <https://m3.material.io/components/navigation-bar/overview>`_
|
||||
|
||||
.. rubric:: Bottom navigation bars allow movement between primary destinations in an app:
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation.png
|
||||
:align: center
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<Root>
|
||||
|
||||
MDBottomNavigation:
|
||||
|
||||
MDBottomNavigationItem:
|
||||
name: "screen 1"
|
||||
|
||||
YourContent:
|
||||
|
||||
MDBottomNavigationItem:
|
||||
name: "screen 2"
|
||||
|
||||
YourContent:
|
||||
|
||||
MDBottomNavigationItem:
|
||||
name: "screen 3"
|
||||
|
||||
YourContent:
|
||||
|
||||
For ease of understanding, this code works like this:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<Root>
|
||||
|
||||
ScreenManager:
|
||||
|
||||
Screen:
|
||||
name: "screen 1"
|
||||
|
||||
YourContent:
|
||||
|
||||
Screen:
|
||||
name: "screen 2"
|
||||
|
||||
YourContent:
|
||||
|
||||
Screen:
|
||||
name: "screen 3"
|
||||
|
||||
YourContent:
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. tab:: Declarative KV style
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
|
||||
def build(self):
|
||||
self.theme_cls.material_style = "M3"
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
return Builder.load_string(
|
||||
'''
|
||||
MDScreen:
|
||||
|
||||
MDBottomNavigation:
|
||||
#panel_color: "#eeeaea"
|
||||
selected_color_background: "orange"
|
||||
text_color_active: "lightgrey"
|
||||
|
||||
MDBottomNavigationItem:
|
||||
name: 'screen 1'
|
||||
text: 'Mail'
|
||||
icon: 'gmail'
|
||||
badge_icon: "numeric-10"
|
||||
|
||||
MDLabel:
|
||||
text: 'Mail'
|
||||
halign: 'center'
|
||||
|
||||
MDBottomNavigationItem:
|
||||
name: 'screen 2'
|
||||
text: 'Twitter'
|
||||
icon: 'twitter'
|
||||
badge_icon: "numeric-5"
|
||||
|
||||
MDLabel:
|
||||
text: 'Twitter'
|
||||
halign: 'center'
|
||||
|
||||
MDBottomNavigationItem:
|
||||
name: 'screen 3'
|
||||
text: 'LinkedIN'
|
||||
icon: 'linkedin'
|
||||
|
||||
MDLabel:
|
||||
text: 'LinkedIN'
|
||||
halign: 'center'
|
||||
'''
|
||||
)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. tab:: Declarative python style
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.bottomnavigation import MDBottomNavigation, MDBottomNavigationItem
|
||||
from kivymd.uix.label import MDLabel
|
||||
from kivymd.uix.screen import MDScreen
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
self.theme_cls.material_style = "M3"
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
return (
|
||||
MDScreen(
|
||||
MDBottomNavigation(
|
||||
MDBottomNavigationItem(
|
||||
MDLabel(
|
||||
text='Mail',
|
||||
halign='center',
|
||||
),
|
||||
name='screen 1',
|
||||
text='Mail',
|
||||
icon='gmail',
|
||||
badge_icon="numeric-10",
|
||||
),
|
||||
MDBottomNavigationItem(
|
||||
MDLabel(
|
||||
text='Twitter',
|
||||
halign='center',
|
||||
),
|
||||
name='screen 1',
|
||||
text='Twitter',
|
||||
icon='twitter',
|
||||
badge_icon="numeric-10",
|
||||
),
|
||||
MDBottomNavigationItem(
|
||||
MDLabel(
|
||||
text='LinkedIN',
|
||||
halign='center',
|
||||
),
|
||||
name='screen 1',
|
||||
text='LinkedIN',
|
||||
icon='linkedin',
|
||||
badge_icon="numeric-10",
|
||||
),
|
||||
selected_color_background="orange",
|
||||
text_color_active="lightgrey",
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation.gif
|
||||
:align: center
|
||||
|
||||
.. rubric:: :class:`~MDBottomNavigationItem` provides the following events for use:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
__events__ = (
|
||||
"on_tab_touch_down",
|
||||
"on_tab_touch_move",
|
||||
"on_tab_touch_up",
|
||||
"on_tab_press",
|
||||
"on_tab_release",
|
||||
)
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
Root:
|
||||
|
||||
MDBottomNavigation:
|
||||
|
||||
MDBottomNavigationItem:
|
||||
on_tab_touch_down: print("on_tab_touch_down")
|
||||
on_tab_touch_move: print("on_tab_touch_move")
|
||||
on_tab_touch_up: print("on_tab_touch_up")
|
||||
on_tab_press: print("on_tab_press")
|
||||
on_tab_release: print("on_tab_release")
|
||||
|
||||
YourContent:
|
||||
|
||||
How to automatically switch a tab?
|
||||
----------------------------------
|
||||
|
||||
Use method :attr:`~MDBottomNavigation.switch_tab` which takes as argument
|
||||
the name of the tab you want to switch to.
|
||||
|
||||
Use custom icon
|
||||
---------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBottomNavigation:
|
||||
|
||||
MDBottomNavigationItem:
|
||||
icon: "icon.png"
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-custom-icon.png
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"TabbedPanelBase",
|
||||
"MDBottomNavigationItem",
|
||||
"MDBottomNavigation",
|
||||
"MDTab",
|
||||
)
|
||||
|
||||
import os
|
||||
from typing import Union
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.clock import Clock
|
||||
from kivy.core.window import Window
|
||||
from kivy.core.window.window_sdl2 import WindowSDL
|
||||
from kivy.lang import Builder
|
||||
from kivy.metrics import dp, sp
|
||||
from kivy.properties import (
|
||||
BooleanProperty,
|
||||
ColorProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
ObjectProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.screenmanager import FadeTransition, ScreenManagerException
|
||||
|
||||
from kivymd import uix_path
|
||||
from kivymd.material_resources import STANDARD_INCREMENT
|
||||
from kivymd.theming import ThemableBehavior, ThemeManager
|
||||
from kivymd.uix.anchorlayout import MDAnchorLayout
|
||||
from kivymd.uix.behaviors import CommonElevationBehavior, DeclarativeBehavior
|
||||
from kivymd.uix.behaviors.backgroundcolor_behavior import (
|
||||
SpecificBackgroundColorBehavior,
|
||||
)
|
||||
from kivymd.uix.floatlayout import MDFloatLayout
|
||||
from kivymd.uix.screen import MDScreen
|
||||
from kivymd.utils.set_bars_colors import set_bars_colors
|
||||
|
||||
with open(
|
||||
os.path.join(uix_path, "bottomnavigation", "bottomnavigation.kv"),
|
||||
encoding="utf-8",
|
||||
) as kv_file:
|
||||
Builder.load_string(kv_file.read())
|
||||
|
||||
|
||||
class MDBottomNavigationHeader(ButtonBehavior, MDAnchorLayout):
|
||||
"""
|
||||
Bottom navigation header class.
|
||||
|
||||
For more information, see in the
|
||||
:class:`~kivy.uix.behaviors.ButtonBehavior` and
|
||||
:class:`~kivymd.uix.anchorlayout.MDAnchorLayout`
|
||||
classes documentation.
|
||||
"""
|
||||
|
||||
panel_color = ColorProperty([1, 1, 1, 0])
|
||||
"""
|
||||
Panel color of bottom navigation in (r, g, b, a) or string format.
|
||||
|
||||
:attr:`panel_color` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `[1, 1, 1, 0]`.
|
||||
"""
|
||||
|
||||
tab = ObjectProperty()
|
||||
"""
|
||||
:attr:`tab` is an :class:`~MDBottomNavigationItem`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
panel = ObjectProperty()
|
||||
"""
|
||||
:attr:`panel` is an :class:`~MDBottomNavigation`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
active = BooleanProperty(False)
|
||||
|
||||
text = StringProperty()
|
||||
"""
|
||||
:attr:`text` is an :class:`~MDTab.text`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
text_color_normal = ColorProperty([1, 1, 1, 1])
|
||||
"""
|
||||
Text color in (r, g, b, a) or string format of the label when it is not
|
||||
selected.
|
||||
|
||||
:attr:`text_color_normal` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `[1, 1, 1, 1]`.
|
||||
"""
|
||||
|
||||
text_color_active = ColorProperty([1, 1, 1, 1])
|
||||
"""
|
||||
Text color in (r, g, b, a) or string format of the label when it is selected.
|
||||
|
||||
:attr:`text_color_active` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `[1, 1, 1, 1]`.
|
||||
"""
|
||||
|
||||
selected_color_background = ColorProperty(None)
|
||||
"""
|
||||
The background color in (r, g, b, a) or string format of the highlighted
|
||||
item when using Material Design v3.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
:attr:`selected_color_background` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
opposite_colors = BooleanProperty(True)
|
||||
|
||||
_label = ObjectProperty()
|
||||
_label_font_size = NumericProperty("12sp")
|
||||
_text_color_normal = ColorProperty([1, 1, 1, 1])
|
||||
_text_color_active = ColorProperty([1, 1, 1, 1])
|
||||
_selected_region_width = NumericProperty(dp(64))
|
||||
|
||||
def __init__(self, panel, tab):
|
||||
self.panel = panel
|
||||
self.tab = tab
|
||||
super().__init__()
|
||||
self._text_color_normal = (
|
||||
self.theme_cls.disabled_hint_text_color
|
||||
if self.text_color_normal == [1, 1, 1, 1]
|
||||
else self.text_color_normal
|
||||
)
|
||||
self._label = self.ids._label
|
||||
self._label_font_size = sp(12)
|
||||
self.theme_cls.bind(disabled_hint_text_color=self._update_theme_style)
|
||||
self.active = False
|
||||
|
||||
def on_press(self) -> None:
|
||||
"""Called when clicking on a panel item."""
|
||||
|
||||
if self.theme_cls.material_style == "M2":
|
||||
Animation(_label_font_size=sp(14), d=0.1).start(self)
|
||||
elif self.theme_cls.material_style == "M3":
|
||||
Animation(
|
||||
_selected_region_width=dp(64),
|
||||
t="in_out_sine",
|
||||
d=0,
|
||||
).start(self)
|
||||
Animation(
|
||||
_text_color_normal=self.theme_cls.primary_color
|
||||
if self.text_color_active == [1, 1, 1, 1]
|
||||
else self.text_color_active,
|
||||
d=0.1,
|
||||
).start(self)
|
||||
|
||||
def _update_theme_style(
|
||||
self, instance_theme_manager: ThemeManager, color: list
|
||||
):
|
||||
"""Called when the application theme style changes (White/Black)."""
|
||||
|
||||
if not self.active:
|
||||
self._text_color_normal = (
|
||||
color
|
||||
if self.text_color_normal == [1, 1, 1, 1]
|
||||
else self.text_color_normal
|
||||
)
|
||||
|
||||
|
||||
class MDTab(MDScreen):
|
||||
"""
|
||||
A tab is simply a screen with meta information that defines the content
|
||||
that goes in the tab header.
|
||||
|
||||
For more information, see in the
|
||||
:class:`~kivymd.uix.screen.MDScreen` class documentation.
|
||||
"""
|
||||
|
||||
__events__ = (
|
||||
"on_tab_touch_down",
|
||||
"on_tab_touch_move",
|
||||
"on_tab_touch_up",
|
||||
"on_tab_press",
|
||||
"on_tab_release",
|
||||
)
|
||||
"""Events provided."""
|
||||
|
||||
text = StringProperty()
|
||||
"""
|
||||
Tab header text.
|
||||
|
||||
:attr:`text` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
icon = StringProperty("checkbox-blank-circle")
|
||||
"""
|
||||
Tab header icon.
|
||||
|
||||
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'checkbox-blank-circle'`.
|
||||
"""
|
||||
|
||||
badge_icon = StringProperty()
|
||||
"""
|
||||
Tab header badge icon.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
:attr:`badge_icon` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.index = 0
|
||||
self.parent_widget = None
|
||||
self.register_event_type("on_tab_touch_down")
|
||||
self.register_event_type("on_tab_touch_move")
|
||||
self.register_event_type("on_tab_touch_up")
|
||||
self.register_event_type("on_tab_press")
|
||||
self.register_event_type("on_tab_release")
|
||||
|
||||
def on_tab_touch_down(self, *args):
|
||||
pass
|
||||
|
||||
def on_tab_touch_move(self, *args):
|
||||
pass
|
||||
|
||||
def on_tab_touch_up(self, *args):
|
||||
pass
|
||||
|
||||
def on_tab_press(self, *args):
|
||||
par = self.parent_widget
|
||||
if par.previous_tab is not self:
|
||||
if par.previous_tab.index > self.index:
|
||||
par.ids.tab_manager.transition.direction = "right"
|
||||
elif par.previous_tab.index < self.index:
|
||||
par.ids.tab_manager.transition.direction = "left"
|
||||
par.ids.tab_manager.current = self.name
|
||||
par.previous_tab = self
|
||||
|
||||
def on_tab_release(self, *args):
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return f"<MDTab name='{self.name}', text='{self.text}'>"
|
||||
|
||||
|
||||
class MDBottomNavigationItem(MDTab):
|
||||
header = ObjectProperty()
|
||||
"""
|
||||
:attr:`header` is an :class:`~MDBottomNavigationHeader`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def animate_header(
|
||||
self, bottom_navigation_object, bottom_navigation_header_object
|
||||
) -> None:
|
||||
if bottom_navigation_object.use_text:
|
||||
Animation(_label_font_size=sp(12), d=0.1).start(
|
||||
bottom_navigation_object.previous_tab.header
|
||||
)
|
||||
Animation(
|
||||
_selected_region_width=0,
|
||||
t="in_out_sine",
|
||||
d=0,
|
||||
).start(bottom_navigation_header_object)
|
||||
Animation(
|
||||
_text_color_normal=bottom_navigation_header_object.text_color_normal
|
||||
if bottom_navigation_object.previous_tab.header.text_color_normal
|
||||
!= [1, 1, 1, 1]
|
||||
else self.theme_cls.disabled_hint_text_color,
|
||||
d=0.1,
|
||||
).start(bottom_navigation_object.previous_tab.header)
|
||||
bottom_navigation_object.previous_tab.header.active = False
|
||||
self.header.active = True
|
||||
|
||||
def on_tab_press(self, *args) -> None:
|
||||
"""Called when clicking on a panel item."""
|
||||
|
||||
bottom_navigation_object = self.parent_widget
|
||||
bottom_navigation_header_object = (
|
||||
bottom_navigation_object.previous_tab.header
|
||||
)
|
||||
|
||||
if bottom_navigation_object.previous_tab is not self:
|
||||
self.animate_header(
|
||||
bottom_navigation_object, bottom_navigation_header_object
|
||||
)
|
||||
|
||||
super().on_tab_press(*args)
|
||||
|
||||
def on_disabled(
|
||||
self, instance_bottom_navigation_item, disabled_value: bool
|
||||
) -> None:
|
||||
self.header.disabled = disabled_value
|
||||
|
||||
def on_leave(self, *args):
|
||||
pass
|
||||
|
||||
|
||||
class TabbedPanelBase(
|
||||
ThemableBehavior, SpecificBackgroundColorBehavior, BoxLayout
|
||||
):
|
||||
"""
|
||||
A class that contains all variables a :class:`~kivy.properties.TabPannel`
|
||||
must have. It is here so I (zingballyhoo) don't get mad about
|
||||
the :class:`~kivy.properties.TabbedPannels` not being DRY.
|
||||
|
||||
For more information, see in the :class:`~kivymd.theming.ThemableBehavior`
|
||||
and :class:`~kivymd.uix.behaviors.SpecificBackgroundColorBehavior`
|
||||
and :class:`~kivy.uix.boxlayout.BoxLayout` classes documentation.
|
||||
"""
|
||||
|
||||
current = StringProperty(None)
|
||||
"""
|
||||
Current tab name.
|
||||
|
||||
:attr:`current` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
previous_tab = ObjectProperty(None, aloownone=True)
|
||||
"""
|
||||
:attr:`previous_tab` is an :class:`~MDTab` and defaults to `None`.
|
||||
"""
|
||||
|
||||
panel_color = ColorProperty(None)
|
||||
"""
|
||||
Panel color of bottom navigation.
|
||||
|
||||
:attr:`panel_color` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
tabs = ListProperty()
|
||||
|
||||
|
||||
class MDBottomNavigation(DeclarativeBehavior, TabbedPanelBase):
|
||||
"""
|
||||
A bottom navigation that is implemented by delegating all items to a
|
||||
:class:`~kivy.uix.screenmanager.ScreenManager`.
|
||||
|
||||
For more information, see in the
|
||||
:class:`~kivymd.uix.behaviors.DeclarativeBehavior` and
|
||||
:class:`~TabbedPanelBase` classes documentation.
|
||||
|
||||
:Events:
|
||||
:attr:`on_switch_tabs`
|
||||
Called when switching tabs. Returns the object of the tab to be
|
||||
opened.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
"""
|
||||
|
||||
transition = ObjectProperty(FadeTransition)
|
||||
"""
|
||||
Transition animation of bottom navigation screen manager.
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
:attr:`transition` is an :class:`~kivy.properties.ObjectProperty`
|
||||
and defaults to `FadeTransition`.
|
||||
"""
|
||||
|
||||
transition_duration = NumericProperty(0.2)
|
||||
"""
|
||||
Duration animation of bottom navigation screen manager.
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
:attr:`transition_duration` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.2`.
|
||||
"""
|
||||
|
||||
text_color_normal = ColorProperty([1, 1, 1, 1])
|
||||
"""
|
||||
Text color of the label when it is not selected.
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBottomNavigation:
|
||||
text_color_normal: 1, 0, 1, 1
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-text_color_normal.png
|
||||
|
||||
:attr:`text_color_normal` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `[1, 1, 1, 1]`.
|
||||
"""
|
||||
|
||||
text_color_active = ColorProperty([1, 1, 1, 1])
|
||||
"""
|
||||
Text color of the label when it is selected.
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBottomNavigation:
|
||||
text_color_active: 0, 0, 0, 1
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-text_color_active.png
|
||||
|
||||
:attr:`text_color_active` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `[1, 1, 1, 1]`.
|
||||
"""
|
||||
|
||||
use_text = BooleanProperty(True)
|
||||
"""
|
||||
Use text for :class:`~MDBottomNavigationItem` or not.
|
||||
If ``True``, the :class:`~MDBottomNavigation` panel height will be reduced
|
||||
by the text height.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-use-text.png
|
||||
:align: center
|
||||
|
||||
:attr:`use_text` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `True`.
|
||||
"""
|
||||
|
||||
selected_color_background = ColorProperty(None)
|
||||
"""
|
||||
The background color of the highlighted item when using Material Design v3.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBottomNavigation:
|
||||
selected_color_background: 0, 0, 1, .4
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation=selected-color-background.png
|
||||
|
||||
:attr:`selected_color_background` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
font_name = StringProperty("Roboto")
|
||||
"""
|
||||
Font name of the label.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBottomNavigation:
|
||||
font_name: "path/to/font.ttf"
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-font-name.png
|
||||
|
||||
:attr:`font_name` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'Roboto'`.
|
||||
"""
|
||||
|
||||
first_widget = ObjectProperty()
|
||||
"""
|
||||
:attr:`first_widget` is an :class:`~MDBottomNavigationItem`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
tab_header = ObjectProperty()
|
||||
"""
|
||||
:attr:`tab_header` is an :class:`~MDBottomNavigationHeader`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
set_bars_color = BooleanProperty(False)
|
||||
"""
|
||||
If `True` the background color of the navigation bar will be set
|
||||
automatically according to the current color of the toolbar.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
:attr:`set_bars_color` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
widget_index = NumericProperty(0)
|
||||
|
||||
# Text active color if it is selected.
|
||||
_active_color = ColorProperty([1, 1, 1, 1])
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.previous_tab = None
|
||||
self.register_event_type("on_switch_tabs")
|
||||
super().__init__(*args, **kwargs)
|
||||
self.theme_cls.bind(material_style=self.refresh_tabs)
|
||||
Window.bind(on_resize=self.on_resize)
|
||||
Clock.schedule_once(lambda x: self.on_resize())
|
||||
Clock.schedule_once(self.set_status_bar_color)
|
||||
|
||||
def set_status_bar_color(self, interval: Union[int, float]) -> None:
|
||||
if self.set_bars_color:
|
||||
set_bars_colors(self.panel_color, None, self.theme_cls.theme_style)
|
||||
|
||||
def switch_tab(self, name_tab) -> None:
|
||||
"""Switching the tab by name."""
|
||||
|
||||
if not self.ids.tab_manager.has_screen(name_tab):
|
||||
raise ScreenManagerException(f"No Screen with name '{name_tab}'.")
|
||||
self.ids.tab_manager.get_screen(name_tab).dispatch("on_tab_press")
|
||||
count_index_screen = [
|
||||
self.ids.tab_manager.screens.index(screen)
|
||||
for screen in self.ids.tab_manager.screens
|
||||
if screen.name == name_tab
|
||||
][0]
|
||||
numbers_screens = list(range(len(self.ids.tab_manager.screens)))
|
||||
numbers_screens.reverse()
|
||||
self.ids.tab_bar.children[
|
||||
numbers_screens.index(count_index_screen)
|
||||
].dispatch("on_press")
|
||||
|
||||
def refresh_tabs(self, *args) -> None:
|
||||
"""Refresh all tabs."""
|
||||
|
||||
if self.ids:
|
||||
tab_bar = self.ids.tab_bar
|
||||
tab_bar.clear_widgets()
|
||||
tab_manager = self.ids.tab_manager
|
||||
self._active_color = self.theme_cls.primary_color
|
||||
|
||||
if self.text_color_active != [1, 1, 1, 1]:
|
||||
self._active_color = self.text_color_active
|
||||
|
||||
for tab in tab_manager.screens:
|
||||
self.tab_header = MDBottomNavigationHeader(tab=tab, panel=self)
|
||||
tab.header = self.tab_header
|
||||
tab_bar.add_widget(self.tab_header)
|
||||
|
||||
if tab is self.first_widget:
|
||||
self.tab_header._text_color_normal = self._active_color
|
||||
self.tab_header._label_font_size = sp(14)
|
||||
self.tab_header.active = True
|
||||
else:
|
||||
self.tab_header.ids._label.font_size = sp(12)
|
||||
self.tab_header._label_font_size = sp(12)
|
||||
|
||||
def on_font_name(self, instance_bottom_navigation, font_name: str) -> None:
|
||||
for tab in self.ids.tab_bar.children:
|
||||
tab.ids._label.font_name = font_name
|
||||
|
||||
def on_selected_color_background(
|
||||
self, instance_bottom_navigation, color: list
|
||||
) -> None:
|
||||
def on_selected_color_background(*args):
|
||||
for tab in self.ids.tab_bar.children:
|
||||
tab.selected_color_background = color
|
||||
|
||||
Clock.schedule_once(on_selected_color_background)
|
||||
|
||||
def on_use_text(
|
||||
self, instance_bottom_navigation, use_text_value: bool
|
||||
) -> None:
|
||||
if not use_text_value:
|
||||
for instance_bottom_navigation_header in self.ids.tab_bar.children:
|
||||
instance_bottom_navigation_header.ids.item_container.remove_widget(
|
||||
instance_bottom_navigation_header.ids._label
|
||||
)
|
||||
if self.theme_cls.material_style == "M2":
|
||||
height = dp(42)
|
||||
else:
|
||||
height = dp(80)
|
||||
self.height = height
|
||||
self.ids.bottom_panel.height = height
|
||||
self.ids.tab_bar.height = height
|
||||
else:
|
||||
if self.theme_cls.material_style == "M2":
|
||||
height = STANDARD_INCREMENT
|
||||
else:
|
||||
height = dp(80)
|
||||
self.height = height
|
||||
self.ids.bottom_panel.height = height
|
||||
self.ids.tab_bar.height = height
|
||||
|
||||
def on_text_color_normal(
|
||||
self, instance_bottom_navigation, color: list
|
||||
) -> None:
|
||||
MDBottomNavigationHeader.text_color_normal = color
|
||||
for tab in self.ids.tab_bar.children:
|
||||
if not tab.active:
|
||||
tab._text_color_normal = color
|
||||
|
||||
def on_text_color_active(
|
||||
self, instance_bottom_navigation, color: list
|
||||
) -> None:
|
||||
def on_text_color_active(*args):
|
||||
MDBottomNavigationHeader.text_color_active = color
|
||||
self.text_color_active = color
|
||||
for tab in self.ids.tab_bar.children:
|
||||
tab.text_color_active = color
|
||||
if tab.active:
|
||||
tab._text_color_normal = color
|
||||
|
||||
Clock.schedule_once(on_text_color_active)
|
||||
|
||||
def on_switch_tabs(self, bottom_navigation_item, name_tab: str) -> None:
|
||||
"""
|
||||
Called when switching tabs. Returns the object of the tab to be opened.
|
||||
"""
|
||||
|
||||
def on_size(self, *args) -> None:
|
||||
self.on_resize()
|
||||
|
||||
def on_resize(
|
||||
self,
|
||||
instance: Union[WindowSDL, None] = None,
|
||||
width: Union[int, None] = None,
|
||||
do_again: bool = True,
|
||||
) -> None:
|
||||
"""Called when the application window is resized."""
|
||||
|
||||
full_width = 0
|
||||
for tab in self.ids.tab_manager.screens:
|
||||
full_width += tab.header.width
|
||||
tab.header.text_color_normal = self.text_color_normal
|
||||
self.ids.tab_bar.width = full_width
|
||||
if do_again:
|
||||
Clock.schedule_once(lambda x: self.on_resize(do_again=False), 0.1)
|
||||
|
||||
def add_widget(self, widget, **kwargs):
|
||||
if isinstance(widget, MDBottomNavigationItem):
|
||||
self.widget_index += 1
|
||||
widget.index = self.widget_index
|
||||
widget.parent_widget = self
|
||||
self.ids.tab_manager.add_widget(widget)
|
||||
if self.widget_index == 1:
|
||||
self.previous_tab = widget
|
||||
self.first_widget = widget
|
||||
self.refresh_tabs()
|
||||
else:
|
||||
super().add_widget(widget)
|
||||
|
||||
def remove_widget(self, widget):
|
||||
if isinstance(widget, MDBottomNavigationItem):
|
||||
self.ids.tab_manager.remove_widget(widget)
|
||||
self.refresh_tabs()
|
||||
else:
|
||||
super().remove_widget(widget)
|
||||
|
||||
def _get_switchig_tab(self, name_tab: str) -> MDBottomNavigationItem:
|
||||
bottom_navigation_item = None
|
||||
for bottom_navigation_header_instance in self.ids.tab_bar.children:
|
||||
if bottom_navigation_header_instance.tab.name == name_tab:
|
||||
bottom_navigation_item = bottom_navigation_header_instance.tab
|
||||
break
|
||||
return bottom_navigation_item
|
||||
|
||||
|
||||
class MDBottomNavigationBar(CommonElevationBehavior, MDFloatLayout):
|
||||
pass
|
|
@ -0,0 +1,11 @@
|
|||
# NOQA F401
|
||||
from .bottomsheet import (
|
||||
MDBottomSheet,
|
||||
MDBottomSheetContent,
|
||||
MDBottomSheetDragHandle,
|
||||
MDBottomSheetDragHandleButton,
|
||||
MDBottomSheetDragHandleTitle,
|
||||
MDCustomBottomSheet,
|
||||
MDGridBottomSheet,
|
||||
MDListBottomSheet,
|
||||
)
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,42 @@
|
|||
<MDBottomSheetContent>
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
|
||||
|
||||
<MDBottomSheetDragHandle>
|
||||
orientation: "vertical"
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
padding: "16dp", "8dp", "16dp", "16dp"
|
||||
|
||||
BottomSheetDragHandle:
|
||||
md_bg_color:
|
||||
app.theme_cls.disabled_hint_text_color \
|
||||
if not root.drag_handle_color else \
|
||||
root.drag_handle_color
|
||||
size_hint: None, None
|
||||
size: "32dp", "4dp"
|
||||
radius: 4
|
||||
pos_hint: {"center_x": .5}
|
||||
|
||||
BottomSheetDragHandleContainer:
|
||||
id: header_container
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
|
||||
|
||||
<MDBottomSheet>
|
||||
orientation: "vertical"
|
||||
md_bg_color: root.bg_color if root.bg_color else app.theme_cls.bg_darkest
|
||||
radius: 16, 16, 0, 0
|
||||
padding: 0, "8dp", 0, 0
|
||||
|
||||
MDBoxLayout:
|
||||
id: drag_handle_container
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
|
||||
MDBoxLayout:
|
||||
id: container
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
File diff suppressed because it is too large
Load diff
103
kivy_venv/lib/python3.11/site-packages/kivymd/uix/boxlayout.py
Normal file
103
kivy_venv/lib/python3.11/site-packages/kivymd/uix/boxlayout.py
Normal file
|
@ -0,0 +1,103 @@
|
|||
"""
|
||||
Components/BoxLayout
|
||||
====================
|
||||
|
||||
:class:`~kivy.uix.boxlayout.BoxLayout` class equivalent. Simplifies working
|
||||
with some widget properties. For example:
|
||||
|
||||
BoxLayout
|
||||
---------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
BoxLayout:
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
|
||||
canvas:
|
||||
Color:
|
||||
rgba: app.theme_cls.primary_color
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
|
||||
MDBoxLayout
|
||||
-----------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBoxLayout:
|
||||
adaptive_height: True
|
||||
md_bg_color: app.theme_cls.primary_color
|
||||
|
||||
Available options are:
|
||||
----------------------
|
||||
|
||||
- adaptive_height_
|
||||
- adaptive_width_
|
||||
- adaptive_size_
|
||||
|
||||
.. adaptive_height:
|
||||
adaptive_height
|
||||
---------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
adaptive_height: True
|
||||
|
||||
Equivalent
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
|
||||
.. adaptive_width:
|
||||
adaptive_width
|
||||
--------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
adaptive_width: True
|
||||
|
||||
Equivalent
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
size_hint_x: None
|
||||
height: self.minimum_width
|
||||
|
||||
.. adaptive_size:
|
||||
adaptive_size
|
||||
-------------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
adaptive_size: True
|
||||
|
||||
Equivalent
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
size_hint: None, None
|
||||
size: self.minimum_size
|
||||
"""
|
||||
|
||||
__all__ = ("MDBoxLayout",)
|
||||
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.uix import MDAdaptiveWidget
|
||||
from kivymd.uix.behaviors import DeclarativeBehavior
|
||||
|
||||
|
||||
class MDBoxLayout(
|
||||
DeclarativeBehavior, ThemableBehavior, BoxLayout, MDAdaptiveWidget
|
||||
):
|
||||
"""
|
||||
Box layout class.
|
||||
|
||||
For more information, see in the
|
||||
:class:`~kivy.uix.boxlayout.BoxLayout` class documentation.
|
||||
"""
|
|
@ -0,0 +1,17 @@
|
|||
# NOQA F401
|
||||
from .button import (
|
||||
BaseButton,
|
||||
ButtonContentsIconText,
|
||||
MDFillRoundFlatButton,
|
||||
MDFillRoundFlatIconButton,
|
||||
MDFlatButton,
|
||||
MDFloatingActionButton,
|
||||
MDFloatingActionButtonSpeedDial,
|
||||
MDIconButton,
|
||||
MDRaisedButton,
|
||||
MDRectangleFlatButton,
|
||||
MDRectangleFlatIconButton,
|
||||
MDRoundFlatButton,
|
||||
MDRoundFlatIconButton,
|
||||
MDTextButton,
|
||||
)
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,225 @@
|
|||
<BaseButton>
|
||||
canvas:
|
||||
Clear
|
||||
Color:
|
||||
group: "bg-color"
|
||||
rgba:
|
||||
self._md_bg_color \
|
||||
if not self.disabled else \
|
||||
self._md_bg_color_disabled
|
||||
RoundedRectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
source: self.source if hasattr(self, "source") else ""
|
||||
radius: [root._radius, ]
|
||||
Color:
|
||||
group: "outline-color"
|
||||
rgba:
|
||||
root._line_color \
|
||||
if not root.disabled else \
|
||||
(root._line_color_disabled or self._disabled_color)
|
||||
Line:
|
||||
width: root.line_width
|
||||
rounded_rectangle:
|
||||
( \
|
||||
self.x, self.y, self.width, self.height, \
|
||||
root._radius, root._radius, root._radius, root._radius, \
|
||||
self.height \
|
||||
)
|
||||
|
||||
size_hint: None, None
|
||||
anchor_x: root.halign
|
||||
anchor_y: root.valign
|
||||
_round_rad: [self._radius] * 4
|
||||
|
||||
|
||||
<ButtonContentsText>
|
||||
lbl_txt: lbl_txt
|
||||
width:
|
||||
max( \
|
||||
root._min_width, \
|
||||
root.padding[0] + lbl_txt.texture_size[0] + root.padding[2] \
|
||||
)
|
||||
size_hint_min_x:
|
||||
max( \
|
||||
root._min_width, \
|
||||
root.padding[0] + lbl_txt.texture_size[0] + root.padding[2] \
|
||||
)
|
||||
height:
|
||||
max( \
|
||||
root._min_height, \
|
||||
root.padding[1] + lbl_txt.texture_size[1] + root.padding[3] \
|
||||
)
|
||||
size_hint_min_y:
|
||||
max( \
|
||||
root._min_height, \
|
||||
root.padding[1] + lbl_txt.texture_size[1] + root.padding[3] \
|
||||
)
|
||||
|
||||
MDLabel:
|
||||
id: lbl_txt
|
||||
text: root.text
|
||||
font_size: root.font_size
|
||||
font_style: root.font_style
|
||||
halign: 'center'
|
||||
valign: 'middle'
|
||||
adaptive_size: True
|
||||
-text_size: None, None
|
||||
theme_text_color: root._theme_text_color
|
||||
text_color: root._text_color
|
||||
markup: True
|
||||
disabled: root.disabled
|
||||
opposite_colors: root.opposite_colors
|
||||
font_name: root.font_name if root.font_name else self.font_name
|
||||
|
||||
|
||||
<ButtonContentsIcon>
|
||||
lbl_ic: lbl_ic
|
||||
size: "48dp", "48dp"
|
||||
padding: "12dp" if root.icon in md_icons else (0, 0, 0, 0)
|
||||
# Backwards compatibility.
|
||||
theme_icon_color: root.theme_icon_color or root.theme_text_color
|
||||
|
||||
MDIcon:
|
||||
id: lbl_ic
|
||||
icon: root.icon
|
||||
font_size: root.icon_size if root.icon_size else self.font_size
|
||||
font_name: root.font_name if root.font_name else self.font_name
|
||||
opposite_colors: root.opposite_colors
|
||||
text_color:
|
||||
# FIXME: ValueError: None is not allowed for MDIcon.text_color.
|
||||
# This is only a temporary fix and does not fix the cause of the error.
|
||||
(root._icon_color if root._icon_color else root.theme_cls.text_color) \
|
||||
if not root.disabled else \
|
||||
root.theme_cls.disabled_hint_text_color \
|
||||
if not root.disabled_color else \
|
||||
root.disabled_color
|
||||
# Fix https://github.com/kivymd/KivyMD/issues/1448
|
||||
# TODO: Perhaps this change may affect other widgets.
|
||||
# You need to create tests.
|
||||
# on_icon:
|
||||
# if self.icon not in md_icons.keys(): self.size_hint = (1, 1)
|
||||
theme_text_color: root._theme_icon_color
|
||||
|
||||
|
||||
<ButtonContentsIconText>
|
||||
lbl_txt: lbl_txt
|
||||
lbl_ic: lbl_ic
|
||||
|
||||
width:
|
||||
max( \
|
||||
root._min_width, \
|
||||
root.padding[0] \
|
||||
+ lbl_ic.texture_size[0] \
|
||||
+ box.spacing \
|
||||
+ lbl_txt.texture_size[0] \
|
||||
+ root.padding[2] \
|
||||
)
|
||||
size_hint_min_x:
|
||||
max( \
|
||||
root._min_width, \
|
||||
root.padding[0] \
|
||||
+ lbl_ic.texture_size[0] \
|
||||
+ box.spacing \
|
||||
+ lbl_txt.texture_size[0] \
|
||||
+ root.padding[2] \
|
||||
)
|
||||
height:
|
||||
max( \
|
||||
root._min_height, \
|
||||
root.padding[1] \
|
||||
+ max(lbl_ic.texture_size[1], lbl_txt.texture_size[1]) \
|
||||
+ root.padding[3] \
|
||||
)
|
||||
size_hint_min_y:
|
||||
max( \
|
||||
root._min_height, \
|
||||
root.padding[1] \
|
||||
+ max(lbl_ic.texture_size[1], lbl_txt.texture_size[1]) \
|
||||
+ root.padding[3] \
|
||||
)
|
||||
|
||||
MDBoxLayout:
|
||||
id: box
|
||||
adaptive_size: True
|
||||
padding: 0
|
||||
spacing: "8dp"
|
||||
|
||||
MDIcon:
|
||||
id: lbl_ic
|
||||
size_hint_x: None
|
||||
pos_hint: {"center_y": .5}
|
||||
icon: root.icon
|
||||
opposite_colors: root.opposite_colors
|
||||
font_size:
|
||||
root.icon_size \
|
||||
if root.icon_size else \
|
||||
(18 / 14 * lbl_txt.font_size)
|
||||
text_color:
|
||||
root._icon_color \
|
||||
if not root.disabled else \
|
||||
root.theme_cls.disabled_hint_text_color
|
||||
theme_text_color: root._theme_icon_color
|
||||
|
||||
MDLabel:
|
||||
id: lbl_txt
|
||||
adaptive_size: True
|
||||
-text_size: None, None
|
||||
pos_hint: {"center_y": .5}
|
||||
halign: 'center'
|
||||
valign: 'middle'
|
||||
text: root.text
|
||||
font_size: root.font_size
|
||||
font_style: root.font_style
|
||||
font_name: root.font_name if root.font_name else self.font_name
|
||||
theme_text_color: root._theme_text_color
|
||||
text_color: root._text_color
|
||||
markup: True
|
||||
disabled: root.disabled
|
||||
opposite_colors: root.opposite_colors
|
||||
|
||||
|
||||
<MDTextButton>
|
||||
adaptive_size: True
|
||||
color: root.theme_cls.primary_color if not root.color else root.color
|
||||
opacity: 1
|
||||
|
||||
|
||||
<BaseFloatingBottomButton>
|
||||
theme_text_color: "Custom"
|
||||
md_bg_color: self.theme_cls.primary_color
|
||||
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba:
|
||||
self.theme_cls.primary_color \
|
||||
if not self._bg_color else \
|
||||
self._bg_color
|
||||
RoundedRectangle:
|
||||
pos:
|
||||
(self.x - self._canvas_width + dp(1.5)) + self._padding_right / 2, \
|
||||
self.y - self._padding_right / 2 + dp(1.5)
|
||||
size:
|
||||
self.width + self._canvas_width - dp(3), \
|
||||
self.height + self._padding_right - dp(3)
|
||||
radius: [self.height / 2]
|
||||
|
||||
|
||||
<MDFloatingRootButton>
|
||||
theme_text_color: "Custom"
|
||||
md_bg_color: self.theme_cls.primary_color
|
||||
|
||||
|
||||
<MDFloatingLabel>
|
||||
padding_x: "8dp"
|
||||
padding_y: "8dp"
|
||||
adaptive_size: True
|
||||
theme_text_color: "Custom"
|
||||
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: self.bg_color
|
||||
RoundedRectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
radius: self.radius
|
2367
kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/button.py
Normal file
2367
kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/button.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,8 @@
|
|||
# NOQA F401
|
||||
from .card import (
|
||||
MDCard,
|
||||
MDCardSwipe,
|
||||
MDCardSwipeFrontBox,
|
||||
MDCardSwipeLayerBox,
|
||||
MDSeparator,
|
||||
)
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,9 @@
|
|||
<MDCardSwipeLayerBox>:
|
||||
md_bg_color: app.theme_cls.divider_color
|
||||
|
||||
|
||||
<MDSeparator>
|
||||
md_bg_color:
|
||||
self.theme_cls.divider_color \
|
||||
if not root.color \
|
||||
else root.color
|
1107
kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/card.py
Normal file
1107
kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/card.py
Normal file
File diff suppressed because it is too large
Load diff
218
kivy_venv/lib/python3.11/site-packages/kivymd/uix/carousel.py
Normal file
218
kivy_venv/lib/python3.11/site-packages/kivymd/uix/carousel.py
Normal file
|
@ -0,0 +1,218 @@
|
|||
"""
|
||||
Components/Carousel
|
||||
===================
|
||||
|
||||
:class:`~kivy.uix.boxlayout.Carousel` class equivalent. Simplifies working
|
||||
with some widget properties. For example:
|
||||
|
||||
|
||||
Carousel
|
||||
---------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
kv='''
|
||||
YourCarousel:
|
||||
BoxLayout:
|
||||
[...]
|
||||
BoxLayout:
|
||||
[...]
|
||||
BoxLayout:
|
||||
[...]
|
||||
'''
|
||||
builder.load_string(kv)
|
||||
|
||||
class YourCarousel(Carousel):
|
||||
def __init__(self,*kwargs):
|
||||
self.register_event_type("on_slide_progress")
|
||||
self.register_event_type("on_slide_complete")
|
||||
|
||||
def on_touch_down(self, *args):
|
||||
["Code to detect when the slide changes"]
|
||||
|
||||
def on_touch_up(self, *args):
|
||||
["Code to detect when the slide changes"]
|
||||
|
||||
def Calculate_slide_pos(self, *args):
|
||||
["Code to calculate the current position of the slide"]
|
||||
|
||||
def do_custom_animation(self, *args):
|
||||
["Code to recreate an animation"]
|
||||
|
||||
|
||||
MDCarousel
|
||||
-----------
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDCarousel:
|
||||
on_slide_progress:
|
||||
do_something()
|
||||
on_slide_complete:
|
||||
do_something()
|
||||
|
||||
"""
|
||||
# TODO: Add documentation.
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.carousel import Carousel
|
||||
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.uix.behaviors import DeclarativeBehavior
|
||||
|
||||
|
||||
class MDCarousel(DeclarativeBehavior, ThemableBehavior, Carousel):
|
||||
"""
|
||||
based on kivy's carousel.
|
||||
|
||||
.. seealso::
|
||||
`kivy.uix.carousel.Carousel <https://kivy.org/doc/stable/api-kivy.uix.carousel.html>`_
|
||||
"""
|
||||
|
||||
_scrolling = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.register_event_type("on_slide_progress")
|
||||
self.register_event_type("on_slide_complete")
|
||||
|
||||
def on_slide_progress(self, *args):
|
||||
"""
|
||||
Event launched when the Slide animation is progress.
|
||||
remember to bind and unbid to this method.
|
||||
"""
|
||||
|
||||
def on_slide_complete(self, *args):
|
||||
"""
|
||||
Event launched when the Slide animation is complete.
|
||||
remember to bind and unbid to this method.
|
||||
"""
|
||||
|
||||
def _position_visible_slides(self, *args):
|
||||
slides, index = self.slides, self.index
|
||||
no_of_slides = len(slides) - 1
|
||||
if not slides:
|
||||
return
|
||||
x, y, width, height = self.x, self.y, self.width, self.height
|
||||
_offset, direction = self._offset, self.direction
|
||||
_prev, _next, _current = self._prev, self._next, self._current
|
||||
get_slide_container = self.get_slide_container
|
||||
last_slide = get_slide_container(slides[-1])
|
||||
first_slide = get_slide_container(slides[0])
|
||||
skip_next = False
|
||||
_loop = self.loop
|
||||
|
||||
if direction[0] in ["r", "l"]:
|
||||
xoff = x + _offset
|
||||
x_prev = {"l": xoff + width, "r": xoff - width}
|
||||
x_next = {"l": xoff - width, "r": xoff + width}
|
||||
if _prev:
|
||||
_prev.pos = (x_prev[direction[0]], y)
|
||||
elif _loop and _next and index == 0:
|
||||
if (_offset > 0 and direction[0] == "r") or (
|
||||
_offset < 0 and direction[0] == "l"
|
||||
):
|
||||
last_slide.pos = (x_prev[direction[0]], y)
|
||||
skip_next = True
|
||||
if _current:
|
||||
_current.pos = (xoff, y)
|
||||
|
||||
if self._scrolling:
|
||||
self.dispatch("on_slide_progress", xoff)
|
||||
|
||||
if skip_next:
|
||||
return
|
||||
if _next:
|
||||
_next.pos = (x_next[direction[0]], y)
|
||||
elif _loop and _prev and index == no_of_slides:
|
||||
if (_offset < 0 and direction[0] == "r") or (
|
||||
_offset > 0 and direction[0] == "l"
|
||||
):
|
||||
first_slide.pos = (x_next[direction[0]], y)
|
||||
if direction[0] in ["t", "b"]:
|
||||
yoff = y + _offset
|
||||
y_prev = {"t": yoff - height, "b": yoff + height}
|
||||
y_next = {"t": yoff + height, "b": yoff - height}
|
||||
if _prev:
|
||||
_prev.pos = (x, y_prev[direction[0]])
|
||||
elif _loop and _next and index == 0:
|
||||
if (_offset > 0 and direction[0] == "t") or (
|
||||
_offset < 0 and direction[0] == "b"
|
||||
):
|
||||
last_slide.pos = (x, y_prev[direction[0]])
|
||||
skip_next = True
|
||||
if _current:
|
||||
_current.pos = (x, yoff)
|
||||
if skip_next:
|
||||
return
|
||||
if _next:
|
||||
_next.pos = (x, y_next[direction[0]])
|
||||
elif _loop and _prev and index == no_of_slides:
|
||||
if (_offset < 0 and direction[0] == "t") or (
|
||||
_offset > 0 and direction[0] == "b"
|
||||
):
|
||||
first_slide.pos = (x, y_next[direction[0]])
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
self._scrolling = True
|
||||
return super().on_touch_down(touch)
|
||||
|
||||
def on_touch_up(self, touch):
|
||||
self._scrolling = False
|
||||
return super().on_touch_up(touch)
|
||||
|
||||
def _start_animation(self, *args, **kwargs):
|
||||
# compute target offset for ease back, next or prev
|
||||
new_offset = 0
|
||||
direction = kwargs.get("direction", self.direction)[0]
|
||||
is_horizontal = direction in "rl"
|
||||
extent = self.width if is_horizontal else self.height
|
||||
min_move = kwargs.get("min_move", self.min_move)
|
||||
_offset = kwargs.get("offset", self._offset)
|
||||
|
||||
if _offset < min_move * -extent:
|
||||
new_offset = -extent
|
||||
elif _offset > min_move * extent:
|
||||
new_offset = extent
|
||||
|
||||
# if new_offset is 0, it wasnt enough to go next/prev
|
||||
dur = self.anim_move_duration
|
||||
if new_offset == 0:
|
||||
dur = self.anim_cancel_duration
|
||||
|
||||
# detect edge cases if not looping
|
||||
len_slides = len(self.slides)
|
||||
index = self.index
|
||||
if not self.loop or len_slides == 1:
|
||||
is_first = index == 0
|
||||
is_last = index == len_slides - 1
|
||||
if direction in "rt":
|
||||
towards_prev = new_offset > 0
|
||||
towards_next = new_offset < 0
|
||||
else:
|
||||
towards_prev = new_offset < 0
|
||||
towards_next = new_offset > 0
|
||||
if (is_first and towards_prev) or (is_last and towards_next):
|
||||
new_offset = 0
|
||||
|
||||
anim = Animation(_offset=new_offset, d=dur, t=self.anim_type)
|
||||
anim.cancel_all(self)
|
||||
|
||||
def _cmp(*args):
|
||||
self.dispatch(
|
||||
"on_slide_complete",
|
||||
self.previous_slide,
|
||||
self.current_slide,
|
||||
self.next_slide,
|
||||
)
|
||||
if self._skip_slide is not None:
|
||||
self.index = self._skip_slide
|
||||
self._skip_slide = None
|
||||
|
||||
anim.bind(
|
||||
on_complete=_cmp,
|
||||
on_progress=lambda *args: self.dispatch(
|
||||
"on_slide_progress", self._offset
|
||||
),
|
||||
)
|
||||
anim.start(self)
|
|
@ -0,0 +1 @@
|
|||
from .chip import MDChip, MDChipText # NOQA F401
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
<MDChip>
|
||||
size_hint_y: None
|
||||
height: "32dp"
|
||||
adaptive_width: True
|
||||
radius:
|
||||
16 \
|
||||
if self.radius == [0, 0, 0, 0] else \
|
||||
(max(self.radius) if max(self.radius) < self.height / 2 else 16)
|
||||
md_bg_color:
|
||||
( \
|
||||
( \
|
||||
app.theme_cls.bg_darkest \
|
||||
if app.theme_cls.theme_style == "Light" else \
|
||||
app.theme_cls.bg_light \
|
||||
) \
|
||||
if not self._origin_md_bg_color else \
|
||||
self._origin_md_bg_color
|
||||
) \
|
||||
if not self.disabled else app.theme_cls.disabled_primary_color
|
||||
line_color:
|
||||
app.theme_cls.disabled_hint_text_color \
|
||||
if self.disabled else ( \
|
||||
self._origin_line_color \
|
||||
if self._origin_line_color else \
|
||||
self.line_color \
|
||||
)
|
||||
|
||||
LeadingIconContainer:
|
||||
id: leading_icon_container
|
||||
adaptive_width: True
|
||||
|
||||
LabelTextContainer:
|
||||
id: label_container
|
||||
adaptive_width: True
|
||||
|
||||
TrailingIconContainer:
|
||||
id: trailing_icon_container
|
||||
adaptive_width: True
|
1244
kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/chip.py
Normal file
1244
kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/chip.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,207 @@
|
|||
"""
|
||||
Components/CircularLayout
|
||||
=========================
|
||||
|
||||
CircularLayout is a special layout that places widgets around a circle.
|
||||
|
||||
MDCircularLayout
|
||||
----------------
|
||||
|
||||
.. rubric:: Usage
|
||||
|
||||
.. code-block::
|
||||
|
||||
from kivy.lang.builder import Builder
|
||||
from kivy.uix.label import Label
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
kv = '''
|
||||
MDScreen:
|
||||
|
||||
MDCircularLayout:
|
||||
id: container
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
row_spacing: min(self.size) * 0.1
|
||||
'''
|
||||
|
||||
|
||||
class Main(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(kv)
|
||||
|
||||
def on_start(self):
|
||||
for x in range(1, 49):
|
||||
self.root.ids.container.add_widget(
|
||||
Label(text=f"{x}", color=[0, 0, 0, 1])
|
||||
)
|
||||
|
||||
|
||||
Main().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-layout.png
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = ("MDCircularLayout",)
|
||||
|
||||
from math import atan2, cos, degrees, radians, sin
|
||||
|
||||
from kivy.properties import BooleanProperty, NumericProperty
|
||||
|
||||
from kivymd.uix.floatlayout import MDFloatLayout
|
||||
|
||||
|
||||
class MDCircularLayout(MDFloatLayout):
|
||||
degree_spacing = NumericProperty(30)
|
||||
"""
|
||||
The space between children in degree.
|
||||
|
||||
:attr:`degree_spacing` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `30`.
|
||||
"""
|
||||
|
||||
circular_radius = NumericProperty(None, allownone=True)
|
||||
"""
|
||||
Radius of circle. Radius will be the greatest value in the layout if `circular_radius` if not specified.
|
||||
|
||||
:attr:`circular_radius` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
start_from = NumericProperty(60)
|
||||
"""
|
||||
The positon of first child in degree.
|
||||
|
||||
:attr:`start_from` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `60`.
|
||||
"""
|
||||
|
||||
max_degree = NumericProperty(360)
|
||||
"""
|
||||
Maximum range in degree allowed for each row of widgets before jumping to the next row.
|
||||
|
||||
:attr:`max_degree` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `360`.
|
||||
"""
|
||||
|
||||
circular_padding = NumericProperty("25dp")
|
||||
"""
|
||||
Padding between outer widgets and the edge of the biggest circle.
|
||||
|
||||
:attr:`circular_padding` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `25dp`.
|
||||
"""
|
||||
|
||||
row_spacing = NumericProperty("50dp")
|
||||
"""
|
||||
Space between each row of widget.
|
||||
|
||||
:attr:`row_spacing` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `50dp`.
|
||||
"""
|
||||
|
||||
clockwise = BooleanProperty(True)
|
||||
"""
|
||||
Direction of widgets in circular direction.
|
||||
|
||||
:attr:`clockwise` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `True`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.bind(
|
||||
row_spacing=self._update_layout,
|
||||
)
|
||||
|
||||
def get_angle(self, pos: tuple) -> float:
|
||||
"""Returns the angle of given pos."""
|
||||
|
||||
center = [self.pos[0] + self.width / 2, self.pos[1] + self.height / 2]
|
||||
(dx, dy) = (center[0] - pos[0], center[1] - pos[1])
|
||||
angle = degrees(atan2(float(dy), float(dx)))
|
||||
angle += 180
|
||||
return angle
|
||||
|
||||
def remove_widget(self, widget, **kwargs):
|
||||
super().remove_widget(widget, **kwargs)
|
||||
self._update_layout()
|
||||
|
||||
def do_layout(self, *largs, **kwargs):
|
||||
self._update_layout()
|
||||
return super().do_layout(*largs, **kwargs)
|
||||
|
||||
def _max_per_row(self):
|
||||
return int(self.max_degree / self.degree_spacing)
|
||||
|
||||
def _update_layout(self, *args):
|
||||
for index, child in enumerate(reversed(self.children)):
|
||||
pos = self._point_on_circle(
|
||||
self._calculate_radius(index),
|
||||
self._calculate_degree(index),
|
||||
)
|
||||
child.center = pos
|
||||
|
||||
def _calculate_radius(self, index):
|
||||
"""Calculates the radius for given index."""
|
||||
|
||||
idx = int(index / self._max_per_row())
|
||||
|
||||
if not self.circular_radius:
|
||||
init_radius = (
|
||||
min([self.width / 2, self.height / 2]) - self.circular_padding
|
||||
)
|
||||
else:
|
||||
init_radius = self.circular_radius
|
||||
|
||||
if idx != 0:
|
||||
space = self.row_spacing * idx
|
||||
init_radius -= space
|
||||
|
||||
return init_radius
|
||||
|
||||
def _calculate_degree(self, index):
|
||||
"""Calculates the angle for given index."""
|
||||
|
||||
if self.clockwise:
|
||||
degree = self.start_from - index * self.degree_spacing
|
||||
else:
|
||||
degree = self.start_from + index * self.degree_spacing
|
||||
|
||||
return degree
|
||||
|
||||
def _point_on_circle(self, radius, degree):
|
||||
angle = radians(degree)
|
||||
center = [self.pos[0] + self.width / 2, self.pos[1] + self.height / 2]
|
||||
x = center[0] + (radius * cos(angle))
|
||||
y = center[1] + (radius * sin(angle))
|
||||
return [x, y]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from kivy.lang.builder import Builder
|
||||
from kivy.uix.label import Label
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
kv = """
|
||||
MDScreen:
|
||||
|
||||
MDCircularLayout:
|
||||
id: container
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
row_spacing: min(self.size) * 0.1
|
||||
"""
|
||||
|
||||
class Main(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(kv)
|
||||
|
||||
def on_start(self):
|
||||
for x in range(1, 49):
|
||||
self.root.ids.container.add_widget(
|
||||
Label(text=f"{x}", color=[0, 0, 0, 1])
|
||||
)
|
||||
|
||||
Main().run()
|
|
@ -0,0 +1,11 @@
|
|||
"""
|
||||
Controllers
|
||||
===========
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
Modules and classes that implement useful methods for getting information
|
||||
about the state of the current application window.
|
||||
"""
|
||||
|
||||
from .windowcontroller import WindowController
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,80 @@
|
|||
"""
|
||||
Controllers/WindowController
|
||||
============================
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
Modules and classes that implement useful methods for getting information
|
||||
about the state of the current application window.
|
||||
|
||||
Controlling the resizing direction of the application window
|
||||
------------------------------------------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# When resizing the application window, the direction of change will be
|
||||
# printed - 'left' or 'right'.
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.controllers import WindowController
|
||||
from kivymd.uix.screen import MDScreen
|
||||
|
||||
|
||||
class MyScreen(MDScreen, WindowController):
|
||||
def on_width(self, *args):
|
||||
print(self.get_window_width_resizing_direction())
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
return MyScreen()
|
||||
|
||||
|
||||
Test().run()
|
||||
"""
|
||||
|
||||
from kivy.core.window import Window
|
||||
from kivy.core.window.window_sdl2 import WindowSDL
|
||||
from kivy.metrics import dp
|
||||
|
||||
|
||||
class WindowController:
|
||||
def __init__(self):
|
||||
self.window_resizing_direction = "unknown"
|
||||
self.real_device_type = "unknown"
|
||||
self.__width = Window.width
|
||||
Window.bind(on_resize=self._on_resize)
|
||||
|
||||
def on_size(self, instance, size: list) -> None:
|
||||
"""Called when the application screen size changes."""
|
||||
|
||||
window_width = size[0]
|
||||
|
||||
if window_width < dp(500):
|
||||
self.real_device_type = "mobile"
|
||||
elif window_width < dp(1100):
|
||||
self.real_device_type = "tablet"
|
||||
else:
|
||||
self.real_device_type = "desktop"
|
||||
|
||||
def get_real_device_type(self) -> str:
|
||||
"""Returns the device type - 'mobile', 'tablet' or 'desktop'."""
|
||||
|
||||
return self.real_device_type
|
||||
|
||||
def get_window_width_resizing_direction(self) -> str:
|
||||
"""Return window width resizing direction - 'left' or 'right'."""
|
||||
|
||||
return self.window_resizing_direction
|
||||
|
||||
def _set_window_width_resizing_direction(self, width: int) -> None:
|
||||
if self.__width > width:
|
||||
self.window_resizing_direction = "left"
|
||||
elif self.__width < width:
|
||||
self.window_resizing_direction = "right"
|
||||
|
||||
def _on_resize(
|
||||
self, window_sdl2: WindowSDL, width: int, height: int
|
||||
) -> None:
|
||||
self._set_window_width_resizing_direction(width)
|
||||
self.__width = width
|
|
@ -0,0 +1 @@
|
|||
from .datatables import MDDataTable # NOQA F401
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,241 @@
|
|||
#:import DEVICE_TYPE kivymd.material_resources.DEVICE_TYPE
|
||||
|
||||
|
||||
<CellRow>
|
||||
orientation: "vertical"
|
||||
md_bg_color:
|
||||
( \
|
||||
( \
|
||||
root.theme_cls.bg_darkest \
|
||||
if root.theme_cls.theme_style == "Light" else \
|
||||
root.theme_cls.bg_light \
|
||||
) \
|
||||
if not root.background_color_selected_cell \
|
||||
else root.background_color_selected_cell \
|
||||
) \
|
||||
if self.selected \
|
||||
else \
|
||||
( \
|
||||
root.theme_cls.bg_normal \
|
||||
if not root.background_color_cell \
|
||||
else root.background_color_cell \
|
||||
)
|
||||
on_press: if DEVICE_TYPE != "desktop": root.table.on_mouse_select(self)
|
||||
on_enter: if DEVICE_TYPE == "desktop": root.table.on_mouse_select(self)
|
||||
|
||||
MDBoxLayout:
|
||||
id: box
|
||||
padding: "8dp", "8dp", 0, "8dp"
|
||||
spacing: "16dp"
|
||||
|
||||
MDCheckbox:
|
||||
id: check
|
||||
size_hint: None, None
|
||||
size: 0, 0
|
||||
opacity: 0
|
||||
|
||||
MDBoxLayout:
|
||||
id: inner_box
|
||||
|
||||
MDIcon:
|
||||
id: icon
|
||||
size_hint: None, None
|
||||
pos_hint: {"center_y": 0.5}
|
||||
size: ("24dp", "24dp") if root.icon else (0, 0)
|
||||
icon: root.icon if root.icon else ""
|
||||
theme_text_color: "Custom"
|
||||
text_color:
|
||||
root.icon_color if root.icon_color else \
|
||||
root.theme_cls.primary_color
|
||||
|
||||
MDLabel:
|
||||
id: label
|
||||
text: " " + root.text
|
||||
markup: True
|
||||
color:
|
||||
(1, 1, 1, 1) \
|
||||
if root.theme_cls.theme_style == "Dark" else \
|
||||
(0, 0, 0, 1)
|
||||
|
||||
MDSeparator:
|
||||
|
||||
|
||||
<CellHeader>
|
||||
orientation: "vertical"
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
spacing: "4dp"
|
||||
tooltip_text: root.tooltip if root.tooltip else root.text
|
||||
|
||||
BoxLayout:
|
||||
id: box
|
||||
size_hint_y: None
|
||||
height: lbl.height
|
||||
|
||||
MDLabel:
|
||||
id: lbl
|
||||
text: " " + root.text
|
||||
adaptive_height: True
|
||||
bold: True
|
||||
markup: True
|
||||
color:
|
||||
(1, 1, 1, 1) \
|
||||
if root.theme_cls.theme_style == "Dark" else \
|
||||
(0, 0, 0, 1)
|
||||
|
||||
MDSeparator:
|
||||
id: separator
|
||||
|
||||
|
||||
<SortButton>
|
||||
id: sort_btn
|
||||
icon: "arrow-up"
|
||||
pos_hint: {"center_y": 0.5}
|
||||
size: [dp(24), dp(0)]
|
||||
theme_text_color: "Custom"
|
||||
text_color: self.theme_cls.secondary_text_color
|
||||
opacity: 0
|
||||
|
||||
|
||||
<TableHeader>
|
||||
bar_width: 0
|
||||
do_scroll: False
|
||||
size_hint: 1, None
|
||||
height: header.height
|
||||
|
||||
MDGridLayout:
|
||||
id: header
|
||||
rows: 1
|
||||
cols_minimum: root.cols_minimum
|
||||
adaptive_size: True
|
||||
padding: 0, "8dp", 0, 0
|
||||
md_bg_color:
|
||||
root.theme_cls.bg_light \
|
||||
if not root.background_color_header \
|
||||
else root.background_color_header
|
||||
|
||||
MDBoxLayout:
|
||||
orientation: "vertical"
|
||||
|
||||
MDBoxLayout:
|
||||
id: box
|
||||
padding: "8dp", "8dp", "4dp", 0
|
||||
spacing: "16dp"
|
||||
|
||||
MDCheckbox:
|
||||
id: check
|
||||
size_hint: None, None
|
||||
size: 0, 0
|
||||
opacity: 0
|
||||
on_release: root.table_data.select_all(self.state)
|
||||
|
||||
CellHeader:
|
||||
id: first_cell
|
||||
|
||||
MDSeparator:
|
||||
|
||||
|
||||
<TableData>
|
||||
data: root.recycle_data
|
||||
data_first_cells: root.data_first_cells
|
||||
key_viewclass: "viewclass"
|
||||
|
||||
TableRecycleGridLayout:
|
||||
id: row_controller
|
||||
key_selection: "selectable"
|
||||
cols: root.total_col_headings
|
||||
cols_minimum: root.cols_minimum
|
||||
default_size: None, dp(52)
|
||||
default_size_hint: 1, None
|
||||
size_hint: None, None
|
||||
height: self.minimum_height
|
||||
width: self.minimum_width
|
||||
multiselect: True
|
||||
touch_multiselect: True
|
||||
|
||||
|
||||
<TablePagination>
|
||||
adaptive_height: True
|
||||
spacing: "8dp"
|
||||
|
||||
MDLabel:
|
||||
text: "Rows per page"
|
||||
shorten: True
|
||||
halign: "right"
|
||||
font_style: "Caption"
|
||||
color:
|
||||
(1, 1, 1, 1) \
|
||||
if root.theme_cls.theme_style == "Dark" else \
|
||||
(0, 0, 0, 1)
|
||||
|
||||
MDDropDownItem:
|
||||
id: drop_item
|
||||
pos_hint: {'center_y': .5}
|
||||
font_size: "14sp"
|
||||
on_release: root.table_data.open_pagination_menu()
|
||||
text:
|
||||
"{}".format( \
|
||||
root.table_data.rows_num \
|
||||
if root.table_data.rows_num < len(root.table_data.row_data) else \
|
||||
len(root.table_data.row_data) \
|
||||
)
|
||||
|
||||
Widget:
|
||||
size_hint_x: None
|
||||
width: "32dp" if DEVICE_TYPE != "mobile" else "8dp"
|
||||
|
||||
MDLabel:
|
||||
id: label_rows_per_page
|
||||
adaptive_size: True
|
||||
-text_size: None, None
|
||||
pos_hint: {"center_y": .5}
|
||||
font_style: "Caption"
|
||||
color:
|
||||
(1, 1, 1, 1) \
|
||||
if root.theme_cls.theme_style == "Dark" else \
|
||||
(0, 0, 0, 1)
|
||||
text:
|
||||
"1-{} of {}".format( \
|
||||
root.table_data.rows_num \
|
||||
if root.table_data.rows_num > len(root.table_data.row_data) else \
|
||||
len(root.table_data.row_data), len(root.table_data.row_data) \
|
||||
)
|
||||
|
||||
MDIconButton:
|
||||
id: button_back
|
||||
icon: "chevron-left"
|
||||
user_font_size: "20sp" if DEVICE_TYPE != "mobile" else "16dp"
|
||||
ripple_scale: .5 if DEVICE_TYPE == "mobile" else 1
|
||||
pos_hint: {'center_y': .5}
|
||||
disabled: True
|
||||
md_bg_color_disabled: 0, 0, 0, 0
|
||||
on_release: root.table_data.set_next_row_data_parts("back")
|
||||
|
||||
MDIconButton:
|
||||
id: button_forward
|
||||
icon: "chevron-right"
|
||||
user_font_size: "20sp" if DEVICE_TYPE != "mobile" else "16dp"
|
||||
ripple_scale: .5 if DEVICE_TYPE == "mobile" else 1
|
||||
pos_hint: {'center_y': .5}
|
||||
disabled: True
|
||||
md_bg_color_disabled: 0, 0, 0, 0
|
||||
on_release: root.table_data.set_next_row_data_parts("forward")
|
||||
|
||||
|
||||
<TableContainer@MDCard>
|
||||
|
||||
|
||||
<MDDataTable>
|
||||
|
||||
TableContainer:
|
||||
id: container
|
||||
orientation: "vertical"
|
||||
elevation: root.elevation
|
||||
shadow_radius: root.shadow_radius
|
||||
shadow_softness: root.shadow_softness
|
||||
shadow_offset: root.shadow_offset
|
||||
shadow_color: root.shadow_color
|
||||
shadow_color: root.shadow_color
|
||||
shadow_softness_size: root.shadow_softness_size
|
||||
padding: "24dp", "24dp", "8dp", "8dp"
|
||||
md_bg_color: app.theme_cls.bg_normal
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1 @@
|
|||
from .dialog import BaseDialog, MDDialog # NOQA F401
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,90 @@
|
|||
#:import images_path kivymd.images_path
|
||||
|
||||
|
||||
<BaseDialog>
|
||||
background: '{}/transparent.png'.format(images_path)
|
||||
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
RoundedRectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
radius: root.radius
|
||||
Scale:
|
||||
origin: self.center
|
||||
x: root._scale_x
|
||||
y: root._scale_y
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
|
||||
|
||||
<DialogContainer@MDCard>
|
||||
shadow_color: 0.0, 0.0, 0.0, 0.0
|
||||
elevation: 0
|
||||
shadow_softness: 0
|
||||
shadow_offset: 0, 0
|
||||
|
||||
|
||||
<MDDialog>
|
||||
|
||||
DialogContainer:
|
||||
id: container
|
||||
orientation: "vertical"
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
padding: "24dp", "24dp", "8dp", "8dp"
|
||||
radius: root.radius
|
||||
md_bg_color:
|
||||
root.theme_cls.bg_dark \
|
||||
if not root.md_bg_color else root.md_bg_color
|
||||
|
||||
MDLabel:
|
||||
id: title
|
||||
text: root.title
|
||||
font_style: "H6"
|
||||
bold: True
|
||||
markup: True
|
||||
size_hint_y: None
|
||||
height: self.texture_size[1]
|
||||
valign: "top"
|
||||
|
||||
BoxLayout:
|
||||
id: spacer_top_box
|
||||
size_hint_y: None
|
||||
height: root._spacer_top
|
||||
|
||||
MDLabel:
|
||||
id: text
|
||||
text: root.text
|
||||
font_style: "Body1"
|
||||
theme_text_color: "Custom"
|
||||
text_color: root.theme_cls.disabled_hint_text_color
|
||||
size_hint_y: None
|
||||
height: self.texture_size[1]
|
||||
markup: True
|
||||
|
||||
ScrollView:
|
||||
id: scroll
|
||||
size_hint_y: None
|
||||
height: root._scroll_height
|
||||
|
||||
MDGridLayout:
|
||||
id: box_items
|
||||
adaptive_height: True
|
||||
cols: 1
|
||||
|
||||
BoxLayout:
|
||||
id: spacer_bottom_box
|
||||
size_hint_y: None
|
||||
height: self.minimum_height
|
||||
|
||||
AnchorLayout:
|
||||
id: root_button_box
|
||||
size_hint_y: None
|
||||
height: "52dp"
|
||||
anchor_x: "right"
|
||||
|
||||
MDBoxLayout:
|
||||
id: button_box
|
||||
adaptive_size: True
|
||||
spacing: "8dp"
|
|
@ -0,0 +1,690 @@
|
|||
"""
|
||||
Components/Dialog
|
||||
=================
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, Dialogs <https://material.io/components/dialogs>`_
|
||||
|
||||
|
||||
.. rubric:: Dialogs inform users about a task and can contain critical
|
||||
information, require decisions, or involve multiple tasks.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialogs.png
|
||||
:align: center
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.button import MDFlatButton
|
||||
from kivymd.uix.dialog import MDDialog
|
||||
|
||||
KV = '''
|
||||
MDFloatLayout:
|
||||
|
||||
MDFlatButton:
|
||||
text: "ALERT DIALOG"
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
on_release: app.show_alert_dialog()
|
||||
'''
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
dialog = None
|
||||
|
||||
def build(self):
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
self.theme_cls.primary_palette = "Orange"
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def show_alert_dialog(self):
|
||||
if not self.dialog:
|
||||
self.dialog = MDDialog(
|
||||
text="Discard draft?",
|
||||
buttons=[
|
||||
MDFlatButton(
|
||||
text="CANCEL",
|
||||
theme_text_color="Custom",
|
||||
text_color=self.theme_cls.primary_color,
|
||||
),
|
||||
MDFlatButton(
|
||||
text="DISCARD",
|
||||
theme_text_color="Custom",
|
||||
text_color=self.theme_cls.primary_color,
|
||||
),
|
||||
],
|
||||
)
|
||||
self.dialog.open()
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/alert-dialog.png
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = ("MDDialog", "BaseDialog")
|
||||
|
||||
import os
|
||||
|
||||
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 (
|
||||
ColorProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
ObjectProperty,
|
||||
OptionProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.modalview import ModalView
|
||||
|
||||
from kivymd import uix_path
|
||||
from kivymd.material_resources import DEVICE_TYPE
|
||||
from kivymd.theming import ThemableBehavior
|
||||
from kivymd.uix.behaviors import CommonElevationBehavior, MotionDialogBehavior
|
||||
from kivymd.uix.button import BaseButton
|
||||
from kivymd.uix.card import MDSeparator
|
||||
from kivymd.uix.list import BaseListItem
|
||||
|
||||
with open(
|
||||
os.path.join(uix_path, "dialog", "dialog.kv"), encoding="utf-8"
|
||||
) as kv_file:
|
||||
Builder.load_string(kv_file.read())
|
||||
|
||||
|
||||
class BaseDialog(
|
||||
ThemableBehavior, MotionDialogBehavior, ModalView, CommonElevationBehavior
|
||||
):
|
||||
elevation = NumericProperty(3)
|
||||
"""
|
||||
See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.elevation`
|
||||
attribute for more information.
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
:attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `3`.
|
||||
"""
|
||||
|
||||
shadow_softness = NumericProperty(24)
|
||||
"""
|
||||
See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_softness`
|
||||
attribute for more information.
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
:attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `24`.
|
||||
"""
|
||||
|
||||
shadow_offset = ListProperty((0, 4))
|
||||
"""
|
||||
See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_offset`
|
||||
attribute for more information.
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
:attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[0, 4]`.
|
||||
"""
|
||||
|
||||
radius = ListProperty([dp(7), dp(7), dp(7), dp(7)])
|
||||
"""
|
||||
Dialog corners rounding value.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[...]
|
||||
self.dialog = MDDialog(
|
||||
text="Oops! Something seems to have gone wrong!",
|
||||
radius=[20, 7, 20, 7],
|
||||
)
|
||||
[...]
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-radius.png
|
||||
:align: center
|
||||
|
||||
:attr:`radius` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[7, 7, 7, 7]`.
|
||||
"""
|
||||
|
||||
_scale_x = NumericProperty(1)
|
||||
_scale_y = NumericProperty(1)
|
||||
|
||||
|
||||
class MDDialog(BaseDialog):
|
||||
"""
|
||||
Dialog class.
|
||||
|
||||
For more information, see in the
|
||||
:class:`~kivymd.theming.ThemableBehavior` and
|
||||
:class:`~kivy.uix.modalview.ModalView` and
|
||||
:class:`~kivymd.uix.behaviors.CommonElevationBehavior`
|
||||
classes documentation.
|
||||
"""
|
||||
|
||||
title = StringProperty()
|
||||
"""
|
||||
Title dialog.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[...]
|
||||
self.dialog = MDDialog(
|
||||
title="Reset settings?",
|
||||
buttons=[
|
||||
MDFlatButton(
|
||||
text="CANCEL",
|
||||
theme_text_color="Custom",
|
||||
text_color=self.theme_cls.primary_color,
|
||||
),
|
||||
MDFlatButton(
|
||||
text="ACCEPT",
|
||||
theme_text_color="Custom",
|
||||
text_color=self.theme_cls.primary_color,
|
||||
),
|
||||
],
|
||||
)
|
||||
[...]
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-title.png
|
||||
:align: center
|
||||
|
||||
:attr:`title` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
text = StringProperty()
|
||||
"""
|
||||
Text dialog.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[...]
|
||||
self.dialog = MDDialog(
|
||||
title="Reset settings?",
|
||||
text="This will reset your device to its default factory settings.",
|
||||
buttons=[
|
||||
MDFlatButton(
|
||||
text="CANCEL",
|
||||
theme_text_color="Custom",
|
||||
text_color=self.theme_cls.primary_color,
|
||||
),
|
||||
MDFlatButton(
|
||||
text="ACCEPT",
|
||||
theme_text_color="Custom",
|
||||
text_color=self.theme_cls.primary_color,
|
||||
),
|
||||
],
|
||||
)
|
||||
[...]
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-text.png
|
||||
:align: center
|
||||
|
||||
:attr:`text` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
buttons = ListProperty()
|
||||
"""
|
||||
List of button objects for dialog.
|
||||
Objects must be inherited from :class:`~kivymd.uix.button.BaseButton` class.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[...]
|
||||
self.dialog = MDDialog(
|
||||
text="Discard draft?",
|
||||
buttons=[
|
||||
MDFlatButton(text="CANCEL"), MDRaisedButton(text="DISCARD"),
|
||||
],
|
||||
)
|
||||
[...]
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-buttons.png
|
||||
:align: center
|
||||
|
||||
:attr:`buttons` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
items = ListProperty()
|
||||
"""
|
||||
List of items objects for dialog.
|
||||
Objects must be inherited from :class:`~kivymd.uix.list.BaseListItem` class.
|
||||
|
||||
With type 'simple'
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import StringProperty
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.dialog import MDDialog
|
||||
from kivymd.uix.list import OneLineAvatarListItem
|
||||
|
||||
KV = '''
|
||||
<Item>
|
||||
|
||||
ImageLeftWidget:
|
||||
source: root.source
|
||||
|
||||
|
||||
MDFloatLayout:
|
||||
|
||||
MDFlatButton:
|
||||
text: "ALERT DIALOG"
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
on_release: app.show_simple_dialog()
|
||||
'''
|
||||
|
||||
|
||||
class Item(OneLineAvatarListItem):
|
||||
divider = None
|
||||
source = StringProperty()
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
dialog = None
|
||||
|
||||
def build(self):
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
self.theme_cls.primary_palette = "Orange"
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def show_simple_dialog(self):
|
||||
if not self.dialog:
|
||||
self.dialog = MDDialog(
|
||||
title="Set backup account",
|
||||
type="simple",
|
||||
items=[
|
||||
Item(text="user01@gmail.com", source="kivymd/images/logo/kivymd-icon-128.png"),
|
||||
Item(text="user02@gmail.com", source="data/logo/kivy-icon-128.png"),
|
||||
],
|
||||
)
|
||||
self.dialog.open()
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-items.png
|
||||
:align: center
|
||||
|
||||
With type 'confirmation'
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.button import MDFlatButton
|
||||
from kivymd.uix.dialog import MDDialog
|
||||
from kivymd.uix.list import OneLineAvatarIconListItem
|
||||
|
||||
KV = '''
|
||||
<ItemConfirm>
|
||||
on_release: root.set_icon(check)
|
||||
|
||||
CheckboxLeftWidget:
|
||||
id: check
|
||||
group: "check"
|
||||
|
||||
|
||||
MDFloatLayout:
|
||||
|
||||
MDFlatButton:
|
||||
text: "ALERT DIALOG"
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
on_release: app.show_confirmation_dialog()
|
||||
'''
|
||||
|
||||
|
||||
class ItemConfirm(OneLineAvatarIconListItem):
|
||||
divider = None
|
||||
|
||||
def set_icon(self, instance_check):
|
||||
instance_check.active = True
|
||||
check_list = instance_check.get_widgets(instance_check.group)
|
||||
for check in check_list:
|
||||
if check != instance_check:
|
||||
check.active = False
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
dialog = None
|
||||
|
||||
def build(self):
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
self.theme_cls.primary_palette = "Orange"
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def show_confirmation_dialog(self):
|
||||
if not self.dialog:
|
||||
self.dialog = MDDialog(
|
||||
title="Phone ringtone",
|
||||
type="confirmation",
|
||||
items=[
|
||||
ItemConfirm(text="Callisto"),
|
||||
ItemConfirm(text="Luna"),
|
||||
ItemConfirm(text="Night"),
|
||||
ItemConfirm(text="Solo"),
|
||||
ItemConfirm(text="Phobos"),
|
||||
ItemConfirm(text="Diamond"),
|
||||
ItemConfirm(text="Sirena"),
|
||||
ItemConfirm(text="Red music"),
|
||||
ItemConfirm(text="Allergio"),
|
||||
ItemConfirm(text="Magic"),
|
||||
ItemConfirm(text="Tic-tac"),
|
||||
],
|
||||
buttons=[
|
||||
MDFlatButton(
|
||||
text="CANCEL",
|
||||
theme_text_color="Custom",
|
||||
text_color=self.theme_cls.primary_color,
|
||||
),
|
||||
MDFlatButton(
|
||||
text="OK",
|
||||
theme_text_color="Custom",
|
||||
text_color=self.theme_cls.primary_color,
|
||||
),
|
||||
],
|
||||
)
|
||||
self.dialog.open()
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-confirmation.png
|
||||
:align: center
|
||||
|
||||
:attr:`items` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
width_offset = NumericProperty(dp(48))
|
||||
"""
|
||||
Dialog offset from device width.
|
||||
|
||||
:attr:`width_offset` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `dp(48)`.
|
||||
"""
|
||||
|
||||
type = OptionProperty(
|
||||
"alert", options=["alert", "simple", "confirmation", "custom"]
|
||||
)
|
||||
"""
|
||||
Dialog type.
|
||||
Available option are `'alert'`, `'simple'`, `'confirmation'`, `'custom'`.
|
||||
|
||||
:attr:`type` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'alert'`.
|
||||
"""
|
||||
|
||||
content_cls = ObjectProperty()
|
||||
"""
|
||||
Custom content class. This attribute is only available when :attr:`type` is
|
||||
set to `'custom'`.
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. tab:: Declarative KV style
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.button import MDFlatButton
|
||||
from kivymd.uix.dialog import MDDialog
|
||||
|
||||
KV = '''
|
||||
<Content>
|
||||
orientation: "vertical"
|
||||
spacing: "12dp"
|
||||
size_hint_y: None
|
||||
height: "120dp"
|
||||
|
||||
MDTextField:
|
||||
hint_text: "City"
|
||||
|
||||
MDTextField:
|
||||
hint_text: "Street"
|
||||
|
||||
|
||||
MDFloatLayout:
|
||||
|
||||
MDFlatButton:
|
||||
text: "ALERT DIALOG"
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
on_release: app.show_confirmation_dialog()
|
||||
'''
|
||||
|
||||
|
||||
class Content(BoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
dialog = None
|
||||
|
||||
def build(self):
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
self.theme_cls.primary_palette = "Orange"
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def show_confirmation_dialog(self):
|
||||
if not self.dialog:
|
||||
self.dialog = MDDialog(
|
||||
title="Address:",
|
||||
type="custom",
|
||||
content_cls=Content(),
|
||||
buttons=[
|
||||
MDFlatButton(
|
||||
text="CANCEL",
|
||||
theme_text_color="Custom",
|
||||
text_color=self.theme_cls.primary_color,
|
||||
),
|
||||
MDFlatButton(
|
||||
text="OK",
|
||||
theme_text_color="Custom",
|
||||
text_color=self.theme_cls.primary_color,
|
||||
),
|
||||
],
|
||||
)
|
||||
self.dialog.open()
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. tab:: Declarative Python style
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.button import MDFlatButton
|
||||
from kivymd.uix.dialog import MDDialog
|
||||
from kivymd.uix.floatlayout import MDFloatLayout
|
||||
from kivymd.uix.textfield import MDTextField
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
dialog = None
|
||||
|
||||
def build(self):
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
self.theme_cls.primary_palette = "Orange"
|
||||
return (
|
||||
MDFloatLayout(
|
||||
MDFlatButton(
|
||||
text="ALERT DIALOG",
|
||||
pos_hint={'center_x': 0.5, 'center_y': 0.5},
|
||||
on_release=self.show_confirmation_dialog,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def show_confirmation_dialog(self, *args):
|
||||
if not self.dialog:
|
||||
self.dialog = MDDialog(
|
||||
title="Address:",
|
||||
type="custom",
|
||||
content_cls=MDBoxLayout(
|
||||
MDTextField(
|
||||
hint_text="City",
|
||||
),
|
||||
MDTextField(
|
||||
hint_text="Street",
|
||||
),
|
||||
orientation="vertical",
|
||||
spacing="12dp",
|
||||
size_hint_y=None,
|
||||
height="120dp",
|
||||
),
|
||||
buttons=[
|
||||
MDFlatButton(
|
||||
text="CANCEL",
|
||||
theme_text_color="Custom",
|
||||
text_color=self.theme_cls.primary_color,
|
||||
),
|
||||
MDFlatButton(
|
||||
text="OK",
|
||||
theme_text_color="Custom",
|
||||
text_color=self.theme_cls.primary_color,
|
||||
),
|
||||
],
|
||||
)
|
||||
self.dialog.open()
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-custom.png
|
||||
:align: center
|
||||
|
||||
:attr:`content_cls` is an :class:`~kivy.properties.ObjectProperty`
|
||||
and defaults to `'None'`.
|
||||
"""
|
||||
|
||||
md_bg_color = ColorProperty(None)
|
||||
"""
|
||||
Background color in the (r, g, b, a) or string format.
|
||||
|
||||
:attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
_scroll_height = NumericProperty("28dp")
|
||||
_spacer_top = NumericProperty("24dp")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
Window.bind(on_resize=self.update_width)
|
||||
|
||||
if self.size_hint == [1, 1] and (
|
||||
DEVICE_TYPE == "desktop" or DEVICE_TYPE == "tablet"
|
||||
):
|
||||
self.size_hint = (None, None)
|
||||
self.width = min(dp(560), Window.width - self.width_offset)
|
||||
elif self.size_hint == [1, 1] and DEVICE_TYPE == "mobile":
|
||||
self.size_hint = (None, None)
|
||||
self.width = min(dp(280), Window.width - self.width_offset)
|
||||
|
||||
if not self.title:
|
||||
self._spacer_top = 0
|
||||
|
||||
if not self.buttons:
|
||||
self.ids.root_button_box.height = 0
|
||||
else:
|
||||
self.create_buttons()
|
||||
|
||||
update_height = False
|
||||
if self.type in ("simple", "confirmation"):
|
||||
if self.type == "confirmation":
|
||||
self.ids.spacer_top_box.add_widget(MDSeparator())
|
||||
self.ids.spacer_bottom_box.add_widget(MDSeparator())
|
||||
self.create_items()
|
||||
if self.type == "custom":
|
||||
if self.content_cls:
|
||||
self.ids.container.remove_widget(self.ids.scroll)
|
||||
self.ids.container.remove_widget(self.ids.text)
|
||||
self.ids.spacer_top_box.add_widget(self.content_cls)
|
||||
self.ids.spacer_top_box.padding = (0, "24dp", "16dp", 0)
|
||||
update_height = True
|
||||
if self.type == "alert":
|
||||
self.ids.scroll.bar_width = 0
|
||||
|
||||
if update_height:
|
||||
Clock.schedule_once(self.update_height)
|
||||
|
||||
def update_width(self, *args) -> None:
|
||||
self.width = max(
|
||||
self.height + self.width_offset,
|
||||
min(
|
||||
dp(560) if DEVICE_TYPE != "mobile" else dp(280),
|
||||
Window.width - self.width_offset,
|
||||
),
|
||||
)
|
||||
|
||||
def update_height(self, *args) -> None:
|
||||
self._spacer_top = self.content_cls.height + dp(24)
|
||||
|
||||
def update_items(self, items: list) -> None:
|
||||
self.ids.box_items.clear_widgets()
|
||||
self.items = items
|
||||
self.create_items()
|
||||
|
||||
def on_open(self) -> None:
|
||||
# TODO: Add scrolling text.
|
||||
self.height = self.ids.container.height
|
||||
super().on_open()
|
||||
|
||||
def get_normal_height(self) -> float:
|
||||
return (
|
||||
(Window.height * 80 / 100)
|
||||
- self._spacer_top
|
||||
- dp(52)
|
||||
- self.ids.container.padding[1]
|
||||
- self.ids.container.padding[-1]
|
||||
- 100
|
||||
)
|
||||
|
||||
def edit_padding_for_item(self, instance_item) -> None:
|
||||
instance_item.ids._left_container.x = 0
|
||||
instance_item._txt_left_pad = "56dp"
|
||||
|
||||
def create_items(self) -> None:
|
||||
if not self.text:
|
||||
self.ids.container.remove_widget(self.ids.text)
|
||||
height = 0
|
||||
else:
|
||||
height = self.ids.text.height
|
||||
|
||||
for item in self.items:
|
||||
if issubclass(item.__class__, BaseListItem):
|
||||
height += item.height # calculate height contents
|
||||
self.edit_padding_for_item(item)
|
||||
self.ids.box_items.add_widget(item)
|
||||
|
||||
if height > Window.height:
|
||||
self.ids.scroll.height = self.get_normal_height()
|
||||
else:
|
||||
self.ids.scroll.height = height
|
||||
|
||||
def create_buttons(self) -> None:
|
||||
for button in self.buttons:
|
||||
if issubclass(button.__class__, BaseButton):
|
||||
self.ids.button_box.add_widget(button)
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue