Mercurial > code > home > repos > light9
annotate flax/Timeline.py @ 148:d6252880a6fc
after the mega merge -- now has a dummy mode
author | dmcc |
---|---|
date | Sun, 06 Jul 2003 14:05:33 +0000 |
parents | 83e2c4ceb79a |
children | 851cf44cea40 |
rev | line source |
---|---|
0 | 1 from TLUtility import make_attributes_from_args, dict_scale, dict_max, \ |
2 DummyClass, last_less_than, first_greater_than | |
3 from time import time | |
4 from __future__ import division # "I'm sending you back to future!" | |
5 | |
6 """ | |
110
490843093506
all of this stuff is super rough and not well thought out yet.
dmcc
parents:
109
diff
changeset
|
7 Quote of the Build (from Ghostbusters II) |
490843093506
all of this stuff is super rough and not well thought out yet.
dmcc
parents:
109
diff
changeset
|
8 Dana: Okay, but after dinner, I don't want you putting any of your old cheap |
490843093506
all of this stuff is super rough and not well thought out yet.
dmcc
parents:
109
diff
changeset
|
9 moves on me. |
490843093506
all of this stuff is super rough and not well thought out yet.
dmcc
parents:
109
diff
changeset
|
10 Peter: Ohhhh no! I've got all NEW cheap moves. |
0 | 11 """ |
12 | |
13 class MissingBlender(Exception): | |
14 """Raised when a TimedEvent is missing a blender.""" | |
15 def __init__(self, timedevent): | |
16 make_attributes_from_args('timedevent') | |
17 Exception.__init__(self, "%r is missing a blender." % \ | |
18 self.timedevent) | |
19 | |
20 # these are chosen so we can multiply by -1 to reverse the direction, | |
21 # and multiply direction by the time difference to determine new times. | |
22 FORWARD = 1 | |
23 BACKWARD = -1 | |
24 | |
135 | 25 MISSING = 'missing' |
26 | |
0 | 27 class TimedEvent: |
28 """Container for a Frame which includes a time that it occurs at, | |
29 and which blender occurs after it.""" | |
135 | 30 def __init__(self, time, frame=MISSING, blender=None, level=1.0): |
0 | 31 make_attributes_from_args('time', 'frame') |
32 self.next_blender = blender | |
122
2ed9bfd1dd0e
I totally wrecked Timeline so that it can run the show. (I hope it can
dmcc
parents:
110
diff
changeset
|
33 self.level = level |
0 | 34 def __float__(self): |
35 return self.time | |
36 def __cmp__(self, other): | |
135 | 37 if other is None: |
38 raise "I can't compare with a None. I am '%s'" % str(self) | |
0 | 39 if type(other) in (float, int): |
40 return cmp(self.time, other) | |
41 else: | |
42 return cmp(self.time, other.time) | |
43 def __repr__(self): | |
122
2ed9bfd1dd0e
I totally wrecked Timeline so that it can run the show. (I hope it can
dmcc
parents:
110
diff
changeset
|
44 return "<TimedEvent %s at %.2f, time=%.2f, next blender=%s>" % \ |
2ed9bfd1dd0e
I totally wrecked Timeline so that it can run the show. (I hope it can
dmcc
parents:
110
diff
changeset
|
45 (self.frame, self.level, self.time, self.next_blender) |
2ed9bfd1dd0e
I totally wrecked Timeline so that it can run the show. (I hope it can
dmcc
parents:
110
diff
changeset
|
46 def get_level(self): |
2ed9bfd1dd0e
I totally wrecked Timeline so that it can run the show. (I hope it can
dmcc
parents:
110
diff
changeset
|
47 return self.level |
123 | 48 def __hash__(self): |
49 return id(self.time) ^ id(self.frame) ^ id(self.next_blender) | |
0 | 50 |
51 class Blender: | |
52 """Blenders are functions that merge the effects of two LevelFrames.""" | |
53 def __init__(self): | |
54 pass | |
135 | 55 def __call__(self, startframe, endframe, blendtime, time_since_startframe): |
0 | 56 """Return a LevelFrame combining two LevelFrames (startframe and |
57 endframe). blendtime is how much of the blend should be performed | |
58 and will be expressed as a percentage divided by 100, i.e. a float | |
135 | 59 between 0.0 and 1.0. time_since_startframe is the time since the |
60 startframe was on screen in seconds (float). | |
0 | 61 |
62 Very important note: Blenders will *not* be asked for values | |
63 at end points (i.e. blendtime=0.0 and blendtime=1.0). | |
64 The LevelFrames will be allowed to specify the values at | |
65 those times. This is unfortunately for implemementation and | |
66 simplicity purposes. In other words, if we didn't do this, | |
67 we could have two blenders covering the same point in time and | |
68 not know which one to ask for the value. Thus, this saves us | |
69 a lot of messiness with minimal or no sacrifice.""" | |
70 pass | |
71 def __str__(self): | |
72 """As a default, we'll just return the name of the class. Subclasses | |
73 can add parameters on if they want.""" | |
74 return str(self.__class__) | |
75 def linear_blend(self, startframe, endframe, blendtime): | |
76 """Utility function to help you produce linear combinations of two | |
77 blends. blendtime is the percent/100 that the blend should | |
78 completed. In other words, 0.25 means it should be 0.75 * startframe + | |
79 0.25 * endframe. This function is included since many blenders are | |
80 just functions on the percentage and still combine start and end frames | |
81 in this way.""" | |
123 | 82 if startframe.frame == endframe.frame: |
135 | 83 level = startframe.level + (blendtime * \ |
84 (endframe.level - startframe.level)) | |
85 levels = {startframe.frame : level} | |
123 | 86 else: |
87 levels = {startframe.frame : (1.0 - blendtime) * startframe.level, | |
88 endframe.frame : blendtime * endframe.level} | |
89 return levels | |
0 | 90 |
91 class InstantEnd(Blender): | |
92 """Instant change from startframe to endframe at the end. In other words, | |
93 the value returned will be the startframe all the way until the very end | |
94 of the blend.""" | |
135 | 95 def __call__(self, startframe, endframe, blendtime, time_since_startframe): |
0 | 96 # "What!?" you say, "Why don't you care about blendtime?" |
97 # This is because Blenders never be asked for blenders at the endpoints | |
98 # (after all, they wouldn't be blenders if they were). Please see | |
99 # 'Very important note' in Blender.__doc__ | |
123 | 100 return {startframe.frame : startframe.level} |
0 | 101 |
102 class InstantStart(Blender): | |
103 """Instant change from startframe to endframe at the beginning. In other | |
104 words, the value returned will be the startframe at the very beginning | |
105 and then be endframe at all times afterwards.""" | |
135 | 106 def __call__(self, startframe, endframe, blendtime, time_since_startframe): |
0 | 107 # "What!?" you say, "Why don't you care about blendtime?" |
108 # This is because Blenders never be asked for blenders at the endpoints | |
109 # (after all, they wouldn't be blenders if they were). Please see | |
110 # 'Very important note' in Blender.__doc__ | |
123 | 111 return {endframe.frame : endframe.level} |
0 | 112 |
113 class LinearBlender(Blender): | |
114 """Linear fade from one frame to another""" | |
135 | 115 def __call__(self, startframe, endframe, blendtime, time_since_startframe): |
0 | 116 return self.linear_blend(startframe, endframe, blendtime) |
117 | |
118 class ExponentialBlender(Blender): | |
119 """Exponential fade fron one frame to another. You get to specify | |
120 the exponent. If my math is correct, exponent=1 means the same thing | |
121 as LinearBlender.""" | |
122 def __init__(self, exponent): | |
123 self.exponent = exponent | |
135 | 124 def __call__(self, startframe, endframe, blendtime, time_since_startframe): |
0 | 125 blendtime = blendtime ** self.exponent |
126 return self.linear_blend(startframe, endframe, blendtime) | |
127 | |
128 # 17:02:53 drewp: this makes a big difference for the SmoothBlender | |
129 # (-x*x*(x-1.5)*2) function | |
130 class SmoothBlender(Blender): | |
131 """Drew's "Smoove" Blender function. Hopefully he'll document and | |
132 parametrize it.""" | |
135 | 133 def __call__(self, startframe, endframe, blendtime, time_since_startframe): |
0 | 134 blendtime = (-1 * blendtime) * blendtime * (blendtime - 1.5) * 2 |
135 return self.linear_blend(startframe, endframe, blendtime) | |
136 | |
135 | 137 class Strobe(Blender): |
138 "Strobes the frame on the right side between offlevel and onlevel." | |
139 def __init__(self, ontime, offtime, onlevel=1, offlevel=0): | |
138 | 140 "times are in seconds (floats)" |
135 | 141 make_attributes_from_args('ontime', 'offtime', 'onlevel', 'offlevel') |
142 self.cycletime = ontime + offtime | |
143 def __call__(self, startframe, endframe, blendtime, time_since_startframe): | |
144 # time into the current period | |
145 period_time = time_since_startframe % self.cycletime | |
146 if period_time <= self.ontime: | |
147 return {endframe.frame : self.onlevel} | |
148 else: | |
149 return {endframe.frame : self.offlevel} | |
150 | |
0 | 151 class TimelineTrack: |
152 """TimelineTrack is a single track in a Timeline. It consists of a | |
153 list of TimedEvents and a name. Length is automatically the location | |
154 of the last TimedEvent. To extend the Timeline past that, add an | |
122
2ed9bfd1dd0e
I totally wrecked Timeline so that it can run the show. (I hope it can
dmcc
parents:
110
diff
changeset
|
155 EmptyTimedEvent (which doesn't exist :-/).""" |
135 | 156 def __init__(self, name, *timedevents, **kw): |
157 if kw.get('default_frame'): | |
158 self.default_frame = kw['default_frame'] | |
159 else: | |
160 self.default_frame = None | |
0 | 161 self.name = name |
138 | 162 self.set_events(list(timedevents)) |
163 def set_events(self, events): | |
164 """This is given a list of TimedEvents. They need not be sorted.""" | |
165 self.events = events | |
166 self._cleaup_events() | |
167 def _cleaup_events(self): | |
168 """This makes sure all events are in the right order and have defaults | |
169 filled in if they have missing frames.""" | |
0 | 170 self.events.sort() |
139 | 171 self.fill_in_missing_frames() |
138 | 172 def add_event(self, event): |
173 """Add a TimedEvent object to this TimelineTrack""" | |
174 self.events.append(event) | |
175 self._cleaup_events(self.events) | |
176 def delete_event(self, event): | |
177 """Delete event by TimedEvent object""" | |
178 self.events.remove(event) | |
179 self._cleaup_events(self.events) | |
180 def delete_event_by_name(self, name): | |
181 """Deletes all events matching a certain name""" | |
182 self.events = [e for e in self.events if e.name is not name] | |
183 self._cleaup_events(self.events) | |
184 def delete_event_by_time(self, starttime, endtime=None): | |
185 """Deletes all events within a certain time range, inclusive. endtime | |
186 is optional.""" | |
187 endtime = endtime or starttime | |
188 self.events = [e for e in self.events | |
189 if e.time >= starttime and e.time <= endtime] | |
190 self._cleaup_events(self.events) | |
139 | 191 def fill_in_missing_frames(self): |
138 | 192 """Runs through all events and sets TimedEvent with missing frames to |
193 the default frame.""" | |
135 | 194 for event in self.events: |
195 if event.frame == MISSING: | |
196 event.frame = self.default_frame | |
0 | 197 def __str__(self): |
198 return "<TimelineTrack with events: %r>" % self.events | |
199 def has_events(self): | |
200 """Whether the TimelineTrack has anything in it. In general, | |
201 empty level Tracks should be avoided. However, empty function tracks | |
202 might be common.""" | |
203 return len(self.events) | |
204 def length(self): | |
205 """Returns the length of this track in pseudosecond time units. | |
206 This is done by finding the position of the last TimedEvent.""" | |
207 return float(self.events[-1]) | |
208 def get(self, key, direction=FORWARD): | |
209 """Returns the event at a specific time key. If there is no event | |
210 at that time, a search will be performed in direction. Also note | |
211 that if there are multiple events at one time, only the first will | |
212 be returned. (Probably first in order of adding.) This is not | |
213 a problem at the present since this method is intended for LevelFrames, | |
214 which must exist at unique times.""" | |
215 if direction == BACKWARD: | |
216 func = last_less_than | |
217 else: | |
218 func = first_greater_than | |
219 | |
220 return func(self.events, key) | |
221 def get_range(self, i, j, direction=FORWARD): | |
222 """Returns all events between i and j, exclusively. If direction | |
223 is FORWARD, j will be included. If direction is BACKWARD, i will | |
224 be included. This is because this is used to find FunctionFrames | |
225 and we assume that any function frames at the start point (which | |
226 could be i or j) have been processed.""" | |
110
490843093506
all of this stuff is super rough and not well thought out yet.
dmcc
parents:
109
diff
changeset
|
227 return [e for e in self.events if e >= i and e <= j] |
490843093506
all of this stuff is super rough and not well thought out yet.
dmcc
parents:
109
diff
changeset
|
228 |
0 | 229 if direction == FORWARD: |
230 return [e for e in self.events if e > i and e <= j] | |
231 else: | |
232 return [e for e in self.events if e >= i and e < j] | |
233 def __getitem__(self, key): | |
234 """Returns the event at or after a specific time key. | |
235 For example: timeline[3] will get the first event at time 3. | |
236 | |
237 If you want to get all events at time 3, you are in trouble, but | |
238 you could achieve it with something like: | |
239 timeline.get_range(2.99, 3.01, FORWARD) | |
240 This is hopefully a bogus problem, since you can't have multiple | |
241 LevelFrames at the same time.""" | |
242 return self.get(key, direction=FORWARD) | |
243 def get_surrounding_frames(self, time): | |
244 """Returns frames before and after a specific time. This returns | |
245 a 2-tuple: (previousframe, nextframe). If you have chosen the exact | |
246 time of a frame, it will be both previousframe and nextframe.""" | |
247 return self.get(time, direction=BACKWARD), \ | |
248 self.get(time, direction=FORWARD) | |
249 def get_levels_at_time(self, time): | |
250 """Returns a LevelFrame with the levels of this track at that time.""" | |
251 before, after = self.get_surrounding_frames(time) | |
252 | |
123 | 253 if not after or before == after: |
254 return {before.frame : before.level} | |
0 | 255 else: # we have a blended value |
256 diff = after.time - before.time | |
257 elapsed = time - before.time | |
258 percent = elapsed / diff | |
259 if not before.next_blender: | |
260 raise MissingBlender, before | |
135 | 261 return before.next_blender(before, after, percent, elapsed) |
0 | 262 |
263 class Timeline: | |
135 | 264 def __init__(self, name, tracks, rate=1, direction=FORWARD): |
0 | 265 """ |
122
2ed9bfd1dd0e
I totally wrecked Timeline so that it can run the show. (I hope it can
dmcc
parents:
110
diff
changeset
|
266 Most/all of this is old: |
0 | 267 |
268 You can have multiple FunctionFrames at the same time. Their | |
269 order is important though, since FunctionFrames will be applied | |
270 in the order seen in this list. blenders is a list of Blenders. | |
271 rate is the rate of playback. If set to 1, 1 unit inside the | |
272 Timeline will be 1 second. direction is the initial direction. | |
273 If you want to do have looping, place a LoopFunction at the end of | |
274 the Timeline. Timelines don't have a set length. Their length | |
275 is bounded by their last frame. You can put an EmptyFrame at | |
276 some time if you want to extend a Timeline.""" | |
277 | |
135 | 278 make_attributes_from_args('name', 'tracks', 'rate', 'direction') |
0 | 279 self.current_time = 0 |
280 self.last_clock_time = None | |
281 self.stopped = 1 | |
282 def length(self): | |
283 """Length of the timeline in pseudoseconds. This is determined by | |
284 finding the length of the longest track.""" | |
285 track_lengths = [track.length() for track in self.tracks] | |
286 return max(track_lengths) | |
287 def play(self): | |
288 """Activates the timeline. Future calls to tick() will advance the | |
289 timeline in the appropriate direction.""" | |
290 self.stopped = 0 | |
291 def stop(self): | |
292 """The timeline will no longer continue in either direction, no | |
293 FunctionFrames will be activated.""" | |
294 self.stopped = 1 | |
295 self.last_clock_time = None | |
296 def reset(self): | |
297 """Resets the timeline to 0. Does not change the stoppedness of the | |
298 timeline.""" | |
299 self.current_time = 0 | |
300 def tick(self): | |
301 """Updates the current_time and runs any FunctionFrames that the cursor | |
302 passed over. This call is ignored if the timeline is stopped.""" | |
303 if self.stopped: | |
304 return | |
305 | |
306 last_time = self.current_time | |
307 last_clock = self.last_clock_time | |
308 | |
309 # first, determine new time | |
310 clock_time = time() | |
311 if last_clock is None: | |
312 last_clock = clock_time | |
313 diff = clock_time - last_clock | |
314 new_time = (self.direction * self.rate * diff) + last_time | |
315 | |
316 # update the time | |
317 self.last_clock_time = clock_time | |
318 self.current_time = new_time | |
319 | |
110
490843093506
all of this stuff is super rough and not well thought out yet.
dmcc
parents:
109
diff
changeset
|
320 # now we make sure we're in bounds (we don't do this before, since it |
490843093506
all of this stuff is super rough and not well thought out yet.
dmcc
parents:
109
diff
changeset
|
321 # can cause us to skip events that are at boundaries. |
490843093506
all of this stuff is super rough and not well thought out yet.
dmcc
parents:
109
diff
changeset
|
322 self.current_time = max(self.current_time, 0) |
490843093506
all of this stuff is super rough and not well thought out yet.
dmcc
parents:
109
diff
changeset
|
323 self.current_time = min(self.current_time, self.length()) |
0 | 324 def reverse_direction(self): |
325 """Reverses the direction of play for this node""" | |
326 self.direction = self.direction * -1 | |
327 def set_direction(self, direction): | |
328 """Sets the direction of playback.""" | |
329 self.direction = direction | |
330 def set_rate(self, new_rate): | |
331 """Sets the rate of playback""" | |
332 self.rate = new_rate | |
333 def set_time(self, new_time): | |
334 """Set the time to a new time.""" | |
335 self.current_time = new_time | |
336 def get_levels(self): | |
337 """Return the current levels from this timeline. This is done by | |
338 adding all the non-functional tracks together.""" | |
122
2ed9bfd1dd0e
I totally wrecked Timeline so that it can run the show. (I hope it can
dmcc
parents:
110
diff
changeset
|
339 levels = [t.get_levels_at_time(self.current_time) |
2ed9bfd1dd0e
I totally wrecked Timeline so that it can run the show. (I hope it can
dmcc
parents:
110
diff
changeset
|
340 for t in self.tracks] |
2ed9bfd1dd0e
I totally wrecked Timeline so that it can run the show. (I hope it can
dmcc
parents:
110
diff
changeset
|
341 return dict_max(*levels) |
0 | 342 |
343 if __name__ == '__main__': | |
124
8de8a2f467db
The "T" function now creates TimedEvents with LinearBlenders for you
dmcc
parents:
123
diff
changeset
|
344 def T(*args, **kw): |
8de8a2f467db
The "T" function now creates TimedEvents with LinearBlenders for you
dmcc
parents:
123
diff
changeset
|
345 """This used to be a synonym for TimedEvent: |
8de8a2f467db
The "T" function now creates TimedEvents with LinearBlenders for you
dmcc
parents:
123
diff
changeset
|
346 |
8de8a2f467db
The "T" function now creates TimedEvents with LinearBlenders for you
dmcc
parents:
123
diff
changeset
|
347 T = TimedEvent |
0 | 348 |
124
8de8a2f467db
The "T" function now creates TimedEvents with LinearBlenders for you
dmcc
parents:
123
diff
changeset
|
349 It now acts the same way, except that it will fill in a default |
8de8a2f467db
The "T" function now creates TimedEvents with LinearBlenders for you
dmcc
parents:
123
diff
changeset
|
350 blender if you don't. The default blender is a LinearBlender.""" |
8de8a2f467db
The "T" function now creates TimedEvents with LinearBlenders for you
dmcc
parents:
123
diff
changeset
|
351 linear = LinearBlender() |
8de8a2f467db
The "T" function now creates TimedEvents with LinearBlenders for you
dmcc
parents:
123
diff
changeset
|
352 if 'blender' not in kw: |
8de8a2f467db
The "T" function now creates TimedEvents with LinearBlenders for you
dmcc
parents:
123
diff
changeset
|
353 kw['blender'] = linear |
8de8a2f467db
The "T" function now creates TimedEvents with LinearBlenders for you
dmcc
parents:
123
diff
changeset
|
354 |
8de8a2f467db
The "T" function now creates TimedEvents with LinearBlenders for you
dmcc
parents:
123
diff
changeset
|
355 return TimedEvent(*args, **kw) |
8de8a2f467db
The "T" function now creates TimedEvents with LinearBlenders for you
dmcc
parents:
123
diff
changeset
|
356 |
0 | 357 quad = ExponentialBlender(2) |
358 invquad = ExponentialBlender(0.5) | |
359 smoove = SmoothBlender() | |
360 | |
123 | 361 track1 = TimelineTrack('red track', |
124
8de8a2f467db
The "T" function now creates TimedEvents with LinearBlenders for you
dmcc
parents:
123
diff
changeset
|
362 T(0, 'red', level=0), |
123 | 363 T(4, 'red', blender=quad, level=0.5), |
364 T(12, 'red', blender=smoove, level=0.7), | |
365 T(15, 'red', level=0.0)) # last TimedEvent doesn't need a blender | |
366 track2 = TimelineTrack('green track', | |
367 T(0, 'green', blender=invquad, level=0.2), | |
368 T(5, 'green', blender=smoove, level=1), | |
124
8de8a2f467db
The "T" function now creates TimedEvents with LinearBlenders for you
dmcc
parents:
123
diff
changeset
|
369 T(10, 'green', level=0.8), |
8de8a2f467db
The "T" function now creates TimedEvents with LinearBlenders for you
dmcc
parents:
123
diff
changeset
|
370 T(15, 'green', level=0.6), |
123 | 371 T(20, 'green', level=0.0)) # last TimedEvent doesn't need a blender |
372 track3 = TimelineTrack('tableau demo', | |
124
8de8a2f467db
The "T" function now creates TimedEvents with LinearBlenders for you
dmcc
parents:
123
diff
changeset
|
373 T(0, 'blue', level=0.0), |
123 | 374 T(2, 'blue', level=1.0, blender=InstantEnd()), |
124
8de8a2f467db
The "T" function now creates TimedEvents with LinearBlenders for you
dmcc
parents:
123
diff
changeset
|
375 T(18, 'blue', level=1.0), |
8de8a2f467db
The "T" function now creates TimedEvents with LinearBlenders for you
dmcc
parents:
123
diff
changeset
|
376 T(20, 'blue', level=0.0)) |
0 | 377 |
135 | 378 tl = Timeline('test', [track1, track2, track3]) |
122
2ed9bfd1dd0e
I totally wrecked Timeline so that it can run the show. (I hope it can
dmcc
parents:
110
diff
changeset
|
379 |
0 | 380 tl.play() |
381 | |
382 import Tix | |
383 root = Tix.Tk() | |
384 colorscalesframe = Tix.Frame(root) | |
385 scalevars = {} | |
386 # wow, this works out so well, it's almost like I planned it! | |
387 # (actually, it's probably just Tk being as cool as it usually is) | |
388 # ps. if this code ever turns into mainstream code for flax, I'll be | |
389 # pissed (reason: we need to use classes, not this hacked crap!) | |
390 colors = 'red', 'blue', 'green', 'yellow', 'purple' | |
391 for color in colors: | |
392 sv = Tix.DoubleVar() | |
393 scalevars[color] = sv | |
122
2ed9bfd1dd0e
I totally wrecked Timeline so that it can run the show. (I hope it can
dmcc
parents:
110
diff
changeset
|
394 scale = Tix.Scale(colorscalesframe, from_=1, to_=0, res=0.01, bg=color, |
0 | 395 variable=sv) |
396 scale.pack(side=Tix.LEFT) | |
397 | |
398 def set_timeline_time(time): | |
399 tl.set_time(float(time)) | |
400 # print 'set_timeline_time', time | |
401 | |
402 def update_scales(): | |
403 levels = tl.get_levels() | |
404 for color in colors: | |
405 scalevars[color].set(levels.get(color, 0)) | |
406 | |
407 colorscalesframe.pack() | |
124
8de8a2f467db
The "T" function now creates TimedEvents with LinearBlenders for you
dmcc
parents:
123
diff
changeset
|
408 time_scale = Tix.Scale(root, from_=0, to_=tl.length(), |
0 | 409 orient=Tix.HORIZONTAL, res=0.01, command=set_timeline_time) |
410 time_scale.pack(side=Tix.BOTTOM, fill=Tix.X, expand=1) | |
411 | |
412 def play_tl(): | |
413 tl.tick() | |
414 update_scales() | |
415 time_scale.set(tl.current_time) | |
416 # print 'time_scale.set', tl.current_time | |
417 root.after(10, play_tl) | |
418 | |
419 controlwindow = Tix.Toplevel() | |
420 Tix.Button(controlwindow, text='Stop', | |
421 command=lambda: tl.stop()).pack(side=Tix.LEFT) | |
422 Tix.Button(controlwindow, text='Play', | |
423 command=lambda: tl.play()).pack(side=Tix.LEFT) | |
424 Tix.Button(controlwindow, text='Reset', | |
425 command=lambda: time_scale.set(0)).pack(side=Tix.LEFT) | |
426 Tix.Button(controlwindow, text='Flip directions', | |
427 command=lambda: tl.reverse_direction()).pack(side=Tix.LEFT) | |
428 Tix.Button(controlwindow, text='1/2x', | |
429 command=lambda: tl.set_rate(0.5 * tl.rate)).pack(side=Tix.LEFT) | |
430 Tix.Button(controlwindow, text='2x', | |
431 command=lambda: tl.set_rate(2 * tl.rate)).pack(side=Tix.LEFT) | |
432 | |
433 root.after(100, play_tl) | |
434 | |
435 # Timeline.set_time = trace(Timeline.set_time) | |
436 | |
437 Tix.mainloop() |