From ec7bbad37210aa674600d7097150938fa0a58d99 Mon Sep 17 00:00:00 2001 From: James Turk Date: Tue, 30 Apr 2024 19:56:54 -0500 Subject: [PATCH] moved animation code to more general interface, and updated clock example --- src/doodles/doodles.py | 33 ++++++++++++++++++--------------- src/doodles/examples/clock.py | 15 ++++++++++++++- src/doodles/lines.py | 18 ++++++++++-------- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/doodles/doodles.py b/src/doodles/doodles.py index 889cdf4..ae98bd3 100644 --- a/src/doodles/doodles.py +++ b/src/doodles/doodles.py @@ -12,11 +12,13 @@ This is a reasonable example of when two classes might reasonable share a file. """ import random import copy +import time from abc import ABC, abstractmethod -from typing import Callable, Self +from typing import Callable, Self, Any from .color import Color from .world import world +UpdateCallable = Callable[[float], Any] class Doodle(ABC): """ @@ -38,7 +40,7 @@ class Doodle(ABC): # annotations for instance attributes _parent: Self | None - _updates: list[Callable] + _updates: list[tuple[str, UpdateCallable]] _color: tuple[int, int, int] _alpha: int _z_index: float @@ -110,17 +112,11 @@ class Doodle(ABC): new._register() return new - # Dynamic Update Logic ############ + # animate ####################### - # These methods relate to a WIP feature - # designed to demonstrate a functional hybrid - # approach to having objects update themselves. - # - # This feature isn't complete, or documented yet. - # TODO - - def register_update(self, method, *args): - self._updates.append((method, args)) + def animate(self, prop_name: str, update_func: UpdateCallable) -> Self: + self._updates.append((prop_name, update_func)) + return self def update(self) -> None: """ @@ -130,9 +126,16 @@ class Doodle(ABC): Can be overriden (see examples.balls) to provide per-object update behavior. """ - for method, args in self._updates: - evaled_args = [arg() for arg in args] - method(*evaled_args) + cur_time = time.time() + + 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 ####################### diff --git a/src/doodles/examples/clock.py b/src/doodles/examples/clock.py index 0d75495..ab3445d 100644 --- a/src/doodles/examples/clock.py +++ b/src/doodles/examples/clock.py @@ -1,9 +1,22 @@ import time 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(): g = Group().pos(400, 300) Circle(g).radius(300).color(Color.BLACK).z(1) Circle(g).radius(290).color(Color.BROWN).z(10) 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) diff --git a/src/doodles/lines.py b/src/doodles/lines.py index 66bc4db..88a1281 100644 --- a/src/doodles/lines.py +++ b/src/doodles/lines.py @@ -63,7 +63,7 @@ class Line(Doodle): self._offset_vec = (x, y) 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. @@ -72,14 +72,16 @@ class Line(Doodle): directly (`to`), but there is also an alternate option that handles commonly used case. """ - if callable(degrees): - self.register_update( - self.to, - lambda: magnitude * math.cos(math.radians(degrees())), - lambda: magnitude * math.sin(math.radians(degrees())), - ) - return self + return self.to( + magnitude * math.cos(math.radians(degrees)), + magnitude * math.sin(math.radians(degrees)), + ) + 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( magnitude * math.cos(math.radians(degrees)), magnitude * math.sin(math.radians(degrees)),