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

Commit 1b7c92a

Browse files
author
Noah Corona
authored
Merge pull request #10 from Zer0897/animation
Animation: Project Overhaul
2 parents 31fd9ce + 8c7e5c5 commit 1b7c92a

File tree

11 files changed

+413
-322
lines changed

11 files changed

+413
-322
lines changed

res/settings.ini

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[DEFAULT]
2-
main.title = Cat Tinder
2+
main.title = App
33
main.geometry = 400x500
44
main.background = black
55
cachesize = 10
@@ -15,5 +15,6 @@ back.background = blue
1515
bio.text = Bio
1616
bio.background = blue
1717

18-
[DEV]
19-
main.title = DEV MODE CAT TINDER
18+
19+
[APP]
20+
title = App

res/widgets.ini

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[base]
2+
3+
[primary]
4+
5+
6+
[secondary]
7+

src/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
RES: Path = ROOT / 'res'
66

77
SETTINGS: Path = RES / 'settings.ini'
8+
THEME: Path = RES / 'widgets.ini'
89
IMAGES: Path = RES / 'images'
910
SOUNDS: Path = RES / 'sounds'

src/__main__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .mainwindow import Tinder
1+
from .main import App
22

33
if __name__ == "__main__":
4-
Tinder().start()
4+
App().mainloop()

src/animate.py

Lines changed: 50 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22

33
import tkinter as tk
44
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
87
from enum import Enum
98
from dataclasses import dataclass
109
from functools import partialmethod, partial
@@ -110,39 +109,51 @@ def __add__(self, other: Direction) -> Coord:
110109
else:
111110
return self.value + other
112111

112+
def flip(self):
113+
return Direction(Coord(0, 0) - self.value)
113114

114-
class Animater(tk.Canvas):
115+
116+
class Animater:
115117
"""
116-
Inherits Canvas. Manager for executing animation move commands.
117-
Currently only event base animations are supported.
118+
Manager for executing animations.
118119
119120
example::
120121
121122
```
122123
motion = Motion(...)
123-
window = Animator(...)
124+
window = Animater(...)
124125
window.add_motion(motion)
125-
window.add_event('<Enter>')
126126
```
127127
"""
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
129147

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))
132150

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)
143154

144-
def add_motion(self, motion: Motion):
145-
self.motions.append(iter(motion))
155+
def clear(self):
156+
self._motions.clear()
146157

147158

148159
@dataclass
@@ -191,70 +202,36 @@ class Motion:
191202
id: int
192203
endpoints: Tuple[Coord]
193204

194-
steps = 60
195205
speed: int = 1
196206

197-
def start(self) -> Generator[Generator[Callable]]:
207+
def start(self) -> Generator[Callable]:
198208
"""
199209
The entry point for generating move commands.
200210
"""
201211
move = partial(self.canvas.move, self.id)
202212

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()
208217

209218
for end in self.endpoints:
210219
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
212223

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))
217226

218227
def __iter__(self):
219228
return self.start()
220229

230+
def __key(self):
231+
return self.id
221232

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())
234235

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()

src/cache.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ def __init__(self, root):
2323
# get settings
2424
cp = configparser.ConfigParser()
2525
cp.read(str(SETTINGS))
26-
cp.read('settings.ini')
2726

2827
# for now, let's just look up the DEV settings
2928
# can change this later
@@ -75,7 +74,7 @@ async def refill(self):
7574
image_bytes = await res.read()
7675
# open and the image in pillow
7776
im = Image.open(io.BytesIO(image_bytes))
78-
im = im.resize((400, 440), Image.NEAREST)
77+
im = im.resize((400, 280), Image.NEAREST)
7978
# make the image a tkinter image
8079
image = ImageTk.PhotoImage(im)
8180
# update the cat data dict

src/front.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import asyncio
2+
3+
from . import widget
4+
from .animate import Direction
5+
from .view import Window, View
6+
from .cache import Cache
7+
8+
9+
class Front(widget.PrimaryFrame):
10+
11+
cachesize = 10
12+
13+
# Quick fix to keep a reference count on the
14+
# last image, making sure the garbage collector
15+
# doesn't delete it before the animation ends
16+
_last = None
17+
18+
def __next(self):
19+
data: dict = self.cache.pop()
20+
data.pop('jumpscare') # not using it for now
21+
if 'name' in data: # TODO Fix
22+
name = data.pop('name')
23+
else:
24+
name = 'None'
25+
image = data.pop('image')
26+
self.__load(name, image, data)
27+
28+
def __load(self, name, image, data):
29+
self.title.config(text=name)
30+
self.bio.data.load(data)
31+
self._last = self.image
32+
self.image = View(image, 'image')
33+
self.update()
34+
35+
def __change_image(self, direction: Direction):
36+
self.__next()
37+
self.window.change_view(self.image, direction)
38+
39+
def init(self):
40+
self.title = widget.PrimaryLabel(self)
41+
self.window = Window(self)
42+
self.commandbar = widget.SecondaryFrame(self)
43+
44+
self.bio = View(Bio(self.window), 'widget')
45+
self.image = None
46+
47+
self.btn_dislike = widget.PrimaryButton(
48+
self.commandbar, text='Nope', bg='red', command=self.cmd_dislike
49+
)
50+
self.btn_bio = widget.SecondaryButton(
51+
self.commandbar, text='Bio', command=self.cmd_bio
52+
)
53+
self.btn_like = widget.PrimaryButton(
54+
self.commandbar, text='Yep', bg='green', command=self.cmd_like
55+
)
56+
self.title.pack(fill='x')
57+
self.window.pack(fill='both')
58+
self.commandbar.pack(side='bottom', fill='x')
59+
60+
self.btn_bio.pack()
61+
self.btn_dislike.pack(side='left')
62+
self.btn_like.pack(side='right')
63+
64+
self._loop = asyncio.get_event_loop()
65+
self.cache = Cache(self.window)
66+
self.cache
67+
68+
def cmd_dislike(self):
69+
self.__change_image('right')
70+
71+
def cmd_like(self):
72+
self.__change_image('left')
73+
74+
def cmd_bio(self):
75+
if self.window.current != self.bio:
76+
self.window.change_view(self.bio, 'up')
77+
else:
78+
self.window.change_view(self.image, 'down')
79+
80+
@property
81+
def cache(self):
82+
if len(self._cache.cats) < self.cachesize:
83+
self._loop.run_until_complete(self._cache.refill())
84+
return self._cache.cats
85+
86+
@cache.setter
87+
def cache(self, data: list):
88+
self._cache = data
89+
# Prime the pump
90+
self.__next()
91+
self.window.set_view(self.image)
92+
93+
94+
class Bio(widget.PrimaryFrame):
95+
96+
# def init(self):
97+
# width = self.master.winfo_width()
98+
# height = self.master.winfo_height()
99+
# self.config(height=height, width=width)
100+
# #self.pack_propagate(0)
101+
102+
def __make_item(self, name, value):
103+
item = widget.SecondaryFrame(self)
104+
name = widget.SecondaryLabel(item, text=name)
105+
value = widget.SecondaryLabel(item, text=value)
106+
name.pack(side='left')
107+
value.pack(side='right')
108+
return item
109+
110+
def load(self, data: dict):
111+
# print(data)
112+
for name, val in data.items():
113+
item = self.__make_item(name, val)
114+
item.pack()

src/main.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import configparser
2+
import tkinter as tk
3+
from pygame import mixer
4+
5+
from .front import Front
6+
from . import SETTINGS
7+
8+
9+
parser = configparser.ConfigParser()
10+
parser.read(SETTINGS)
11+
12+
13+
class App(tk.Tk):
14+
appconfig = parser['APP']
15+
16+
def __init__(self, *args, **kwds):
17+
title = self.appconfig.pop('title')
18+
super().__init__(*args, **kwds)
19+
self.title = title
20+
mixer.init()
21+
22+
self.geometry = '400x500'
23+
self.minsize(400, 500)
24+
self.maxsize(400, 500)
25+
26+
self.front = Front(self)
27+
28+
self.front.pack(fill='both', expand=True)

0 commit comments

Comments
 (0)