first commit
This commit is contained in:
commit
417e54da96
5696 changed files with 900003 additions and 0 deletions
|
@ -0,0 +1,35 @@
|
|||
"""
|
||||
Behaviors
|
||||
=========
|
||||
|
||||
Modules and classes implementing various behaviors for buttons etc.
|
||||
"""
|
||||
|
||||
from .backgroundcolor_behavior import (
|
||||
BackgroundColorBehavior,
|
||||
SpecificBackgroundColorBehavior,
|
||||
)
|
||||
|
||||
# flake8: NOQA
|
||||
from .declarative_behavior import DeclarativeBehavior
|
||||
from .elevation import (
|
||||
CircularElevationBehavior,
|
||||
CommonElevationBehavior,
|
||||
FakeCircularElevationBehavior,
|
||||
FakeRectangularElevationBehavior,
|
||||
RectangularElevationBehavior,
|
||||
RoundedRectangularElevationBehavior,
|
||||
)
|
||||
from .motion_behavior import (
|
||||
MotionDialogBehavior,
|
||||
MotionShackBehavior,
|
||||
MotionDropDownMenuBehavior,
|
||||
)
|
||||
from .magic_behavior import MagicBehavior
|
||||
from .ripple_behavior import CircularRippleBehavior, RectangularRippleBehavior
|
||||
from .rotate_behavior import RotateBehavior
|
||||
from .scale_behavior import ScaleBehavior
|
||||
from .stencil_behavior import StencilBehavior
|
||||
from .touch_behavior import TouchBehavior
|
||||
|
||||
from .hover_behavior import HoverBehavior # isort:skip
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,290 @@
|
|||
"""
|
||||
Behaviors/Background Color
|
||||
==========================
|
||||
|
||||
.. note:: The following classes are intended for in-house use of the library.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
__all__ = ("BackgroundColorBehavior", "SpecificBackgroundColorBehavior")
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import (
|
||||
ColorProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
OptionProperty,
|
||||
ReferenceListProperty,
|
||||
StringProperty,
|
||||
VariableListProperty,
|
||||
)
|
||||
from kivy.utils import get_color_from_hex
|
||||
|
||||
from kivymd.color_definitions import hue, palette, text_colors
|
||||
from kivymd.theming import ThemeManager
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
#:import RelativeLayout kivy.uix.relativelayout.RelativeLayout
|
||||
|
||||
|
||||
<BackgroundColorBehavior>
|
||||
canvas:
|
||||
PushMatrix
|
||||
Rotate:
|
||||
angle: self.angle
|
||||
origin: self._background_origin
|
||||
Color:
|
||||
rgba: self._md_bg_color
|
||||
RoundedRectangle:
|
||||
group: "Background_instruction"
|
||||
size: self.size
|
||||
pos: self.pos if not isinstance(self, RelativeLayout) else (0, 0)
|
||||
# FIXME: Sometimes the radius has the value [], which get a
|
||||
# `GraphicException:
|
||||
# Invalid radius value, must be list of tuples/numerics` error`
|
||||
radius: root.radius if root.radius else [0, 0, 0, 0]
|
||||
source: root.background
|
||||
Color:
|
||||
rgba: self.line_color if self.line_color else (0, 0, 0, 0)
|
||||
# TODO: maybe we should use SmoothLine,
|
||||
# but this should be tested on all widgets.
|
||||
Line:
|
||||
width: root.line_width
|
||||
rounded_rectangle:
|
||||
[ \
|
||||
self.x,
|
||||
self.y, \
|
||||
self.width, \
|
||||
self.height, \
|
||||
*self.radius, \
|
||||
]
|
||||
PopMatrix
|
||||
""",
|
||||
filename="BackgroundColorBehavior.kv",
|
||||
)
|
||||
|
||||
|
||||
class BackgroundColorBehavior:
|
||||
background = StringProperty()
|
||||
"""
|
||||
Background image path.
|
||||
|
||||
:attr:`background` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
radius = VariableListProperty([0], length=4)
|
||||
"""
|
||||
Canvas radius.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Top left corner slice.
|
||||
MDBoxLayout:
|
||||
md_bg_color: app.theme_cls.primary_color
|
||||
radius: [25, 0, 0, 0]
|
||||
|
||||
:attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
|
||||
and defaults to `[0, 0, 0, 0]`.
|
||||
"""
|
||||
|
||||
# FIXME: in this case, we will not be able to animate this property
|
||||
# using the `Animation` class.
|
||||
md_bg_color = ColorProperty([1, 1, 1, 0])
|
||||
"""
|
||||
The background color of the widget (:class:`~kivy.uix.widget.Widget`)
|
||||
that will be inherited from the :attr:`BackgroundColorBehavior` class.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
Widget:
|
||||
canvas:
|
||||
Color:
|
||||
rgba: 0, 1, 1, 1
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
|
||||
similar to code:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<MyWidget@BackgroundColorBehavior>
|
||||
md_bg_color: 0, 1, 1, 1
|
||||
|
||||
:attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `[1, 1, 1, 0]`.
|
||||
"""
|
||||
|
||||
line_color = ColorProperty([0, 0, 0, 0])
|
||||
"""
|
||||
If a custom value is specified for the `line_color parameter`, the border
|
||||
of the specified color will be used to border the widget:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDBoxLayout:
|
||||
size_hint: .5, .2
|
||||
md_bg_color: 0, 1, 1, .5
|
||||
line_color: 0, 0, 1, 1
|
||||
radius: [24, ]
|
||||
|
||||
.. versionadded:: 0.104.2
|
||||
|
||||
:attr:`line_color` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `[0, 0, 0, 0]`.
|
||||
"""
|
||||
|
||||
line_width = NumericProperty(1)
|
||||
"""
|
||||
Border of the specified width will be used to border the widget.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
:attr:`line_width` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
angle = NumericProperty(0)
|
||||
background_origin = ListProperty(None)
|
||||
|
||||
_background_x = NumericProperty(0)
|
||||
_background_y = NumericProperty(0)
|
||||
_background_origin = ReferenceListProperty(_background_x, _background_y)
|
||||
_md_bg_color = ColorProperty([0, 0, 0, 0])
|
||||
_origin_line_color = ColorProperty(None)
|
||||
_origin_md_bg_color = ColorProperty(None)
|
||||
|
||||
def __init__(self, **kwarg):
|
||||
super().__init__(**kwarg)
|
||||
self.bind(
|
||||
pos=self.update_background_origin,
|
||||
disabled=self.restore_color_origin,
|
||||
)
|
||||
|
||||
def restore_color_origin(self, instance_md_widget, value: bool) -> None:
|
||||
"""Called when the values of :attr:`disabled` change."""
|
||||
|
||||
if not value:
|
||||
if self._origin_line_color:
|
||||
self.line_color = self._origin_line_color
|
||||
if self._origin_md_bg_color:
|
||||
self.md_bg_color = self._origin_md_bg_color
|
||||
|
||||
def on_line_color(self, instance_md_widget, value: list | str) -> None:
|
||||
"""Called when the values of :attr:`line_color` change."""
|
||||
|
||||
if not self.disabled:
|
||||
self._origin_line_color = value
|
||||
|
||||
def on_md_bg_color(self, instance_md_widget, color: list | str):
|
||||
"""Called when the values of :attr:`md_bg_color` change."""
|
||||
|
||||
if (
|
||||
hasattr(self, "theme_cls")
|
||||
and self.theme_cls.theme_style_switch_animation
|
||||
):
|
||||
Animation(
|
||||
_md_bg_color=color,
|
||||
d=self.theme_cls.theme_style_switch_animation_duration,
|
||||
t="linear",
|
||||
).start(self)
|
||||
else:
|
||||
self._md_bg_color = color
|
||||
|
||||
if not self.disabled:
|
||||
self._origin_md_bg_color = color
|
||||
|
||||
def update_background_origin(self, instance_md_widget, pos: list) -> None:
|
||||
"""Called when the values of :attr:`pos` change."""
|
||||
|
||||
if self.background_origin:
|
||||
self._background_origin = self.background_origin
|
||||
else:
|
||||
self._background_origin = self.center
|
||||
|
||||
|
||||
class SpecificBackgroundColorBehavior(BackgroundColorBehavior):
|
||||
background_palette = OptionProperty(
|
||||
"Primary", options=["Primary", "Accent", *palette]
|
||||
)
|
||||
"""
|
||||
See :attr:`kivymd.color_definitions.palette`.
|
||||
|
||||
:attr:`background_palette` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'Primary'`.
|
||||
"""
|
||||
|
||||
background_hue = OptionProperty("500", options=hue)
|
||||
"""
|
||||
See :attr:`kivymd.color_definitions.hue`.
|
||||
|
||||
:attr:`background_hue` is an :class:`~kivy.properties.OptionProperty`
|
||||
and defaults to `'500'`.
|
||||
"""
|
||||
|
||||
specific_text_color = ColorProperty([0, 0, 0, 0.87])
|
||||
"""
|
||||
:attr:`specific_text_color` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `[0, 0, 0, 0.87]`.
|
||||
"""
|
||||
|
||||
specific_secondary_text_color = ColorProperty([0, 0, 0, 0.87])
|
||||
"""
|
||||
:attr:`specific_secondary_text_color`is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `[0, 0, 0, 0.87]`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
if hasattr(self, "theme_cls"):
|
||||
self.theme_cls.bind(
|
||||
primary_palette=self._update_specific_text_color,
|
||||
accent_palette=self._update_specific_text_color,
|
||||
theme_style=self._update_specific_text_color,
|
||||
)
|
||||
self.bind(
|
||||
background_hue=self._update_specific_text_color,
|
||||
background_palette=self._update_specific_text_color,
|
||||
)
|
||||
self._update_specific_text_color(None, None)
|
||||
|
||||
def _update_specific_text_color(
|
||||
self, instance_theme_manager: ThemeManager, theme_style: str
|
||||
) -> None:
|
||||
if hasattr(self, "theme_cls"):
|
||||
palette = {
|
||||
"Primary": self.theme_cls.primary_palette,
|
||||
"Accent": self.theme_cls.accent_palette,
|
||||
}.get(self.background_palette, self.background_palette)
|
||||
else:
|
||||
palette = {"Primary": "Blue", "Accent": "Amber"}.get(
|
||||
self.background_palette, self.background_palette
|
||||
)
|
||||
color = get_color_from_hex(text_colors[palette][self.background_hue])
|
||||
secondary_color = color[:]
|
||||
# Check for black text (need to adjust opacity).
|
||||
if (color[0] + color[1] + color[2]) == 0:
|
||||
color[3] = 0.87
|
||||
secondary_color[3] = 0.54
|
||||
else:
|
||||
secondary_color[3] = 0.7
|
||||
|
||||
if (
|
||||
hasattr(self, "theme_cls")
|
||||
and self.theme_cls.theme_style_switch_animation
|
||||
):
|
||||
Animation(
|
||||
specific_text_color=color,
|
||||
specific_secondary_text_color=secondary_color,
|
||||
d=self.theme_cls.theme_style_switch_animation_duration,
|
||||
t="linear",
|
||||
).start(self)
|
||||
else:
|
||||
self.specific_text_color = color
|
||||
self.specific_secondary_text_color = secondary_color
|
|
@ -0,0 +1,317 @@
|
|||
"""
|
||||
Behaviors/Declarative
|
||||
=====================
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; height: auto;">
|
||||
<iframe
|
||||
src="https://www.youtube.com/embed/_kiaJacLz8o"
|
||||
frameborder="0"
|
||||
allowfullscreen
|
||||
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
As you already know, the Kivy framework provides the best/simplest/modern
|
||||
UI creation tool that allows you to separate the logic of your application
|
||||
from the description of the properties of widgets/GUI components.
|
||||
This tool is named `KV Language <https://kivy.org/doc/stable/guide/lang.html>`_.
|
||||
|
||||
But in addition to creating a user interface using the KV Language Kivy allows
|
||||
you to create user interface elements directly in the Python code.
|
||||
And if you've ever created a user interface in Python code, you know how ugly
|
||||
it looks. Even in the simplest user interface design, which was created using
|
||||
Python code it is impossible to trace the widget tree, because in Python code
|
||||
you build the user interface in an imperative style.
|
||||
|
||||
Imperative style
|
||||
----------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.bottomnavigation import MDBottomNavigation, MDBottomNavigationItem
|
||||
from kivymd.uix.label import MDLabel
|
||||
from kivymd.uix.screen import MDScreen
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
screen = MDScreen()
|
||||
bottom_navigation = MDBottomNavigation(
|
||||
panel_color="#eeeaea",
|
||||
selected_color_background="#97ecf8",
|
||||
text_color_active="white",
|
||||
)
|
||||
|
||||
data = {
|
||||
"screen 1": {"text": "Mail", "icon": "gmail"},
|
||||
"screen 2": {"text": "Discord", "icon": "discord"},
|
||||
"screen 3": {"text": "LinkedIN", "icon": "linkedin"},
|
||||
}
|
||||
for key in data.keys():
|
||||
text = data[key]["text"]
|
||||
navigation_item = MDBottomNavigationItem(
|
||||
name=key, text=text, icon=data[key]["icon"]
|
||||
)
|
||||
navigation_item.add_widget(MDLabel(text=text, halign="center"))
|
||||
bottom_navigation.add_widget(navigation_item)
|
||||
|
||||
screen.add_widget(bottom_navigation)
|
||||
return screen
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-styles-programming.png
|
||||
:align: center
|
||||
|
||||
Take a look at the above code example. This is a very simple UI. But looking
|
||||
at this code, you will not be able to figure the widget tree and understand
|
||||
which UI this code implements. This is named imperative programming style,
|
||||
which is used in Kivy.
|
||||
|
||||
Now let's see how the same code is implemented using the KV language,
|
||||
which uses a declarative style of describing widget properties.
|
||||
|
||||
Declarative style with KV language
|
||||
----------------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(
|
||||
'''
|
||||
MDScreen:
|
||||
|
||||
MDBottomNavigation:
|
||||
panel_color: "#eeeaea"
|
||||
selected_color_background: "#97ecf8"
|
||||
text_color_active: "white"
|
||||
|
||||
MDBottomNavigationItem:
|
||||
name: "screen 1"
|
||||
text: "Mail"
|
||||
icon: "gmail"
|
||||
|
||||
MDLabel:
|
||||
text: "Mail"
|
||||
halign: "center"
|
||||
|
||||
MDBottomNavigationItem:
|
||||
name: "screen 2"
|
||||
text: "Discord"
|
||||
icon: "discord"
|
||||
|
||||
MDLabel:
|
||||
text: "Discord"
|
||||
halign: "center"
|
||||
|
||||
MDBottomNavigationItem:
|
||||
name: "screen 3"
|
||||
text: "LinkedIN"
|
||||
icon: "linkedin"
|
||||
|
||||
MDLabel:
|
||||
text: "LinkedIN"
|
||||
halign: "center"
|
||||
'''
|
||||
)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-styles-programming.png
|
||||
:align: center
|
||||
|
||||
Looking at this code, we can now clearly see the widget tree and their properties.
|
||||
We can quickly navigate through the components of the screen and quickly
|
||||
change/add new properties/widgets. This is named declarative UI creation style.
|
||||
|
||||
But now the KivyMD library allows you to write Python code in a declarative style.
|
||||
Just as it is implemented in Flutter/Jetpack Compose/SwiftUI.
|
||||
|
||||
Declarative style with Python code
|
||||
----------------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.bottomnavigation import MDBottomNavigation, MDBottomNavigationItem
|
||||
from kivymd.uix.label import MDLabel
|
||||
from kivymd.uix.screen import MDScreen
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return (
|
||||
MDScreen(
|
||||
MDBottomNavigation(
|
||||
MDBottomNavigationItem(
|
||||
MDLabel(
|
||||
text="Mail",
|
||||
halign="center",
|
||||
),
|
||||
name="screen 1",
|
||||
text="Mail",
|
||||
icon="gmail",
|
||||
),
|
||||
MDBottomNavigationItem(
|
||||
MDLabel(
|
||||
text="Discord",
|
||||
halign="center",
|
||||
),
|
||||
name="screen 2",
|
||||
text="Discord",
|
||||
icon="discord",
|
||||
),
|
||||
MDBottomNavigationItem(
|
||||
MDLabel(
|
||||
text="LinkedIN",
|
||||
halign="center",
|
||||
),
|
||||
name="screen 3",
|
||||
text="LinkedIN",
|
||||
icon="linkedin",
|
||||
),
|
||||
panel_color="#eeeaea",
|
||||
selected_color_background="#97ecf8",
|
||||
text_color_active="white",
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. note:: The KivyMD library does not support creating Kivy widgets in Python
|
||||
code in a declarative style.
|
||||
|
||||
But you can still use the declarative style of creating Kivy widgets in Python code.
|
||||
To do this, you need to create a new class that will be inherited from the Kivy
|
||||
widget and the :class:`~DeclarativeBehavior` class:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.button import Button
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import DeclarativeBehavior
|
||||
|
||||
|
||||
class DeclarativeStyleBoxLayout(DeclarativeBehavior, BoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return (
|
||||
DeclarativeStyleBoxLayout(
|
||||
Button(),
|
||||
Button(),
|
||||
orientation="vertical",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
Get objects by identifiers
|
||||
--------------------------
|
||||
|
||||
In the declarative style in Python code, the ids parameter of the specified
|
||||
widget will return only the id of the child widget/container, ignoring other ids.
|
||||
Therefore, to get objects by identifiers in declarative style in Python code,
|
||||
you must specify all the container ids in which the widget is nested until you
|
||||
get to the desired id:
|
||||
|
||||
.. code-block::
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.button import MDRaisedButton
|
||||
from kivymd.uix.floatlayout import MDFloatLayout
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return (
|
||||
MDBoxLayout(
|
||||
MDFloatLayout(
|
||||
MDRaisedButton(
|
||||
id="button_1",
|
||||
text="Button 1",
|
||||
pos_hint={"center_x": 0.5, "center_y": 0.5},
|
||||
),
|
||||
id="box_container_1",
|
||||
),
|
||||
MDBoxLayout(
|
||||
MDFloatLayout(
|
||||
MDRaisedButton(
|
||||
id="button_2",
|
||||
text="Button 2",
|
||||
pos_hint={"center_x": 0.5, "center_y": 0.5},
|
||||
),
|
||||
id="float_container",
|
||||
),
|
||||
id="box_container_2",
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def on_start(self):
|
||||
# {
|
||||
# 'box_container_1': <kivymd.uix.floatlayout.MDFloatLayout>,
|
||||
# 'box_container_2': <kivymd.uix.boxlayout.MDBoxLayout object>
|
||||
# }
|
||||
print(self.root.ids)
|
||||
|
||||
# <kivymd.uix.button.button.MDRaisedButton>
|
||||
print(self.root.ids.box_container_2.ids.float_container.ids.button_2)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
Yes, this is not a very good solution, but I think it will be fixed soon.
|
||||
|
||||
.. warning:: Declarative programming style in Python code in the KivyMD library
|
||||
is an experimental feature. Therefore, if you receive errors, do not hesitate
|
||||
to create new issue in the KivyMD repository.
|
||||
"""
|
||||
|
||||
from kivy.properties import StringProperty
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
|
||||
class DeclarativeBehavior:
|
||||
"""
|
||||
Implements the creation and addition of child widgets as declarative
|
||||
programming style.
|
||||
"""
|
||||
|
||||
id = StringProperty()
|
||||
"""
|
||||
Widget ID.
|
||||
|
||||
:attr:`id` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `''`.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
for child in args:
|
||||
if issubclass(child.__class__, Widget):
|
||||
self.add_widget(child)
|
||||
if hasattr(child, "id") and child.id:
|
||||
self.ids[child.id] = child
|
|
@ -0,0 +1,772 @@
|
|||
"""
|
||||
Behaviors/Elevation
|
||||
===================
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Material Design spec, Elevation <https://material.io/design/environment/elevation.html>`_
|
||||
|
||||
.. rubric:: Elevation is the relative distance between two surfaces along the z-axis.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/elevation-previous.png
|
||||
:align: center
|
||||
|
||||
To create an elevation effect, use the :class:`~CommonElevationBehavior` class.
|
||||
For example, let's create a button with a rectangular elevation effect:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. tab:: Declarative style with KV
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import (
|
||||
RectangularRippleBehavior,
|
||||
BackgroundColorBehavior,
|
||||
CommonElevationBehavior,
|
||||
)
|
||||
|
||||
KV = '''
|
||||
<RectangularElevationButton>
|
||||
size_hint: None, None
|
||||
size: "250dp", "50dp"
|
||||
|
||||
|
||||
MDScreen:
|
||||
|
||||
# With elevation effect
|
||||
RectangularElevationButton:
|
||||
pos_hint: {"center_x": .5, "center_y": .6}
|
||||
elevation: 4
|
||||
shadow_offset: 0, -6
|
||||
shadow_softness: 4
|
||||
|
||||
# Without elevation effect
|
||||
RectangularElevationButton:
|
||||
pos_hint: {"center_x": .5, "center_y": .4}
|
||||
'''
|
||||
|
||||
|
||||
class RectangularElevationButton(
|
||||
RectangularRippleBehavior,
|
||||
CommonElevationBehavior,
|
||||
ButtonBehavior,
|
||||
BackgroundColorBehavior,
|
||||
):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.md_bg_color = "red"
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. tab:: Declarative python style
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import (
|
||||
RectangularRippleBehavior,
|
||||
BackgroundColorBehavior,
|
||||
CommonElevationBehavior,
|
||||
)
|
||||
from kivymd.uix.screen import MDScreen
|
||||
|
||||
|
||||
class RectangularElevationButton(
|
||||
RectangularRippleBehavior,
|
||||
CommonElevationBehavior,
|
||||
ButtonBehavior,
|
||||
BackgroundColorBehavior,
|
||||
):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.md_bg_color = "red"
|
||||
self.size_hint = (None, None)
|
||||
self.size = ("250dp", "50dp")
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return (
|
||||
MDScreen(
|
||||
RectangularElevationButton(
|
||||
pos_hint={"center_x": .5, "center_y": .6},
|
||||
elevation=4,
|
||||
shadow_softness=4,
|
||||
shadow_offset=(0, -6),
|
||||
),
|
||||
RectangularElevationButton(
|
||||
pos_hint={"center_x": .5, "center_y": .4},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-elevation-effect.png
|
||||
:align: center
|
||||
|
||||
.. warning::
|
||||
|
||||
If before the KivyMD 1.1.0 library version you used the elevation property
|
||||
with an average value of `12` for the shadow, then starting with the KivyMD
|
||||
1.1.0 library version, the average value of the elevation property will be
|
||||
somewhere `4`.
|
||||
|
||||
Similarly, create a circular button:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. tab:: Declarative style with KV
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import CircularRippleBehavior, CommonElevationBehavior
|
||||
from kivymd.uix.floatlayout import MDFloatLayout
|
||||
|
||||
KV = '''
|
||||
<CircularElevationButton>
|
||||
size_hint: None, None
|
||||
size: "100dp", "100dp"
|
||||
radius: self.size[0] / 2
|
||||
shadow_radius: self.radius[0]
|
||||
md_bg_color: "red"
|
||||
|
||||
MDIcon:
|
||||
icon: "hand-heart"
|
||||
halign: "center"
|
||||
valign: "center"
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
size: root.size
|
||||
pos: root.pos
|
||||
font_size: root.size[0] * .6
|
||||
theme_text_color: "Custom"
|
||||
text_color: "white"
|
||||
|
||||
|
||||
MDScreen:
|
||||
|
||||
CircularElevationButton:
|
||||
pos_hint: {"center_x": .5, "center_y": .6}
|
||||
elevation: 4
|
||||
shadow_softness: 4
|
||||
'''
|
||||
|
||||
|
||||
class CircularElevationButton(
|
||||
CommonElevationBehavior,
|
||||
CircularRippleBehavior,
|
||||
ButtonBehavior,
|
||||
MDFloatLayout,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. tab:: Declarative python style
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.metrics import dp
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import CircularRippleBehavior, CommonElevationBehavior
|
||||
from kivymd.uix.floatlayout import MDFloatLayout
|
||||
from kivymd.uix.label import MDIcon
|
||||
from kivymd.uix.screen import MDScreen
|
||||
|
||||
|
||||
class CircularElevationButton(
|
||||
CommonElevationBehavior,
|
||||
CircularRippleBehavior,
|
||||
ButtonBehavior,
|
||||
MDFloatLayout,
|
||||
):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.size_hint = (None, None)
|
||||
self.size = (dp(100), dp(100))
|
||||
self.radius = dp(100) / 2
|
||||
self.shadow_radius = dp(100) / 2
|
||||
self.md_bg_color = "red"
|
||||
self.add_widget(
|
||||
MDIcon(
|
||||
icon="hand-heart",
|
||||
halign="center",
|
||||
valign="center",
|
||||
pos_hint={"center_x": .5, "center_y": .5},
|
||||
size=self.size,
|
||||
theme_text_color="Custom",
|
||||
text_color="white",
|
||||
font_size=self.size[0] * 0.6,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return (
|
||||
MDScreen(
|
||||
CircularElevationButton(
|
||||
pos_hint={"center_x": .5, "center_y": .5},
|
||||
elevation=4,
|
||||
shadow_softness=4,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-elevation-effect.png
|
||||
:align: center
|
||||
|
||||
Animating the elevation
|
||||
-----------------------
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. tab:: Declarative style with KV
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import CommonElevationBehavior, RectangularRippleBehavior
|
||||
from kivymd.uix.widget import MDWidget
|
||||
|
||||
KV = '''
|
||||
MDScreen:
|
||||
|
||||
ElevatedWidget:
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
size_hint: None, None
|
||||
size: 100, 100
|
||||
md_bg_color: 0, 0, 1, 1
|
||||
elevation: 2
|
||||
radius: 18
|
||||
'''
|
||||
|
||||
|
||||
class ElevatedWidget(
|
||||
CommonElevationBehavior,
|
||||
RectangularRippleBehavior,
|
||||
ButtonBehavior,
|
||||
MDWidget,
|
||||
):
|
||||
_elev = 0 # previous elevation value
|
||||
|
||||
def on_press(self, *args):
|
||||
if not self._elev:
|
||||
self._elev = self.elevation
|
||||
Animation(elevation=self.elevation + 2, d=0.4).start(self)
|
||||
|
||||
def on_release(self, *args):
|
||||
Animation.cancel_all(self, "elevation")
|
||||
Animation(elevation=self._elev, d=0.1).start(self)
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. tab:: Declarative python style
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import CommonElevationBehavior, RectangularRippleBehavior
|
||||
from kivymd.uix.screen import MDScreen
|
||||
from kivymd.uix.widget import MDWidget
|
||||
|
||||
|
||||
class ElevatedWidget(
|
||||
CommonElevationBehavior,
|
||||
RectangularRippleBehavior,
|
||||
ButtonBehavior,
|
||||
MDWidget,
|
||||
):
|
||||
_elev = 0 # previous elevation value
|
||||
|
||||
def on_press(self, *args):
|
||||
if not self._elev:
|
||||
self._elev = self.elevation
|
||||
Animation(elevation=self.elevation + 2, d=0.4).start(self)
|
||||
|
||||
def on_release(self, *args):
|
||||
Animation.cancel_all(self, "elevation")
|
||||
Animation(elevation=self._elev, d=0.1).start(self)
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return (
|
||||
MDScreen(
|
||||
ElevatedWidget(
|
||||
pos_hint={'center_x': .5, 'center_y': .5},
|
||||
size_hint=(None, None),
|
||||
size=(100, 100),
|
||||
md_bg_color="blue",
|
||||
elevation=2,
|
||||
radius=18,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-elevation-animation-effect.gif
|
||||
:align: center
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
__all__ = (
|
||||
"CommonElevationBehavior",
|
||||
"RectangularElevationBehavior",
|
||||
"CircularElevationBehavior",
|
||||
"RoundedRectangularElevationBehavior",
|
||||
"FakeRectangularElevationBehavior",
|
||||
"FakeCircularElevationBehavior",
|
||||
)
|
||||
|
||||
from kivy import Logger
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import (
|
||||
BoundedNumericProperty,
|
||||
ColorProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
VariableListProperty,
|
||||
)
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<CommonElevationBehavior>
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
Scale:
|
||||
x: self.scale_value_x
|
||||
y: self.scale_value_y
|
||||
z: self.scale_value_x
|
||||
origin:
|
||||
self.center \
|
||||
if not self.scale_value_center else \
|
||||
self.scale_value_center
|
||||
Rotate:
|
||||
angle: self.rotate_value_angle
|
||||
axis: tuple(self.rotate_value_axis)
|
||||
origin: self.center
|
||||
Color:
|
||||
rgba:
|
||||
(0, 0, 0, 0) \
|
||||
if self.disabled or not self.elevation else \
|
||||
root.shadow_color
|
||||
BoxShadow:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
offset: root.shadow_offset
|
||||
spread_radius: -(root.shadow_softness), -(root.shadow_softness)
|
||||
blur_radius: root.elevation * 10
|
||||
border_radius:
|
||||
(root.radius if hasattr(self, "radius") else [0, 0, 0, 0]) \
|
||||
if root.shadow_radius == [0.0, 0.0, 0.0, 0.0] else \
|
||||
root.shadow_radius
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class CommonElevationBehavior(Widget):
|
||||
"""
|
||||
Common base class for rectangular and circular elevation behavior.
|
||||
|
||||
For more information, see in the :class:`~kivy.uix.widget.Widget`
|
||||
class documentation.
|
||||
"""
|
||||
|
||||
elevation = BoundedNumericProperty(0, min=0, errorvalue=0)
|
||||
"""
|
||||
Elevation of the widget.
|
||||
|
||||
:attr:`elevation` is an :class:`~kivy.properties.BoundedNumericProperty`
|
||||
and defaults to `0`.
|
||||
"""
|
||||
|
||||
shadow_radius = VariableListProperty([0], length=4)
|
||||
"""
|
||||
Radius of the corners of the shadow.
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
You don't have to use this parameter.
|
||||
The radius of the elevation effect is calculated automatically one way
|
||||
or another based on the radius of the parent widget, for example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
KV = '''
|
||||
MDScreen:
|
||||
|
||||
MDCard:
|
||||
radius: 12, 46, 12, 46
|
||||
size_hint: .5, .3
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
elevation: 2
|
||||
shadow_softness: 4
|
||||
shadow_offset: (2, -2)
|
||||
'''
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-radius.png
|
||||
:align: center
|
||||
|
||||
:attr:`shadow_radius` is an :class:`~kivy.properties.VariableListProperty`
|
||||
and defaults to `[0, 0, 0, 0]`.
|
||||
"""
|
||||
|
||||
shadow_softness = NumericProperty(0.0)
|
||||
"""
|
||||
Softness of the shadow.
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import BackgroundColorBehavior, CommonElevationBehavior
|
||||
|
||||
KV = '''
|
||||
<RectangularElevationButton>
|
||||
size_hint: None, None
|
||||
size: "250dp", "50dp"
|
||||
|
||||
|
||||
MDScreen:
|
||||
|
||||
RectangularElevationButton:
|
||||
pos_hint: {"center_x": .5, "center_y": .6}
|
||||
elevation: 6
|
||||
shadow_softness: 6
|
||||
|
||||
RectangularElevationButton:
|
||||
pos_hint: {"center_x": .5, "center_y": .4}
|
||||
elevation: 6
|
||||
shadow_softness: 12
|
||||
'''
|
||||
|
||||
|
||||
class RectangularElevationButton(CommonElevationBehavior, BackgroundColorBehavior):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.md_bg_color = "blue"
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-softness.png
|
||||
:align: center
|
||||
|
||||
:attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `12`.
|
||||
"""
|
||||
|
||||
shadow_softness_size = BoundedNumericProperty(2, min=2, deprecated=True)
|
||||
"""
|
||||
The value of the softness of the shadow.
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
.. deprecated:: 1.2.0
|
||||
|
||||
:attr:`shadow_softness_size` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `2`.
|
||||
"""
|
||||
|
||||
shadow_offset = ListProperty((0, 0))
|
||||
"""
|
||||
Offset of the shadow.
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import BackgroundColorBehavior, CommonElevationBehavior
|
||||
|
||||
KV = '''
|
||||
<RectangularElevationButton>
|
||||
size_hint: None, None
|
||||
size: "100dp", "100dp"
|
||||
|
||||
|
||||
MDScreen:
|
||||
|
||||
RectangularElevationButton:
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
elevation: 6
|
||||
shadow_radius: 6
|
||||
shadow_softness: 12
|
||||
shadow_offset: -12, -12
|
||||
'''
|
||||
|
||||
|
||||
class RectangularElevationButton(CommonElevationBehavior, BackgroundColorBehavior):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.md_bg_color = "blue"
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-1.png
|
||||
:align: center
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
RectangularElevationButton:
|
||||
shadow_offset: 12, -12
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-2.png
|
||||
:align: center
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
RectangularElevationButton:
|
||||
shadow_offset: 12, 12
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-3.png
|
||||
:align: center
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
RectangularElevationButton:
|
||||
shadow_offset: -12, 12
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-4.png
|
||||
:align: center
|
||||
|
||||
:attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `(0, 0)`.
|
||||
"""
|
||||
|
||||
shadow_color = ColorProperty([0, 0, 0, 0.6])
|
||||
"""
|
||||
Offset of the shadow.
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
RectangularElevationButton:
|
||||
shadow_color: 0, 0, 1, .8
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-color.png
|
||||
:align: center
|
||||
|
||||
:attr:`shadow_color` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `[0, 0, 0, 0.6]`.
|
||||
"""
|
||||
|
||||
scale_value_x = NumericProperty(1)
|
||||
"""
|
||||
X-axis value.
|
||||
|
||||
.. versionadded:: 1.2.0
|
||||
|
||||
:attr:`scale_value_x` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
scale_value_y = NumericProperty(1)
|
||||
"""
|
||||
Y-axis value.
|
||||
|
||||
.. versionadded:: 1.2.0
|
||||
|
||||
:attr:`scale_value_y` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
scale_value_z = NumericProperty(1)
|
||||
"""
|
||||
Z-axis value.
|
||||
|
||||
.. versionadded:: 1.2.0
|
||||
|
||||
:attr:`scale_value_z` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
scale_value_center = ListProperty()
|
||||
"""
|
||||
Origin of the scale.
|
||||
|
||||
.. versionadded:: 1.2.0
|
||||
|
||||
The format of the origin can be either (x, y) or (x, y, z).
|
||||
|
||||
:attr:`scale_value_center` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
||||
|
||||
rotate_value_angle = NumericProperty(0)
|
||||
"""
|
||||
Property for getting/setting the angle of the rotation.
|
||||
|
||||
.. versionadded:: 1.2.0
|
||||
|
||||
:attr:`rotate_value_angle` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0`.
|
||||
"""
|
||||
|
||||
rotate_value_axis = ListProperty((0, 0, 1))
|
||||
"""
|
||||
Property for getting/setting the axis of the rotation.
|
||||
|
||||
.. versionadded:: 1.2.0
|
||||
|
||||
:attr:`rotate_value_axis` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `(0, 0, 1)`.
|
||||
"""
|
||||
|
||||
_elevation = 0
|
||||
|
||||
def on_elevation(self, instance, value) -> None:
|
||||
self._elevation = value
|
||||
|
||||
|
||||
class RectangularElevationBehavior(CommonElevationBehavior):
|
||||
"""
|
||||
.. deprecated:: 1.1.0
|
||||
Use :class:`~CommonElevationBehavior` class instead.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
Logger.warning(
|
||||
"KivyMD: "
|
||||
"The `RectangularElevationBehavior` class has been deprecated. "
|
||||
"Use the `CommonElevationBehavior` class instead.`"
|
||||
)
|
||||
|
||||
|
||||
class CircularElevationBehavior(CommonElevationBehavior):
|
||||
"""
|
||||
.. deprecated:: 1.1.0
|
||||
Use :class:`~CommonElevationBehavior` class instead.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
Logger.warning(
|
||||
"KivyMD: "
|
||||
"The `CircularElevationBehavior` class has been deprecated. "
|
||||
"Use the `CommonElevationBehavior` class instead.`"
|
||||
)
|
||||
|
||||
|
||||
class RoundedRectangularElevationBehavior(CommonElevationBehavior):
|
||||
"""
|
||||
.. deprecated:: 1.1.0
|
||||
Use :class:`~CommonElevationBehavior` class instead.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
Logger.warning(
|
||||
"KivyMD: "
|
||||
"The `RoundedRectangularElevationBehavior` class has been "
|
||||
"deprecated. Use the `CommonElevationBehavior` class instead.`"
|
||||
)
|
||||
|
||||
|
||||
class FakeRectangularElevationBehavior(CommonElevationBehavior):
|
||||
"""
|
||||
.. deprecated:: 1.1.0
|
||||
Use :class:`~CommonElevationBehavior` class instead.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
Logger.warning(
|
||||
"KivyMD: "
|
||||
"The `FakeRectangularElevationBehavior` class has been "
|
||||
"deprecated. Use the `CommonElevationBehavior` class instead."
|
||||
)
|
||||
|
||||
|
||||
class FakeCircularElevationBehavior(CommonElevationBehavior):
|
||||
"""
|
||||
.. deprecated:: 1.1.0
|
||||
Use :class:`~CommonElevationBehavior` class instead.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
Logger.warning(
|
||||
"KivyMD: "
|
||||
"The `FakeCircularElevationBehavior` class has been deprecated. "
|
||||
"Use the `CommonElevationBehavior` class instead."
|
||||
)
|
|
@ -0,0 +1,147 @@
|
|||
"""
|
||||
Behaviors/Focus
|
||||
===============
|
||||
|
||||
.. rubric:: Changing the background color when the mouse is on the widget.
|
||||
|
||||
To apply focus behavior, you must create a new class that is inherited from the
|
||||
widget to which you apply the behavior and from the :class:`FocusBehavior` class.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import RectangularElevationBehavior
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.behaviors.focus_behavior import FocusBehavior
|
||||
|
||||
KV = '''
|
||||
MDScreen:
|
||||
md_bg_color: 1, 1, 1, 1
|
||||
|
||||
FocusWidget:
|
||||
size_hint: .5, .3
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
md_bg_color: app.theme_cls.bg_light
|
||||
|
||||
MDLabel:
|
||||
text: "Label"
|
||||
theme_text_color: "Primary"
|
||||
pos_hint: {"center_y": .5}
|
||||
halign: "center"
|
||||
'''
|
||||
|
||||
|
||||
class FocusWidget(MDBoxLayout, RectangularElevationBehavior, FocusBehavior):
|
||||
pass
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/focus-widget.gif
|
||||
:align: center
|
||||
|
||||
Color change at focus/defocus
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
FocusWidget:
|
||||
focus_color: 1, 0, 1, 1
|
||||
unfocus_color: 0, 0, 1, 1
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/focus-defocus-color.gif
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = ("FocusBehavior",)
|
||||
|
||||
from kivy.app import App
|
||||
from kivy.properties import BooleanProperty, ColorProperty
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.uix.behaviors import HoverBehavior
|
||||
|
||||
|
||||
class FocusBehavior(HoverBehavior, ButtonBehavior):
|
||||
"""
|
||||
Focus behavior class.
|
||||
|
||||
For more information, see in the :class:`~kivymd.uix.behavior.HoverBehavior`
|
||||
and :class:`~kivy.uix.button.ButtonBehavior` classes documentation.
|
||||
|
||||
:Events:
|
||||
:attr:`on_enter`
|
||||
Called when mouse enters the bbox of the widget AND the widget is visible
|
||||
:attr:`on_leave`
|
||||
Called when the mouse exits the widget AND the widget is visible
|
||||
"""
|
||||
|
||||
focus_behavior = BooleanProperty(True)
|
||||
"""
|
||||
Using focus when hovering over a widget.
|
||||
|
||||
:attr:`focus_behavior` is a :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
focus_color = ColorProperty(None)
|
||||
"""
|
||||
The color of the widget when the mouse enters the bbox of the widget.
|
||||
|
||||
:attr:`focus_color` is a :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
unfocus_color = ColorProperty(None)
|
||||
"""
|
||||
The color of the widget when the mouse exits the bbox widget.
|
||||
|
||||
:attr:`unfocus_color` is a :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
def on_enter(self):
|
||||
"""Called when mouse enter the bbox of the widget."""
|
||||
|
||||
if (
|
||||
hasattr(self, "md_bg_color") or hasattr(self, "bg_color")
|
||||
) and self.focus_behavior:
|
||||
if hasattr(self, "theme_cls") and not self.focus_color:
|
||||
color = self.theme_cls.bg_normal
|
||||
else:
|
||||
if not self.focus_color:
|
||||
color = App.get_running_app().theme_cls.bg_normal
|
||||
else:
|
||||
color = self.focus_color
|
||||
self._set_bg_color(color)
|
||||
|
||||
def on_leave(self):
|
||||
"""Called when the mouse exit the widget."""
|
||||
|
||||
if (
|
||||
hasattr(self, "md_bg_color") or hasattr(self, "bg_color")
|
||||
) and self.focus_behavior:
|
||||
if hasattr(self, "theme_cls") and not self.unfocus_color:
|
||||
color = self.theme_cls.bg_light
|
||||
else:
|
||||
if not self.unfocus_color:
|
||||
color = App.get_running_app().theme_cls.bg_light
|
||||
else:
|
||||
color = self.unfocus_color
|
||||
self._set_bg_color(color)
|
||||
|
||||
def _set_bg_color(self, color):
|
||||
if hasattr(self, "md_bg_color"):
|
||||
self.md_bg_color = color
|
||||
elif hasattr(self, "bg_color"):
|
||||
self.bg_color = color
|
|
@ -0,0 +1,234 @@
|
|||
"""
|
||||
Behaviors/Hover
|
||||
===============
|
||||
|
||||
.. rubric:: Changing when the mouse is on the widget and the widget is visible.
|
||||
|
||||
To apply hover behavior, you must create a new class that is inherited from the
|
||||
widget to which you apply the behavior and from the :attr:`HoverBehavior` class.
|
||||
|
||||
In `KV file`:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<HoverItem@MDBoxLayout+HoverBehavior>
|
||||
|
||||
In `python file`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class HoverItem(MDBoxLayout, HoverBehavior):
|
||||
'''Custom item implementing hover behavior.'''
|
||||
|
||||
After creating a class, you must define two methods for it:
|
||||
:attr:`HoverBehavior.on_enter` and :attr:`HoverBehavior.on_leave`, which will be automatically called
|
||||
when the mouse cursor is over the widget and when the mouse cursor goes beyond
|
||||
the widget.
|
||||
|
||||
.. note::
|
||||
|
||||
:class:`~HoverBehavior` will by default check to see if the current Widget is visible (i.e. not covered by a modal or popup and not a part of a Relative Layout, MDTab or Carousel that is not currently visible etc) and will only issue events if the widget is visible.
|
||||
|
||||
To get the legacy behavior that the events are always triggered, you can set `detect_visible` on the Widget to `False`.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import HoverBehavior
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
|
||||
KV = '''
|
||||
Screen
|
||||
|
||||
MDBoxLayout:
|
||||
id: box
|
||||
pos_hint: {'center_x': .5, 'center_y': .5}
|
||||
size_hint: .8, .8
|
||||
md_bg_color: app.theme_cls.bg_darkest
|
||||
'''
|
||||
|
||||
|
||||
class HoverItem(MDBoxLayout, HoverBehavior):
|
||||
'''Custom item implementing hover behavior.'''
|
||||
|
||||
def on_enter(self, *args):
|
||||
'''The method will be called when the mouse cursor
|
||||
is within the borders of the current widget.'''
|
||||
|
||||
self.md_bg_color = (1, 1, 1, 1)
|
||||
|
||||
def on_leave(self, *args):
|
||||
'''The method will be called when the mouse cursor goes beyond
|
||||
the borders of the current widget.'''
|
||||
|
||||
self.md_bg_color = self.theme_cls.bg_darkest
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
self.screen = Builder.load_string(KV)
|
||||
for i in range(5):
|
||||
self.screen.ids.box.add_widget(HoverItem())
|
||||
return self.screen
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hover-behavior.gif
|
||||
:width: 250 px
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = ("HoverBehavior",)
|
||||
|
||||
from kivy.core.window import Window
|
||||
from kivy.properties import BooleanProperty, ObjectProperty
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
|
||||
class HoverBehavior(object):
|
||||
"""
|
||||
:Events:
|
||||
:attr:`on_enter`
|
||||
Called when mouse enters the bbox of the widget AND the widget is visible
|
||||
:attr:`on_leave`
|
||||
Called when the mouse exits the widget AND the widget is visible
|
||||
"""
|
||||
|
||||
hovering = BooleanProperty(False)
|
||||
"""
|
||||
`True`, if the mouse cursor is within the borders of the widget.
|
||||
|
||||
Note that this is set and cleared even if the widget is not visible
|
||||
|
||||
:attr:`hover` is a :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
hover_visible = BooleanProperty(False)
|
||||
"""
|
||||
`True` if hovering is True AND is the current widget is visible
|
||||
|
||||
:attr:`hover_visible` is a :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `False`.
|
||||
"""
|
||||
|
||||
enter_point = ObjectProperty(allownone=True)
|
||||
"""
|
||||
Holds the last position where the mouse pointer crossed into the Widget
|
||||
if the Widget is visible and is currently in a hovering state
|
||||
|
||||
:attr:`enter_point` is a :class:`~kivy.properties.ObjectProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
detect_visible = BooleanProperty(True)
|
||||
"""
|
||||
Should this widget perform the visibility check?
|
||||
|
||||
:attr:`detect_visible` is a :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `True`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.register_event_type("on_enter")
|
||||
self.register_event_type("on_leave")
|
||||
Window.bind(mouse_pos=self.on_mouse_update)
|
||||
super(HoverBehavior, self).__init__(**kwargs)
|
||||
|
||||
def on_mouse_update(self, *args):
|
||||
# If the Widget currently has no parent, do nothing
|
||||
if not self.get_root_window():
|
||||
return
|
||||
pos = args[1]
|
||||
#
|
||||
# is the pointer in the same position as the widget?
|
||||
# If not - then issue an on_exit event if needed
|
||||
#
|
||||
if not self.collide_point(*self.to_widget(*pos)):
|
||||
self.hovering = False
|
||||
self.enter_point = None
|
||||
if self.hover_visible:
|
||||
self.hover_visible = False
|
||||
self.dispatch("on_leave")
|
||||
return
|
||||
|
||||
#
|
||||
# The pointer is in the same position as the widget
|
||||
#
|
||||
|
||||
if self.hovering:
|
||||
#
|
||||
# nothing to do here. Not - this does not handle the case where
|
||||
# a popup comes over an existing hover event.
|
||||
# This seems reasonable
|
||||
#
|
||||
return
|
||||
|
||||
#
|
||||
# Otherwise - set the hovering attribute
|
||||
#
|
||||
self.hovering = True
|
||||
|
||||
#
|
||||
# We need to traverse the tree to see if the Widget is visible
|
||||
#
|
||||
# This is a two stage process:
|
||||
# - first go up the tree to the root Window.
|
||||
# At each stage - check that the Widget is actually visible
|
||||
# - Second - At the root Window check that there is not another branch
|
||||
# covering the Widget
|
||||
#
|
||||
|
||||
self.hover_visible = True
|
||||
if self.detect_visible:
|
||||
widget: Widget = self
|
||||
while True:
|
||||
# Walk up the Widget tree from the target Widget
|
||||
parent = widget.parent
|
||||
try:
|
||||
# See if the mouse point collides with the parent
|
||||
# using both local and glabal coordinates to cover absoluet and relative layouts
|
||||
pinside = parent.collide_point(
|
||||
*parent.to_widget(*pos)
|
||||
) or parent.collide_point(*pos)
|
||||
except Exception:
|
||||
# The collide_point will error when you reach the root Window
|
||||
break
|
||||
if not pinside:
|
||||
self.hover_visible = False
|
||||
break
|
||||
# Iterate upwards
|
||||
widget = parent
|
||||
|
||||
#
|
||||
# parent = root window
|
||||
# widget = first Widget on the current branch
|
||||
#
|
||||
|
||||
children = parent.children
|
||||
for child in children:
|
||||
# For each top level widget - check if is current branch
|
||||
# If it is - then break.
|
||||
# If not then - since we start at 0 - this widget is visible
|
||||
#
|
||||
# Check to see if it should take the hover
|
||||
#
|
||||
if child == widget:
|
||||
# this means that the current widget is visible
|
||||
break
|
||||
if child.collide_point(*pos):
|
||||
# this means that the current widget is covered by a modal or popup
|
||||
self.hover_visible = False
|
||||
break
|
||||
if self.hover_visible:
|
||||
self.enter_point = pos
|
||||
self.dispatch("on_enter")
|
||||
|
||||
def on_enter(self):
|
||||
"""Called when mouse enters the bbox of the widget AND the widget is visible."""
|
||||
|
||||
def on_leave(self):
|
||||
"""Called when the mouse exits the widget AND the widget is visible."""
|
|
@ -0,0 +1,189 @@
|
|||
"""
|
||||
Behaviors/Magic
|
||||
===============
|
||||
|
||||
.. rubric:: Magical effects for buttons.
|
||||
|
||||
.. warning:: Magic effects do not work correctly with `KivyMD` buttons!
|
||||
|
||||
To apply magic effects, you must create a new class that is inherited from the
|
||||
widget to which you apply the effect and from the :attr:`MagicBehavior` class.
|
||||
|
||||
In `KV file`:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<MagicButton@MagicBehavior+MDRectangleFlatButton>
|
||||
|
||||
In `python file`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MagicButton(MagicBehavior, MDRectangleFlatButton):
|
||||
pass
|
||||
|
||||
.. rubric:: The :attr:`MagicBehavior` class provides five effects:
|
||||
|
||||
- :attr:`MagicBehavior.wobble`
|
||||
- :attr:`MagicBehavior.grow`
|
||||
- :attr:`MagicBehavior.shake`
|
||||
- :attr:`MagicBehavior.twist`
|
||||
- :attr:`MagicBehavior.shrink`
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
|
||||
KV = '''
|
||||
<MagicButton@MagicBehavior+MDRectangleFlatButton>
|
||||
|
||||
|
||||
MDFloatLayout:
|
||||
|
||||
MagicButton:
|
||||
text: "WOBBLE EFFECT"
|
||||
on_release: self.wobble()
|
||||
pos_hint: {"center_x": .5, "center_y": .3}
|
||||
|
||||
MagicButton:
|
||||
text: "GROW EFFECT"
|
||||
on_release: self.grow()
|
||||
pos_hint: {"center_x": .5, "center_y": .4}
|
||||
|
||||
MagicButton:
|
||||
text: "SHAKE EFFECT"
|
||||
on_release: self.shake()
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
|
||||
MagicButton:
|
||||
text: "TWIST EFFECT"
|
||||
on_release: self.twist()
|
||||
pos_hint: {"center_x": .5, "center_y": .6}
|
||||
|
||||
MagicButton:
|
||||
text: "SHRINK EFFECT"
|
||||
on_release: self.shrink()
|
||||
pos_hint: {"center_x": .5, "center_y": .7}
|
||||
'''
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/magic-button.gif
|
||||
:width: 250 px
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = ("MagicBehavior",)
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import NumericProperty
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<MagicBehavior>
|
||||
translate_x: 0
|
||||
translate_y: 0
|
||||
scale_x: 1
|
||||
scale_y: 1
|
||||
rotate: 0
|
||||
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
Translate:
|
||||
x: self.translate_x or 0
|
||||
y: self.translate_y or 0
|
||||
Rotate:
|
||||
origin: self.center
|
||||
angle: self.rotate or 0
|
||||
Scale:
|
||||
origin: self.center
|
||||
x: self.scale_x or 1
|
||||
y: self.scale_y or 1
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class MagicBehavior:
|
||||
magic_speed = NumericProperty(1)
|
||||
"""
|
||||
Animation playback speed.
|
||||
|
||||
:attr:`magic_speed` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
def grow(self) -> None:
|
||||
"""Grow effect animation."""
|
||||
|
||||
(
|
||||
Animation(
|
||||
scale_x=1.2,
|
||||
scale_y=1.2,
|
||||
t="out_quad",
|
||||
d=0.03 / self.magic_speed,
|
||||
)
|
||||
+ Animation(
|
||||
scale_x=1, scale_y=1, t="out_elastic", d=0.4 / self.magic_speed
|
||||
)
|
||||
).start(self)
|
||||
|
||||
def shake(self) -> None:
|
||||
"""Shake effect animation."""
|
||||
|
||||
(
|
||||
Animation(translate_x=50, t="out_quad", d=0.02 / self.magic_speed)
|
||||
+ Animation(
|
||||
translate_x=0, t="out_elastic", d=0.5 / self.magic_speed
|
||||
)
|
||||
).start(self)
|
||||
|
||||
def wobble(self) -> None:
|
||||
"""Wobble effect animation."""
|
||||
|
||||
(
|
||||
(
|
||||
Animation(scale_y=0.7, t="out_quad", d=0.03 / self.magic_speed)
|
||||
& Animation(
|
||||
scale_x=1.4, t="out_quad", d=0.03 / self.magic_speed
|
||||
)
|
||||
)
|
||||
+ (
|
||||
Animation(scale_y=1, t="out_elastic", d=0.5 / self.magic_speed)
|
||||
& Animation(
|
||||
scale_x=1, t="out_elastic", d=0.4 / self.magic_speed
|
||||
)
|
||||
)
|
||||
).start(self)
|
||||
|
||||
def twist(self) -> None:
|
||||
"""Twist effect animation."""
|
||||
|
||||
(
|
||||
Animation(rotate=25, t="out_quad", d=0.05 / self.magic_speed)
|
||||
+ Animation(rotate=0, t="out_elastic", d=0.5 / self.magic_speed)
|
||||
).start(self)
|
||||
|
||||
def shrink(self) -> None:
|
||||
"""Shrink effect animation."""
|
||||
|
||||
Animation(
|
||||
scale_x=0.95, scale_y=0.95, t="out_quad", d=0.1 / self.magic_speed
|
||||
).start(self)
|
||||
|
||||
def on_touch_up(self, *args):
|
||||
Animation.stop_all(self)
|
||||
return super().on_touch_up(*args)
|
|
@ -0,0 +1,285 @@
|
|||
"""
|
||||
Behaviors/Motion
|
||||
================
|
||||
|
||||
.. rubric:: Use motion to make a UI expressive and easy to use.
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/motion.png
|
||||
:align: center
|
||||
|
||||
.. versionadded:: 1.2.0
|
||||
|
||||
Classes of the `Motion` type implement the display behavior of widgets such
|
||||
as dialogs, dropdown menu, snack bars, and so on.
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"MotionBase",
|
||||
"MotionDropDownMenuBehavior",
|
||||
"MotionDialogBehavior",
|
||||
"MotionShackBehavior",
|
||||
)
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.clock import Clock
|
||||
from kivy.core.window import Window
|
||||
from kivy.properties import StringProperty, NumericProperty
|
||||
|
||||
from kivymd.uix.behaviors.stencil_behavior import StencilBehavior
|
||||
|
||||
|
||||
class MotionBase:
|
||||
"""Base class for widget display movement behavior."""
|
||||
|
||||
show_transition = StringProperty("linear")
|
||||
"""
|
||||
The type of transition of the widget opening.
|
||||
|
||||
:attr:`show_transition` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'linear'`.
|
||||
"""
|
||||
|
||||
show_duration = NumericProperty(0.2)
|
||||
"""
|
||||
Duration of widget display transition.
|
||||
|
||||
:attr:`show_duration` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.2`.
|
||||
"""
|
||||
|
||||
hide_transition = StringProperty("linear")
|
||||
"""
|
||||
The type of transition of the widget closing.
|
||||
|
||||
:attr:`hide_transition` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'linear'`.
|
||||
"""
|
||||
|
||||
hide_duration = NumericProperty(0.2)
|
||||
"""
|
||||
Duration of widget closing transition.
|
||||
|
||||
:attr:`hide_duration` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.2`.
|
||||
"""
|
||||
|
||||
|
||||
class MotionDropDownMenuBehavior(MotionBase):
|
||||
"""
|
||||
Base class for the dropdown menu movement behavior.
|
||||
|
||||
For more information, see in the :class:`~MotionBase` class documentation.
|
||||
"""
|
||||
|
||||
show_transition = StringProperty("out_back")
|
||||
"""
|
||||
The type of transition of the widget opening.
|
||||
|
||||
:attr:`show_transition` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'out_back'`.
|
||||
"""
|
||||
|
||||
show_duration = NumericProperty(0.4)
|
||||
"""
|
||||
Duration of widget display transition.
|
||||
|
||||
:attr:`show_duration` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.2`.
|
||||
"""
|
||||
|
||||
hide_transition = StringProperty("out_cubic")
|
||||
"""
|
||||
The type of transition of the widget closing.
|
||||
|
||||
:attr:`hide_transition` is a :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'out_cubic'`.
|
||||
"""
|
||||
|
||||
_scale_x = NumericProperty(None)
|
||||
"""
|
||||
Default X-axis scaling values.
|
||||
|
||||
:attr:`_scale_x` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
_scale_y = NumericProperty(None)
|
||||
"""
|
||||
Default Y-axis scaling values.
|
||||
|
||||
:attr:`_scale_y` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
_opacity = NumericProperty(None)
|
||||
"""
|
||||
Menu transparency values.
|
||||
|
||||
:attr:`_opacity` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.set_scale()
|
||||
# self.set_opacity()
|
||||
|
||||
def set_opacity(self) -> None:
|
||||
self._opacity = 0
|
||||
|
||||
def set_scale(self) -> None:
|
||||
self._scale_x = 0
|
||||
self._scale_y = 0
|
||||
|
||||
def on_dismiss(self) -> None:
|
||||
anim = Animation(
|
||||
_scale_x=0,
|
||||
_scale_y=0,
|
||||
# _opacity=0,
|
||||
duration=self.hide_duration,
|
||||
transition=self.hide_transition,
|
||||
)
|
||||
anim.bind(on_complete=lambda *args: Window.remove_widget(self))
|
||||
anim.start(self)
|
||||
|
||||
def on_open(self, *args):
|
||||
anim = Animation(
|
||||
_scale_y=1,
|
||||
# _opacity=1,
|
||||
duration=self.show_duration,
|
||||
transition=self.show_transition,
|
||||
)
|
||||
anim &= Animation(
|
||||
_scale_x=1,
|
||||
duration=self.show_duration - 0.3,
|
||||
transition="out_quad",
|
||||
)
|
||||
anim.start(self)
|
||||
|
||||
def on__opacity(self, instance, value):
|
||||
self.opacity = value
|
||||
|
||||
def on__scale_x(self, instance, value):
|
||||
self.scale_value_x = value
|
||||
|
||||
def on__scale_y(self, instance, value):
|
||||
self.scale_value_y = value
|
||||
|
||||
|
||||
class MotionDialogBehavior(MotionBase):
|
||||
"""
|
||||
Base class for dialog movement behavior.
|
||||
|
||||
For more information, see in the :class:`~MotionBase` class documentation.
|
||||
"""
|
||||
|
||||
show_duration = NumericProperty(0.1)
|
||||
"""
|
||||
Duration of widget display transition.
|
||||
|
||||
:attr:`show_duration` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.1`.
|
||||
"""
|
||||
|
||||
scale_x = NumericProperty(1.5)
|
||||
"""
|
||||
Default X-axis scaling values.
|
||||
|
||||
:attr:`scale_x` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1.5`.
|
||||
"""
|
||||
|
||||
scale_y = NumericProperty(1.5)
|
||||
"""
|
||||
Default Y-axis scaling values.
|
||||
|
||||
:attr:`scale_y` is a :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1.5`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.set_default_values()
|
||||
|
||||
def set_default_values(self):
|
||||
"""Sets default scaled and transparency values."""
|
||||
|
||||
self.scale_value_x = self.scale_x
|
||||
self.scale_value_y = self.scale_y
|
||||
self.opacity = 0
|
||||
|
||||
def on_dismiss(self, *args):
|
||||
"""Called when a dialog closed."""
|
||||
|
||||
self.set_default_values()
|
||||
|
||||
def on_open(self, *args):
|
||||
"""Called when a dialog opened."""
|
||||
|
||||
Animation(
|
||||
opacity=1,
|
||||
scale_value_x=1,
|
||||
scale_value_y=1,
|
||||
t=self.show_transition,
|
||||
d=self.show_duration,
|
||||
).start(self)
|
||||
|
||||
|
||||
class MotionShackBehavior(StencilBehavior, MotionBase):
|
||||
"""
|
||||
The base class for the behavior of the movement of snack bars.
|
||||
|
||||
For more information, see in the
|
||||
:class:`~MotionBase` class and
|
||||
:class:`~kivy.uix.behaviors.stencil_behavior.StencilBehavior` class
|
||||
documentation.
|
||||
"""
|
||||
|
||||
_interval = 0
|
||||
_height = 0
|
||||
|
||||
def on_dismiss(self, *args):
|
||||
"""Called when a snackbar closed."""
|
||||
|
||||
def remove_snackbar(*args):
|
||||
Window.parent.remove_widget(self)
|
||||
self.height = self._height
|
||||
self.dispatch("on_dismiss")
|
||||
|
||||
Clock.unschedule(self._wait_interval)
|
||||
anim = Animation(
|
||||
opacity=0,
|
||||
height=0,
|
||||
t=self.hide_transition,
|
||||
d=self.hide_duration,
|
||||
)
|
||||
anim.bind(on_complete=remove_snackbar)
|
||||
anim.start(self)
|
||||
|
||||
def on_open(self, *args):
|
||||
"""Called when a snackbar opened."""
|
||||
|
||||
def open(*args):
|
||||
self._height = self.height
|
||||
self.height = 0
|
||||
anim = Animation(
|
||||
opacity=1,
|
||||
height=self._height,
|
||||
t=self.show_transition,
|
||||
d=self.show_duration,
|
||||
)
|
||||
anim.bind(
|
||||
on_complete=lambda *args: Clock.schedule_interval(
|
||||
self._wait_interval, 1
|
||||
)
|
||||
)
|
||||
anim.start(self)
|
||||
|
||||
Clock.schedule_once(open)
|
||||
self.dispatch("on_open")
|
||||
|
||||
def _wait_interval(self, interval):
|
||||
self._interval += interval
|
||||
if self._interval > self.duration:
|
||||
self.dismiss()
|
||||
self._interval = 0
|
|
@ -0,0 +1,538 @@
|
|||
"""
|
||||
Behaviors/Ripple
|
||||
================
|
||||
|
||||
.. rubric:: Classes implements a circular and rectangular ripple effects.
|
||||
|
||||
To create a widget with сircular ripple effect, you must create a new class
|
||||
that inherits from the :class:`~CircularRippleBehavior` class.
|
||||
|
||||
For example, let's create an image button with a circular ripple effect:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
from kivy.uix.image import Image
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import CircularRippleBehavior
|
||||
|
||||
KV = '''
|
||||
MDScreen:
|
||||
|
||||
CircularRippleButton:
|
||||
source: "data/logo/kivy-icon-256.png"
|
||||
size_hint: None, None
|
||||
size: "250dp", "250dp"
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
'''
|
||||
|
||||
|
||||
class CircularRippleButton(CircularRippleBehavior, ButtonBehavior, Image):
|
||||
def __init__(self, **kwargs):
|
||||
self.ripple_scale = 0.85
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-ripple-effect.gif
|
||||
:align: center
|
||||
|
||||
To create a widget with rectangular ripple effect, you must create a new class
|
||||
that inherits from the :class:`~RectangularRippleBehavior` class:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import RectangularRippleBehavior, BackgroundColorBehavior
|
||||
|
||||
KV = '''
|
||||
MDScreen:
|
||||
|
||||
RectangularRippleButton:
|
||||
size_hint: None, None
|
||||
size: "250dp", "50dp"
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
'''
|
||||
|
||||
|
||||
class RectangularRippleButton(
|
||||
RectangularRippleBehavior, ButtonBehavior, BackgroundColorBehavior
|
||||
):
|
||||
md_bg_color = [0, 0, 1, 1]
|
||||
|
||||
|
||||
class Example(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Example().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-ripple-effect.gif
|
||||
:align: center
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"CommonRipple",
|
||||
"RectangularRippleBehavior",
|
||||
"CircularRippleBehavior",
|
||||
)
|
||||
|
||||
from typing import NoReturn
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.graphics import (
|
||||
Color,
|
||||
Ellipse,
|
||||
StencilPop,
|
||||
StencilPush,
|
||||
StencilUnUse,
|
||||
StencilUse,
|
||||
)
|
||||
from kivy.graphics.vertex_instructions import RoundedRectangle
|
||||
from kivy.properties import (
|
||||
BooleanProperty,
|
||||
ColorProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.behaviors import ToggleButtonBehavior
|
||||
|
||||
|
||||
class CommonRipple:
|
||||
"""Base class for ripple effect."""
|
||||
|
||||
ripple_rad_default = NumericProperty(1)
|
||||
"""
|
||||
The starting value of the radius of the ripple effect.
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
CircularRippleButton:
|
||||
ripple_rad_default: 100
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-rad-default.gif
|
||||
:align: center
|
||||
|
||||
:attr:`ripple_rad_default` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
ripple_color = ColorProperty(None)
|
||||
"""
|
||||
Ripple color in (r, g, b, a) format.
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
CircularRippleButton:
|
||||
ripple_color: app.theme_cls.primary_color
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-color.gif
|
||||
:align: center
|
||||
|
||||
:attr:`ripple_color` is an :class:`~kivy.properties.ColorProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
ripple_alpha = NumericProperty(0.5)
|
||||
"""
|
||||
Alpha channel values for ripple effect.
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
CircularRippleButton:
|
||||
ripple_alpha: .9
|
||||
ripple_color: app.theme_cls.primary_color
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-alpha.gif
|
||||
:align: center
|
||||
|
||||
:attr:`ripple_alpha` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.5`.
|
||||
"""
|
||||
|
||||
ripple_scale = NumericProperty(None)
|
||||
"""
|
||||
Ripple effect scale.
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
CircularRippleButton:
|
||||
ripple_scale: .5
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-scale-05.gif
|
||||
:align: center
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
CircularRippleButton:
|
||||
ripple_scale: 1
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-scale-1.gif
|
||||
:align: center
|
||||
|
||||
:attr:`ripple_scale` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `None`.
|
||||
"""
|
||||
|
||||
ripple_duration_in_fast = NumericProperty(0.3)
|
||||
"""
|
||||
Ripple duration when touching to widget.
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
CircularRippleButton:
|
||||
ripple_duration_in_fast: .1
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-duration-in-fast.gif
|
||||
:align: center
|
||||
|
||||
:attr:`ripple_duration_in_fast` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.3`.
|
||||
"""
|
||||
|
||||
ripple_duration_in_slow = NumericProperty(2)
|
||||
"""
|
||||
Ripple duration when long touching to widget.
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
CircularRippleButton:
|
||||
ripple_duration_in_slow: 5
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-duration-in-slow.gif
|
||||
:align: center
|
||||
|
||||
:attr:`ripple_duration_in_slow` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `2`.
|
||||
"""
|
||||
|
||||
ripple_duration_out = NumericProperty(0.3)
|
||||
"""
|
||||
The duration of the disappearance of the wave effect.
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
CircularRippleButton:
|
||||
ripple_duration_out: 5
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-duration-out.gif
|
||||
:align: center
|
||||
|
||||
:attr:`ripple_duration_out` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.3`.
|
||||
"""
|
||||
|
||||
ripple_canvas_after = BooleanProperty(True)
|
||||
"""
|
||||
The ripple effect is drawn above/below the content.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDIconButton:
|
||||
ripple_canvas_after: True
|
||||
icon: "android"
|
||||
ripple_alpha: .8
|
||||
ripple_color: app.theme_cls.primary_color
|
||||
icon_size: "100sp"
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-canvas-after-true.gif
|
||||
:align: center
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
MDIconButton:
|
||||
ripple_canvas_after: False
|
||||
icon: "android"
|
||||
ripple_alpha: .8
|
||||
ripple_color: app.theme_cls.primary_color
|
||||
icon_size: "100sp"
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ripple-canvas-after-false.gif
|
||||
:align: center
|
||||
|
||||
:attr:`ripple_canvas_after` is an :class:`~kivy.properties.BooleanProperty`
|
||||
and defaults to `True`.
|
||||
"""
|
||||
|
||||
ripple_func_in = StringProperty("out_quad")
|
||||
"""
|
||||
Type of animation for ripple in effect.
|
||||
|
||||
:attr:`ripple_func_in` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'out_quad'`.
|
||||
"""
|
||||
|
||||
ripple_func_out = StringProperty("out_quad")
|
||||
"""
|
||||
Type of animation for ripple out effect.
|
||||
|
||||
:attr:`ripple_func_out` is an :class:`~kivy.properties.StringProperty`
|
||||
and defaults to `'ripple_func_out'`.
|
||||
"""
|
||||
|
||||
_ripple_rad = NumericProperty()
|
||||
_doing_ripple = BooleanProperty(False)
|
||||
_finishing_ripple = BooleanProperty(False)
|
||||
_fading_out = BooleanProperty(False)
|
||||
_no_ripple_effect = BooleanProperty(False)
|
||||
_round_rad = ListProperty([0, 0, 0, 0])
|
||||
|
||||
def lay_canvas_instructions(self) -> NoReturn:
|
||||
raise NotImplementedError
|
||||
|
||||
def start_ripple(self) -> None:
|
||||
if not self._doing_ripple:
|
||||
self._doing_ripple = True
|
||||
anim = Animation(
|
||||
_ripple_rad=self.finish_rad,
|
||||
t="linear",
|
||||
duration=self.ripple_duration_in_slow,
|
||||
)
|
||||
anim.bind(on_complete=self.fade_out)
|
||||
anim.start(self)
|
||||
|
||||
def finish_ripple(self) -> None:
|
||||
if self._doing_ripple and not self._finishing_ripple:
|
||||
self._finishing_ripple = True
|
||||
self._doing_ripple = False
|
||||
Animation.cancel_all(self, "_ripple_rad")
|
||||
anim = Animation(
|
||||
_ripple_rad=self.finish_rad,
|
||||
t=self.ripple_func_in,
|
||||
duration=self.ripple_duration_in_fast,
|
||||
)
|
||||
anim.bind(on_complete=self.fade_out)
|
||||
anim.start(self)
|
||||
|
||||
def fade_out(self, *args) -> None:
|
||||
rc = self.ripple_color
|
||||
if not self._fading_out:
|
||||
self._fading_out = True
|
||||
Animation.cancel_all(self, "ripple_color")
|
||||
anim = Animation(
|
||||
ripple_color=[rc[0], rc[1], rc[2], 0.0],
|
||||
t=self.ripple_func_out,
|
||||
duration=self.ripple_duration_out,
|
||||
)
|
||||
anim.bind(on_complete=self.anim_complete)
|
||||
anim.start(self)
|
||||
|
||||
def anim_complete(self, *args) -> None:
|
||||
self._doing_ripple = False
|
||||
self._finishing_ripple = False
|
||||
self._fading_out = False
|
||||
|
||||
if not self.ripple_canvas_after:
|
||||
canvas = self.canvas.before
|
||||
else:
|
||||
canvas = self.canvas.after
|
||||
|
||||
canvas.remove_group("circular_ripple_behavior")
|
||||
canvas.remove_group("rectangular_ripple_behavior")
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
# FIXME: in fact, the output of the super method is extra.
|
||||
# But without this, the list (`ScrollView`) placed in the `MDCard`
|
||||
# widget will not scroll.
|
||||
super().on_touch_down(touch)
|
||||
if touch.is_mouse_scrolling:
|
||||
return False
|
||||
if not self.collide_point(touch.x, touch.y):
|
||||
return False
|
||||
if not self.disabled:
|
||||
self.call_ripple_animation_methods(touch)
|
||||
# FIXME: this check is needed for the `MDTabsLabel` object.
|
||||
# With the normal `return True`, events for tabs from the `MDTabs`
|
||||
# class are not processed.
|
||||
# There may be problems with other widgets.
|
||||
# Status: requires check.
|
||||
if isinstance(self, ToggleButtonBehavior):
|
||||
return super().on_touch_down(touch)
|
||||
else:
|
||||
return True
|
||||
|
||||
def call_ripple_animation_methods(self, touch) -> None:
|
||||
if self._doing_ripple:
|
||||
Animation.cancel_all(
|
||||
self, "_ripple_rad", "ripple_color", "rect_color"
|
||||
)
|
||||
self.anim_complete()
|
||||
self._ripple_rad = self.ripple_rad_default
|
||||
self.ripple_pos = (touch.x, touch.y)
|
||||
|
||||
if self.ripple_color:
|
||||
pass
|
||||
elif hasattr(self, "theme_cls"):
|
||||
self.ripple_color = self.theme_cls.ripple_color
|
||||
else:
|
||||
# If no theme, set Gray 300.
|
||||
self.ripple_color = [
|
||||
0.8784313725490196,
|
||||
0.8784313725490196,
|
||||
0.8784313725490196,
|
||||
self.ripple_alpha,
|
||||
]
|
||||
self.ripple_color[3] = self.ripple_alpha
|
||||
self.lay_canvas_instructions()
|
||||
self.finish_rad = max(self.width, self.height) * self.ripple_scale
|
||||
self.start_ripple()
|
||||
|
||||
def on_touch_move(self, touch, *args):
|
||||
if not self.collide_point(touch.x, touch.y):
|
||||
if not self._finishing_ripple and self._doing_ripple:
|
||||
self.finish_ripple()
|
||||
return super().on_touch_move(touch, *args)
|
||||
|
||||
def on_touch_up(self, touch):
|
||||
if self.collide_point(touch.x, touch.y) and self._doing_ripple:
|
||||
self.finish_ripple()
|
||||
return super().on_touch_up(touch)
|
||||
|
||||
def _set_ellipse(self, instance, value):
|
||||
self.ellipse.size = (self._ripple_rad, self._ripple_rad)
|
||||
|
||||
# Adjust ellipse pos here
|
||||
|
||||
def _set_color(self, instance, value):
|
||||
self.col_instruction.a = value[3]
|
||||
|
||||
|
||||
class RectangularRippleBehavior(CommonRipple):
|
||||
"""
|
||||
Class implements a rectangular ripple effect.
|
||||
|
||||
For more information, see in the :class:`~kivymd.uix.behavior.CommonRipple`
|
||||
class documentation.
|
||||
"""
|
||||
|
||||
ripple_scale = NumericProperty(2.75)
|
||||
"""
|
||||
See :class:`~CommonRipple.ripple_scale`.
|
||||
|
||||
:attr:`ripple_scale` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `2.75`.
|
||||
"""
|
||||
|
||||
def lay_canvas_instructions(self) -> None:
|
||||
if self._no_ripple_effect:
|
||||
return
|
||||
|
||||
with self.canvas.after if self.ripple_canvas_after else self.canvas.before:
|
||||
if hasattr(self, "radius"):
|
||||
if isinstance(self.radius, (float, int)):
|
||||
self.radius = [
|
||||
self.radius,
|
||||
]
|
||||
self._round_rad = self.radius
|
||||
StencilPush(group="rectangular_ripple_behavior")
|
||||
RoundedRectangle(
|
||||
pos=self.pos,
|
||||
size=self.size,
|
||||
radius=self._round_rad,
|
||||
group="rectangular_ripple_behavior",
|
||||
)
|
||||
StencilUse(group="rectangular_ripple_behavior")
|
||||
self.col_instruction = Color(
|
||||
rgba=self.ripple_color, group="rectangular_ripple_behavior"
|
||||
)
|
||||
self.ellipse = Ellipse(
|
||||
size=(self._ripple_rad, self._ripple_rad),
|
||||
pos=(
|
||||
self.ripple_pos[0] - self._ripple_rad / 2.0,
|
||||
self.ripple_pos[1] - self._ripple_rad / 2.0,
|
||||
),
|
||||
group="rectangular_ripple_behavior",
|
||||
)
|
||||
StencilUnUse(group="rectangular_ripple_behavior")
|
||||
RoundedRectangle(
|
||||
pos=self.pos,
|
||||
size=self.size,
|
||||
radius=self._round_rad,
|
||||
group="rectangular_ripple_behavior",
|
||||
)
|
||||
StencilPop(group="rectangular_ripple_behavior")
|
||||
self.bind(ripple_color=self._set_color, _ripple_rad=self._set_ellipse)
|
||||
|
||||
def _set_ellipse(self, instance, value):
|
||||
super()._set_ellipse(instance, value)
|
||||
self.ellipse.pos = (
|
||||
self.ripple_pos[0] - self._ripple_rad / 2.0,
|
||||
self.ripple_pos[1] - self._ripple_rad / 2.0,
|
||||
)
|
||||
|
||||
|
||||
class CircularRippleBehavior(CommonRipple):
|
||||
"""
|
||||
Class implements a circular ripple effect.
|
||||
|
||||
For more information, see in the :class:`~kivymd.uix.behavior.CommonRipple`
|
||||
class documentation.
|
||||
"""
|
||||
|
||||
ripple_scale = NumericProperty(1)
|
||||
"""
|
||||
See :class:`~CommonRipple.ripple_scale`.
|
||||
|
||||
:attr:`ripple_scale` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
def lay_canvas_instructions(self) -> None:
|
||||
if self._no_ripple_effect:
|
||||
return
|
||||
|
||||
with self.canvas.after if self.ripple_canvas_after else self.canvas.before:
|
||||
StencilPush(group="circular_ripple_behavior")
|
||||
self.stencil = Ellipse(
|
||||
size=(
|
||||
self.width * self.ripple_scale,
|
||||
self.height * self.ripple_scale,
|
||||
),
|
||||
pos=(
|
||||
self.center_x - (self.width * self.ripple_scale) / 2,
|
||||
self.center_y - (self.height * self.ripple_scale) / 2,
|
||||
),
|
||||
group="circular_ripple_behavior",
|
||||
)
|
||||
StencilUse(group="circular_ripple_behavior")
|
||||
self.col_instruction = Color(rgba=self.ripple_color)
|
||||
self.ellipse = Ellipse(
|
||||
size=(self._ripple_rad, self._ripple_rad),
|
||||
pos=(
|
||||
self.center_x - self._ripple_rad / 2.0,
|
||||
self.center_y - self._ripple_rad / 2.0,
|
||||
),
|
||||
group="circular_ripple_behavior",
|
||||
)
|
||||
StencilUnUse(group="circular_ripple_behavior")
|
||||
Ellipse(
|
||||
pos=self.pos, size=self.size, group="circular_ripple_behavior"
|
||||
)
|
||||
StencilPop(group="circular_ripple_behavior")
|
||||
self.bind(
|
||||
ripple_color=self._set_color, _ripple_rad=self._set_ellipse
|
||||
)
|
||||
|
||||
def _set_ellipse(self, instance, value):
|
||||
super()._set_ellipse(instance, value)
|
||||
if self.ellipse.size[0] > self.width * 0.6 and not self._fading_out:
|
||||
self.fade_out()
|
||||
self.ellipse.pos = (
|
||||
self.center_x - self._ripple_rad / 2.0,
|
||||
self.center_y - self._ripple_rad / 2.0,
|
||||
)
|
|
@ -0,0 +1,137 @@
|
|||
"""
|
||||
Behaviors/Rotate
|
||||
================
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
Base class for controlling the rotate of the widget.
|
||||
|
||||
.. note:: See `kivy.graphics.Rotate
|
||||
<https://kivy.org/doc/stable/api-kivy.graphics.html#kivy.graphics.Rotate>`_
|
||||
for more information.
|
||||
|
||||
Kivy
|
||||
----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.lang import Builder
|
||||
from kivy.app import App
|
||||
from kivy.properties import NumericProperty
|
||||
from kivy.uix.button import Button
|
||||
|
||||
KV = '''
|
||||
Screen:
|
||||
|
||||
RotateButton:
|
||||
size_hint: .5, .5
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
on_release: app.change_rotate(self)
|
||||
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
Rotate:
|
||||
angle: self.rotate_value_angle
|
||||
axis: 0, 0, 1
|
||||
origin: self.center
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
'''
|
||||
|
||||
|
||||
class RotateButton(Button):
|
||||
rotate_value_angle = NumericProperty(0)
|
||||
|
||||
|
||||
class Test(App):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def change_rotate(self, instance_button: Button) -> None:
|
||||
Animation(rotate_value_angle=45, d=0.3).start(instance_button)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
KivyMD
|
||||
------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import RotateBehavior
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
|
||||
KV = '''
|
||||
MDScreen:
|
||||
|
||||
RotateBox:
|
||||
size_hint: .5, .5
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
on_release: app.change_rotate(self)
|
||||
md_bg_color: "red"
|
||||
'''
|
||||
|
||||
|
||||
class RotateBox(ButtonBehavior, RotateBehavior, MDBoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def change_rotate(self, instance_button: RotateBox) -> None:
|
||||
Animation(rotate_value_angle=45, d=0.3).start(instance_button)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. warning:: Do not use `RotateBehavior` class with classes that inherited`
|
||||
from `CommonElevationBehavior` class. `CommonElevationBehavior` classes
|
||||
by default contains attributes for rotate widget.
|
||||
"""
|
||||
|
||||
__all__ = ("RotateBehavior",)
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import ListProperty, NumericProperty
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<RotateBehavior>
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
Rotate:
|
||||
angle: self.rotate_value_angle
|
||||
axis: tuple(self.rotate_value_axis)
|
||||
origin: self.center
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class RotateBehavior:
|
||||
"""Base class for controlling the rotate of the widget."""
|
||||
|
||||
rotate_value_angle = NumericProperty(0)
|
||||
"""
|
||||
Property for getting/setting the angle of the rotation.
|
||||
|
||||
:attr:`rotate_value_angle` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0`.
|
||||
"""
|
||||
|
||||
rotate_value_axis = ListProperty((0, 0, 1))
|
||||
"""
|
||||
Property for getting/setting the axis of the rotation.
|
||||
|
||||
:attr:`rotate_value_axis` is an :class:`~kivy.properties.ListProperty`
|
||||
and defaults to `(0, 0, 1)`.
|
||||
"""
|
|
@ -0,0 +1,175 @@
|
|||
"""
|
||||
Behaviors/Scale
|
||||
===============
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
Base class for controlling the scale of the widget.
|
||||
|
||||
.. note:: See `kivy.graphics.Rotate
|
||||
<https://kivy.org/doc/stable/api-kivy.graphics.html#kivy.graphics.Scale>`_
|
||||
for more information.
|
||||
|
||||
Kivy
|
||||
----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import NumericProperty
|
||||
from kivy.uix.button import Button
|
||||
from kivy.app import App
|
||||
|
||||
|
||||
KV = '''
|
||||
Screen:
|
||||
|
||||
ScaleButton:
|
||||
size_hint: .5, .5
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
on_release: app.change_scale(self)
|
||||
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
Scale:
|
||||
x: self.scale_value_x
|
||||
y: self.scale_value_y
|
||||
z: self.scale_value_x
|
||||
origin: self.center
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
'''
|
||||
|
||||
|
||||
class ScaleButton(Button):
|
||||
scale_value_x = NumericProperty(1)
|
||||
scale_value_y = NumericProperty(1)
|
||||
scale_value_z = NumericProperty(1)
|
||||
|
||||
|
||||
class Test(App):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def change_scale(self, instance_button: Button) -> None:
|
||||
Animation(
|
||||
scale_value_x=0.5,
|
||||
scale_value_y=0.5,
|
||||
scale_value_z=0.5,
|
||||
d=0.3,
|
||||
).start(instance_button)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
KivyMD
|
||||
------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import ScaleBehavior
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
|
||||
KV = '''
|
||||
MDScreen:
|
||||
|
||||
ScaleBox:
|
||||
size_hint: .5, .5
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
on_release: app.change_scale(self)
|
||||
md_bg_color: "red"
|
||||
'''
|
||||
|
||||
|
||||
class ScaleBox(ButtonBehavior, ScaleBehavior, MDBoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
def change_scale(self, instance_button: ScaleBox) -> None:
|
||||
Animation(
|
||||
scale_value_x=0.5,
|
||||
scale_value_y=0.5,
|
||||
scale_value_z=0.5,
|
||||
d=0.3,
|
||||
).start(instance_button)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. warning:: Do not use `ScaleBehavior` class with classes that inherited`
|
||||
from `CommonElevationBehavior` class. `CommonElevationBehavior` classes
|
||||
by default contains attributes for scale widget.
|
||||
"""
|
||||
|
||||
__all__ = ("ScaleBehavior",)
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import ListProperty, NumericProperty
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<ScaleBehavior>
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
Scale:
|
||||
x: self.scale_value_x
|
||||
y: self.scale_value_y
|
||||
z: self.scale_value_z
|
||||
origin:
|
||||
self.center \
|
||||
if not self.scale_value_center else \
|
||||
self.scale_value_center
|
||||
canvas.after:
|
||||
PopMatrix
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class ScaleBehavior:
|
||||
"""Base class for controlling the scale of the widget."""
|
||||
|
||||
scale_value_x = NumericProperty(1)
|
||||
"""
|
||||
X-axis value.
|
||||
|
||||
:attr:`scale_value_x` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
scale_value_y = NumericProperty(1)
|
||||
"""
|
||||
Y-axis value.
|
||||
|
||||
:attr:`scale_value_y` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
scale_value_z = NumericProperty(1)
|
||||
"""
|
||||
Z-axis value.
|
||||
|
||||
:attr:`scale_value_z` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `1`.
|
||||
"""
|
||||
|
||||
scale_value_center = ListProperty()
|
||||
"""
|
||||
Origin of the scale.
|
||||
|
||||
.. versionadded:: 1.2.0
|
||||
|
||||
The format of the origin can be either (x, y) or (x, y, z).
|
||||
|
||||
:attr:`scale_value_center` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `[]`.
|
||||
"""
|
|
@ -0,0 +1,134 @@
|
|||
"""
|
||||
Behaviors/Stencil
|
||||
=================
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
Base class for controlling the stencil instructions of the widget.
|
||||
|
||||
.. note:: See `Stencil instructions
|
||||
<https://kivy.org/doc/stable/api-kivy.graphics.stencil_instructions.html>`_
|
||||
for more information.
|
||||
|
||||
Kivy
|
||||
----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.app import App
|
||||
|
||||
KV = '''
|
||||
Carousel:
|
||||
|
||||
Button:
|
||||
size_hint: .9, .8
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
|
||||
canvas.before:
|
||||
StencilPush
|
||||
RoundedRectangle:
|
||||
pos: root.pos
|
||||
size: root.size
|
||||
StencilUse
|
||||
canvas.after:
|
||||
StencilUnUse
|
||||
RoundedRectangle:
|
||||
pos: root.pos
|
||||
size: root.size
|
||||
StencilPop
|
||||
'''
|
||||
|
||||
|
||||
class Test(App):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
KivyMD
|
||||
------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import StencilBehavior
|
||||
from kivymd.uix.fitimage import FitImage
|
||||
|
||||
KV = '''
|
||||
#:import os os
|
||||
#:import images_path kivymd.images_path
|
||||
|
||||
|
||||
MDCarousel:
|
||||
|
||||
StencilImage:
|
||||
size_hint: .9, .8
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
source: os.path.join(images_path, "logo", "kivymd-icon-512.png")
|
||||
'''
|
||||
|
||||
|
||||
class StencilImage(FitImage, StencilBehavior):
|
||||
pass
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Test().run()
|
||||
"""
|
||||
|
||||
__all__ = ("StencilBehavior",)
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import VariableListProperty
|
||||
|
||||
Builder.load_string(
|
||||
"""
|
||||
<StencilBehavior>
|
||||
canvas.before:
|
||||
StencilPush
|
||||
RoundedRectangle:
|
||||
pos: root.pos
|
||||
size: root.size
|
||||
# FIXME: Sometimes the radius has the value [], which get a
|
||||
# `GraphicException: Invalid radius value, must be list of tuples/numerics` error
|
||||
radius: root.radius if root.radius else [0, 0, 0, 0]
|
||||
StencilUse
|
||||
canvas.after:
|
||||
StencilUnUse
|
||||
RoundedRectangle:
|
||||
pos: root.pos
|
||||
size: root.size
|
||||
# FIXME: Sometimes the radius has the value [], which get a
|
||||
# `GraphicException: Invalid radius value, must be list of tuples/numerics` error
|
||||
radius: root.radius if root.radius else [0, 0, 0, 0]
|
||||
StencilPop
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class StencilBehavior:
|
||||
"""Base class for controlling the stencil instructions of the widget."""
|
||||
|
||||
radius = VariableListProperty([0], length=4)
|
||||
"""
|
||||
Canvas radius.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Top left corner slice.
|
||||
MDWidget:
|
||||
radius: [25, 0, 0, 0]
|
||||
|
||||
:attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
|
||||
and defaults to `[0, 0, 0, 0]`.
|
||||
"""
|
|
@ -0,0 +1,252 @@
|
|||
"""
|
||||
Behaviors/ToggleButton
|
||||
======================
|
||||
|
||||
This behavior must always be inherited after the button's Widget class since it
|
||||
works with the inherited properties of the button class.
|
||||
|
||||
example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyToggleButtonWidget(MDFlatButton, MDToggleButton):
|
||||
# [...]
|
||||
pass
|
||||
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. tab:: Declarative KV style
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors.toggle_behavior import MDToggleButton
|
||||
from kivymd.uix.button import MDFlatButton
|
||||
|
||||
KV = '''
|
||||
MDScreen:
|
||||
|
||||
MDBoxLayout:
|
||||
adaptive_size: True
|
||||
spacing: "12dp"
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
|
||||
MyToggleButton:
|
||||
text: "Show ads"
|
||||
group: "x"
|
||||
|
||||
MyToggleButton:
|
||||
text: "Do not show ads"
|
||||
group: "x"
|
||||
|
||||
MyToggleButton:
|
||||
text: "Does not matter"
|
||||
group: "x"
|
||||
'''
|
||||
|
||||
|
||||
class MyToggleButton(MDFlatButton, MDToggleButton):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.background_down = self.theme_cls.primary_color
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
self.theme_cls.primary_palette = "Orange"
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. tab:: Declarative python style
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors.toggle_behavior import MDToggleButton
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.button import MDFlatButton
|
||||
from kivymd.uix.screen import MDScreen
|
||||
|
||||
|
||||
class MyToggleButton(MDFlatButton, MDToggleButton):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.background_down = self.theme_cls.primary_color
|
||||
|
||||
|
||||
class Test(MDApp):
|
||||
def build(self):
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
self.theme_cls.primary_palette = "Orange"
|
||||
return (
|
||||
MDScreen(
|
||||
MDBoxLayout(
|
||||
MyToggleButton(
|
||||
text="Show ads",
|
||||
group="x",
|
||||
),
|
||||
MyToggleButton(
|
||||
text="Do not show ads",
|
||||
group="x",
|
||||
),
|
||||
MyToggleButton(
|
||||
text="Does not matter",
|
||||
group="x",
|
||||
),
|
||||
adaptive_size=True,
|
||||
spacing="12dp",
|
||||
pos_hint={"center_x": .5, "center_y": .5},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Test().run()
|
||||
|
||||
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toggle-button-1.gif
|
||||
:align: center
|
||||
|
||||
You can inherit the ``MyToggleButton`` class only from the following classes
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
- :class:`~kivymd.uix.button.MDRaisedButton`
|
||||
- :class:`~kivymd.uix.button.MDFlatButton`
|
||||
- :class:`~kivymd.uix.button.MDRectangleFlatButton`
|
||||
- :class:`~kivymd.uix.button.MDRectangleFlatIconButton`
|
||||
- :class:`~kivymd.uix.button.MDRoundFlatButton`
|
||||
- :class:`~kivymd.uix.button.MDRoundFlatIconButton`
|
||||
- :class:`~kivymd.uix.button.MDFillRoundFlatButton`
|
||||
- :class:`~kivymd.uix.button.MDFillRoundFlatIconButton`
|
||||
"""
|
||||
|
||||
__all__ = ("MDToggleButton",)
|
||||
|
||||
from kivy.properties import BooleanProperty, ColorProperty
|
||||
from kivy.uix.behaviors import ToggleButtonBehavior
|
||||
|
||||
from kivymd.uix.button import (
|
||||
ButtonContentsIconText,
|
||||
MDFillRoundFlatButton,
|
||||
MDFillRoundFlatIconButton,
|
||||
MDFlatButton,
|
||||
MDRaisedButton,
|
||||
MDRectangleFlatButton,
|
||||
MDRectangleFlatIconButton,
|
||||
MDRoundFlatButton,
|
||||
MDRoundFlatIconButton,
|
||||
)
|
||||
|
||||
|
||||
class MDToggleButton(ToggleButtonBehavior):
|
||||
background_normal = ColorProperty(None)
|
||||
"""
|
||||
Color of the button in ``rgba`` format for the 'normal' state.
|
||||
|
||||
:attr:`background_normal` is a :class:`~kivy.properties.ColorProperty`
|
||||
and is defaults to `None`.
|
||||
"""
|
||||
|
||||
background_down = ColorProperty(None)
|
||||
"""
|
||||
Color of the button in ``rgba`` format for the 'down' state.
|
||||
|
||||
:attr:`background_down` is a :class:`~kivy.properties.ColorProperty`
|
||||
and is defaults to `None`.
|
||||
"""
|
||||
|
||||
font_color_normal = ColorProperty(None)
|
||||
"""
|
||||
Color of the font's button in ``rgba`` format for the 'normal' state.
|
||||
|
||||
:attr:`font_color_normal` is a :class:`~kivy.properties.ColorProperty`
|
||||
and is defaults to `None`.
|
||||
"""
|
||||
|
||||
font_color_down = ColorProperty([1, 1, 1, 1])
|
||||
"""
|
||||
Color of the font's button in ``rgba`` format for the 'down' state.
|
||||
|
||||
:attr:`font_color_down` is a :class:`~kivy.properties.ColorProperty`
|
||||
and is defaults to `[1, 1, 1, 1]`.
|
||||
"""
|
||||
|
||||
__is_filled = BooleanProperty(False)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
classinfo = (
|
||||
MDRaisedButton,
|
||||
MDFlatButton,
|
||||
MDRectangleFlatButton,
|
||||
MDRectangleFlatIconButton,
|
||||
MDRoundFlatButton,
|
||||
MDRoundFlatIconButton,
|
||||
MDFillRoundFlatButton,
|
||||
MDFillRoundFlatIconButton,
|
||||
)
|
||||
# Do the object inherited from the "supported" buttons?
|
||||
if not issubclass(self.__class__, classinfo):
|
||||
raise ValueError(
|
||||
f"Class {self.__class__} must be inherited from one of the "
|
||||
f"classes in the list {classinfo}"
|
||||
)
|
||||
if (
|
||||
not self.background_normal
|
||||
): # This means that if the value == [] or None will return True.
|
||||
# If the object inherits from buttons with background:
|
||||
if isinstance(
|
||||
self,
|
||||
(
|
||||
MDRaisedButton,
|
||||
MDFillRoundFlatButton,
|
||||
MDFillRoundFlatIconButton,
|
||||
),
|
||||
):
|
||||
self.__is_filled = True
|
||||
self.background_normal = self.theme_cls.primary_color
|
||||
# If not background_normal must be the same as the inherited one.
|
||||
else:
|
||||
self.background_normal = (
|
||||
self.md_bg_color[:] if self.md_bg_color else (0, 0, 0, 0)
|
||||
)
|
||||
# If no background_down is setter.
|
||||
if (
|
||||
not self.background_down
|
||||
): # This means that if the value == [] or None will return True.
|
||||
self.background_down = (
|
||||
self.theme_cls.primary_dark
|
||||
) # get the primary_color dark from theme_cls
|
||||
if not self.font_color_normal:
|
||||
self.font_color_normal = self.theme_cls.primary_color
|
||||
# Alternative to bind the function to the property.
|
||||
# self.bind(state=self._update_bg)
|
||||
self.fbind("state", self._update_bg)
|
||||
|
||||
def _update_bg(self, ins, val):
|
||||
"""Updates the color of the background."""
|
||||
|
||||
if val == "down":
|
||||
self.md_bg_color = self.background_down
|
||||
if (
|
||||
self.__is_filled is False
|
||||
): # If the background is transparent, and the button it toggled,
|
||||
# the font color must be withe [1, 1, 1, 1].
|
||||
self.text_color = self.font_color_down
|
||||
if self.group:
|
||||
self._release_group(self)
|
||||
else:
|
||||
self.md_bg_color = self.background_normal
|
||||
if (
|
||||
self.__is_filled is False
|
||||
): # If the background is transparent, the font color must be the
|
||||
# primary color.
|
||||
self.text_color = self.font_color_normal
|
||||
|
||||
if issubclass(self.__class__, ButtonContentsIconText):
|
||||
self.icon_color = self.text_color
|
|
@ -0,0 +1,100 @@
|
|||
"""
|
||||
Behaviors/Touch
|
||||
===============
|
||||
|
||||
.. rubric:: Provides easy access to events.
|
||||
|
||||
The following events are available:
|
||||
|
||||
- on_long_touch
|
||||
- on_double_tap
|
||||
- on_triple_tap
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import TouchBehavior
|
||||
from kivymd.uix.button import MDRaisedButton
|
||||
|
||||
KV = '''
|
||||
MDScreen:
|
||||
|
||||
MyButton:
|
||||
text: "PRESS ME"
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
'''
|
||||
|
||||
|
||||
class MyButton(MDRaisedButton, TouchBehavior):
|
||||
def on_long_touch(self, *args):
|
||||
print("<on_long_touch> event")
|
||||
|
||||
def on_double_tap(self, *args):
|
||||
print("<on_double_tap> event")
|
||||
|
||||
def on_triple_tap(self, *args):
|
||||
print("<on_triple_tap> event")
|
||||
|
||||
|
||||
class MainApp(MDApp):
|
||||
def build(self):
|
||||
return Builder.load_string(KV)
|
||||
|
||||
|
||||
MainApp().run()
|
||||
"""
|
||||
|
||||
__all__ = ("TouchBehavior",)
|
||||
|
||||
from functools import partial
|
||||
|
||||
from kivy.clock import Clock
|
||||
from kivy.properties import NumericProperty
|
||||
|
||||
|
||||
class TouchBehavior:
|
||||
duration_long_touch = NumericProperty(0.4)
|
||||
"""
|
||||
Time for a long touch.
|
||||
|
||||
:attr:`duration_long_touch` is an :class:`~kivy.properties.NumericProperty`
|
||||
and defaults to `0.4`.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.bind(
|
||||
on_touch_down=self.create_clock, on_touch_up=self.delete_clock
|
||||
)
|
||||
|
||||
def create_clock(self, widget, touch, *args):
|
||||
if self.collide_point(touch.x, touch.y):
|
||||
if "event" not in touch.ud:
|
||||
callback = partial(self.on_long_touch, touch)
|
||||
Clock.schedule_once(callback, self.duration_long_touch)
|
||||
touch.ud["event"] = callback
|
||||
|
||||
if touch.is_double_tap:
|
||||
self.on_double_tap(touch, *args)
|
||||
if touch.is_triple_tap:
|
||||
self.on_triple_tap(touch, *args)
|
||||
|
||||
def delete_clock(self, widget, touch, *args):
|
||||
if self.collide_point(touch.x, touch.y):
|
||||
if "event" in touch.ud:
|
||||
Clock.unschedule(touch.ud["event"])
|
||||
del touch.ud["event"]
|
||||
|
||||
def on_long_touch(self, touch, *args):
|
||||
"""Called when the widget is pressed for a long time."""
|
||||
|
||||
def on_double_tap(self, touch, *args):
|
||||
"""Called by double clicking on the widget."""
|
||||
|
||||
def on_triple_tap(self, touch, *args):
|
||||
"""Called by triple clicking on the widget."""
|
Loading…
Add table
Add a link
Reference in a new issue