pytermgui.animations
All animation-related classes & functions.
The biggest exports are Animation
and its subclasses, as well as Animator
. A
global instance of Animator
is also exported, under the animator
name.
These can be used both within a WindowManager context (where stepping is done
automatically by the pytermgui.window_manager.Compositor
on every frame, or manually,
by calling animator.step
with an elapsed time argument.
You can register animations to the Animator using either its schedule
method, with
an already constructed Animation
subclass, or either Animator.animate_attr
or
Animator.animate_float
for an in-place construction of the animation instance.
View Source
0"""All animation-related classes & functions. 1 2The biggest exports are `Animation` and its subclasses, as well as `Animator`. A 3global instance of `Animator` is also exported, under the `animator` name. 4 5These can be used both within a WindowManager context (where stepping is done 6automatically by the `pytermgui.window_manager.Compositor` on every frame, or manually, 7by calling `animator.step` with an elapsed time argument. 8 9You can register animations to the Animator using either its `schedule` method, with 10an already constructed `Animation` subclass, or either `Animator.animate_attr` or 11`Animator.animate_float` for an in-place construction of the animation instance. 12""" 13 14# pylint: disable=too-many-arguments, too-many-instance-attributes 15 16from __future__ import annotations 17 18from enum import Enum 19from dataclasses import dataclass, field 20from typing import Callable, TYPE_CHECKING, Any 21 22if TYPE_CHECKING: 23 from .widgets import Widget 24else: 25 Widget = Any 26 27__all__ = ["Animator", "FloatAnimation", "AttrAnimation", "animator", "is_animated"] 28 29 30def _add_flag(target: object, attribute: str) -> None: 31 """Adds attribute to `target.__ptg_animated__`. 32 33 If the list doesn't exist, it is created with the attribute. 34 """ 35 36 if not hasattr(target, "__ptg_animated__"): 37 setattr(target, "__ptg_animated__", []) 38 39 animated = getattr(target, "__ptg_animated__") 40 animated.append(attribute) 41 42 43def _remove_flag(target: object, attribute: str) -> None: 44 """Removes attribute from `target.__ptg_animated__`. 45 46 If the animated list is empty, it is `del`-d from the object. 47 """ 48 49 animated = getattr(target, "__ptg_animated__", None) 50 if animated is None: 51 raise ValueError(f"Object {target!r} seems to not be animated.") 52 53 animated.remove(attribute) 54 if len(animated) == 0: 55 del target.__dict__["__ptg_animated__"] 56 57 58def is_animated(target: object, attribute: str) -> bool: 59 """Determines whether the given object.attribute is animated. 60 61 This looks for `__ptg_animated__`, and whether it contains the given attribute. 62 """ 63 64 if not hasattr(target, "__ptg_animated__"): 65 return False 66 67 animated = getattr(target, "__ptg_animated__") 68 69 return attribute in animated 70 71 72class Direction(Enum): 73 """Animation directions.""" 74 75 FORWARD = 1 76 BACKWARD = -1 77 78 79@dataclass 80class Animation: 81 """The baseclass for all animations.""" 82 83 duration: int 84 direction: Direction 85 loop: bool 86 87 on_step: Callable[[Animation], bool] | None 88 on_finish: Callable[[Animation], None] | None 89 90 state: float 91 _remaining: float 92 93 def __post_init__(self) -> None: 94 self.state = 0.0 if self.direction is Direction.FORWARD else 1.0 95 self._remaining = self.duration 96 97 def _update_state(self, elapsed: float) -> bool: 98 """Updates the internal float state of the animation. 99 100 Args: 101 elapsed: The time elapsed since last update. 102 103 Returns: 104 True if the animation deems itself complete, False otherwise. 105 """ 106 107 self._remaining -= elapsed * 1000 108 109 self.state = (self.duration - self._remaining) / self.duration 110 111 if self.direction is Direction.BACKWARD: 112 self.state = 1 - self.state 113 114 self.state = min(self.state, 1.0) 115 116 if not 0.0 <= self.state < 1.0: 117 if not self.loop: 118 return True 119 120 self._remaining = self.duration 121 self.direction = Direction(self.direction.value * -1) 122 123 return False 124 125 def step(self, elapsed: float) -> bool: 126 """Updates animation state. 127 128 This should call `_update_state`, passing in the elapsed value. That call 129 will update the `state` attribute, which can then be used to animate things. 130 131 Args: 132 elapsed: The time elapsed since last update. 133 """ 134 135 state_finished = self._update_state(elapsed) 136 137 step_finished = False 138 if self.on_step is not None: 139 step_finished = self.on_step(self) 140 141 return state_finished or step_finished 142 143 def finish(self) -> None: 144 """Finishes and cleans up after the animation. 145 146 Called by `Animator` after `on_step` returns True. Should call `on_finish` if it 147 is not None. 148 """ 149 150 if self.on_finish is not None: 151 self.on_finish(self) 152 153 154@dataclass 155class FloatAnimation(Animation): 156 """Transitions a floating point number from 0.0 to 1.0. 157 158 Note that this is just a wrapper over the base class, and provides no extra 159 functionality. 160 """ 161 162 duration: int 163 164 on_step: Callable[[Animation], bool] | None = None 165 on_finish: Callable[[Animation], None] | None = None 166 167 direction: Direction = Direction.FORWARD 168 loop: bool = False 169 170 state: float = field(init=False) 171 _remaining: int = field(init=False) 172 173 174@dataclass 175class AttrAnimation(Animation): 176 """Animates an attribute going from one value to another.""" 177 178 target: object = None 179 attr: str = "" 180 value_type: type = int 181 end: int | float = 0 182 start: int | float | None = None 183 184 on_step: Callable[[Animation], bool] | None = None 185 on_finish: Callable[[Animation], None] | None = None 186 187 direction: Direction = Direction.FORWARD 188 loop: bool = False 189 190 state: float = field(init=False) 191 _remaining: int = field(init=False) 192 193 def __post_init__(self) -> None: 194 super().__post_init__() 195 196 if self.start is None: 197 self.start = getattr(self.target, self.attr) 198 199 if self.end < self.start: 200 self.start, self.end = self.end, self.start 201 self.direction = Direction.BACKWARD 202 203 self.end -= self.start 204 205 _add_flag(self.target, self.attr) 206 207 def step(self, elapsed: float) -> bool: 208 """Steps forward in the attribute animation.""" 209 210 state_finished = self._update_state(elapsed) 211 212 step_finished = False 213 214 assert self.start is not None 215 216 updated = self.start + (self.end * self.state) 217 setattr(self.target, self.attr, self.value_type(updated)) 218 219 if self.on_step is not None: 220 step_finished = self.on_step(self) 221 222 if step_finished or state_finished: 223 return True 224 225 return False 226 227 def finish(self) -> None: 228 """Deletes `__ptg_animated__` flag, calls `on_finish`.""" 229 230 _remove_flag(self.target, self.attr) 231 super().finish() 232 233 234class Animator: 235 """The Animator class 236 237 This class maintains a list of animations (self._animations), stepping 238 each of them forward as long as they return False. When they return 239 False, the animation is removed from the tracked animations. 240 241 This stepping is done when `step` is called. 242 """ 243 244 def __init__(self) -> None: 245 """Initializes an animator.""" 246 247 self._animations: list[Animation] = [] 248 249 @property 250 def is_active(self) -> bool: 251 """Determines whether there are any active animations.""" 252 253 return len(self._animations) > 0 254 255 def step(self, elapsed: float) -> None: 256 """Steps the animation forward by the given elapsed time.""" 257 258 for animation in self._animations.copy(): 259 if animation.step(elapsed): 260 self._animations.remove(animation) 261 animation.finish() 262 263 def schedule(self, animation: Animation) -> None: 264 """Starts an animation on the next step.""" 265 266 self._animations.append(animation) 267 268 def animate_attr(self, **animation_args: Any) -> AttrAnimation: 269 """Creates and schedules an AttrAnimation. 270 271 All arguments are passed to the `AttrAnimation` constructor. `direction`, if 272 given as an integer, will be converted to a `Direction` before being passed. 273 274 Returns: 275 The created animation. 276 """ 277 278 if "direction" in animation_args: 279 animation_args["direction"] = Direction(animation_args["direction"]) 280 281 anim = AttrAnimation(**animation_args) 282 self.schedule(anim) 283 284 return anim 285 286 def animate_float(self, **animation_args: Any) -> FloatAnimation: 287 """Creates and schedules an Animation. 288 289 All arguments are passed to the `Animation` constructor. `direction`, if 290 given as an integer, will be converted to a `Direction` before being passed. 291 292 Returns: 293 The created animation. 294 """ 295 296 if "direction" in animation_args: 297 animation_args["direction"] = Direction(animation_args["direction"]) 298 299 anim = FloatAnimation(**animation_args) 300 self.schedule(anim) 301 302 return anim 303 304 305animator = Animator() 306"""The global Animator instance used by all of the library."""
View Source
235class Animator: 236 """The Animator class 237 238 This class maintains a list of animations (self._animations), stepping 239 each of them forward as long as they return False. When they return 240 False, the animation is removed from the tracked animations. 241 242 This stepping is done when `step` is called. 243 """ 244 245 def __init__(self) -> None: 246 """Initializes an animator.""" 247 248 self._animations: list[Animation] = [] 249 250 @property 251 def is_active(self) -> bool: 252 """Determines whether there are any active animations.""" 253 254 return len(self._animations) > 0 255 256 def step(self, elapsed: float) -> None: 257 """Steps the animation forward by the given elapsed time.""" 258 259 for animation in self._animations.copy(): 260 if animation.step(elapsed): 261 self._animations.remove(animation) 262 animation.finish() 263 264 def schedule(self, animation: Animation) -> None: 265 """Starts an animation on the next step.""" 266 267 self._animations.append(animation) 268 269 def animate_attr(self, **animation_args: Any) -> AttrAnimation: 270 """Creates and schedules an AttrAnimation. 271 272 All arguments are passed to the `AttrAnimation` constructor. `direction`, if 273 given as an integer, will be converted to a `Direction` before being passed. 274 275 Returns: 276 The created animation. 277 """ 278 279 if "direction" in animation_args: 280 animation_args["direction"] = Direction(animation_args["direction"]) 281 282 anim = AttrAnimation(**animation_args) 283 self.schedule(anim) 284 285 return anim 286 287 def animate_float(self, **animation_args: Any) -> FloatAnimation: 288 """Creates and schedules an Animation. 289 290 All arguments are passed to the `Animation` constructor. `direction`, if 291 given as an integer, will be converted to a `Direction` before being passed. 292 293 Returns: 294 The created animation. 295 """ 296 297 if "direction" in animation_args: 298 animation_args["direction"] = Direction(animation_args["direction"]) 299 300 anim = FloatAnimation(**animation_args) 301 self.schedule(anim) 302 303 return anim
The Animator class
This class maintains a list of animations (self._animations), stepping each of them forward as long as they return False. When they return False, the animation is removed from the tracked animations.
This stepping is done when step
is called.
View Source
Initializes an animator.
Determines whether there are any active animations.
View Source
Steps the animation forward by the given elapsed time.
View Source
Starts an animation on the next step.
View Source
269 def animate_attr(self, **animation_args: Any) -> AttrAnimation: 270 """Creates and schedules an AttrAnimation. 271 272 All arguments are passed to the `AttrAnimation` constructor. `direction`, if 273 given as an integer, will be converted to a `Direction` before being passed. 274 275 Returns: 276 The created animation. 277 """ 278 279 if "direction" in animation_args: 280 animation_args["direction"] = Direction(animation_args["direction"]) 281 282 anim = AttrAnimation(**animation_args) 283 self.schedule(anim) 284 285 return anim
Creates and schedules an AttrAnimation.
All arguments are passed to the AttrAnimation
constructor. direction
, if
given as an integer, will be converted to a Direction
before being passed.
Returns
The created animation.
View Source
287 def animate_float(self, **animation_args: Any) -> FloatAnimation: 288 """Creates and schedules an Animation. 289 290 All arguments are passed to the `Animation` constructor. `direction`, if 291 given as an integer, will be converted to a `Direction` before being passed. 292 293 Returns: 294 The created animation. 295 """ 296 297 if "direction" in animation_args: 298 animation_args["direction"] = Direction(animation_args["direction"]) 299 300 anim = FloatAnimation(**animation_args) 301 self.schedule(anim) 302 303 return anim
Creates and schedules an Animation.
All arguments are passed to the Animation
constructor. direction
, if
given as an integer, will be converted to a Direction
before being passed.
Returns
The created animation.
View Source
155@dataclass 156class FloatAnimation(Animation): 157 """Transitions a floating point number from 0.0 to 1.0. 158 159 Note that this is just a wrapper over the base class, and provides no extra 160 functionality. 161 """ 162 163 duration: int 164 165 on_step: Callable[[Animation], bool] | None = None 166 on_finish: Callable[[Animation], None] | None = None 167 168 direction: Direction = Direction.FORWARD 169 loop: bool = False 170 171 state: float = field(init=False) 172 _remaining: int = field(init=False)
Transitions a floating point number from 0.0 to 1.0.
Note that this is just a wrapper over the base class, and provides no extra functionality.
View Source
175@dataclass 176class AttrAnimation(Animation): 177 """Animates an attribute going from one value to another.""" 178 179 target: object = None 180 attr: str = "" 181 value_type: type = int 182 end: int | float = 0 183 start: int | float | None = None 184 185 on_step: Callable[[Animation], bool] | None = None 186 on_finish: Callable[[Animation], None] | None = None 187 188 direction: Direction = Direction.FORWARD 189 loop: bool = False 190 191 state: float = field(init=False) 192 _remaining: int = field(init=False) 193 194 def __post_init__(self) -> None: 195 super().__post_init__() 196 197 if self.start is None: 198 self.start = getattr(self.target, self.attr) 199 200 if self.end < self.start: 201 self.start, self.end = self.end, self.start 202 self.direction = Direction.BACKWARD 203 204 self.end -= self.start 205 206 _add_flag(self.target, self.attr) 207 208 def step(self, elapsed: float) -> bool: 209 """Steps forward in the attribute animation.""" 210 211 state_finished = self._update_state(elapsed) 212 213 step_finished = False 214 215 assert self.start is not None 216 217 updated = self.start + (self.end * self.state) 218 setattr(self.target, self.attr, self.value_type(updated)) 219 220 if self.on_step is not None: 221 step_finished = self.on_step(self) 222 223 if step_finished or state_finished: 224 return True 225 226 return False 227 228 def finish(self) -> None: 229 """Deletes `__ptg_animated__` flag, calls `on_finish`.""" 230 231 _remove_flag(self.target, self.attr) 232 super().finish()
Animates an attribute going from one value to another.
View Source
208 def step(self, elapsed: float) -> bool: 209 """Steps forward in the attribute animation.""" 210 211 state_finished = self._update_state(elapsed) 212 213 step_finished = False 214 215 assert self.start is not None 216 217 updated = self.start + (self.end * self.state) 218 setattr(self.target, self.attr, self.value_type(updated)) 219 220 if self.on_step is not None: 221 step_finished = self.on_step(self) 222 223 if step_finished or state_finished: 224 return True 225 226 return False
Steps forward in the attribute animation.
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal.
>>> int('0b100', base=0)
4
Inherited Members
- builtins.int
- int
- conjugate
- bit_length
- bit_count
- to_bytes
- from_bytes
- as_integer_ratio
- real
- imag
- numerator
- denominator
The global Animator instance used by all of the library.
View Source
59def is_animated(target: object, attribute: str) -> bool: 60 """Determines whether the given object.attribute is animated. 61 62 This looks for `__ptg_animated__`, and whether it contains the given attribute. 63 """ 64 65 if not hasattr(target, "__ptg_animated__"): 66 return False 67 68 animated = getattr(target, "__ptg_animated__") 69 70 return attribute in animated
Determines whether the given object.attribute is animated.
This looks for __ptg_animated__
, and whether it contains the given attribute.