globalforest/kivymd/uix/behaviors/ripplebehavior.py

392 lines
12 KiB
Python
Raw Normal View History

2020-10-14 00:19:43 -04:00
"""
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 = '''
#:import images_path kivymd.images_path
Screen:
CircularRippleButton:
source: f"{images_path}/kivymd.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):
self.theme_cls.theme_style = "Dark"
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 = '''
Screen:
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):
self.theme_cls.theme_style = "Dark"
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 kivy.animation import Animation
from kivy.graphics import (
Color,
Ellipse,
Rectangle,
StencilPop,
StencilPush,
StencilUnUse,
StencilUse,
)
from kivy.properties import (
BooleanProperty,
ListProperty,
NumericProperty,
StringProperty,
)
class CommonRipple(object):
"""Base class for ripple effect."""
ripple_rad_default = NumericProperty(1)
"""
Default value of the ripple effect radius.
.. 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 = ListProperty()
"""
Ripple color in ``rgba`` format.
:attr:`ripple_color` is an :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
ripple_alpha = NumericProperty(0.5)
"""
Alpha channel values for ripple effect.
:attr:`ripple_alpha` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0.5`.
"""
ripple_scale = NumericProperty(None)
"""
Ripple effect scale.
.. 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.
: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.
: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.
.. 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_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_in` 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)
def lay_canvas_instructions(self):
raise NotImplementedError
def start_ripple(self):
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):
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):
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):
self._doing_ripple = False
self._finishing_ripple = False
self._fading_out = False
self.canvas.after.clear()
def on_touch_down(self, touch):
if touch.is_mouse_scrolling:
return False
if not self.collide_point(touch.x, touch.y):
return False
if not self.disabled:
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()
return super().on_touch_down(touch)
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."""
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):
if self._no_ripple_effect:
return
with self.canvas.after:
StencilPush()
Rectangle(pos=self.pos, size=self.size)
StencilUse()
self.col_instruction = Color(rgba=self.ripple_color)
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,
),
)
StencilUnUse()
Rectangle(pos=self.pos, size=self.size)
StencilPop()
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."""
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):
with self.canvas.after:
StencilPush()
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,
),
)
StencilUse()
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,
),
)
StencilUnUse()
Ellipse(pos=self.pos, size=self.size)
StencilPop()
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,
)