This repository has been archived on 2025-02-01. You can view files and clone it, but cannot push or open issues or pull requests.
PyBitmessage-2025-02-01/mockenv/lib/python3.6/site-packages/kivy/uix/behaviors/touchripple.py
2022-07-22 16:13:59 +05:30

319 lines
10 KiB
Python

'''
Touch Ripple
============
.. versionadded:: 1.10.1
.. warning::
This code is still experimental, and its API is subject to change in a
future version.
This module contains `mixin <https://en.wikipedia.org/wiki/Mixin>`_ classes
to add a touch ripple visual effect known from `Google Material Design
<https://en.wikipedia.org/wiki/Material_Design>_` to widgets.
For an overview of behaviors, please refer to the :mod:`~kivy.uix.behaviors`
documentation.
The class :class:`~kivy.uix.behaviors.touchripple.TouchRippleBehavior` provides
rendering the ripple animation.
The class :class:`~kivy.uix.behaviors.touchripple.TouchRippleButtonBehavior`
basically provides the same functionality as
:class:`~kivy.uix.behaviors.button.ButtonBehavior` but rendering the ripple
animation instead of default press/release visualization.
'''
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.graphics import CanvasBase, Color, Ellipse, ScissorPush, ScissorPop
from kivy.properties import BooleanProperty, ListProperty, NumericProperty, \
ObjectProperty, StringProperty
from kivy.uix.relativelayout import RelativeLayout
__all__ = (
'TouchRippleBehavior',
'TouchRippleButtonBehavior'
)
class TouchRippleBehavior(object):
'''Touch ripple behavior.
Supposed to be used as mixin on widget classes.
Ripple behavior does not trigger automatically, concrete implementation
needs to call :func:`ripple_show` respective :func:`ripple_fade` manually.
Example
-------
Here we create a Label which renders the touch ripple animation on
interaction::
class RippleLabel(TouchRippleBehavior, Label):
def __init__(self, **kwargs):
super(RippleLabel, self).__init__(**kwargs)
def on_touch_down(self, touch):
collide_point = self.collide_point(touch.x, touch.y)
if collide_point:
touch.grab(self)
self.ripple_show(touch)
return True
return False
def on_touch_up(self, touch):
if touch.grab_current is self:
touch.ungrab(self)
self.ripple_fade()
return True
return False
'''
ripple_rad_default = NumericProperty(10)
'''Default radius the animation starts from.
:attr:`ripple_rad_default` is a :class:`~kivy.properties.NumericProperty`
and defaults to `10`.
'''
ripple_duration_in = NumericProperty(.5)
'''Animation duration taken to show the overlay.
:attr:`ripple_duration_in` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.5`.
'''
ripple_duration_out = NumericProperty(.2)
'''Animation duration taken to fade the overlay.
:attr:`ripple_duration_out` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.2`.
'''
ripple_fade_from_alpha = NumericProperty(.5)
'''Alpha channel for ripple color the animation starts with.
:attr:`ripple_fade_from_alpha` is a
:class:`~kivy.properties.NumericProperty` and defaults to `0.5`.
'''
ripple_fade_to_alpha = NumericProperty(.8)
'''Alpha channel for ripple color the animation targets to.
:attr:`ripple_fade_to_alpha` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.8`.
'''
ripple_scale = NumericProperty(2.)
'''Max scale of the animation overlay calculated from max(width/height) of
the decorated widget.
:attr:`ripple_scale` is a :class:`~kivy.properties.NumericProperty`
and defaults to `2.0`.
'''
ripple_func_in = StringProperty('in_cubic')
'''Animation callback for showing the overlay.
:attr:`ripple_func_in` is a :class:`~kivy.properties.StringProperty`
and defaults to `in_cubic`.
'''
ripple_func_out = StringProperty('out_quad')
'''Animation callback for hiding the overlay.
:attr:`ripple_func_out` is a :class:`~kivy.properties.StringProperty`
and defaults to `out_quad`.
'''
ripple_rad = NumericProperty(10)
ripple_pos = ListProperty([0, 0])
ripple_color = ListProperty((1., 1., 1., .5))
def __init__(self, **kwargs):
super(TouchRippleBehavior, self).__init__(**kwargs)
self.ripple_pane = CanvasBase()
self.canvas.add(self.ripple_pane)
self.bind(
ripple_color=self._ripple_set_color,
ripple_pos=self._ripple_set_ellipse,
ripple_rad=self._ripple_set_ellipse
)
self.ripple_ellipse = None
self.ripple_col_instruction = None
def ripple_show(self, touch):
'''Begin ripple animation on current widget.
Expects touch event as argument.
'''
Animation.cancel_all(self, 'ripple_rad', 'ripple_color')
self._ripple_reset_pane()
x, y = self.to_window(*self.pos)
width, height = self.size
if isinstance(self, RelativeLayout):
self.ripple_pos = ripple_pos = (touch.x - x, touch.y - y)
else:
self.ripple_pos = ripple_pos = (touch.x, touch.y)
rc = self.ripple_color
ripple_rad = self.ripple_rad
self.ripple_color = [rc[0], rc[1], rc[2], self.ripple_fade_from_alpha]
with self.ripple_pane:
ScissorPush(
x=int(round(x)),
y=int(round(y)),
width=int(round(width)),
height=int(round(height))
)
self.ripple_col_instruction = Color(rgba=self.ripple_color)
self.ripple_ellipse = Ellipse(
size=(ripple_rad, ripple_rad),
pos=(
ripple_pos[0] - ripple_rad / 2.,
ripple_pos[1] - ripple_rad / 2.
)
)
ScissorPop()
anim = Animation(
ripple_rad=max(width, height) * self.ripple_scale,
t=self.ripple_func_in,
ripple_color=[rc[0], rc[1], rc[2], self.ripple_fade_to_alpha],
duration=self.ripple_duration_in
)
anim.start(self)
def ripple_fade(self):
'''Finish ripple animation on current widget.
'''
Animation.cancel_all(self, 'ripple_rad', 'ripple_color')
width, height = self.size
rc = self.ripple_color
duration = self.ripple_duration_out
anim = Animation(
ripple_rad=max(width, height) * self.ripple_scale,
ripple_color=[rc[0], rc[1], rc[2], 0.],
t=self.ripple_func_out,
duration=duration
)
anim.bind(on_complete=self._ripple_anim_complete)
anim.start(self)
def _ripple_set_ellipse(self, instance, value):
ellipse = self.ripple_ellipse
if not ellipse:
return
ripple_pos = self.ripple_pos
ripple_rad = self.ripple_rad
ellipse.size = (ripple_rad, ripple_rad)
ellipse.pos = (
ripple_pos[0] - ripple_rad / 2.,
ripple_pos[1] - ripple_rad / 2.
)
def _ripple_set_color(self, instance, value):
if not self.ripple_col_instruction:
return
self.ripple_col_instruction.rgba = value
def _ripple_anim_complete(self, anim, instance):
self._ripple_reset_pane()
def _ripple_reset_pane(self):
self.ripple_rad = self.ripple_rad_default
self.ripple_pane.clear()
class TouchRippleButtonBehavior(TouchRippleBehavior):
'''
This `mixin <https://en.wikipedia.org/wiki/Mixin>`_ class provides
a similar behavior to :class:`~kivy.uix.behaviors.button.ButtonBehavior`
but provides touch ripple animation instead of button pressed/released as
visual effect.
:Events:
`on_press`
Fired when the button is pressed.
`on_release`
Fired when the button is released (i.e. the touch/click that
pressed the button goes away).
'''
last_touch = ObjectProperty(None)
'''Contains the last relevant touch received by the Button. This can
be used in `on_press` or `on_release` in order to know which touch
dispatched the event.
:attr:`last_touch` is a :class:`~kivy.properties.ObjectProperty` and
defaults to `None`.
'''
always_release = BooleanProperty(False)
'''This determines whether or not the widget fires an `on_release` event if
the touch_up is outside the widget.
:attr:`always_release` is a :class:`~kivy.properties.BooleanProperty` and
defaults to `False`.
'''
def __init__(self, **kwargs):
self.register_event_type('on_press')
self.register_event_type('on_release')
super(TouchRippleButtonBehavior, self).__init__(**kwargs)
def on_touch_down(self, touch):
if super(TouchRippleButtonBehavior, self).on_touch_down(touch):
return True
if touch.is_mouse_scrolling:
return False
if not self.collide_point(touch.x, touch.y):
return False
if self in touch.ud:
return False
touch.grab(self)
touch.ud[self] = True
self.last_touch = touch
self.ripple_show(touch)
self.dispatch('on_press')
return True
def on_touch_move(self, touch):
if touch.grab_current is self:
return True
if super(TouchRippleButtonBehavior, self).on_touch_move(touch):
return True
return self in touch.ud
def on_touch_up(self, touch):
if touch.grab_current is not self:
return super(TouchRippleButtonBehavior, self).on_touch_up(touch)
assert(self in touch.ud)
touch.ungrab(self)
self.last_touch = touch
if self.disabled:
return
self.ripple_fade()
if not self.always_release and not self.collide_point(*touch.pos):
return
# defer on_release until ripple_fade has completed
def defer_release(dt):
self.dispatch('on_release')
Clock.schedule_once(defer_release, self.ripple_duration_out)
return True
def on_disabled(self, instance, value):
# ensure ripple animation completes if disabled gets set to True
if value:
self.ripple_fade()
return super(TouchRippleButtonBehavior, self).on_disabled(
instance, value)
def on_press(self):
pass
def on_release(self):
pass