Version

Quick search

Table Of Contents

Source code for kivy.effects.kinetic

'''
Kinetic effect
==============

.. versionadded:: 1.7.0

The :class:`KineticEffect` is the base class that is used to compute the
velocity out of a movement. When the movement is finished, the effect will
compute the position of the movement according to the velocity, and reduce the
velocity with a friction. The movement stop until the velocity is 0.

Conceptually, the usage could be::

    >>> effect = KineticEffect()
    >>> effect.start(10)
    >>> effect.update(15)
    >>> effect.update(30)
    >>> effect.stop(48)

Over the time, you will start a movement of a value, update it, and stop the
movement. At this time, you'll get the movement value into
:attr:`KineticEffect.value`. On the example i've typed manually, the computed
velocity will be::

    >>> effect.velocity
    3.1619100231163046

After multiple clock interaction, the velocity will decrease according to
:attr:`KineticEffect.friction`. The computed value will be stored in
:attr:`KineticEffect.value`. The output of this `value` could be::

    46.30038145219605
    54.58302451968686
    61.9229016256196
    # ...

'''

__all__ = ('KineticEffect', )


from time import time
from kivy.event import EventDispatcher
from kivy.properties import NumericProperty, BooleanProperty
from kivy.clock import Clock


[docs]class KineticEffect(EventDispatcher): '''Kinetic effect class. See module documentation for more information. ''' velocity = NumericProperty(0) '''Velocity of the movement. :attr:`velocity` is a :class:`~kivy.properties.NumericProperty` and defaults to 0. ''' friction = NumericProperty(0.05) '''Friction to apply on the velocity :attr:`friction` is a :class:`~kivy.properties.NumericProperty` and defaults to 0.05. ''' value = NumericProperty(0) '''Value (during the movement and computed) of the effect. :attr:`value` is a :class:`~kivy.properties.NumericProperty` and defaults to 0. ''' is_manual = BooleanProperty(False) '''Indicate if a movement is in progress (True) or not (False). :attr:`is_manual` is a :class:`~kivy.properties.BooleanProperty` and defaults to False. ''' max_history = NumericProperty(5) '''Save up to `max_history` movement value into the history. This is used for correctly calculating the velocity according to the movement. :attr:`max_history` is a :class:`~kivy.properties.NumericProperty` and defaults to 5. ''' min_distance = NumericProperty(.1) '''The minimal distance for a movement to have nonzero velocity. .. versionadded:: 1.8.0 :attr:`min_distance` is :class:`~kivy.properties.NumericProperty` and defaults to 0.1. ''' min_velocity = NumericProperty(.5) '''Velocity below this quantity is normalized to 0. In other words, any motion whose velocity falls below this number is stopped. .. versionadded:: 1.8.0 :attr:`min_velocity` is a :class:`~kivy.properties.NumericProperty` and defaults to 0.5. ''' std_dt = NumericProperty(0.017) ''' std_dt correction update_velocity if dt is not constant .. versionadded:: 2.0.0 :attr:`std_dt` is a :class:`~kivy.properties.NumericProperty` and defaults to 0.017. ''' def __init__(self, **kwargs): self.history = [] self.trigger_velocity_update = Clock.create_trigger( self.update_velocity, 0) super(KineticEffect, self).__init__(**kwargs) def apply_distance(self, distance): if abs(distance) < self.min_distance: self.velocity = 0 self.value += distance
[docs] def start(self, val, t=None): '''Start the movement. :Parameters: `val`: float or int Value of the movement `t`: float, defaults to None Time when the movement happen. If no time is set, it will use time.time() ''' self.is_manual = True t = t or time() self.velocity = 0 self.history = [(t, val)]
[docs] def update(self, val, t=None): '''Update the movement. See :meth:`start` for the arguments. ''' t = t or time() distance = val - self.history[-1][1] self.apply_distance(distance) self.history.append((t, val)) if len(self.history) > self.max_history: self.history.pop(0)
[docs] def stop(self, val, t=None): '''Stop the movement. See :meth:`start` for the arguments. ''' self.is_manual = False t = t or time() distance = val - self.history[-1][1] self.apply_distance(distance) newest_sample = (t, val) old_sample = self.history[0] for sample in self.history: if (newest_sample[0] - sample[0]) < 10. / 60.: break old_sample = sample distance = newest_sample[1] - old_sample[1] duration = abs(newest_sample[0] - old_sample[0]) self.velocity = (distance / max(duration, 0.0001)) self.trigger_velocity_update()
[docs] def cancel(self): '''Cancel a movement. This can be used in case :meth:`stop` cannot be called. It will reset :attr:`is_manual` to False, and compute the movement if the velocity is > 0. ''' self.is_manual = False self.trigger_velocity_update()
[docs] def update_velocity(self, dt): '''(internal) Update the velocity according to the frametime and friction. ''' if abs(self.velocity) <= self.min_velocity: self.velocity = 0 return self.velocity -= self.velocity * self.friction * dt / self.std_dt self.apply_distance(self.velocity * dt) self.trigger_velocity_update()