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