33import logging
44
55import asyncio
6+ import sys
67from time import time
78from tracemalloc import start
8- from typing import Callable
9+ from typing import Callable , TypeVar
910
1011from dataclasses import dataclass
1112
1213from ._timer import Timer
1314from ._types import MessageTarget
1415
16+ if sys .version_info >= (3 , 8 ):
17+ from typing import Protocol
18+ else :
19+ from typing_extensions import Protocol
20+
1521
1622EasingFunction = Callable [[float ], float ]
1723
24+ T = TypeVar ("T" )
25+
26+
27+ class Animatable (Protocol ):
28+ def blend (self : T , destination : T , factor : float ) -> T :
29+ ...
30+
31+
1832# https://easings.net/
1933EASING = {
2034 "none" : lambda x : 1.0 ,
2539 "out_cubic" : lambda x : 1 - pow (1 - x , 3 ),
2640}
2741
42+ DEFAULT_EASING = "in_out_cubic"
43+
2844
2945log = logging .getLogger ("rich" )
3046
@@ -35,30 +51,43 @@ class Animation:
3551 attribute : str
3652 start_time : float
3753 duration : float
38- start_value : float
39- end_value : float
54+ start_value : float | Animatable
55+ end_value : float | Animatable
4056 easing_function : EasingFunction
4157
4258 def __call__ (self , time : float ) -> bool :
59+ def blend_float (start : float , end : float , factor : float ) -> float :
60+ return start + (end - start ) * factor
61+
62+ AnimatableT = TypeVar ("AnimatableT" , bound = Animatable )
63+
64+ def blend (start : AnimatableT , end : AnimatableT , factor : float ) -> AnimatableT :
65+ return start .blend (end , factor )
66+
67+ blend_function = (
68+ blend_float if isinstance (self .start_value , (int , float )) else blend
69+ )
4370
4471 if self .duration == 0 :
4572 value = self .end_value
4673 else :
47- progress = min (1.0 , (time - self .start_time ) / self .duration )
74+ factor = min (1.0 , (time - self .start_time ) / self .duration )
75+ eased_factor = self .easing_function (factor )
76+ # value = blend_function(self.start_value, self.end_value, eased_factor)
77+
4878 if self .end_value > self .start_value :
49- eased_progress = self .easing_function (progress )
79+ eased_factor = self .easing_function (factor )
5080 value = (
5181 self .start_value
52- + (self .end_value - self .start_value ) * eased_progress
82+ + (self .end_value - self .start_value ) * eased_factor
5383 )
5484 else :
55- eased_progress = 1 - self .easing_function (progress )
85+ eased_factor = 1 - self .easing_function (factor )
5686 value = (
57- self .end_value
58- + (self .start_value - self .end_value ) * eased_progress
87+ self .end_value + (self .start_value - self .end_value ) * eased_factor
5988 )
60-
6189 setattr (self .obj , self .attribute , value )
90+ log .debug ("ANIMATE %r %r -> %r" , self .obj , self .attribute , value )
6291 return value == self .end_value
6392
6493
@@ -74,7 +103,7 @@ def __call__(
74103 * ,
75104 duration : float | None = None ,
76105 speed : float | None = None ,
77- easing : EasingFunction | str = "in_out_cubic" ,
106+ easing : EasingFunction | str = DEFAULT_EASING ,
78107 ) -> None :
79108 easing_function = EASING [easing ] if isinstance (easing , str ) else easing
80109 self ._animator .animate (
@@ -88,7 +117,7 @@ def __call__(
88117
89118
90119class Animator :
91- def __init__ (self , target : MessageTarget , frames_per_second : int = 30 ) -> None :
120+ def __init__ (self , target : MessageTarget , frames_per_second : int = 60 ) -> None :
92121 self ._animations : dict [tuple [object , str ], Animation ] = {}
93122 self ._timer = Timer (target , 1 / frames_per_second , target , callback = self )
94123
@@ -109,7 +138,7 @@ def animate(
109138 * ,
110139 duration : float | None = None ,
111140 speed : float | None = None ,
112- easing : EasingFunction = EASING [ "in_out_cubic" ] ,
141+ easing : EasingFunction | str = DEFAULT_EASING ,
113142 ) -> None :
114143
115144 start_time = time ()
@@ -123,15 +152,15 @@ def animate(
123152 animation_duration = duration
124153 else :
125154 animation_duration = abs (value - start_value ) / (speed or 50 )
126-
155+ easing_function = EASING [ easing ] if isinstance ( easing , str ) else easing
127156 animation = Animation (
128157 obj ,
129158 attribute = attribute ,
130159 start_time = start_time ,
131160 duration = animation_duration ,
132161 start_value = start_value ,
133162 end_value = value ,
134- easing_function = easing ,
163+ easing_function = easing_function ,
135164 )
136165 self ._animations [animation_key ] = animation
137166 self ._timer .resume ()
0 commit comments