Skip to content
This repository was archived by the owner on Mar 31, 2020. It is now read-only.

Commit bce279a

Browse files
committed
Dynamic pathing support
1 parent 1ae61b3 commit bce279a

File tree

4 files changed

+125
-46
lines changed

4 files changed

+125
-46
lines changed

Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ flake8 = "*"
88
pytest = "*"
99

1010
[packages]
11+
more-itertools = "*"
1112

1213
[requires]
1314
python_version = "3.7"

Pipfile.lock

Lines changed: 12 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/__main__.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
import tkinter as tk
22

3-
from .animate import vector, Coord
3+
from .animate import Motion, Animater, Coord, Direction
44

5-
start = Coord(0, 5)
6-
end = Coord(5, 0)
5+
root = tk.Tk()
76

8-
result = vector(start, end, 5)
9-
print(len(result), result)
7+
window = Animater(root)
8+
window.pack()
9+
10+
c1 = Coord(50, 55)
11+
c2 = Coord(60, 65)
12+
rect = window.create_rectangle(c1, c2)
13+
14+
end = c1 + Direction.RIGHT * 50
15+
end2 = end + Direction.DOWN * 50
16+
end3 = end2 + (Direction.UP + Direction.LEFT) * 50
17+
18+
animation = Motion(window, rect, (end, end2, end3), speed=1)
19+
20+
window.add_motion(animation)
21+
window.add_event('<B1-Motion>')
22+
23+
root.mainloop()

src/animate.py

Lines changed: 93 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
import tkinter as tk
44
import operator
5-
from math import log2
6-
from typing import NamedTuple, Callable, TypeVar, List
7-
from enum import Flag
5+
from typing import NamedTuple, Callable, TypeVar, List, Generator, Tuple
6+
from contextlib import suppress
7+
from more_itertools import chunked
8+
from enum import Enum
89
from dataclasses import dataclass
9-
from functools import partialmethod
10+
from functools import partialmethod, partial
1011

1112

1213
class Coord(NamedTuple):
@@ -17,34 +18,29 @@ class Coord(NamedTuple):
1718
it to the x and y values individually.
1819
1920
param:
20-
x: int -- X position.
21-
y: int -- Y position.
21+
x: float -- X position.
22+
y: float -- Y position.
2223
2324
example::
2425
2526
```
2627
c1 = c2 = Coord(1, 1)
2728
c1 + c2
2829
>>> Coord(2, 2)
29-
# For convenience, integers are accepted as well
30+
# For convenience, numbers are accepted as well
3031
c1 = Coord(1, 1)
3132
c1 + 1 # 1 is cast to Coord(1, 1)
3233
>>> Coord(2, 2)
3334
```
34-
35-
note:
36-
37-
True divide `round`s in order to remain compatible with tkinter
38-
coordinate values (`int`).
3935
"""
4036

41-
x: int
42-
y: int
37+
x: float
38+
y: float
4339

44-
Operand = TypeVar('Operand', 'Coord', int)
40+
Operand = TypeVar('Operand', 'Coord', float)
4541

4642
def __apply(self, op: Callable, other: Coord.Operand) -> Coord:
47-
if isinstance(other, int):
43+
if not isinstance(other, Coord):
4844
other = Coord(other, other)
4945

5046
x = op(self.x, other.x)
@@ -76,28 +72,23 @@ def distance(self, other: Coord) -> int:
7672
dist = map(abs, other - self)
7773
return sum(dist)
7874

79-
def __truediv__(self, other):
80-
result = self.__apply(operator.truediv, other)
81-
return Coord(*map(round, result))
82-
8375
__add__ = partialmethod(__apply, operator.add)
8476
__sub__ = partialmethod(__apply, operator.sub)
8577
__mul__ = partialmethod(__apply, operator.mul)
8678
__mod__ = partialmethod(__apply, operator.mod)
8779
__pow__ = partialmethod(__apply, operator.pow)
8880
__floordiv__ = partialmethod(__apply, operator.floordiv)
81+
__truediv__ = partialmethod(__apply, operator.truediv)
8982

9083

91-
class Direction(Flag):
84+
class Direction(Enum):
9285
"""
9386
Defines base directions.
94-
95-
9687
"""
9788
LEFT = Coord(-1, 0)
9889
RIGHT = Coord(1, 0)
99-
UP = Coord(0, 1)
100-
DOWN = Coord(0, -1)
90+
UP = Coord(0, -1)
91+
DOWN = Coord(0, 1)
10192

10293
def __mul__(self, other: int) -> Coord:
10394
return self.value * other
@@ -106,36 +97,100 @@ def __add__(self, other: Direction) -> Coord:
10697
return self.value + other.value
10798

10899

109-
class Window(tk.Canvas):
100+
class Animater(tk.Canvas):
110101

111-
events = []
102+
motions = []
112103

113-
def set_event(self, event: tk.EventType):
104+
def add_event(self, event: tk.EventType):
114105
self.bind(event, self.run)
115106

116107
def run(self, event):
117-
pass
108+
active = []
109+
for motion in self.motions:
110+
with suppress(StopIteration):
111+
moves = next(motion)
112+
for move in moves:
113+
move()
114+
self.update_idletasks()
115+
active.append(motion)
116+
self.motions = active
117+
118+
def add_motion(self, motion: Motion):
119+
self.motions.append(iter(motion))
118120

119121

120122
@dataclass
121123
class Motion:
124+
"""
125+
Defines the movements derived from the given vector.
126+
The result is a two dimensional generator: the first dimension yields
127+
a "frame" generator, which in turn yields move commands. This structure allows
128+
for different `speed`s of motion, as the length of the second
129+
dimension is determined by `speed`. In other words, the `speed` determines
130+
how many movements occur in one frame.
131+
132+
param:
133+
canvas: tk.Canvas -- The parent canvas to issue the move command with.
134+
id: int -- The id of the widget to be animated.
135+
endpoints: Tuple[Coord] -- The final position(s) of the widget. Multiple positions allow for
136+
more intricate pathing.
137+
speed: int (optional) -- The multipler for move commands per frame.
138+
Defaults to 1.
139+
140+
example::
122141
142+
```
143+
root = tk.Tk()
144+
145+
window = Animater(root)
146+
window.pack()
147+
148+
c1 = Coord(50, 55)
149+
c2 = Coord(60, 65)
150+
rect = window.create_rectangle(c1, c2)
151+
152+
end = c1 + Direction.RIGHT * 50
153+
end2 = end + Direction.DOWN * 50
154+
end3 = end2 + (Direction.UP + Direction.LEFT) * 50
155+
156+
animation = Motion(window, rect, (end, end2, end3), speed=1)
157+
158+
window.add_motion(animation)
159+
window.add_event('<B1-Motion>')
160+
161+
root.mainloop()
162+
```
163+
"""
123164
canvas: tk.Canvas
124-
direction: Direction
125-
speed: int
165+
id: int
166+
endpoints: Tuple[Coord]
126167

127-
frames = 30
168+
steps = 60
169+
speed: int = 1
128170

171+
def start(self) -> Generator[Generator[Callable]]:
172+
"""
173+
The entry point for generating move commands.
174+
"""
175+
move = partial(self.canvas.move, self.id)
129176

130-
class Animate:
177+
def frame(points: List, last: Coord) -> Generator[Callable]:
178+
for point in points:
179+
offset = point - last
180+
yield partial(move, *offset)
181+
last = point
131182

132-
def __init__(self, obj: tk.Widget):
133-
self.obj = obj
183+
for end in self.endpoints:
184+
start = Coord(*self.canvas.coords(self.id)[:2])
185+
vec = vector(start, end, self.steps)
134186

135-
def set_event(self, event: tk.EventType):
136-
self.bind(event, self.run)
187+
last = vec[0]
188+
for points in chunked(vec, self.speed):
189+
yield frame(points, last)
190+
last = points[-1]
137191

138-
# def add(self, )
192+
def __iter__(self):
193+
return self.start()
139194

140195

141196
def vector(start: Coord, end: Coord, step: int = 60) -> List[Coord]:

0 commit comments

Comments
 (0)