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