first commit

This commit is contained in:
Yura 2024-09-15 15:12:16 +03:00
commit 417e54da96
5696 changed files with 900003 additions and 0 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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."
)

View file

@ -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

View file

@ -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."""

View file

@ -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)

View file

@ -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

View file

@ -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,
)

View file

@ -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)`.
"""

View file

@ -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 `[]`.
"""

View file

@ -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]`.
"""

View file

@ -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

View file

@ -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."""