1import time
2
3from ..drawing import Drawing
4from .drawing_widget import DrawingWidget
5
6
7class AsyncAnimation(DrawingWidget):
8 '''AsyncAnimation is a Jupyter notebook widget for asynchronously displaying
9 an animation.
10
11 Example:
12 # Jupyter cell 1:
13 widget = AsyncAnimation(fps=10)
14 widget
15 # [Animation is displayed here]
16
17 # Jupyter cell 2:
18 global_variable = 'a'
19 @widget.set_draw_frame # Animation above is automatically updated
20 def draw_frame(secs=0):
21 # Draw something...
22 d = draw.Drawing(300, 40)
23 d.append(draw.Text(global_variable, 20, 0, 10))
24 d.append(draw.Text(str(secs), 20, 30, 10))
25 return d
26
27 # Jupyter cell 3:
28 global_variable = 'b' # Animation above now displays 'b'
29
30 Attributes:
31 fps: The animation frame rate (frames per second).
32 draw_frame: A function that takes a single argument (animation time) and
33 returns a Drawing.
34 paused: While True, the animation will not run. Only the current frame
35 will be shown.
36 disable: While True, the widget will not be interactive and the
37 animation will not update.
38 click_pause: If True, clicking the drawing will pause or resume the
39 animation.
40 mousemove_pause: If True, moving the mouse up across the drawing will
41 pause the animation and moving the mouse down will resume it.
42 mousemove_y_threshold: Controls the sensitivity of mousemove_pause in
43 web browser pixels.
44 '''
45
46 def __init__(self, fps=10, draw_frame=None, *, paused=False, disable=False,
47 click_pause=True, mousemove_pause=False,
48 mousemove_y_threshold=10):
49 self._fps = fps
50 self._paused = paused
51 if draw_frame is None:
52 def draw_frame(secs):
53 return Drawing(0, 0)
54 self._draw_frame = draw_frame
55 self._last_secs = 0
56 self.click_pause = click_pause
57 self.mousemove_pause = mousemove_pause
58 self.mousemove_y_threshold = mousemove_y_threshold
59 self._start_time = 0
60 self._stop_time = 0
61 self._y_loc = 0
62 self._y_max = 0
63 self._y_min = 0
64 if self._paused:
65 frame_delay = -1
66 else:
67 frame_delay = 1000 // self._fps
68 self._start_time = time.monotonic()
69 initial_drawing = self.draw_frame(0)
70 super().__init__(initial_drawing, throttle=True, disable=disable,
71 frame_delay=frame_delay)
72
73 # Register callbacks
74 @self.mousedown
75 def mousedown(self, x, y, info):
76 if not self.click_pause:
77 return
78 self._y_min = self._y_max = self._y_loc
79 self.paused = not self.paused
80
81 @self.mousemove
82 def mousemove(self, x, y, info):
83 self._y_loc += info['movementY']
84 if not self.mousemove_pause:
85 self._y_min = self._y_max = self._y_loc
86 return
87 self._y_max = max(self._y_max, self._y_loc)
88 self._y_min = min(self._y_min, self._y_loc)
89 thresh = self.mousemove_y_threshold
90 invert = thresh < 0
91 thresh = max(0.01, abs(thresh))
92 down_triggered = self._y_loc - self._y_min >= thresh
93 up_triggered = self._y_max - self._y_loc >= thresh
94 if down_triggered:
95 self._y_min = self._y_loc
96 if up_triggered:
97 self._y_max = self._y_loc
98 if invert:
99 down_triggered, up_triggered = up_triggered, down_triggered
100 if down_triggered:
101 self.paused = False
102 if up_triggered:
103 self.paused = True
104
105 @self.timed
106 def timed(self, info):
107 secs = time.monotonic() - self._start_time
108 self.drawing = self.draw_frame(secs)
109 self._last_secs = secs
110
111 @self.on_exception
112 def on_exception(self, e):
113 self.paused = True
114
115 @property
116 def fps(self):
117 return self._fps
118
119 @fps.setter
120 def fps(self, new_fps):
121 self._fps = new_fps
122 if self.paused:
123 return
124 self.frame_delay = 1000 // self._fps
125
126 @property
127 def paused(self):
128 return self._paused
129
130 @paused.setter
131 def paused(self, new_paused):
132 if bool(self._paused) == bool(new_paused):
133 return
134 self._paused = new_paused
135 if self._paused:
136 self.frame_delay = -1
137 self._stop_time = time.monotonic()
138 else:
139 self._start_time += time.monotonic() - self._stop_time
140 self.frame_delay = 1000 // self._fps
141
142 @property
143 def draw_frame(self):
144 return self._draw_frame
145
146 @draw_frame.setter
147 def draw_frame(self, new_draw_frame):
148 self._draw_frame = new_draw_frame
149 if self.paused:
150 # Redraw if paused
151 self.drawing = self._draw_frame(self._last_secs)
152
153 def set_draw_frame(self, new_draw_frame):
154 self.draw_frame = new_draw_frame
155 return new_draw_frame