|
2 | 2 |
|
3 | 3 | import tkinter as tk |
4 | 4 | import operator |
5 | | -from typing import NamedTuple, Callable, TypeVar, List, Generator, Tuple |
6 | | -from contextlib import suppress |
7 | | -from more_itertools import chunked |
| 5 | +import time |
| 6 | +from typing import NamedTuple, Callable, TypeVar, Generator, Tuple |
8 | 7 | from enum import Enum |
9 | 8 | from dataclasses import dataclass |
10 | 9 | from functools import partialmethod, partial |
@@ -110,39 +109,51 @@ def __add__(self, other: Direction) -> Coord: |
110 | 109 | else: |
111 | 110 | return self.value + other |
112 | 111 |
|
| 112 | + def flip(self): |
| 113 | + return Direction(Coord(0, 0) - self.value) |
113 | 114 |
|
114 | | -class Animater(tk.Canvas): |
| 115 | + |
| 116 | +class Animater: |
115 | 117 | """ |
116 | | - Inherits Canvas. Manager for executing animation move commands. |
117 | | - Currently only event base animations are supported. |
| 118 | + Manager for executing animations. |
118 | 119 |
|
119 | 120 | example:: |
120 | 121 |
|
121 | 122 | ``` |
122 | 123 | motion = Motion(...) |
123 | | - window = Animator(...) |
| 124 | + window = Animater(...) |
124 | 125 | window.add_motion(motion) |
125 | | - window.add_event('<Enter>') |
126 | 126 | ``` |
127 | 127 | """ |
128 | | - motions = [] |
| 128 | + _motions = set() |
| 129 | + fps = 60 |
| 130 | + |
| 131 | + def __init__(self, canvas: tk.Canvas): |
| 132 | + self.canvas = canvas |
| 133 | + |
| 134 | + def start(self): |
| 135 | + while self._motions: |
| 136 | + time.sleep(1/self.fps) |
| 137 | + self.run() |
| 138 | + |
| 139 | + def run(self): |
| 140 | + for motion in self._motions: |
| 141 | + try: |
| 142 | + next(motion)() |
| 143 | + self.canvas.update() |
| 144 | + except StopIteration: |
| 145 | + self._motions.remove(motion) |
| 146 | + break |
129 | 147 |
|
130 | | - def add_event(self, event: tk.EventType): |
131 | | - self.bind(event, self.run) |
| 148 | + def add(self, motion: Motion): |
| 149 | + self._motions.add(iter(motion)) |
132 | 150 |
|
133 | | - def run(self, event): |
134 | | - active = [] |
135 | | - for motion in self.motions: |
136 | | - with suppress(StopIteration): |
137 | | - moves = next(motion) |
138 | | - for move in moves: |
139 | | - move() |
140 | | - self.update_idletasks() |
141 | | - active.append(motion) |
142 | | - self.motions = active |
| 151 | + def add_motion(self, id: int, end: Coord, **kwargs): |
| 152 | + motion = Motion(self.canvas, id, (end,), **kwargs) |
| 153 | + self.add(motion) |
143 | 154 |
|
144 | | - def add_motion(self, motion: Motion): |
145 | | - self.motions.append(iter(motion)) |
| 155 | + def clear(self): |
| 156 | + self._motions.clear() |
146 | 157 |
|
147 | 158 |
|
148 | 159 | @dataclass |
@@ -191,70 +202,36 @@ class Motion: |
191 | 202 | id: int |
192 | 203 | endpoints: Tuple[Coord] |
193 | 204 |
|
194 | | - steps = 60 |
195 | 205 | speed: int = 1 |
196 | 206 |
|
197 | | - def start(self) -> Generator[Generator[Callable]]: |
| 207 | + def start(self) -> Generator[Callable]: |
198 | 208 | """ |
199 | 209 | The entry point for generating move commands. |
200 | 210 | """ |
201 | 211 | move = partial(self.canvas.move, self.id) |
202 | 212 |
|
203 | | - def frame(points: List, last: Coord) -> Generator[Callable]: |
204 | | - for point in points: |
205 | | - offset = point - last |
206 | | - yield partial(move, *offset) |
207 | | - last = point |
| 213 | + def frame(increment: Coord, count: int): |
| 214 | + for _ in range(count): |
| 215 | + move(*increment) |
| 216 | + self.canvas.master.update_idletasks() |
208 | 217 |
|
209 | 218 | for end in self.endpoints: |
210 | 219 | start = Coord(*self.canvas.coords(self.id)[:2]) |
211 | | - vec = vector(start, end, self.steps) |
| 220 | + steps = round(start.distance(end) / self.speed) |
| 221 | + frames = round(steps / self.speed) |
| 222 | + increment = (end - start) / steps |
212 | 223 |
|
213 | | - last = vec[0] |
214 | | - for points in chunked(vec, self.speed): |
215 | | - yield frame(points, last) |
216 | | - last = points[-1] |
| 224 | + for _ in range(frames): |
| 225 | + yield partial(frame, increment, round(steps / frames)) |
217 | 226 |
|
218 | 227 | def __iter__(self): |
219 | 228 | return self.start() |
220 | 229 |
|
| 230 | + def __key(self): |
| 231 | + return self.id |
221 | 232 |
|
222 | | -def vector(start: Coord, end: Coord, step: int = 60) -> List[Coord]: |
223 | | - """ |
224 | | - Creates a list of all the Coords on a straight line from `start` to `end` (inclusive). |
225 | | -
|
226 | | - param: |
227 | | - start: Coord -- The starting point. |
228 | | - end: Coord -- The end point. |
229 | | - step: int (optional) -- The desired number of points to include. Defaults to 60. |
230 | | - Actual resulting length may vary. |
231 | | -
|
232 | | - return: |
233 | | - List[Coord] -- All points that fall on the line from start to end. |
| 233 | + def __hash__(self): |
| 234 | + return hash(self.__key()) |
234 | 235 |
|
235 | | - example:: |
236 | | -
|
237 | | - ``` |
238 | | - start = Coord(0, 5) |
239 | | - end = Coord(5, 0) |
240 | | - vector(start, end, 5) # ends up being 8 |
241 | | - >>> [ |
242 | | - Coord(x=0, y=5), Coord(x=1, y=4), Coord(x=1, y=4), |
243 | | - Coord(x=2, y=2), Coord(x=2, y=2), Coord(x=4, y=1), |
244 | | - Coord(x=4, y=1), Coord(x=5, y=0) |
245 | | - ] |
246 | | - ``` |
247 | | -
|
248 | | - note: |
249 | | -
|
250 | | - The current implementation recursively finds midpoints to build the line. |
251 | | - This means the resulting length may vary, due to its eager nature. |
252 | | - """ |
253 | | - mid = start.midpoint(end) |
254 | | - instep = round(step / 2) |
255 | | - if instep: |
256 | | - back = vector(start, mid, step=instep) |
257 | | - front = vector(mid, end, step=instep) |
258 | | - return back + front |
259 | | - else: |
260 | | - return [start, end] |
| 236 | + def __eq__(self): |
| 237 | + return isinstance(self, type(other)) and self.__key() == other.__key() |
0 commit comments