moved animation code to more general interface, and updated clock example

This commit is contained in:
James Turk 2024-04-30 19:56:54 -05:00
parent fdd7ee24d3
commit ec7bbad372
3 changed files with 42 additions and 24 deletions

View File

@ -12,11 +12,13 @@ This is a reasonable example of when two classes might reasonable share a file.
""" """
import random import random
import copy import copy
import time
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Callable, Self from typing import Callable, Self, Any
from .color import Color from .color import Color
from .world import world from .world import world
UpdateCallable = Callable[[float], Any]
class Doodle(ABC): class Doodle(ABC):
""" """
@ -38,7 +40,7 @@ class Doodle(ABC):
# annotations for instance attributes # annotations for instance attributes
_parent: Self | None _parent: Self | None
_updates: list[Callable] _updates: list[tuple[str, UpdateCallable]]
_color: tuple[int, int, int] _color: tuple[int, int, int]
_alpha: int _alpha: int
_z_index: float _z_index: float
@ -110,17 +112,11 @@ class Doodle(ABC):
new._register() new._register()
return new return new
# Dynamic Update Logic ############ # animate #######################
# These methods relate to a WIP feature def animate(self, prop_name: str, update_func: UpdateCallable) -> Self:
# designed to demonstrate a functional hybrid self._updates.append((prop_name, update_func))
# approach to having objects update themselves. return self
#
# This feature isn't complete, or documented yet.
# TODO
def register_update(self, method, *args):
self._updates.append((method, args))
def update(self) -> None: def update(self) -> None:
""" """
@ -130,9 +126,16 @@ class Doodle(ABC):
Can be overriden (see examples.balls) Can be overriden (see examples.balls)
to provide per-object update behavior. to provide per-object update behavior.
""" """
for method, args in self._updates: cur_time = time.time()
evaled_args = [arg() for arg in args]
method(*evaled_args) for prop, anim_func in self._updates:
# attributes on Doodle are set via setter functions
# prop is the name of a setter function, which we
# retrieve here, and then populate with the result
# of anim_func(time)
setter = getattr(self, prop)
new_val = anim_func(cur_time)
setter(new_val)
# Setters ####################### # Setters #######################

View File

@ -1,9 +1,22 @@
import time import time
from doodles import Circle, Color, Line, Group from doodles import Circle, Color, Line, Group
def color_func(t):
print("updating color")
cycle = [Color.RED, Color.ORANGE, Color.GREEN, Color.BLUE]
return cycle[int(t) % 4]
def create(): def create():
g = Group().pos(400, 300) g = Group().pos(400, 300)
Circle(g).radius(300).color(Color.BLACK).z(1) Circle(g).radius(300).color(Color.BLACK).z(1)
Circle(g).radius(290).color(Color.BROWN).z(10) Circle(g).radius(290).color(Color.BROWN).z(10)
Circle(g).radius(20).color(Color.BLACK).z(50) Circle(g).radius(20).color(Color.BLACK).z(50)
Line(g).vec(lambda: time.time() % 60 / 60 * 360, 200).z(100) # Line(g).vec(
# lambda: time.time() % 60 / 60 * 360,
# 200
# ).z(100)
l = Line(g).vec(0, 200).z(100).animate("degrees", lambda t: t % 60 / 60 * 360)
l.animate("color", color_func)

View File

@ -63,7 +63,7 @@ class Line(Doodle):
self._offset_vec = (x, y) self._offset_vec = (x, y)
return self return self
def vec(self, degrees: float | Callable, magnitude: float): def vec(self, degrees: float, magnitude: float):
""" """
Alternate setter, to create offset vector from angle & length. Alternate setter, to create offset vector from angle & length.
@ -72,14 +72,16 @@ class Line(Doodle):
directly (`to`), but there is also an alternate option directly (`to`), but there is also an alternate option
that handles commonly used case. that handles commonly used case.
""" """
if callable(degrees): return self.to(
self.register_update( magnitude * math.cos(math.radians(degrees)),
self.to, magnitude * math.sin(math.radians(degrees)),
lambda: magnitude * math.cos(math.radians(degrees())), )
lambda: magnitude * math.sin(math.radians(degrees())),
)
return self
def degrees(self, degrees: float):
"""
Alternate setter, like calling vec(new_degrees, old_magnitude).
"""
magnitude = math.sqrt(self._offset_vec[0]**2 + self._offset_vec[1]**2)
return self.to( return self.to(
magnitude * math.cos(math.radians(degrees)), magnitude * math.cos(math.radians(degrees)),
magnitude * math.sin(math.radians(degrees)), magnitude * math.sin(math.radians(degrees)),