globalforest/kivymd/uix/behaviors/elevation.py
2020-10-14 23:38:48 +03:00

304 lines
8.9 KiB
Python
Executable file

"""
Behaviors/Elevation
===================
.. rubric:: Classes implements a circular and rectangular elevation effects.
To create a widget with rectangular or circular elevation effect,
you must create a new class that inherits from the
:class:`~RectangularElevationBehavior` or :class:`~CircularElevationBehavior`
class.
For example, let's create an button with a rectangular elevation effect:
.. 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,
RectangularElevationBehavior,
)
KV = '''
<RectangularElevationButton>:
size_hint: None, None
size: "250dp", "50dp"
Screen:
# With elevation effect
RectangularElevationButton:
pos_hint: {"center_x": .5, "center_y": .6}
elevation: 11
# Without elevation effect
RectangularElevationButton:
pos_hint: {"center_x": .5, "center_y": .4}
'''
class RectangularElevationButton(
RectangularRippleBehavior,
RectangularElevationBehavior,
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-elevation-effect.gif
:align: center
Similarly, create a button with a circular elevation effect:
.. code-block:: python
from kivy.lang import Builder
from kivy.uix.image import Image
from kivy.uix.behaviors import ButtonBehavior
from kivymd.app import MDApp
from kivymd.uix.behaviors import (
CircularRippleBehavior,
CircularElevationBehavior,
)
KV = '''
#:import images_path kivymd.images_path
<CircularElevationButton>:
size_hint: None, None
size: "100dp", "100dp"
source: f"{images_path}/kivymd.png"
Screen:
# With elevation effect
CircularElevationButton:
pos_hint: {"center_x": .5, "center_y": .6}
elevation: 5
# Without elevation effect
CircularElevationButton:
pos_hint: {"center_x": .5, "center_y": .4}
elevation: 0
'''
class CircularElevationButton(
CircularRippleBehavior,
CircularElevationBehavior,
ButtonBehavior,
Image,
):
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/circular-elevation-effect.gif
:align: center
"""
__all__ = (
"CommonElevationBehavior",
"RectangularElevationBehavior",
"CircularElevationBehavior",
)
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.properties import ListProperty, NumericProperty, ObjectProperty
from kivymd.app import MDApp
Builder.load_string(
"""
<RectangularElevationBehavior>
canvas.before:
Color:
a: self._soft_shadow_a
Rectangle:
texture: self._soft_shadow_texture
size: self._soft_shadow_size
pos: self._soft_shadow_pos
Color:
a: self._hard_shadow_a
Rectangle:
texture: self._hard_shadow_texture
size: self._hard_shadow_size
pos: self._hard_shadow_pos
Color:
a: 1
<CircularElevationBehavior>
canvas.before:
Color:
a: self._soft_shadow_a
Rectangle:
texture: self._soft_shadow_texture
size: self._soft_shadow_size
pos: self._soft_shadow_pos
Color:
a: self._hard_shadow_a
Rectangle:
texture: self._hard_shadow_texture
size: self._hard_shadow_size
pos: self._hard_shadow_pos
Color:
a: 1
"""
)
class CommonElevationBehavior(object):
"""Common base class for rectangular and circular elevation behavior."""
elevation = NumericProperty(1)
"""
Elevation value.
:attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
and defaults to 1.
"""
_elevation = NumericProperty(0)
_soft_shadow_texture = ObjectProperty()
_soft_shadow_size = ListProperty((0, 0))
_soft_shadow_pos = ListProperty((0, 0))
_soft_shadow_a = NumericProperty(0)
_hard_shadow_texture = ObjectProperty()
_hard_shadow_size = ListProperty((0, 0))
_hard_shadow_pos = ListProperty((0, 0))
_hard_shadow_a = NumericProperty(0)
def __init__(self, **kwargs):
self.bind(
elevation=self._update_elevation,
pos=self._update_shadow,
size=self._update_shadow,
)
super().__init__(**kwargs)
def _update_shadow(self, *args):
raise NotImplementedError
def _update_elevation(self, instance, value):
if not self._elevation:
self._elevation = value
self._update_shadow(instance, value)
class RectangularElevationBehavior(CommonElevationBehavior):
"""Base class for rectangular elevation behavior.
Controls the size and position of the shadow."""
def _update_shadow(self, *args):
if self._elevation > 0:
# Set shadow size.
ratio = self.width / (self.height if self.height != 0 else 1)
if -2 < ratio < 2:
self._shadow = MDApp.get_running_app().theme_cls.quad_shadow
width = soft_width = self.width * 1.9
height = soft_height = self.height * 1.9
elif ratio <= -2:
self._shadow = MDApp.get_running_app().theme_cls.rec_st_shadow
ratio = abs(ratio)
if ratio > 5:
ratio = ratio * 22
else:
ratio = ratio * 11.5
width = soft_width = self.width * 1.9
height = self.height + dp(ratio)
soft_height = (
self.height + dp(ratio) + dp(self._elevation) * 0.5
)
else:
self._shadow = MDApp.get_running_app().theme_cls.quad_shadow
width = soft_width = self.width * 1.8
height = soft_height = self.height * 1.8
self._soft_shadow_size = (soft_width, soft_height)
self._hard_shadow_size = (width, height)
# Set ``soft_shadow`` parameters.
self._soft_shadow_pos = (
self.center_x - soft_width / 2,
self.center_y
- soft_height / 2
- dp(0.1 * 1.5 ** self._elevation),
)
self._soft_shadow_a = 0.1 * 1.1 ** self._elevation
self._soft_shadow_texture = self._shadow.textures[
str(int(round(self._elevation)))
]
# Set ``hard_shadow`` parameters.
self._hard_shadow_pos = (
self.center_x - width / 2,
self.center_y - height / 2 - dp(0.5 * 1.18 ** self._elevation),
)
self._hard_shadow_a = 0.4 * 0.9 ** self._elevation
self._hard_shadow_texture = self._shadow.textures[
str(int(round(self._elevation)))
]
else:
self._soft_shadow_a = 0
self._hard_shadow_a = 0
class CircularElevationBehavior(CommonElevationBehavior):
"""Base class for circular elevation behavior.
Controls the size and position of the shadow."""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._shadow = MDApp.get_running_app().theme_cls.round_shadow
def _update_shadow(self, *args):
if self.elevation > 0:
# Set shadow size.
width = self.width * 2
height = self.height * 2
x = self.center_x - width / 2
self._soft_shadow_size = (width, height)
self._hard_shadow_size = (width, height)
# Set ``soft_shadow`` parameters.
y = self.center_y - height / 2 - dp(0.1 * 1.5 ** self._elevation)
self._soft_shadow_pos = (x, y)
self._soft_shadow_a = 0.1 * 1.1 ** self._elevation
if hasattr(self, "_shadow"):
self._soft_shadow_texture = self._shadow.textures[
str(int(round(self._elevation)))
]
# Set ``hard_shadow`` parameters.
y = self.center_y - height / 2 - dp(0.5 * 1.18 ** self._elevation)
self._hard_shadow_pos = (x, y)
self._hard_shadow_a = 0.4 * 0.9 ** self._elevation
if hasattr(self, "_shadow"):
self._hard_shadow_texture = self._shadow.textures[
str(int(round(self._elevation)))
]
else:
self._soft_shadow_a = 0
self._hard_shadow_a = 0