225 lines
6.2 KiB
Python
225 lines
6.2 KiB
Python
|
"""
|
||
|
Animation
|
||
|
=========
|
||
|
|
||
|
.. versionadded:: 2.0.0
|
||
|
|
||
|
Adds new transitions to the :class:`~kivy.animation.AnimationTransition` class:
|
||
|
- "easing_standard"
|
||
|
- "easing_decelerated"
|
||
|
- "easing_accelerated"
|
||
|
- "easing_linear"
|
||
|
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
|
||
|
from kivy.lang import Builder
|
||
|
from kivy.animation import Animation
|
||
|
from kivy.uix.boxlayout import BoxLayout
|
||
|
from kivy.clock import Clock
|
||
|
from kivy.metrics import dp
|
||
|
from kivy.properties import ListProperty
|
||
|
|
||
|
from kivymd.app import MDApp
|
||
|
|
||
|
|
||
|
class AnimBox(BoxLayout):
|
||
|
obj_pos = ListProperty([0, 0])
|
||
|
|
||
|
|
||
|
UI = '''
|
||
|
<AnimBox>:
|
||
|
transition:"in_out_bounce"
|
||
|
size_hint_y:None
|
||
|
height:dp(100)
|
||
|
obj_pos:[dp(40), self.pos[-1] + dp(40)]
|
||
|
canvas:
|
||
|
Color:
|
||
|
rgba:app.theme_cls.primaryContainerColor
|
||
|
Rectangle:
|
||
|
size:[self.size[0], dp(5)]
|
||
|
pos:self.pos[0], self.pos[-1] + dp(50)
|
||
|
Color:
|
||
|
rgba:app.theme_cls.primaryColor
|
||
|
Rectangle:
|
||
|
size:[dp(30)] * 2
|
||
|
pos:root.obj_pos
|
||
|
MDLabel:
|
||
|
adaptive_height:True
|
||
|
text:root.transition
|
||
|
padding:[dp(10), 0]
|
||
|
halign:"center"
|
||
|
|
||
|
MDGridLayout:
|
||
|
orientation:"lr-tb"
|
||
|
cols:1
|
||
|
md_bg_color:app.theme_cls.backgroundColor
|
||
|
spacing:dp(10)
|
||
|
'''
|
||
|
|
||
|
|
||
|
class MotionApp(MDApp):
|
||
|
|
||
|
def build(self):
|
||
|
return Builder.load_string(UI)
|
||
|
|
||
|
def on_start(self):
|
||
|
for transition in [
|
||
|
"easing_linear",
|
||
|
"easing_accelerated",
|
||
|
"easing_decelerated",
|
||
|
"easing_standard",
|
||
|
"in_out_cubic"
|
||
|
]: # Add more here for comparison
|
||
|
print(transition)
|
||
|
widget = AnimBox()
|
||
|
widget.transition = transition
|
||
|
self.root.add_widget(widget)
|
||
|
Clock.schedule_once(self.run_animation, 1)
|
||
|
|
||
|
_inverse = True
|
||
|
def run_animation(self, dt):
|
||
|
x = (self.root.children[0].width - dp(30)) if self._inverse else 0
|
||
|
for widget in self.root.children:
|
||
|
Animation(
|
||
|
obj_pos=[x, widget.obj_pos[-1]], t=widget.transition, d=3
|
||
|
).start(widget)
|
||
|
self._inverse = not self._inverse
|
||
|
Clock.schedule_once(self.run_animation, 3.1)
|
||
|
|
||
|
MotionApp().run()
|
||
|
|
||
|
.. image:: https://github.com/kivymd/KivyMD/assets/68729523/21c847b0-284a-4796-b704-e4a2531fbb1b
|
||
|
:align: center
|
||
|
"""
|
||
|
|
||
|
import math
|
||
|
import sys
|
||
|
import kivy.animation
|
||
|
|
||
|
float_epsilon = 8.3446500e-7
|
||
|
|
||
|
if sys.version_info < (3, 11):
|
||
|
cbrt = lambda number: (abs(number) ** (1/3)) * (-1 if number < 0 else 1)
|
||
|
else:
|
||
|
cbrt = math.cbrt
|
||
|
|
||
|
class CubicBezier:
|
||
|
"""Ported from Android source code"""
|
||
|
|
||
|
p0 = 0
|
||
|
p1 = 0
|
||
|
p2 = 0
|
||
|
p3 = 0
|
||
|
|
||
|
def __init__(self, *args):
|
||
|
self.p0, self.p1, self.p2, self.p3 = args
|
||
|
|
||
|
def evaluate_cubic(self, p1, p2, t):
|
||
|
a = 1.0 / 3.0 + (p1 - p2)
|
||
|
b = p2 - 2.0 * p1
|
||
|
c = p1
|
||
|
return 3.0 * ((a * t + b) * t + c) * t
|
||
|
|
||
|
def clamp_range(self, r):
|
||
|
if r < 0.0:
|
||
|
if -float_epsilon <= r < 0.0:
|
||
|
return 0.0
|
||
|
else:
|
||
|
return math.nan
|
||
|
elif r > 1.0:
|
||
|
if 1.0 <= r <= 1.0 + float_epsilon:
|
||
|
return 1.0
|
||
|
else:
|
||
|
return math.nan
|
||
|
else:
|
||
|
return r
|
||
|
|
||
|
def close_to(self, x, y):
|
||
|
return abs(x - y) < float_epsilon
|
||
|
|
||
|
def find_first_cubic_root(self, p0, p1, p2, p3):
|
||
|
a = 3.0 * (p0 - 2.0 * p1 + p2)
|
||
|
b = 3.0 * (p1 - p0)
|
||
|
c = p0
|
||
|
d = -p0 + 3.0 * (p1 - p2) + p3
|
||
|
if self.close_to(d, 0.0):
|
||
|
if self.close_to(a, 0.0):
|
||
|
if self.close_to(b, 0.0):
|
||
|
return math.nan
|
||
|
return self.clamp_range(-c / b)
|
||
|
else:
|
||
|
q = math.sqrt(b * b - 4.0 * a * c)
|
||
|
a2 = 2.0 * a
|
||
|
root = self.clamp_range((q - b) / a2)
|
||
|
if not math.isnan(root):
|
||
|
return root
|
||
|
return self.clamp_range((-b - q) / a2)
|
||
|
a /= d
|
||
|
b /= d
|
||
|
c /= d
|
||
|
o3 = (3.0 * b - a * a) / 9.0
|
||
|
q2 = (2.0 * a * a * a - 9.0 * a * b + 27.0 * c) / 54.0
|
||
|
discriminant = q2 * q2 + o3 * o3 * o3
|
||
|
a3 = a / 3.0
|
||
|
|
||
|
if discriminant < 0.0:
|
||
|
mp33 = -(o3 * o3 * o3)
|
||
|
r = math.sqrt(mp33)
|
||
|
t = -q2 / r
|
||
|
cos_phi = max(-1.0, min(t, 1.0))
|
||
|
phi = math.acos(cos_phi)
|
||
|
t1 = 2.0 * cbrt(r)
|
||
|
root = self.clamp_range(t1 * math.cos(phi / 3.0) - a3)
|
||
|
if not math.isnan(root):
|
||
|
return root
|
||
|
root = self.clamp_range(
|
||
|
t1 * math.cos((phi + 2.0 * math.pi) / 3.0) - a3
|
||
|
)
|
||
|
if not math.isnan(root):
|
||
|
return root
|
||
|
return self.clamp_range(
|
||
|
t1 * math.cos((phi + 4.0 * math.pi) / 3.0) - a3
|
||
|
)
|
||
|
|
||
|
elif self.close_to(discriminant, 0.0):
|
||
|
u1 = -cbrt(q2)
|
||
|
root = self.clamp_range(2.0 * u1 - a3)
|
||
|
if not math.isnan(root):
|
||
|
return root
|
||
|
return self.clamp_range(-u1 - a3)
|
||
|
|
||
|
sd = math.sqrt(discriminant)
|
||
|
u1 = cbrt(-q2 + sd)
|
||
|
v1 = cbrt(q2 + sd)
|
||
|
return self.clamp_range(u1 - v1 - a3)
|
||
|
|
||
|
def t(self, value: float):
|
||
|
return self.evaluate_cubic(
|
||
|
self.p1,
|
||
|
self.p3,
|
||
|
self.find_first_cubic_root(
|
||
|
-value,
|
||
|
self.p0 - value,
|
||
|
self.p2 - value,
|
||
|
1.0 - value,
|
||
|
),
|
||
|
)
|
||
|
|
||
|
|
||
|
class MDAnimationTransition(kivy.animation.AnimationTransition):
|
||
|
"""KivyMD's equivalent of kivy's `AnimationTransition`"""
|
||
|
|
||
|
easing_standard = CubicBezier(0.4, 0.0, 0.2, 1.0).t
|
||
|
easing_decelerated = CubicBezier(0.0, 0.0, 0.2, 1.0).t
|
||
|
easing_accelerated = CubicBezier(0.4, 0.0, 1.0, 1.0).t
|
||
|
easing_linear = CubicBezier(0.0, 0.0, 1.0, 1.0).t
|
||
|
|
||
|
# TODO: add `easing_emphasized` here
|
||
|
# it's defination is
|
||
|
# path(M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1)
|
||
|
|
||
|
# Monkey patch kivy's animation module
|
||
|
kivy.animation.AnimationTransition = MDAnimationTransition
|