working condition

This commit is contained in:
Yura 2024-09-15 20:57:02 +03:00
parent 417e54da96
commit 511e0b0379
517 changed files with 29187 additions and 32696 deletions

View file

@ -5,10 +5,8 @@ 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):
class MDAdaptiveWidget:
adaptive_height = BooleanProperty(False)
"""
If `True`, the following properties will be applied to the widget:

View file

@ -15,7 +15,7 @@ AnchorLayout
AnchorLayout:
canvas:
Color:
rgba: app.theme_cls.primary_color
rgba: app.theme_cls.primaryColor
Rectangle:
pos: self.pos
size: self.size
@ -26,7 +26,7 @@ MDAnchorLayout
.. code-block:: kv
MDAnchorLayout:
md_bg_color: app.theme_cls.primary_color
md_bg_color: app.theme_cls.primaryColor
"""
__all__ = ("MDAnchorLayout",)
@ -35,13 +35,24 @@ from kivy.uix.anchorlayout import AnchorLayout
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
from kivymd.uix.behaviors import DeclarativeBehavior
from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior
class MDAnchorLayout(
DeclarativeBehavior, ThemableBehavior, AnchorLayout, MDAdaptiveWidget
DeclarativeBehavior,
ThemableBehavior,
BackgroundColorBehavior,
AnchorLayout,
MDAdaptiveWidget,
):
"""
Anchor layout class. For more information, see in the
:class:`~kivy.uix.anchorlayout.AnchorLayout` class documentation.
Anchor layout class.
For more information, see in the
:class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
:class:`~kivymd.theming.ThemableBehavior` and
:class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
:class:`~kivy.uix.anchorlayout.AnchorLayout` and
:class:`~kivymd.uix.MDAdaptiveWidget`
classes documentation.
"""

View file

@ -0,0 +1,11 @@
# NOQA F401
from .appbar import (
MDTopAppBar,
MDTopAppBarTitle,
MDBottomAppBar,
MDActionTopAppBarButton,
MDActionBottomAppBarButton,
MDFabBottomAppBarButton,
MDTopAppBarLeadingButtonContainer,
MDTopAppBarTrailingButtonContainer,
)

View file

@ -0,0 +1,110 @@
<MDTopAppBarLeadingButtonContainer>
size_hint_x: None
width: self.minimum_width
padding: "8dp", 0, "16dp", 0
<MDTopAppBarTrailingButtonContainer>
size_hint_x: None
width: self.minimum_width
padding: "16dp", 0, "16dp", 0
spacing: "4dp"
<MDActionTopAppBarButton>
pos_hint: {"center_y": .5}
color:
self.theme_cls.onSurfaceVariantColor \
if self.theme_icon_color == "Primary" else \
self.icon_color
<MDTopAppBarTitle>
padding:
[
self._appbar._left_padding if self._appbar else 0,
0,
self._appbar._right_padding if self._appbar else 0,
0,
]
font_style:
{ \
"small": "Title", \
"medium": "Headline", \
"large": "Headline", \
}[self._appbar.type if self._appbar else "small"]
role:
{ \
"small": "large", \
"medium": "small", \
"large": "medium", \
}[self._appbar.type if self._appbar else "large"]
adaptive_width:
( \
True \
if self._appbar.type == "small" else \
False \
) \
if self._appbar else True
size_hint_x:
( \
None \
if self._appbar.type == "small" else \
1 \
) \
if self._appbar else None
<MDTopAppBar>
canvas:
Color:
group: "md-top-app-bar-color"
rgba:
self.theme_cls.surfaceColor \
if self.theme_bg_color == "Primary" else \
self.md_bg_color
Rectangle:
pos: self.pos
size: self.size
orientation:
"vertical" \
if self.type in ("medium", "large") else \
"horizontal"
size_hint_y: None
height:
{ \
"small": "64dp", \
"medium": "112dp", \
"large": "152dp", \
}[self.type]
BoxLayout:
id: root_box
BoxLayout:
id: title_box
padding: "16dp", 0, "16dp", 0
<MDFabBottomAppBarButton>
elevation_level: 0
theme_shadow_color: "Custom"
shadow_color: self.theme_cls.transparentColor
<MDBottomAppBar>
size_hint_y: None
height: "80dp"
elevation_level:
2 \
if self.theme_elevation_level == "Primary" else \
self.elevation_level
shadow_softness:
2 \
if self.theme_shadow_softness == "Primary" else \
self.shadow_softness
md_bg_color:
self.theme_cls.surfaceContainerColor \
if self.theme_bg_color == "Primary" else \
self.md_bg_color

File diff suppressed because it is too large Load diff

View file

@ -1 +0,0 @@
from .backdrop import MDBackdrop # NOQA F401

View file

@ -1,50 +0,0 @@
<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"

View file

@ -1,548 +0,0 @@
"""
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

View file

@ -0,0 +1 @@
from .badge import MDBadge # NOQA F401

View file

@ -0,0 +1,19 @@
<MDBadge>
font_style: "Label"
role: "small"
radius: [self.texture_size[1] / 2, ]
pos_hint: {"center_x": 0.5, "center_y": 0.5}
padding: "4dp", "2dp"
halign: "center"
valign: "center"
adaptive_size: True
md_bg_color: self.theme_cls.errorColor
text_color: self.theme_cls.onErrorColor
size_hint: None, None
size: self.texture_size
pos:
( \
self.parent.x + (self.parent.width / 2), \
self.parent.y + (self.parent.height / 2) \
) \
if self.parent else (0, 0)

View file

@ -0,0 +1,74 @@
"""
Components/Badge
================
.. versionadded:: 2.0.0
.. seealso::
`Material Design 3 spec, Badge <https://m3.material.io/components/badges/overview>`_
.. rubric:: Badges show notifications, counts, or status information on
navigation items and icons.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/badges.png
:align: center
Example
-------
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
MDScreen:
md_bg_color: self.theme_cls.backgroundColor
MDIcon:
icon: "gmail"
pos_hint: {'center_x': .5, 'center_y': .5}
MDBadge:
text: "12"
'''
class Example(MDApp):
def build(self):
return Builder.load_string(KV)
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/badges-example.png
:align: center
"""
__all__ = ("MDBadge",)
import os
from kivy.lang import Builder
from kivymd.uix.label import MDLabel
from kivymd import uix_path
with open(
os.path.join(uix_path, "badge", "badge.kv"), encoding="utf-8"
) as kv_file:
Builder.load_string(kv_file.read())
class MDBadge(MDLabel):
"""
Badge class.
.. versionadded:: 2.0.0
For more information see in the
:class:`~kivymd.uix.label.label.MDLabel` class documentation.
"""

View file

@ -1 +0,0 @@
from .banner import MDBanner # NOQA F401

View file

@ -1,85 +0,0 @@
#: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

View file

@ -1,439 +0,0 @@
"""
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

View file

@ -5,21 +5,11 @@ Behaviors
Modules and classes implementing various behaviors for buttons etc.
"""
from .backgroundcolor_behavior import (
BackgroundColorBehavior,
SpecificBackgroundColorBehavior,
)
from .backgroundcolor_behavior import BackgroundColorBehavior
# flake8: NOQA
from .declarative_behavior import DeclarativeBehavior
from .elevation import (
CircularElevationBehavior,
CommonElevationBehavior,
FakeCircularElevationBehavior,
FakeRectangularElevationBehavior,
RectangularElevationBehavior,
RoundedRectangularElevationBehavior,
)
from .elevation import CommonElevationBehavior
from .motion_behavior import (
MotionDialogBehavior,
MotionShackBehavior,

View file

@ -7,7 +7,7 @@ Behaviors/Background Color
from __future__ import annotations
__all__ = ("BackgroundColorBehavior", "SpecificBackgroundColorBehavior")
__all__ = ("BackgroundColorBehavior",)
from kivy.animation import Animation
from kivy.lang import Builder
@ -15,15 +15,10 @@ 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(
"""
@ -37,8 +32,9 @@ Builder.load_string(
angle: self.angle
origin: self._background_origin
Color:
group: "backgroundcolor-behavior-bg-color"
rgba: self._md_bg_color
RoundedRectangle:
SmoothRoundedRectangle:
group: "Background_instruction"
size: self.size
pos: self.pos if not isinstance(self, RelativeLayout) else (0, 0)
@ -49,11 +45,17 @@ Builder.load_string(
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:
SmoothLine:
width: root.line_width
rounded_rectangle:
[ \
0,
0, \
self.width, \
self.height, \
*self.radius, \
] \
if isinstance(self, RelativeLayout) else \
[ \
self.x,
self.y, \
@ -73,7 +75,7 @@ class BackgroundColorBehavior:
Background image path.
:attr:`background` is a :class:`~kivy.properties.StringProperty`
and defaults to `None`.
and defaults to `''`.
"""
radius = VariableListProperty([0], length=4)
@ -93,7 +95,7 @@ class BackgroundColorBehavior:
# 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])
md_bg_color = ColorProperty([0, 0, 0, 0])
"""
The background color of the widget (:class:`~kivy.uix.widget.Widget`)
that will be inherited from the :attr:`BackgroundColorBehavior` class.
@ -118,12 +120,12 @@ class 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]`.
and defaults to `[0, 0, 0, 0]`.
"""
line_color = ColorProperty([0, 0, 0, 0])
"""
If a custom value is specified for the `line_color parameter`, the border
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
@ -157,37 +159,18 @@ class BackgroundColorBehavior:
_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,
)
self.bind(pos=self.update_background_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."""
def on_md_bg_color(self, instance, color: list | str):
"""Fired when the values of :attr:`md_bg_color` change."""
if (
hasattr(self, "theme_cls")
and self.theme_cls.theme_style_switch_animation
and self.__class__.__name__ != "MDDropdownMenu"
):
Animation(
_md_bg_color=color,
@ -197,94 +180,10 @@ class BackgroundColorBehavior:
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."""
def update_background_origin(self, instance, pos: list) -> None:
"""Fired 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

View file

@ -33,31 +33,35 @@ 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.navigationbar import (
MDNavigationBar,
MDNavigationItem,
MDNavigationItemIcon,
MDNavigationItemLabel,
)
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",
)
bottom_navigation = MDNavigationBar()
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"]
datas = [
{"text": "Mail", "icon": "gmail"},
{"text": "GitHub", "icon": "git"},
{"text": "LinkedIN", "icon": "linkedin"},
]
for data in datas:
text = data["text"]
navigation_item = MDNavigationItem(
MDNavigationItemIcon(
icon=data["icon"],
),
MDNavigationItemLabel(
text=text,
),
)
navigation_item.add_widget(MDLabel(text=text, halign="center"))
bottom_navigation.add_widget(navigation_item)
screen.add_widget(bottom_navigation)
@ -66,9 +70,6 @@ Imperative style
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,
@ -87,51 +88,42 @@ Declarative style with KV language
from kivymd.app import MDApp
class Test(MDApp):
class Example(MDApp):
def build(self):
return Builder.load_string(
'''
MDScreen:
MDBottomNavigation:
panel_color: "#eeeaea"
selected_color_background: "#97ecf8"
text_color_active: "white"
MDNavigationBar:
MDBottomNavigationItem:
name: "screen 1"
text: "Mail"
icon: "gmail"
MDNavigationItem:
MDLabel:
MDNavigationItemIcon:
icon: "gmail"
MDNavigationItemLabel:
text: "Mail"
halign: "center"
MDBottomNavigationItem:
name: "screen 2"
text: "Discord"
icon: "discord"
MDNavigationItem:
MDLabel:
text: "Discord"
halign: "center"
MDNavigationItemIcon:
icon: "git"
MDBottomNavigationItem:
name: "screen 3"
text: "LinkedIN"
icon: "linkedin"
MDNavigationItemLabel:
text: "GitHub"
MDLabel:
MDNavigationItem:
MDNavigationItemIcon:
icon: "linkedin"
MDNavigationItemLabel:
text: "LinkedIN"
halign: "center"
'''
)
Test().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-styles-programming.png
:align: center
Example().run()
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
@ -146,48 +138,41 @@ 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
from kivymd.uix.navigationbar import (
MDNavigationBar,
MDNavigationItemIcon,
MDNavigationItem,
MDNavigationItemLabel,
)
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",
)
)
return MDNavigationBar(
MDNavigationItem(
MDNavigationItemIcon(
icon="gmail",
),
MDNavigationItemLabel(
text="Mail",
),
),
MDNavigationItem(
MDNavigationItemIcon(
icon="twitter",
),
MDNavigationItemLabel(
text="Twitter",
),
),
MDNavigationItem(
MDNavigationItemIcon(
icon="linkedin",
),
MDNavigationItemLabel(
text="LinkedIN",
),
),
)
@ -239,7 +224,7 @@ get to the desired id:
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDRaisedButton
from kivymd.uix.button import MDButton, MDButtonText
from kivymd.uix.floatlayout import MDFloatLayout
@ -248,18 +233,22 @@ get to the desired id:
return (
MDBoxLayout(
MDFloatLayout(
MDRaisedButton(
MDButton(
MDButtonText(
text="Button 1",
),
id="button_1",
text="Button 1",
pos_hint={"center_x": 0.5, "center_y": 0.5},
),
id="box_container_1",
),
MDBoxLayout(
MDFloatLayout(
MDRaisedButton(
MDButton(
MDButtonText(
text="Button 2",
),
id="button_2",
text="Button 2",
pos_hint={"center_x": 0.5, "center_y": 0.5},
),
id="float_container",
@ -271,13 +260,13 @@ get to the desired id:
def on_start(self):
# {
# 'box_container_1': <kivymd.uix.floatlayout.MDFloatLayout>,
# 'box_container_2': <kivymd.uix.boxlayout.MDBoxLayout object>
# 'button_1': <kivymd.uix.button.button.MDButton object at 0x11d93c9e0>,
# 'button_2': <kivymd.uix.button.button.MDButton object at 0x11da128f0>,
# 'float_container': <kivymd.uix.floatlayout.MDFloatLayout object at 0x11da228f0>,
# 'box_container_1': <kivymd.uix.floatlayout.MDFloatLayout object at 0x11d9fc3c0>,
# 'box_container_2': <kivymd.uix.boxlayout.MDBoxLayout object at 0x11dbf06d0>,
# }
print(self.root.ids)
# <kivymd.uix.button.button.MDRaisedButton>
print(self.root.ids.box_container_2.ids.float_container.ids.button_2)
print(self.root.get_ids())
Example().run()
@ -293,6 +282,15 @@ from kivy.properties import StringProperty
from kivy.uix.widget import Widget
class _Dict(dict):
"""Implements access to dictionary values via a dot."""
def __getattr__(self, name):
return self[name]
# TODO: Add cleaning of the `__ids` collection when removing child widgets
# from the parent.
class DeclarativeBehavior:
"""
Implements the creation and addition of child widgets as declarative
@ -307,6 +305,8 @@ class DeclarativeBehavior:
and defaults to `''`.
"""
__ids = _Dict()
def __init__(self, *args, **kwargs):
super().__init__(**kwargs)
@ -314,4 +314,12 @@ class DeclarativeBehavior:
if issubclass(child.__class__, Widget):
self.add_widget(child)
if hasattr(child, "id") and child.id:
self.ids[child.id] = child
self.__ids[child.id] = child
def get_ids(self) -> dict:
"""
Returns a dictionary of widget IDs defined in Python
code that is written in a declarative style.
"""
return self.__ids

View file

@ -31,7 +31,7 @@ For example, let's create a button with a rectangular elevation effect:
)
KV = '''
<RectangularElevationButton>
<ElevationWidget>
size_hint: None, None
size: "250dp", "50dp"
@ -39,19 +39,19 @@ For example, let's create a button with a rectangular elevation effect:
MDScreen:
# With elevation effect
RectangularElevationButton:
ElevationWidget:
pos_hint: {"center_x": .5, "center_y": .6}
elevation: 4
shadow_offset: 0, -6
shadow_softness: 4
# Without elevation effect
RectangularElevationButton:
ElevationWidget:
pos_hint: {"center_x": .5, "center_y": .4}
'''
class RectangularElevationButton(
class ElevationWidget(
RectangularRippleBehavior,
CommonElevationBehavior,
ButtonBehavior,
@ -84,7 +84,7 @@ For example, let's create a button with a rectangular elevation effect:
from kivymd.uix.screen import MDScreen
class RectangularElevationButton(
class ElevationWidget(
RectangularRippleBehavior,
CommonElevationBehavior,
ButtonBehavior,
@ -101,13 +101,13 @@ For example, let's create a button with a rectangular elevation effect:
def build(self):
return (
MDScreen(
RectangularElevationButton(
ElevationWidget(
pos_hint={"center_x": .5, "center_y": .6},
elevation=4,
shadow_softness=4,
shadow_offset=(0, -6),
),
RectangularElevationButton(
ElevationWidget(
pos_hint={"center_x": .5, "center_y": .4},
),
)
@ -271,7 +271,7 @@ Animating the elevation
size: 100, 100
md_bg_color: 0, 0, 1, 1
elevation: 2
radius: 18
radius: dp(18)
'''
@ -306,6 +306,7 @@ Animating the elevation
from kivy.animation import Animation
from kivy.uix.behaviors import ButtonBehavior
from kivy.metrics import dp
from kivymd.app import MDApp
from kivymd.uix.behaviors import CommonElevationBehavior, RectangularRippleBehavior
@ -341,7 +342,7 @@ Animating the elevation
size=(100, 100),
md_bg_color="blue",
elevation=2,
radius=18,
radius=dp(18),
)
)
)
@ -355,23 +356,17 @@ Animating the elevation
from __future__ import annotations
__all__ = (
"CommonElevationBehavior",
"RectangularElevationBehavior",
"CircularElevationBehavior",
"RoundedRectangularElevationBehavior",
"FakeRectangularElevationBehavior",
"FakeCircularElevationBehavior",
)
__all__ = ("CommonElevationBehavior",)
from kivy import Logger
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.properties import (
BoundedNumericProperty,
ColorProperty,
ListProperty,
NumericProperty,
VariableListProperty,
DictProperty,
)
from kivy.uix.widget import Widget
@ -393,18 +388,15 @@ Builder.load_string(
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
rgba: root.shadow_color
BoxShadow:
pos: self.pos
pos: self.pos if not isinstance(self, RelativeLayout) else (0, 0)
size: self.size
offset: root.shadow_offset
spread_radius: -(root.shadow_softness), -(root.shadow_softness)
blur_radius: root.elevation * 10
blur_radius: root.elevation_levels[root.elevation_level]
border_radius:
(root.radius if hasattr(self, "radius") else [0, 0, 0, 0]) \
(root.radius if hasattr(self, "radius") and root.radius else [0, 0, 0, 0]) \
if root.shadow_radius == [0.0, 0.0, 0.0, 0.0] else \
root.shadow_radius
canvas.after:
@ -421,6 +413,36 @@ class CommonElevationBehavior(Widget):
class documentation.
"""
elevation_level = BoundedNumericProperty(0, min=0, max=5)
"""
Elevation level (values from 0 to 5)
.. versionadded:: 1.2.0
:attr:`elevation_level` is an :class:`~kivy.properties.BoundedNumericProperty`
and defaults to `0`.
"""
elevation_levels = DictProperty(
{
0: 0,
1: dp(8),
2: dp(12),
3: dp(16),
4: dp(20),
5: dp(24),
}
)
"""
Elevation is measured as the distance between components along the z-axis
in density-independent pixels (dps).
.. versionadded:: 1.2.0
:attr:`elevation_levels` is an :class:`~kivy.properties.DictProperty`
and defaults to `{0: dp(0), 1: dp(8), 2: dp(23), 3: dp(16), 4: dp(20), 5: dp(24)}`.
"""
elevation = BoundedNumericProperty(0, min=0, errorvalue=0)
"""
Elevation of the widget.
@ -449,7 +471,7 @@ class CommonElevationBehavior(Widget):
MDScreen:
MDCard:
radius: 12, 46, 12, 46
radius: dp(12), dp(46), dp(12), dp(46)
size_hint: .5, .3
pos_hint: {"center_x": .5, "center_y": .5}
elevation: 2
@ -486,26 +508,26 @@ class CommonElevationBehavior(Widget):
from kivymd.uix.behaviors import BackgroundColorBehavior, CommonElevationBehavior
KV = '''
<RectangularElevationButton>
<ElevationWidget>
size_hint: None, None
size: "250dp", "50dp"
MDScreen:
RectangularElevationButton:
ElevationWidget:
pos_hint: {"center_x": .5, "center_y": .6}
elevation: 6
shadow_softness: 6
RectangularElevationButton:
ElevationWidget:
pos_hint: {"center_x": .5, "center_y": .4}
elevation: 6
shadow_softness: 12
'''
class RectangularElevationButton(CommonElevationBehavior, BackgroundColorBehavior):
class ElevationWidget(CommonElevationBehavior, BackgroundColorBehavior):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.md_bg_color = "blue"
@ -522,19 +544,7 @@ class CommonElevationBehavior(Widget):
: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`.
and defaults to `0.0`.
"""
shadow_offset = ListProperty((0, 0))
@ -551,23 +561,23 @@ class CommonElevationBehavior(Widget):
from kivymd.uix.behaviors import BackgroundColorBehavior, CommonElevationBehavior
KV = '''
<RectangularElevationButton>
<ElevationWidget>
size_hint: None, None
size: "100dp", "100dp"
MDScreen:
RectangularElevationButton:
ElevationWidget:
pos_hint: {"center_x": .5, "center_y": .5}
elevation: 6
shadow_radius: 6
shadow_radius: dp(6)
shadow_softness: 12
shadow_offset: -12, -12
'''
class RectangularElevationButton(CommonElevationBehavior, BackgroundColorBehavior):
class ElevationWidget(CommonElevationBehavior, BackgroundColorBehavior):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.md_bg_color = "blue"
@ -585,7 +595,7 @@ class CommonElevationBehavior(Widget):
.. code-block:: kv
RectangularElevationButton:
ElevationWidget:
shadow_offset: 12, -12
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-2.png
@ -593,7 +603,7 @@ class CommonElevationBehavior(Widget):
.. code-block:: kv
RectangularElevationButton:
ElevationWidget:
shadow_offset: 12, 12
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-3.png
@ -601,7 +611,7 @@ class CommonElevationBehavior(Widget):
.. code-block:: kv
RectangularElevationButton:
ElevationWidget:
shadow_offset: -12, 12
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-4.png
@ -619,7 +629,7 @@ class CommonElevationBehavior(Widget):
.. code-block:: python
RectangularElevationButton:
ElevationWidget:
shadow_color: 0, 0, 1, .8
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-color.png
@ -691,82 +701,10 @@ class CommonElevationBehavior(Widget):
and defaults to `(0, 0, 1)`.
"""
_elevation = 0
# _elevation = 0
_elevation_level = 0
_shadow_softness = 0
_shadow_color = (0, 0, 0, 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."
)
# def on_elevation(self, instance, value) -> None:
# self._elevation = value

View file

@ -15,7 +15,7 @@ Usage
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.behaviors import RectangularElevationBehavior
from kivymd.uix.behaviors import CommonElevationBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.behaviors.focus_behavior import FocusBehavior
@ -36,7 +36,7 @@ Usage
'''
class FocusWidget(MDBoxLayout, RectangularElevationBehavior, FocusBehavior):
class FocusWidget(MDBoxLayout, CommonElevationBehavior, FocusBehavior):
pass
@ -65,25 +65,26 @@ Color change at focus/defocus
__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):
class FocusBehavior(HoverBehavior):
"""
Focus behavior class.
For more information, see in the :class:`~kivymd.uix.behavior.HoverBehavior`
and :class:`~kivy.uix.button.ButtonBehavior` classes documentation.
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
Fired 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
Fired when the mouse exits the widget AND the widget is visible.
"""
focus_behavior = BooleanProperty(True)
@ -109,39 +110,3 @@ class FocusBehavior(HoverBehavior, ButtonBehavior):
: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

View file

@ -27,9 +27,13 @@ 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.
: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
RelativeLayout, 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`.
To get the legacy behavior that the events are always triggered, you can
set `detect_visible` on the Widget to `False`.
.. code-block:: python
@ -40,13 +44,14 @@ the widget.
from kivymd.uix.boxlayout import MDBoxLayout
KV = '''
Screen
MDScreen
md_bg_color: self.theme_cls.backgroundColor
MDBoxLayout:
id: box
pos_hint: {'center_x': .5, 'center_y': .5}
size_hint: .8, .8
md_bg_color: app.theme_cls.bg_darkest
md_bg_color: self.theme_cls.secondaryContainerColor
'''
@ -54,19 +59,23 @@ the widget.
'''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.'''
'''
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)
self.md_bg_color = "white"
def on_leave(self, *args):
'''The method will be called when the mouse cursor goes beyond
the borders of the current widget.'''
'''
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
self.md_bg_color = self.theme_cls.secondaryContainerColor
class Test(MDApp):
class Example(MDApp):
def build(self):
self.screen = Builder.load_string(KV)
for i in range(5):
@ -74,10 +83,9 @@ the widget.
return self.screen
Test().run()
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hover-behavior.gif
:width: 250 px
:align: center
"""
@ -85,23 +93,25 @@ __all__ = ("HoverBehavior",)
from kivy.core.window import Window
from kivy.properties import BooleanProperty, ObjectProperty
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.widget import Widget
class HoverBehavior(object):
class HoverBehavior:
"""
:Events:
:attr:`on_enter`
Called when mouse enters the bbox of the widget AND the widget is visible
Fired 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
Fired 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
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`.
@ -109,7 +119,7 @@ class HoverBehavior(object):
hover_visible = BooleanProperty(False)
"""
`True` if hovering is True AND is the current widget is visible
`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`.
@ -118,7 +128,7 @@ class HoverBehavior(object):
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
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`.
@ -132,22 +142,24 @@ class HoverBehavior(object):
and defaults to `True`.
"""
def __init__(self, **kwargs):
def __init__(self, *args, **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)
super().__init__(*args, **kwargs)
def on_mouse_update(self, *args):
# If the Widget currently has no parent, do nothing
# 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)):
# 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)
if not isinstance(self, RelativeLayout)
else (pos[0], pos[1])
):
self.hovering = False
self.enter_point = None
if self.hover_visible:
@ -155,72 +167,59 @@ class HoverBehavior(object):
self.dispatch("on_leave")
return
#
# The pointer is in the same position as the widget
#
# 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
#
# 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
#
# 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
# 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
# using both local and global coordinates to cover absolute
# 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
# The collide_point will error when you reach the root
# Window.
break
if not pinside:
self.hover_visible = False
break
# Iterate upwards
# 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
# 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 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
# 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
# This means that the current widget is covered by a modal
# or popup.
self.hover_visible = False
break
if self.hover_visible:
@ -228,7 +227,7 @@ class HoverBehavior(object):
self.dispatch("on_enter")
def on_enter(self):
"""Called when mouse enters the bbox of the widget AND the widget is visible."""
"""Fired when mouse enter the bbox of the widget."""
def on_leave(self):
"""Called when the mouse exits the widget AND the widget is visible."""
"""Fired when the mouse goes outside the widget border."""

View file

@ -15,17 +15,20 @@ as dialogs, dropdown menu, snack bars, and so on.
__all__ = (
"MotionBase",
"MotionExtendedFabButtonBehavior",
"MotionDropDownMenuBehavior",
"MotionDialogBehavior",
"MotionShackBehavior",
"MotionDatePickerBehavior",
"MotionTimePickerBehavior",
)
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
from kivy.metrics import dp
from kivy.properties import StringProperty, NumericProperty, ObjectProperty
from kivymd.uix.behaviors.scale_behavior import ScaleBehavior
class MotionBase:
@ -166,80 +169,288 @@ class MotionDropDownMenuBehavior(MotionBase):
self.scale_value_y = value
class MotionDialogBehavior(MotionBase):
class MotionExtendedFabButtonBehavior(MotionBase):
"""
Base class for dialog movement behavior.
Base class for extended Fab button movement behavior.
For more information, see in the :class:`~MotionBase` class documentation.
"""
show_duration = NumericProperty(0.1)
show_transition = StringProperty("out_circ")
"""
The type of transition of the widget opening.
:attr:`show_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'out_circ'`.
"""
shift_transition = StringProperty("out_sine")
"""
Text label transition.
:attr:`shift_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'out_sine'`.
"""
show_duration = NumericProperty(0.3)
"""
Duration of widget display transition.
:attr:`show_duration` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.1`.
and defaults to `0.3`.
"""
scale_x = NumericProperty(1.5)
hide_transition = StringProperty("out_sine")
"""
Default X-axis scaling values.
The type of transition of the widget closing.
:attr:`scale_x` is a :class:`~kivy.properties.NumericProperty`
and defaults to `1.5`.
:attr:`hide_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'linear'`.
"""
scale_y = NumericProperty(1.5)
hide_duration = NumericProperty(0.2)
"""
Default Y-axis scaling values.
Duration of widget closing transition.
:attr:`scale_y` is a :class:`~kivy.properties.NumericProperty`
and defaults to `1.5`.
:attr:`hide_duration` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.2`.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.set_default_values()
_x = NumericProperty(0)
_anim_opacity = None
def set_default_values(self):
"""Sets default scaled and transparency values."""
def collapse(self, *args) -> None:
"""Collapses the button."""
self.scale_value_x = self.scale_x
self.scale_value_y = self.scale_y
self.opacity = 0
def collapse(*args):
if self._label and self._icon:
Animation(_x=0, d=self.hide_duration).start(self)
anim = Animation(
width=dp(56), d=self.hide_duration, t=self.hide_transition
)
anim.bind(on_progress=self._check_collapse_progress)
anim.start(self)
Clock.schedule_once(collapse)
def expand(self, *args) -> None:
"""Expands the button."""
def expand(*args):
if self._label and self._icon:
anim = Animation(
width=self.width
+ self._label.texture_size[0]
+ (dp(18) if self._icon else 0),
d=self.show_duration,
t=self.show_transition,
)
anim.bind(on_progress=self._check_expand_progress)
anim.start(self)
Animation(
_x=dp(12), d=self.show_duration, t=self.shift_transition
).start(self)
Clock.schedule_once(expand)
def set_opacity_text_button(self, value: int) -> None:
if self._label:
self._anim_opacity = Animation(
opacity=value,
d=self.show_duration * 16.666666666666668 / 100
if value
else self.show_duration * 1.6666666666666667 / 100,
)
self._anim_opacity.bind(
on_complete=lambda *x: setattr(self, "_anim_opacity", None)
)
self._anim_opacity.start(self._label)
def _check_collapse_progress(self, animation, instance, progress) -> None:
if progress > 0.1:
if not self._anim_opacity:
self.set_opacity_text_button(0)
def _check_expand_progress(self, animation, instance, progress) -> None:
if progress > 0.3:
if not self._anim_opacity:
self.set_opacity_text_button(1)
class MotionDialogBehavior(ScaleBehavior, MotionBase):
"""
Base class for dialog movement behavior.
For more information, see in the
:class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior`
:class:`~MotionBase`
classes documentation.
"""
show_transition = StringProperty("out_expo")
"""
The type of transition of the widget opening.
:attr:`show_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'out_expo'`.
"""
show_button_container_transition = StringProperty("out_circ")
"""
The type of transition of the widget opening.
:attr:`show_button_container_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'out_circ'`.
"""
hide_transition = StringProperty("out_circ")
"""
The type of transition of the widget opening.
:attr:`show_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'hide_transition'`.
"""
show_duration = NumericProperty(0.4)
"""
Duration of widget display transition.
:attr:`show_duration` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.2`.
"""
def on_dismiss(self, *args):
"""Called when a dialog closed."""
"""Fired when a dialog closed."""
self.set_default_values()
def remove_dialog(*args):
Window.remove_widget(self)
if self._scrim:
Window.remove_widget(self._scrim)
def on_open(self, *args):
"""Called when a dialog opened."""
if self._scrim:
Animation(alpha=0, d=self.hide_duration).start(self._scrim)
Animation(
opacity=1,
scale_value_x=1,
scale_value_y=1,
t=self.show_transition,
d=self.show_duration,
).start(self)
y=self.ids.content_container.y,
t=self.hide_transition,
d=self.hide_duration,
).start(self.ids.button_container)
anim = Animation(
opacity=0,
scale_value_y=0,
t=self.hide_transition,
d=self.hide_duration,
)
anim.bind(on_complete=remove_dialog)
anim.start(self)
def on_open(self, *args):
"""Fired when a dialog opened."""
def open(*args):
self.scale_value_y = 0
self.scale_value_center = (0, self.center[1] + self.height / 2)
Animation(
opacity=1,
scale_value_y=1,
t=self.show_transition,
d=self.show_duration,
).start(self)
Animation(
y=dp(24),
t=self.show_button_container_transition,
d=self.show_duration + 0.15,
).start(self.ids.button_container)
if self._scrim:
Animation(alpha=0.4, d=self.show_duration).start(self._scrim)
Clock.schedule_once(open)
class MotionShackBehavior(StencilBehavior, MotionBase):
class MotionPickerBehavior(MotionDialogBehavior):
"""
Base class for date/time pickers movement behavior.
For more information, see in the
:class:`~MotionDialogBehavior` class documentation.
"""
_scrim = ObjectProperty()
def on_open(self, *args):
"""Fired when a dialog opened."""
def open(*args):
self.scale_value_y = 0
self.scale_value_center = (0, self.center[1] + self.height / 2)
Animation(
opacity=1,
scale_value_y=1,
t=self.show_transition,
d=self.show_duration,
).start(self)
if self._scrim:
Animation(alpha=0.4, d=self.show_duration).start(self._scrim)
Clock.schedule_once(open)
def on_dismiss(self, *args):
"""Fired when a dialog closed."""
def remove_dialog(*args):
Window.remove_widget(self)
if self._scrim:
Window.remove_widget(self._scrim)
self.dispatch("on_dismiss")
if self._scrim:
Animation(alpha=0, d=self.hide_duration).start(self._scrim)
anim = Animation(
opacity=0,
scale_value_y=0,
t=self.hide_transition,
d=self.hide_duration,
)
anim.bind(on_complete=remove_dialog)
anim.start(self)
class MotionTimePickerBehavior(MotionPickerBehavior):
"""
Base class for time picker movement behavior.
For more information, see in the
:class:`~MotionPickerBehavior` class documentation.
"""
class MotionDatePickerBehavior(MotionPickerBehavior):
"""
Base class for date picker movement behavior.
For more information, see in the
:class:`~MotionPickerBehavior` class documentation.
"""
class MotionShackBehavior(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.
:class:`~MotionBase` class documentation.
"""
_interval = 0
_height = 0
def on_dismiss(self, *args):
"""Called when a snackbar closed."""
"""Fired when a snackbar closed."""
def remove_snackbar(*args):
Window.parent.remove_widget(self)
@ -257,7 +468,7 @@ class MotionShackBehavior(StencilBehavior, MotionBase):
anim.start(self)
def on_open(self, *args):
"""Called when a snackbar opened."""
"""Fired when a snackbar opened."""
def open(*args):
self._height = self.height
@ -274,9 +485,9 @@ class MotionShackBehavior(StencilBehavior, MotionBase):
)
)
anim.start(self)
self.dispatch("on_open")
Clock.schedule_once(open)
self.dispatch("on_open")
Clock.schedule_once(open, 0.2)
def _wait_interval(self, interval):
self._interval += interval

View file

@ -285,11 +285,18 @@ class CommonRipple:
and defaults to `'ripple_func_out'`.
"""
ripple_effect = BooleanProperty(True)
"""
Should I use the ripple effect.
:attr:`ripple_effect` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `True`.
"""
_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:
@ -333,6 +340,8 @@ class CommonRipple:
anim.start(self)
def anim_complete(self, *args) -> None:
"""Fired when the "fade_out" animation complete."""
self._doing_ripple = False
self._finishing_ripple = False
self._fading_out = False
@ -378,7 +387,7 @@ class CommonRipple:
if self.ripple_color:
pass
elif hasattr(self, "theme_cls"):
self.ripple_color = self.theme_cls.ripple_color
self.ripple_color = self.theme_cls.rippleColor
else:
# If no theme, set Gray 300.
self.ripple_color = [
@ -429,7 +438,11 @@ class RectangularRippleBehavior(CommonRipple):
"""
def lay_canvas_instructions(self) -> None:
if self._no_ripple_effect:
"""
Adds graphic instructions to the canvas to implement ripple animation.
"""
if not self.ripple_effect:
return
with self.canvas.after if self.ripple_canvas_after else self.canvas.before:
@ -493,7 +506,7 @@ class CircularRippleBehavior(CommonRipple):
"""
def lay_canvas_instructions(self) -> None:
if self._no_ripple_effect:
if not self.ripple_effect:
return
with self.canvas.after if self.ripple_canvas_after else self.canvas.before:

View file

@ -0,0 +1,537 @@
# TODO: Add docs.
"""
Behaviors/State Layer
=====================
.. seealso::
`Material Design spec, State layers <https://m3.material.io/foundations/interaction/states/state-layers>`_
"""
from kivy import platform
from kivy.lang import Builder
from kivy.properties import ColorProperty, NumericProperty
from kivymd.uix.behaviors.focus_behavior import FocusBehavior
Builder.load_string(
"""
<StateLayerBehavior>
canvas.after:
Color
rgba: self.state_layer_color
RoundedRectangle:
group: "State_layer_instruction"
size: self.size
pos: self.pos
radius: self.radius if hasattr(self, "radius") else [0, ]
""",
filename="StateLayerBehavior.kv",
)
# TODO: Add methods `set_text_color` and `set_icon_color`
# (methods that set the color of text and icons in the state
# `on_enter` and `on_leave` and `pressed`).
class StateLayerBehavior(FocusBehavior):
state_layer_color = ColorProperty([0, 0, 0, 0])
"""
The color of the layer state.
:attr:`state_layer_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
"""
state_hover = NumericProperty(0.08)
"""
The transparency level of the layer as a percentage when hovering.
:attr:`state_hover` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0.08`.
"""
state_press = NumericProperty(0.12)
"""
The transparency level of the layer as a percentage when pressed.
:attr:`state_press` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0.12`.
"""
state_drag = NumericProperty(0.16)
"""
The transparency level of the layer as a percentage when dragged.
:attr:`state_drag` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0.16`.
"""
# The transparency value of the disabled state.
# These values are specified in the M3 specification.
# -------------------------------------------------------------------------
# MDIconButton
# -------------------------------------------------------------------------
# Filled.
icon_button_filled_opacity_value_disabled_container = NumericProperty(0.12)
icon_button_filled_opacity_value_disabled_icon = NumericProperty(0.38)
# Tonal.
icon_button_tonal_opacity_value_disabled_container = NumericProperty(0.12)
icon_button_tonal_opacity_value_disabled_icon = NumericProperty(0.38)
# Outlined.
icon_button_outlined_opacity_value_disabled_container = NumericProperty(
0.12
)
icon_button_outlined_opacity_value_disabled_line = NumericProperty(0.12)
icon_button_outlined_opacity_value_disabled_icon = NumericProperty(0.38)
# Standard.
icon_button_standard_opacity_value_disabled_icon = NumericProperty(0.38)
# -------------------------------------------------------------------------
# MDFabButton
# -------------------------------------------------------------------------
fab_button_opacity_value_disabled_container = NumericProperty(0.12)
fab_button_opacity_value_disabled_icon = NumericProperty(0.38)
# -------------------------------------------------------------------------
# MDButton
# -------------------------------------------------------------------------
# Filled.
button_filled_opacity_value_disabled_container = NumericProperty(0.12)
button_filled_opacity_value_disabled_icon = NumericProperty(0.38)
button_filled_opacity_value_disabled_text = NumericProperty(0.38)
# Tonal.
button_tonal_opacity_value_disabled_container = NumericProperty(0.12)
button_tonal_opacity_value_disabled_icon = NumericProperty(0.38)
button_tonal_opacity_value_disabled_text = NumericProperty(0.38)
# Outlined.
button_outlined_opacity_value_disabled_container = NumericProperty(0.12)
button_outlined_opacity_value_disabled_line = NumericProperty(0.12)
button_outlined_opacity_value_disabled_icon = NumericProperty(0.38)
button_outlined_opacity_value_disabled_text = NumericProperty(0.38)
# Elevated.
button_elevated_opacity_value_disabled_container = NumericProperty(0.12)
button_elevated_opacity_value_disabled_icon = NumericProperty(0.38)
button_elevated_opacity_value_disabled_text = NumericProperty(0.38)
# Text.
button_text_opacity_value_disabled_icon = NumericProperty(0.38)
button_text_opacity_value_disabled_text = NumericProperty(0.38)
# -------------------------------------------------------------------------
# MDLabel
# -------------------------------------------------------------------------
label_opacity_value_disabled_text = NumericProperty(0.38)
# -------------------------------------------------------------------------
# MDCard
# -------------------------------------------------------------------------
card_filled_opacity_value_disabled_state_container = NumericProperty(0.38)
card_outlined_opacity_value_disabled_state_container = NumericProperty(0.12)
card_opacity_value_disabled_state_elevated_container = NumericProperty(0.38)
# -------------------------------------------------------------------------
# MDSegmentedButton
# -------------------------------------------------------------------------
segmented_button_opacity_value_disabled_container = NumericProperty(0.12)
segmented_button_opacity_value_disabled_container_active = NumericProperty(
0.38
)
segmented_button_opacity_value_disabled_line = NumericProperty(0.12)
segmented_button_opacity_value_disabled_icon = NumericProperty(0.38)
segmented_button_opacity_value_disabled_text = NumericProperty(0.38)
# -------------------------------------------------------------------------
# MDChip
# -------------------------------------------------------------------------
chip_opacity_value_disabled_container = NumericProperty(0.12)
chip_opacity_value_disabled_text = NumericProperty(0.38)
chip_opacity_value_disabled_icon = NumericProperty(0.38)
# -------------------------------------------------------------------------
# MDSwitch
# -------------------------------------------------------------------------
switch_opacity_value_disabled_line = NumericProperty(0.12)
switch_opacity_value_disabled_container = NumericProperty(0.12)
switch_thumb_opacity_value_disabled_container = NumericProperty(0.38)
switch_opacity_value_disabled_icon = NumericProperty(0.38)
# -------------------------------------------------------------------------
# MDCheckbox
# -------------------------------------------------------------------------
checkbox_opacity_value_disabled_container = NumericProperty(0.38)
# -------------------------------------------------------------------------
# List
# -------------------------------------------------------------------------
list_opacity_value_disabled_container = NumericProperty(0.38)
list_opacity_value_disabled_leading_avatar = NumericProperty(0.38)
# -------------------------------------------------------------------------
# MDTextField
# -------------------------------------------------------------------------
text_field_filled_opacity_value_disabled_state_container = NumericProperty(
0.18
)
text_field_outlined_opacity_value_disabled_state_container = (
NumericProperty(0)
)
text_field_opacity_value_disabled_max_length_label = NumericProperty(0.60)
text_field_opacity_value_disabled_helper_text_label = NumericProperty(0.60)
text_field_opacity_value_disabled_hint_text_label = NumericProperty(0.60)
text_field_opacity_value_disabled_leading_icon = NumericProperty(0.60)
text_field_opacity_value_disabled_trailing_icon = NumericProperty(0.60)
text_field_opacity_value_disabled_line = NumericProperty(0.12)
_state = 0.0
_bg_color = (0, 0, 0, 0)
_is_already_disabled = False
_shadow_softness = [0, 0]
_elevation_level = 0
# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
def set_properties_widget(self) -> None:
"""Fired `on_release/on_press/on_enter/on_leave` events."""
if not self.disabled:
self._restore_properties()
self._set_state_layer_color()
def on_disabled(self, instance, value) -> None:
"""Fired when the `disabled` value changes."""
from kivymd.uix.card import MDCard
from kivymd.uix.button import (
MDIconButton,
MDButton,
MDFabButton,
MDExtendedFabButton,
)
from kivymd.uix.segmentedbutton import MDSegmentedButtonItem
from kivymd.uix.segmentedbutton.segmentedbutton import (
MDSegmentButtonSelectedIcon,
)
from kivymd.uix.selectioncontrol import MDSwitch
from kivymd.uix.list import BaseListItem
from kivymd.uix.textfield import MDTextField
if value and not self._is_already_disabled:
self._is_already_disabled = True
if isinstance(self, MDCard):
self.state_layer_color = (
{
"filled": self.theme_cls.surfaceColor[:-1]
+ [
self.card_filled_opacity_value_disabled_state_container
],
"outlined": self.theme_cls.outlineColor[:-1]
+ [
self.card_outlined_opacity_value_disabled_state_container
],
"elevated": self.theme_cls.surfaceVariantColor[:-1]
+ [
self.card_opacity_value_disabled_state_elevated_container
],
}[self.style]
if not self.md_bg_color_disabled
else self.md_bg_color_disabled
)
elif isinstance(self, MDIconButton):
self.state_layer_color = (
{
"tonal": self.theme_cls.onSurfaceColor[:-1]
+ [
self.icon_button_tonal_opacity_value_disabled_container
],
"filled": self.theme_cls.onSurfaceColor[:-1]
+ [
self.icon_button_filled_opacity_value_disabled_container
],
"outlined": self.theme_cls.onSurfaceColor[:-1]
+ [
self.icon_button_outlined_opacity_value_disabled_container
],
"standard": self.theme_cls.transparentColor,
}[self.style]
if not self.md_bg_color_disabled
else self.md_bg_color_disabled
)
elif isinstance(self, MDButton):
self.state_layer_color = (
{
"elevated": self.theme_cls.onSurfaceColor[:-1]
+ [
self.button_elevated_opacity_value_disabled_container
],
"tonal": self.theme_cls.onSurfaceColor[:-1]
+ [self.button_tonal_opacity_value_disabled_container],
"filled": self.theme_cls.onSurfaceColor[:-1]
+ [self.button_filled_opacity_value_disabled_container],
"outlined": self.theme_cls.onSurfaceColor[:-1]
+ [
self.button_outlined_opacity_value_disabled_container
],
"text": self.theme_cls.transparentColor,
}[self.style]
if not self.md_bg_color_disabled
else self.md_bg_color_disabled
)
elif isinstance(self, (MDFabButton, MDExtendedFabButton)):
self.state_layer_color = (
self.theme_cls.onSurfaceColor[:-1]
+ [self.fab_button_opacity_value_disabled_container]
if not self.md_bg_color_disabled
else self.md_bg_color_disabled
)
elif isinstance(self, MDTextField):
if self.mode == "filled":
self.state_layer_color = self.theme_cls.onSurfaceColor[
:-1
] + [
self.text_field_filled_opacity_value_disabled_state_container
]
else:
self.state_layer_color = self.theme_cls.transparentColor
elif isinstance(self.parent, MDSegmentedButtonItem):
self.state_layer_color = (
self.theme_cls.onSurfaceColor[:-1]
+ [self.segmented_button_opacity_value_disabled_container]
if not self.parent.md_bg_color_disabled
else self.parent.md_bg_color_disabled
)
elif isinstance(self, MDSwitch):
self.state_layer_color = (
(
self.theme_cls.surfaceContainerHighestColor
if not self.active
else self.theme_cls.onSurfaceColor
)[:-1]
+ [self.switch_opacity_value_disabled_container]
if not self.md_bg_color_disabled
else self.md_bg_color_disabled
)
elif isinstance(self, BaseListItem):
self.state_layer_color = (
self.theme_cls.onSurfaceColor[:-1]
+ [self.list_opacity_value_disabled_container]
if not self.md_bg_color_disabled
else self.md_bg_color_disabled
)
elif not value and self._is_already_disabled:
self.state_layer_color = self.theme_cls.transparentColor
self._is_already_disabled = False
def on_enter(self) -> None:
"""Fired when mouse enter the bbox of the widget."""
self._state = self.state_hover
self.set_properties_widget()
def on_leave(self) -> None:
"""Fired when the mouse goes outside the widget border."""
self._state = 0.0
self.set_properties_widget()
def _on_release(self, *args):
"""
Fired when the button is released
(i.e. the touch/click that pressed the button goes away).
"""
if platform in ["android", "ios"]:
self._state = 0.0
self.set_properties_widget()
else:
self.on_enter()
def _on_press(self, *args):
"""Fired when the button is pressed."""
self._state = self.state_press
self.set_properties_widget()
def _restore_properties(self):
if self._state == self.state_hover and self.focus_behavior:
if hasattr(self, "elevation_level"):
self._elevation_level = self.elevation_level
if hasattr(self, "shadow_softness"):
self._shadow_softness = self.shadow_softness
if hasattr(self, "md_bg_color"):
self._bg_color = self.md_bg_color
elif not self._state:
if hasattr(self, "elevation_level"):
self.elevation_level = self._elevation_level
if hasattr(self, "shadow_softness"):
self.shadow_softness = self._shadow_softness
if hasattr(self, "bg_color"):
self.bg_color = self._md_bg_color
# FIXME: For some widgets, the color of the state of its elements is
# ignored. For example, for the `MDSwitch` widget, the color of the status
# of the `Thumb` element and the color of the icon are ignored.
def _get_target_color(self):
from kivymd.uix.card import MDCard
from kivymd.uix.button import (
MDIconButton,
MDButton,
MDFabButton,
MDExtendedFabButton,
)
from kivymd.uix.segmentedbutton.segmentedbutton import (
MDSegmentedButtonContainer,
)
from kivymd.uix.chip import MDChip
from kivymd.uix.selectioncontrol import MDSwitch, MDCheckbox
from kivymd.uix.list import BaseListItem
from kivymd.uix.textfield import MDTextField
from kivymd.uix.navigationdrawer import MDNavigationDrawerItem
target_color = None
if not self.disabled:
self._restore_properties()
if isinstance(self, MDTextField):
if self.mode == "filled":
target_color = self.theme_cls.onSurfaceColor
else:
target_color = self.theme_cls.transparentColor
elif isinstance(self, (MDCard, BaseListItem)) and not isinstance(
self, MDNavigationDrawerItem
):
target_color = self.theme_cls.onSurfaceColor
elif isinstance(self, MDNavigationDrawerItem):
target_color = self.theme_cls.onSecondaryContainerColor
elif isinstance(self.parent, MDSegmentedButtonContainer):
target_color = (
self.theme_cls.onSurfaceColor
if not self.active
else self.theme_cls.onSecondaryContainerColor
)
elif isinstance(self, MDChip):
# Here, depending on the widget state (focus/pressed...)
# we set the target color of the widget's layer.
# For example:
#
# if self._state == self.state_press:
# target_color = [., ., ., .]
# else:
# ...
if self.type == "assist":
target_color = self.theme_cls.onSurfaceColor
elif self.type in ["filter", "input", "suggestion"]:
target_color = self.theme_cls.onSurfaceVariantColor
elif isinstance(self, MDIconButton):
if self.style == "filled":
target_color = self.theme_cls.onPrimaryColor
elif self.style == "tonal":
target_color = self.theme_cls.onSecondaryContainerColor
elif self.style in ["outlined", "standard"]:
target_color = self.theme_cls.onSurfaceVariantColor
elif isinstance(self, MDButton):
target_color = (
self.theme_cls.onPrimaryColor
if self.style == "filled"
else self.theme_cls.primaryColor
)
elif isinstance(self, MDCheckbox):
target_color = (
self.theme_cls.primaryColor
if self.active
else self.theme_cls.onSurfaceColor
)
elif isinstance(self, (MDFabButton, MDExtendedFabButton)):
target_color = self.theme_cls.onPrimaryContainerColor
elif isinstance(self, MDSwitch):
target_color = (
self.theme_cls.primaryColor
if self.active
else self.theme_cls.onSurfaceVariantColor
)
else:
target_color = self.theme_cls.onSurfaceColor
return target_color
def _set_state_layer_color(self):
from kivymd.uix.card import MDCard
from kivymd.uix.button import (
MDIconButton,
MDButton,
MDFabButton,
MDExtendedFabButton,
)
from kivymd.uix.segmentedbutton.segmentedbutton import (
MDSegmentedButtonContainer,
)
from kivymd.uix.chip import MDChip
from kivymd.uix.selectioncontrol import MDSwitch, MDCheckbox
from kivymd.uix.list import BaseListItem
from kivymd.uix.textfield import MDTextField
from kivymd.uix.tab.tab import MDTabsItemBase
target_color = self._get_target_color()
if (
isinstance(
self,
(
MDCard,
MDTextField,
MDIconButton,
MDButton,
MDFabButton,
MDExtendedFabButton,
MDChip,
MDSwitch,
MDCheckbox,
BaseListItem,
MDTabsItemBase,
),
)
or isinstance(self.parent, MDSegmentedButtonContainer)
and target_color
):
if self._state == self.state_hover and self.focus_behavior:
if (
not self.focus_color
or self.theme_cls.dynamic_color
and self.theme_focus_color == "Primary"
):
if (
isinstance(self, MDTextField)
and self.mode == "outlined"
):
self.state_layer_color = target_color
else:
self.state_layer_color = target_color[:-1] + [
self._state
]
else:
self.state_layer_color = self.focus_color
elif self._state == self.state_press:
self.state_layer_color = target_color[:-1] + [self._state]
elif not self._state:
self.state_layer_color = target_color[:-1] + [self._state]

View file

@ -63,7 +63,7 @@ KivyMD
#:import images_path kivymd.images_path
MDCarousel:
Carousel:
StencilImage:
size_hint: .9, .8

View file

@ -125,25 +125,16 @@ You can inherit the ``MyToggleButton`` class only from the following classes
- :class:`~kivymd.uix.button.MDFillRoundFlatIconButton`
"""
__all__ = ("MDToggleButton",)
__all__ = ("MDToggleButtonBehavior",)
from kivy import Logger
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,
)
from kivymd.uix.button import MDButton, MDIconButton, MDFabButton, BaseButton
class MDToggleButton(ToggleButtonBehavior):
class MDToggleButtonBehavior(ToggleButtonBehavior):
background_normal = ColorProperty(None)
"""
Color of the button in ``rgba`` format for the 'normal' state.
@ -180,38 +171,29 @@ class MDToggleButton(ToggleButtonBehavior):
def __init__(self, **kwargs):
super().__init__(**kwargs)
classinfo = (
MDRaisedButton,
MDFlatButton,
MDRectangleFlatButton,
MDRectangleFlatIconButton,
MDRoundFlatButton,
MDRoundFlatIconButton,
MDFillRoundFlatButton,
MDFillRoundFlatIconButton,
)
classinfo = (MDButton, MDIconButton, MDFabButton)
# 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}"
)
else:
print(666, self.md_bg_color)
# self.theme_bg_color = "Custom"
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,
),
):
if isinstance(self, BaseButton):
print(111)
self.__is_filled = True
self.background_normal = self.theme_cls.primary_color
self.background_normal = (
"yellow" # self.theme_cls.primary_color
)
# If not background_normal must be the same as the inherited one.
else:
print(222)
self.background_normal = (
self.md_bg_color[:] if self.md_bg_color else (0, 0, 0, 0)
)
@ -228,25 +210,44 @@ class MDToggleButton(ToggleButtonBehavior):
# self.bind(state=self._update_bg)
self.fbind("state", self._update_bg)
def _update_bg(self, ins, val):
def _update_bg(self, instance, value):
"""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.theme_bg_color == "Primary":
self.theme_bg_color = "Custom"
if self.theme_icon_color == "Primary":
self.theme_icon_color = "Custom"
if value == "down":
if isinstance(self, MDIconButton):
self.md_bg_color = self.theme_cls.primaryColor
self.icon_color = self.theme_cls.onPrimaryColor
# if (
# self.__is_filled is False
# ):
# 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 isinstance(self, MDIconButton):
self.md_bg_color = self.theme_cls.surfaceContainerHighestColor
self.icon_color = self.theme_cls.primaryColor
if issubclass(self.__class__, ButtonContentsIconText):
self.icon_color = self.text_color
# if (
# self.__is_filled is False
# ):
# self.text_color = self.font_color_normal
# if issubclass(self.__class__, ButtonContentsIconText):
# self.icon_color = self.text_color
class MDToggleButton(MDToggleButtonBehavior):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Logger.warning(
f"KivyMD: "
f"The `{self.__class__.__name__}` class has been deprecated. "
f"Use the `MDToggleButtonBehavior` class instead."
)

View file

@ -16,21 +16,32 @@ Usage
.. code-block:: python
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivymd.app import MDApp
from kivymd.uix.behaviors import TouchBehavior
from kivymd.uix.button import MDRaisedButton
from kivymd.uix.button import MDButton
KV = '''
MDScreen:
<TouchBehaviorButton>
style: "elevated"
MyButton:
text: "PRESS ME"
MDButtonText:
text: root.text
MDScreen:
md_bg_color: self.theme_cls.backgroundColor
TouchBehaviorButton:
text: "TouchBehavior"
pos_hint: {"center_x": .5, "center_y": .5}
'''
class MyButton(MDRaisedButton, TouchBehavior):
class TouchBehaviorButton(MDButton, TouchBehavior):
text = StringProperty()
def on_long_touch(self, *args):
print("<on_long_touch> event")
@ -41,12 +52,12 @@ Usage
print("<on_triple_tap> event")
class MainApp(MDApp):
class Example(MDApp):
def build(self):
return Builder.load_string(KV)
MainApp().run()
Example().run()
"""
__all__ = ("TouchBehavior",)
@ -85,16 +96,18 @@ class TouchBehavior:
self.on_triple_tap(touch, *args)
def delete_clock(self, widget, touch, *args):
"""Removes a key event from `touch.ud`."""
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."""
"""Fired when the widget is pressed for a long time."""
def on_double_tap(self, touch, *args):
"""Called by double clicking on the widget."""
"""Fired by double-clicking on the widget."""
def on_triple_tap(self, touch, *args):
"""Called by triple clicking on the widget."""
"""Fired by triple clicking on the widget."""

View file

@ -1,2 +0,0 @@
# NOQA F401
from .bottomnavigation import MDBottomNavigation, MDBottomNavigationItem

View file

@ -1,118 +0,0 @@
#: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

View file

@ -1,880 +0,0 @@
"""
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

View file

@ -1,11 +1,7 @@
# NOQA F401
from .bottomsheet import (
MDBottomSheet,
MDBottomSheetContent,
MDBottomSheetDragHandle,
MDBottomSheetDragHandleButton,
MDBottomSheetDragHandleTitle,
MDCustomBottomSheet,
MDGridBottomSheet,
MDListBottomSheet,
)

View file

@ -1,6 +1,4 @@
<MDBottomSheetContent>
size_hint_y: None
height: self.minimum_height
#:import Window kivy.core.window.Window
<MDBottomSheetDragHandle>
@ -10,13 +8,19 @@
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
canvas:
Color:
rgba:
app.theme_cls.disabled_hint_text_color \
if not root.drag_handle_color else \
root.drag_handle_color
SmoothRoundedRectangle:
pos: self.pos
size: self.size
radius: [dp(4), ]
size_hint: None, None
size: "32dp", "4dp"
radius: 4
pos_hint: {"center_x": .5}
BottomSheetDragHandleContainer:
@ -27,16 +31,14 @@
<MDBottomSheet>
orientation: "vertical"
md_bg_color: root.bg_color if root.bg_color else app.theme_cls.bg_darkest
radius: 16, 16, 0, 0
radius: "16dp", "16dp", 0, 0
padding: 0, "8dp", 0, 0
-x: 0
width: Window.width if Window.width <= dp(640) else dp(640)
pos_hint: {"center_x": .5}
y: self.height * (self.open_progress - 1)
MDBoxLayout:
BoxLayout:
id: drag_handle_container
size_hint_y: None
height: self.minimum_height
MDBoxLayout:
id: container
size_hint_y: None
height: self.minimum_height

View file

@ -16,7 +16,7 @@ BoxLayout
canvas:
Color:
rgba: app.theme_cls.primary_color
rgba: app.theme_cls.primaryColor
Rectangle:
pos: self.pos
size: self.size
@ -28,7 +28,7 @@ MDBoxLayout
MDBoxLayout:
adaptive_height: True
md_bg_color: app.theme_cls.primary_color
md_bg_color: app.theme_cls.primaryColor
Available options are:
----------------------
@ -38,6 +38,7 @@ Available options are:
- adaptive_size_
.. adaptive_height:
adaptive_height
---------------
@ -53,6 +54,7 @@ Equivalent
height: self.minimum_height
.. adaptive_width:
adaptive_width
--------------
@ -68,6 +70,7 @@ Equivalent
height: self.minimum_width
.. adaptive_size:
adaptive_size
-------------
@ -89,15 +92,24 @@ from kivy.uix.boxlayout import BoxLayout
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
from kivymd.uix.behaviors import DeclarativeBehavior
from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior
class MDBoxLayout(
DeclarativeBehavior, ThemableBehavior, BoxLayout, MDAdaptiveWidget
DeclarativeBehavior,
ThemableBehavior,
BackgroundColorBehavior,
BoxLayout,
MDAdaptiveWidget,
):
"""
Box layout class.
For more information, see in the
:class:`~kivy.uix.boxlayout.BoxLayout` class documentation.
For more information see in the
:class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
:class:`~kivymd.theming.ThemableBehavior` and
:class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
:class:`~kivy.uix.boxlayout.BoxLayout` and
:class:`~kivymd.uix.MDAdaptiveWidget`
classes documentation.
"""

View file

@ -1,17 +1,13 @@
# NOQA F401
from .button import (
BaseButton,
ButtonContentsIconText,
MDFillRoundFlatButton,
MDFillRoundFlatIconButton,
MDFlatButton,
MDFloatingActionButton,
MDFloatingActionButtonSpeedDial,
MDButton,
MDButtonIcon,
MDButtonText,
MDIconButton,
MDRaisedButton,
MDRectangleFlatButton,
MDRectangleFlatIconButton,
MDRoundFlatButton,
MDRoundFlatIconButton,
MDTextButton,
MDFabButton,
BaseButton,
BaseFabButton,
MDExtendedFabButton,
MDExtendedFabButtonIcon,
MDExtendedFabButtonText,
)

View file

@ -1,225 +1,391 @@
<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 \
)
<MDFabButton>
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] \
text_size: self.size
halign: "center"
valign: "center"
size:
{ \
"standard": ("56dp", "56dp"), \
"small": ("40dp", "40dp"), \
"large": ("96dp", "96dp"), \
}[self.style]
radius:
{ \
"standard": [dp(16), ], \
"small": [dp(12), ], \
"large": [dp(28), ], \
}[self.style]
shadow_radius:
{ \
"standard": [dp(14), ], \
"small": [dp(10), ], \
"large": [dp(26), ], \
}[self.style]
shadow_offset: 0, -1
elevation_level:
{ \
"standard": 1, \
"small": 1, \
"large": 1, \
}[self.style]
shadow_color:
( \
self.theme_cls.shadowColor[:-1] + [.5] \
if self.theme_shadow_color == "Primary" else \
self.shadow_color \
) \
if not self.disabled else self.theme_cls.transparentColor
icon_color:
{ \
"surface": self.theme_cls.onPrimaryContainerColor, \
"secondary": self.theme_cls.onSecondaryContainerColor, \
"tertiary": self.theme_cls.onTertiaryContainerColor \
}[self.color_map] \
if self.theme_icon_color == "Primary" else \
( \
self.icon_color \
if self.icon_color else \
self.theme_cls.transparentColor \
)
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
disabled_color:
self.theme_cls.onSurfaceColor[:-1] + \
[self.fab_button_opacity_value_disabled_icon] \
if not self.icon_color_disabled else self.icon_color_disabled
theme_font_size: "Custom"
font_size:
{ \
"standard": "24sp", \
"small": "24sp", \
"large": "36sp", \
}[root.style]
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:
{ \
"surface": self.theme_cls.surfaceColor, \
"secondary": self.theme_cls.secondaryColor, \
"tertiary": self.theme_cls.tertiaryColor \
}[self.color_map] \
if self.theme_bg_color == "Primary" else \
self.md_bg_color
SmoothRoundedRectangle:
size: self.size
pos: self.pos
radius: self.radius
<MDExtendedFabButtonIcon>
x: "16dp"
icon_color:
( \
{ \
"surface": self.theme_cls.onPrimaryContainerColor, \
"secondary": self.theme_cls.onSecondaryContainerColor, \
"tertiary": self.theme_cls.onTertiaryContainerColor \
}[self.parent.color_map] \
if self.theme_icon_color == "Primary" else \
( \
self.icon_color \
if self.icon_color else self.theme_cls.transparentColor \
) \
) \
if self.parent else self.theme_cls.transparentColor
disabled_color:
self.theme_cls.onSurfaceColor[:-1] + \
[self.fab_button_opacity_value_disabled_icon] \
if not self.icon_color_disabled else self.icon_color_disabled
pos_hint: {"center_y": .5}
<MDExtendedFabButtonText>
adaptive_width: True
text_color:
( \
{ \
"surface": self.theme_cls.onPrimaryContainerColor, \
"secondary": self.theme_cls.onSecondaryContainerColor, \
"tertiary": self.theme_cls.onTertiaryContainerColor \
}[self.parent.color_map] \
if self.theme_text_color == "Primary" else self.text_color \
) \
if self.parent else self.text_color
disabled_color:
( \
self.theme_cls.onSurfaceColor[:-1] + \
[self.fab_button_opacity_value_disabled_icon] \
if not self.parent.icon_color_disabled else \
self.parent.icon_color_disabled \
) \
if self.parent else self.theme_cls.transparentColor
pos_hint: {"center_y": .5}
<MDExtendedFabButton>
size_hint: None, None
size: "56dp", "56dp"
radius: [dp(16), ]
shadow_radius: [dp(14), ]
shadow_offset: 0, -1
# shadow_softness: 2
elevation_level: 1
shadow_color:
( \
self.theme_cls.shadowColor \
if self.theme_shadow_color == "Primary" else \
self.shadow_color \
) \
if not self.disabled else self.theme_cls.transparentColor
theme_font_size: "Custom"
font_size: "24sp"
canvas.before:
Color:
rgba:
{ \
"standard": self.theme_cls.surfaceContainerColor \
if self.color_map == "surface" else \
{ \
"secondary": self.theme_cls.secondaryContainerColor, \
"tertiary": self.theme_cls.tertiaryContainerColor \
}[self.color_map], \
"small": self.theme_cls.surfaceContainerHighColor \
if self.color_map == "surface" else \
{ \
"secondary": self.theme_cls.secondaryContainerColor, \
"tertiary": self.theme_cls.tertiaryColor \
}[self.color_map], \
"large": self.theme_cls.surfaceContainerColor \
if self.color_map == "surface" else \
{ \
"secondary": self.theme_cls.secondaryContainerColor, \
"tertiary": self.theme_cls.tertiaryColor \
}[self.color_map], \
}[self.style] \
if self.theme_bg_color == "Primary" else \
self.md_bg_color
SmoothRoundedRectangle:
size: self.size
pos: 0, 0
radius: self.radius
<MDIconButton>
canvas.before:
Color:
group: "md-icon-button-bg-color"
rgba:
( \
{ \
"standard": self.theme_cls.transparentColor, \
"outlined": self.theme_cls.transparentColor, \
"tonal": self.theme_cls.secondaryContainerColor, \
"filled": self.theme_cls.primaryColor, \
}[self.style] \
if self.theme_bg_color == "Primary" else \
self.md_bg_color \
) \
if not self.disabled else \
( \
( \
{ \
"standard": self.theme_cls.transparentColor, \
"outlined": self.theme_cls.transparentColor, \
"tonal": self.theme_cls.onSurfaceColor[:-1] \
+ [self.icon_button_tonal_opacity_value_disabled_container], \
"filled": self.theme_cls.onSurfaceColor[:-1] \
+ [self.icon_button_filled_opacity_value_disabled_container], \
}[self.style] \
) \
if not self.md_bg_color_disabled else self.md_bg_color_disabled \
)
SmoothRoundedRectangle:
size: self.size
pos: self.pos
radius: self.radius
radius: [self.height / 2,]
halign: "center"
valign: "center"
size_hint: None, None
size: dp(40), dp(40)
text_size: self.size
line_color:
( \
( \
self.theme_cls.outlineColor \
if self.theme_line_color == "Primary" else \
( \
self._line_color \
if self._line_color else \
self.line_color \
) \
) \
if not self.disabled else \
self.theme_cls.onSurfaceColor[:-1] + \
[self.icon_button_outlined_opacity_value_disabled_line] \
) \
if self.style == "outlined" else self.theme_cls.transparentColor
icon_color:
( \
{ \
"standard": self.theme_cls.primaryColor, \
"tonal": self.theme_cls.onSecondaryContainerColor, \
"filled": self.theme_cls.onPrimaryColor, \
"outlined": self.theme_cls.onSurfaceVariantColor, \
}[self.style] \
if self.theme_icon_color == "Primary" else \
( \
self.icon_color \
if self.icon_color else self.theme_cls.transparentColor \
) \
)
disabled_color:
{ \
"standard": self.theme_cls.onSurfaceColor[:-1] + \
[self.icon_button_standard_opacity_value_disabled_icon], \
"tonal": self.theme_cls.onSurfaceColor[:-1] + \
[self.icon_button_tonal_opacity_value_disabled_icon], \
"filled": self.theme_cls.onSurfaceColor[:-1] + \
[self.icon_button_filled_opacity_value_disabled_icon], \
"outlined": self.theme_cls.onSurfaceColor[:-1] + \
[self.icon_button_outlined_opacity_value_disabled_icon], \
}[self.style] \
if not self.icon_color_disabled else self.icon_color_disabled
<MDButton>
md_bg_color:
{ \
"elevated": self.theme_cls.surfaceContainerLowColor, \
"filled": self.theme_cls.primaryColor, \
"tonal": self.theme_cls.secondaryContainerColor, \
"outlined": self.theme_cls.transparentColor, \
"text": self.theme_cls.transparentColor, \
}[self.style] \
if self.theme_bg_color == "Primary" else self.md_bg_color
line_color:
( \
( \
self.theme_cls.outlineColor \
if not self.disabled else \
self.theme_cls.onSurfaceColor[:-1] + \
[self.button_outlined_opacity_value_disabled_line] \
) \
if self.style == "outlined" else \
self.theme_cls.transparentColor \
) \
if self.theme_line_color == "Primary" else self.line_color
size_hint_x: None if self.theme_width == "Primary" else self.size_hint_x
size_hint_y: None if self.theme_height == "Primary" else self.size_hint_y
height: "40dp"
elevation: self.elevation_levels[self.elevation_level]
shadow_color:
( \
( \
self.theme_cls.shadowColor[:-1] + [.5] \
if self.theme_shadow_color == "Primary" else \
self.shadow_color \
) \
if self.style not in ["outlined", "text"] else \
self.theme_cls.transparentColor \
) \
if not self.disabled else self.theme_cls.transparentColor
shadow_radius: self.radius
elevation_level:
{ \
"elevated": 1, \
"filled": 0, \
"tonal": 0, \
"outlined": 0, \
"text": 0, \
}[self.style]
shadow_offset: [0, -1] if self.style == "elevated" else [0, 0]
<MDButtonText>
adaptive_size: True
pos_hint: {"center_y": .5}
font_style: "Label"
role: "large"
markup: True
disabled: self._button.disabled if self._button else False
text_color:
( \
( \
( \
{ \
"elevated": self.theme_cls.primaryColor, \
"filled": self.theme_cls.onPrimaryColor, \
"tonal": self.theme_cls.onSecondaryContainerColor, \
"outlined": self.theme_cls.primaryColor, \
"text": self.theme_cls.primaryColor, \
}[self._button.style] \
) \
if self._button else self.theme_cls.transparentColor \
) \
if self.theme_text_color == "Primary" else self.text_color \
)
disabled_color:
( \
{ \
"elevated": self.theme_cls.onSurfaceColor[:-1] + \
[self.button_elevated_opacity_value_disabled_text], \
"filled": self.theme_cls.onSurfaceColor[:-1] + \
[self.button_filled_opacity_value_disabled_text], \
"tonal": self.theme_cls.onSurfaceColor[:-1] + \
[self.button_tonal_opacity_value_disabled_text], \
"outlined": self.theme_cls.onSurfaceColor[:-1] + \
[self.button_outlined_opacity_value_disabled_text], \
"text": self.theme_cls.onSurfaceColor[:-1] + \
[self.button_text_opacity_value_disabled_text], \
}[self._button.style] \
) \
if self._button else self.theme_cls.transparentColor
<MDButtonIcon>
size_hint: None, None
size: "18dp", "18dp"
theme_font_size: "Custom"
font_size: "20sp"
x: "16dp"
pos_hint: {"center_y": .5}
icon_color:
( \
( \
( \
{ \
"elevated": self.theme_cls.primaryColor, \
"filled": self.theme_cls.onPrimaryColor, \
"tonal": self.theme_cls.onSecondaryContainerColor, \
"outlined": self.theme_cls.primaryColor, \
"text": self.theme_cls.primaryColor, \
}[self._button.style] \
) \
if self._button else self.theme_cls.transparentColor \
) \
if self.theme_icon_color == "Primary" else \
( \
self.icon_color \
if self.icon_color else \
self.theme_cls.transparentColor \
) \
)
disabled_color:
( \
{ \
"elevated": self.theme_cls.onSurfaceColor[:-1] + \
[self.button_elevated_opacity_value_disabled_icon], \
"filled": self.theme_cls.onSurfaceColor[:-1] + \
[self.button_filled_opacity_value_disabled_icon], \
"tonal": self.theme_cls.onSurfaceColor[:-1] + \
[self.button_tonal_opacity_value_disabled_icon], \
"outlined": self.theme_cls.onSurfaceColor[:-1] + \
[self.button_outlined_opacity_value_disabled_icon], \
"text": self.theme_cls.onSurfaceColor[:-1] + \
[self.button_text_opacity_value_disabled_icon], \
}[self._button.style] \
if not self.icon_color_disabled else self.icon_color_disabled \
) \
if self._button else self.theme_cls.transparentColor

View file

@ -4,5 +4,4 @@ from .card import (
MDCardSwipe,
MDCardSwipeFrontBox,
MDCardSwipeLayerBox,
MDSeparator,
)

View file

@ -1,9 +1,95 @@
<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
app.theme_cls.secondaryContainerColor \
if self.theme_bg_color == "Primary" else \
self.md_bg_color
<MDCard>
shadow_radius: [(self.radius[0] / 2) + dp(2), ]
shadow_color:
{ \
"filled": self.theme_cls.transparentColor, \
"outlined": self.theme_cls.transparentColor, \
"elevated": self.theme_cls.shadowColor \
if self.theme_shadow_color == "Primary" else \
self.shadow_color, \
}[self.style] \
if not self.disabled else \
{ \
"filled": self.theme_cls.transparentColor, \
"outlined": self.theme_cls.transparentColor, \
"elevated": self.theme_cls.shadowColor[:-1] + [.5] \
if self.theme_shadow_color == "Primary" else \
self.shadow_color[:-1] + [.5],
}[self.style]
shadow_offset:
( \
{ \
"filled": [0, 0], \
"outlined": [0, 0], \
"elevated": [0, -2], \
}[self.style] \
if not self.disabled else \
{ \
"filled": [0, 0], \
"outlined": [0, 0], \
"elevated": [0, -2], \
}[self.style] \
) \
if self.theme_shadow_offset == "Primary" else self.shadow_offset
shadow_softness:
( \
{ \
"filled": 0, \
"outlined": 0, \
"elevated": dp(4), \
}[self.style] \
if not self.disabled else \
{ \
"filled": 0, \
"outlined": 0, \
"elevated": dp(4), \
}[self.style] \
) \
if self.theme_shadow_softness == "Primary" else self.shadow_softness
elevation_level:
( \
( \
{ \
"filled": 0, \
"outlined": 0, \
"elevated": self.elevation_level if self.elevation_level else 1, \
}[self.style] \
) \
if self.theme_elevation_level == "Primary" else self.elevation_level \
) \
if not self.disabled else \
{ \
"filled": 1, \
"outlined": 0, \
"elevated": self.elevation_level if self.elevation_level else 1, \
}[self.style]
elevation: self.elevation_levels[self.elevation_level]
line_color:
(\
( \
self.theme_cls.outlineColor \
if not self.disabled else \
self.theme_cls.onSurfaceColor[:-1] + \
[self.button_outlined_opacity_value_disabled_line] \
) \
if self.style == "outlined" else \
self.theme_cls.transparentColor \
) \
if self.theme_line_color == "Primary" else self.line_color
md_bg_color:
( \
{ \
"filled": app.theme_cls.surfaceContainerHighestColor, \
"outlined": app.theme_cls.surfaceColor, \
"elevated": app.theme_cls.surfaceContainerLowColor, \
}[self.style] \
if self.theme_bg_color == "Primary" else \
self.md_bg_color \
)

View file

@ -1,218 +0,0 @@
"""
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)

View file

@ -1 +1,7 @@
from .chip import MDChip, MDChipText # NOQA F401
from .chip import (
MDChip,
MDChipText,
MDChipLeadingIcon,
MDChipTrailingIcon,
MDChipLeadingAvatar,
) # NOQA F401

View file

@ -1,29 +1,91 @@
<BaseChipIcon>
icon_color:
( \
{ \
"filter": app.theme_cls.onSurfaceVariantColor, \
"suggestion": app.theme_cls.onSurfaceVariantColor, \
"input": app.theme_cls.onSurfaceVariantColor, \
"assist": app.theme_cls.primaryColor, \
}[self._type] \
if self.theme_icon_color == "Primary" else self.icon_color \
) \
if not root.disabled else self.disabled_color
disabled_color:
{ \
"filter": app.theme_cls.onSurfaceColor[:-1] + \
[self.chip_opacity_value_disabled_icon], \
"suggestion": app.theme_cls.onSurfaceColor[:-1] + \
[self.chip_opacity_value_disabled_icon], \
"input": app.theme_cls.onSurfaceColor[:-1] + \
[self.chip_opacity_value_disabled_icon], \
"assist": app.theme_cls.onSurfaceColor[:-1] + \
[self.chip_opacity_value_disabled_icon], \
}[self._type] \
if not self.icon_color_disabled else self.icon_color_disabled
<MDChipText>
font_style: "Label"
role: "large"
text_color:
( \
{ \
"filter": app.theme_cls.onSurfaceVariantColor, \
"suggestion": app.theme_cls.onSurfaceVariantColor, \
"input": app.theme_cls.onSurfaceVariantColor, \
"assist": app.theme_cls.onSurfaceColor, \
}[self._type] \
if root.theme_text_color == "Primary" else root.text_color \
) \
if not root.disabled else self.disabled_color
disabled_color:
{ \
"filter": app.theme_cls.onSurfaceColor[:-1] + \
[self.chip_opacity_value_disabled_text], \
"suggestion": app.theme_cls.onSurfaceColor[:-1] + \
[self.chip_opacity_value_disabled_text], \
"input": app.theme_cls.onSurfaceColor[:-1] + \
[self.chip_opacity_value_disabled_text], \
"assist": app.theme_cls.onSurfaceColor[:-1] + \
[self.chip_opacity_value_disabled_text], \
}[self._type] \
if not self.text_color_disabled else self.text_color_disabled
<MDChip>
size_hint_y: None
height: "32dp"
adaptive_width: True
radius:
16 \
dp(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
(max(self.radius) if max(self.radius) < self.height / 2 else dp(16))
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 \
( \
( \
self.theme_cls.outlineColor \
if not self.disabled else \
self.theme_cls.onSurfaceColor[:-1] + \
[self.chip_opacity_value_disabled_container] \
) \
if self.type != "filter" else \
self.theme_cls.transparentColor \
) \
if self.theme_line_color == "Primary" else \
self._line_color if not self.disabled else \
( \
self.line_color_disabled \
if self.line_color_disabled else \
self._line_color \
)
md_bg_color:
{ \
"filter": self.theme_cls.surfaceContainerLowColor, \
"suggestion": self.theme_cls.surfaceContainerLowColor, \
"input": self.theme_cls.surfaceContainerLowColor, \
"assist": self.theme_cls.surfaceContainerLowColor, \
}[self.type] \
if self.theme_bg_color == "Primary" else self.md_bg_color
LeadingIconContainer:
id: leading_icon_container

View file

@ -4,7 +4,7 @@ Components/Chip
.. seealso::
`Material Design spec, Chips <https://m3.material.io/components/chips/overview>`_
`Material Design 3 spec, Chips <https://m3.material.io/components/chips/overview>`_
.. rubric:: Chips can show multiple interactive elements together in the same
area, such as a list of selectable movie times, or a series of email
@ -17,6 +17,25 @@ Components/Chip
Usage
-----
.. code-block:: kv
MDChip:
MDChipLeadingAvatar: # MDChipLeadingIcon
MDChipText:
MDChipTrailingIcon:
Anatomy
=======
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chip-anatomy.png
:align: center
Example
-------
.. tabs::
.. tab:: Declarative KV style
@ -75,34 +94,6 @@ Usage
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chip.png
:align: center
Anatomy
-------
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/anatomy-chip.png
:align: center
1. Container
2. Label text
3. Leading icon or image (optional)
4. Trailing remove icon (optional, input & filter chips only)
Container
---------
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/radius-chip.png
:align: center
All chips are slightly rounded with an 8dp corner.
Shadows and elevation
---------------------
Chip containers can be elevated if the placement requires protection, such as
on top of an image.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadows-elevation-chip.png
:align: center
The following types of chips are available:
-------------------------------------------
@ -115,6 +106,7 @@ The following types of chips are available:
- Suggestion_
.. Assist:
Assist
------
@ -225,6 +217,7 @@ Example of assist
:align: center
.. Filter:
Filter
------
@ -248,18 +241,22 @@ Example of filtering
from kivymd.app import MDApp
from kivymd.uix.chip import MDChip, MDChipText
from kivymd.uix.list import OneLineIconListItem
from kivymd.uix.list import MDListItem
from kivymd.icon_definitions import md_icons
from kivymd.uix.screen import MDScreen
from kivymd.utils import asynckivy
import asynckivy
Builder.load_string(
'''
<CustomOneLineIconListItem>
IconLeftWidget:
MDListItemLeadingIcon:
icon: root.icon
MDListItemHeadlineText:
text: root.text
<PreviewIconsScreen>
@ -270,11 +267,15 @@ Example of filtering
MDTextField:
id: search_field
hint_text: "Search icon"
mode: "rectangle"
icon_left: "magnify"
mode: "outlined"
on_text: root.set_list_md_icons(self.text, True)
MDTextFieldLeadingIcon:
icon: "magnify"
MDTextFieldHintText:
text: "Search icon"
MDBoxLayout:
id: chip_box
spacing: "12dp"
@ -294,18 +295,19 @@ Example of filtering
orientation: "vertical"
'''
)
class CustomOneLineIconListItem(OneLineIconListItem):
class CustomOneLineIconListItem(MDListItem):
icon = StringProperty()
text = StringProperty()
class PreviewIconsScreen(MDScreen):
filter = ListProperty() # list of tags for filtering icons
def set_filter_chips(self):
'''Asynchronously creates and adds chips to the container.'''
async def set_filter_chips():
for tag in ["Outline", "Off", "On"]:
await asynckivy.sleep(0)
@ -318,7 +320,7 @@ Example of filtering
)
chip.bind(active=lambda x, y, z=tag: self.set_filter(y, z))
self.ids.chip_box.add_widget(chip)
asynckivy.start(set_filter_chips())
def set_filter(self, active: bool, tag: str) -> None:
@ -358,7 +360,6 @@ Example of filtering
def build(self) -> PreviewIconsScreen:
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "LightGreen"
return self.screen
def on_start(self) -> None:
@ -380,7 +381,8 @@ Tap a chip to select it. Multiple chips can be selected or unselected:
from kivymd.app import MDApp
from kivymd.uix.chip import MDChip, MDChipText
from kivymd.uix.screen import MDScreen
from kivymd.utils import asynckivy
import asynckivy
Builder.load_string(
'''
@ -402,10 +404,12 @@ Tap a chip to select it. Multiple chips can be selected or unselected:
MDWidget:
MDFlatButton:
text: "Uncheck chips"
MDButton:
pos: "20dp", "20dp"
on_release: root.unchecks_chips()
MDButtonText:
text: "Uncheck chips"
'''
)
@ -442,7 +446,6 @@ Tap a chip to select it. Multiple chips can be selected or unselected:
def build(self) -> ChipScreen:
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "LightGreen"
return self.screen
def on_start(self) -> None:
@ -465,7 +468,8 @@ menus:
from kivymd.app import MDApp
from kivymd.uix.chip import MDChip, MDChipText
from kivymd.uix.screen import MDScreen
from kivymd.utils import asynckivy
import asynckivy
Builder.load_string(
'''
@ -485,11 +489,6 @@ menus:
spacing: "12dp"
adaptive_height: True
MDFillRoundFlatButton:
text: "Add to cart"
md_bg_color: "green"
size_hint_x: 1
MDWidget:
'''
)
@ -542,6 +541,7 @@ menus:
:align: center
.. Input:
Input
-----
@ -595,6 +595,7 @@ Example of input
:align: center
.. Suggestion:
Suggestion
----------
@ -641,7 +642,7 @@ Example of suggestion
API break
=========
1.1.1 version
1.2.0 version
-------------
.. code-block:: python
@ -670,7 +671,7 @@ API break
Test().run()
1.2.0 version
2.0.0 version
-------------
.. code-block:: python
@ -724,7 +725,6 @@ from kivy.properties import (
BooleanProperty,
ColorProperty,
OptionProperty,
StringProperty,
VariableListProperty,
)
from kivy.uix.behaviors import ButtonBehavior
@ -738,6 +738,7 @@ from kivymd.uix.behaviors import (
ScaleBehavior,
TouchBehavior,
)
from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.label import MDIcon, MDLabel
@ -750,6 +751,29 @@ with open(
class BaseChipIcon(
CircularRippleBehavior, ScaleBehavior, ButtonBehavior, MDIcon
):
icon_color = ColorProperty(None)
"""
Button icon color in (r, g, b, a) or string format.
:attr:`icon_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
icon_color_disabled = ColorProperty(None)
"""
The icon color in (r, g, b, a) or string format of the chip when
the chip is disabled.
.. versionadded:: 2.0.0
:attr:`icon_color_disabled` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
_type = OptionProperty(
"suggestion", options=["assist", "filter", "input", "suggestion"]
)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.ripple_scale = 1.5
@ -760,7 +784,8 @@ class BaseChipIcon(
# icon size according to the standards of material design version 3.
if (
self.font_name == "Icons"
and self.theme_cls.font_styles["Icon"][1] == self.font_size
and self.theme_cls.font_styles["Icon"]["large"]["font-size"]
== self.font_size
):
self.font_size = (
"18sp"
@ -792,10 +817,10 @@ class MDChipLeadingAvatar(BaseChipIcon):
Implements the leading avatar for the chip.
For more information, see in the
:class:`~kivymd.uix.behaviors.CircularRippleBehavior` and
:class:`~kivymd.uix.behaviors.ScaleBehavior` and
:class:`~kivymd.uix.behaviors.ripple_behavior.CircularRippleBehavior` and
:class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and
:class:`~kivy.uix.behaviors.ButtonBehavior` and
:class:`~kivymd.uix.label.MDIcon`
:class:`~kivymd.uix.label.label.MDIcon`
classes documentation.
"""
@ -805,10 +830,10 @@ class MDChipLeadingIcon(BaseChipIcon):
Implements the leading icon for the chip.
For more information, see in the
:class:`~kivymd.uix.behaviors.CircularRippleBehavior` and
:class:`~kivymd.uix.behaviors.ScaleBehavior` and
:class:`~kivymd.uix.behaviors.ripple_behavior.CircularRippleBehavior` and
:class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and
:class:`~kivy.uix.behaviors.ButtonBehavior` and
:class:`~kivymd.uix.label.MDIcon`
:class:`~kivymd.uix.label.label.MDIcon`
classes documentation.
"""
@ -818,10 +843,10 @@ class MDChipTrailingIcon(BaseChipIcon):
Implements the trailing icon for the chip.
For more information, see in the
:class:`~kivymd.uix.behaviors.CircularRippleBehavior` and
:class:`~kivymd.uix.behaviors.ScaleBehavior` and
:class:`~kivymd.uix.behaviors.ripple_behavior.CircularRippleBehavior` and
:class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and
:class:`~kivy.uix.behaviors.ButtonBehavior` and
:class:`~kivymd.uix.label.MDIcon`
:class:`~kivymd.uix.label.label.MDIcon`
classes documentation.
"""
@ -831,9 +856,24 @@ class MDChipText(MDLabel):
Implements the label for the chip.
For more information, see in the
:class:`~kivymd.uix.label.MDLabel` classes documentation.
:class:`~kivymd.uix.label.label.MDLabel` classes documentation.
"""
text_color_disabled = ColorProperty(None)
"""
The text color in (r, g, b, a) or string format of the chip when
the chip is disabled.
.. versionadded:: 2.0.0
:attr:`text_color_disabled` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
_type = OptionProperty(
"suggestion", options=["assist", "filter", "input", "suggestion"]
)
class MDChip(
MDBoxLayout,
@ -841,16 +881,17 @@ class MDChip(
ButtonBehavior,
CommonElevationBehavior,
TouchBehavior,
StateLayerBehavior,
):
"""
Chip class.
For more information, see in the
:class:`~kivymd.uix.boxlayout.MDBoxLayout` and
:class:`~kivymd.uix.behaviors.RectangularRippleBehavior` and
:class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and
:class:`~kivy.uix.behaviors.ButtonBehavior` and
:class:`~kivymd.uix.behaviors.CommonElevationBehavior` and
:class:`~kivymd.uix.behaviors.TouchBehavior`
:class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and
:class:`~kivymd.uix.behaviors.touch_behavior.TouchBehavior`
classes documentation.
"""
@ -862,23 +903,13 @@ class MDChip(
and defaults to `[dp(8), dp(8), dp(8), dp(8)]`.
"""
text = StringProperty(deprecated=True)
"""
Chip text.
.. deprecated:: 1.2.0
:attr:`text` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
type = OptionProperty(
"suggestion", options=["assist", "filter", "input", "suggestion"]
)
"""
Type of chip.
.. versionadded:: 1.2.0
.. versionadded:: 2.0.0
Available options are: `'assist'`, `'filter'`, `'input'`, `'suggestion'`.
@ -886,74 +917,6 @@ class MDChip(
and defaults to `'suggestion'`.
"""
icon_left = StringProperty(deprecated=True)
"""
Chip left icon.
.. versionadded:: 1.0.0
.. deprecated:: 1.2.0
:attr:`icon_left` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
icon_right = StringProperty(deprecated=True)
"""
Chip right icon.
.. versionadded:: 1.0.0
.. deprecated:: 1.2.0
:attr:`icon_right` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
text_color = ColorProperty(None, deprecated=True)
"""
Chip's text color in (r, g, b, a) or string format.
.. deprecated:: 1.2.0
:attr:`text_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
icon_right_color = ColorProperty(None, deprecated=True)
"""
Chip's right icon color in (r, g, b, a) or string format.
.. versionadded:: 1.0.0
.. deprecated:: 1.2.0
:attr:`icon_right_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
icon_left_color = ColorProperty(None, deprecated=True)
"""
Chip's left icon color in (r, g, b, a) or string format.
.. versionadded:: 1.0.0
.. deprecated:: 1.2.0
:attr:`icon_left_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
icon_check_color = ColorProperty(None)
"""
Chip's check icon color in (r, g, b, a) or string format.
.. versionadded:: 1.0.0
:attr:`icon_check_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
active = BooleanProperty(False)
"""
Whether the check is marked or not.
@ -969,12 +932,23 @@ class MDChip(
The background color of the chip in the marked state in (r, g, b, a)
or string format.
.. versionadded:: 1.2.0
.. versionadded:: 2.0.0
:attr:`selected_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
line_color_disabled = ColorProperty(None)
"""
The color of the outline in the disabled state
.. versionadded:: 2.0.0
:attr:`line_color_disabled` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
_line_color = ColorProperty(None)
_current_md_bg_color = ColorProperty(None)
# A flag that disallow ripple animation of the chip
# at the time of clicking the chip icons.
@ -986,11 +960,19 @@ class MDChip(
super().__init__(*args, **kwargs)
def on_long_touch(self, *args) -> None:
"""Fired when the widget is pressed for a long time."""
if self.type == "filter":
self.active = not self.active
def on_line_color(self, instance, value) -> None:
"""Fired when the values of :attr:`line_color` change."""
if not self.disabled:
self._line_color = value
def on_type(self, instance, value: str) -> None:
"""Called when the values of :attr:`type` change."""
"""Fired when the values of :attr:`type` change."""
def adjust_padding(*args):
"""
@ -1069,7 +1051,14 @@ class MDChip(
self.set_chip_bg_color(
self.selected_color
if self.selected_color
else self.theme_cls.primary_color
else {
"filter": self.theme_cls.surfaceContainerLowColor,
"suggestion": self.theme_cls.surfaceContainerLowColor,
"input": self.theme_cls.surfaceContainerLowColor,
"assist": self.theme_cls.surfaceContainerLowColor,
}[self.type]
if self.theme_bg_color == "Primary"
else self.md_bg_color
)
else:
if (
@ -1125,11 +1114,26 @@ class MDChip(
Animation(md_bg_color=color, d=0.2).start(self)
self._anim_complete = not self._anim_complete
def on_press(self, *args):
def on_press(self, *args) -> None:
"""Fired when the button is pressed."""
if self.active:
self.active = False
self._on_press(args)
def on_release(self, *args) -> None:
"""
Fired when the button is released
(i.e. the touch/click that pressed the button goes away).
"""
self._on_release(args)
def add_widget(self, widget, *args, **kwargs):
def set_type(*args):
widget._type = self.type
def add_icon_leading_trailing(container):
if len(container.children):
type_icon = (
@ -1213,6 +1217,7 @@ class MDChip(
container.add_widget(widget)
if isinstance(widget, MDChipText):
Clock.schedule_once(set_type)
widget.adaptive_size = True
widget.pos_hint = {"center_y": 0.5}
if self.type == "suggestion":
@ -1221,12 +1226,14 @@ class MDChip(
lambda x: self.ids.label_container.add_widget(widget)
)
elif isinstance(widget, (MDChipLeadingIcon, MDChipLeadingAvatar)):
Clock.schedule_once(set_type)
Clock.schedule_once(
lambda x: add_icon_leading_trailing(
self.ids.leading_icon_container
)
)
elif isinstance(widget, MDChipTrailingIcon):
Clock.schedule_once(set_type)
Clock.schedule_once(
lambda x: add_icon_leading_trailing(
self.ids.trailing_icon_container
@ -1236,6 +1243,10 @@ class MDChip(
widget,
(LabelTextContainer, LeadingIconContainer, TrailingIconContainer),
):
if isinstance(
widget, (LeadingIconContainer, TrailingIconContainer)
):
Clock.schedule_once(set_type)
return super().add_widget(widget)
def _set_allow_chip_ripple(

View file

@ -16,7 +16,7 @@ MDCircularLayout
from kivymd.app import MDApp
kv = '''
KV = '''
MDScreen:
MDCircularLayout:
@ -26,20 +26,18 @@ MDCircularLayout
'''
class Main(MDApp):
class Example(MDApp):
def build(self):
return Builder.load_string(kv)
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])
)
self.root.ids.container.add_widget(Label(text=f"{x}")
Main().run()
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-layout.png
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-layout-dark.png
:align: center
"""
@ -48,11 +46,10 @@ __all__ = ("MDCircularLayout",)
from math import atan2, cos, degrees, radians, sin
from kivy.properties import BooleanProperty, NumericProperty
from kivymd.uix.floatlayout import MDFloatLayout
from kivy.uix.floatlayout import FloatLayout
class MDCircularLayout(MDFloatLayout):
class MDCircularLayout(FloatLayout):
degree_spacing = NumericProperty(30)
"""
The space between children in degree.
@ -63,7 +60,8 @@ class MDCircularLayout(MDFloatLayout):
circular_radius = NumericProperty(None, allownone=True)
"""
Radius of circle. Radius will be the greatest value in the layout if `circular_radius` if not specified.
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`.
@ -79,7 +77,8 @@ class MDCircularLayout(MDFloatLayout):
max_degree = NumericProperty(360)
"""
Maximum range in degree allowed for each row of widgets before jumping to the next row.
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`.
@ -185,7 +184,7 @@ if __name__ == "__main__":
from kivymd.app import MDApp
kv = """
KV = """
MDScreen:
MDCircularLayout:
@ -194,14 +193,12 @@ MDScreen:
row_spacing: min(self.size) * 0.1
"""
class Main(MDApp):
class Example(MDApp):
def build(self):
return Builder.load_string(kv)
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])
)
self.root.ids.container.add_widget(Label(text=f"{x}"))
Main().run()
Example().run()

View file

@ -1 +0,0 @@
from .datatables import MDDataTable # NOQA F401

Some files were not shown because too many files have changed in this diff Show more