... | ... |
@@ -25,7 +25,7 @@ from gi.repository import GLib |
25 | 25 |
|
26 | 26 |
class Animation(object): |
27 | 27 |
|
28 |
- step = 0.03 # seconds |
|
28 |
+ step = 0.03 # seconds |
|
29 | 29 |
|
30 | 30 |
def __init__(self, dot_widget): |
31 | 31 |
self.dot_widget = dot_widget |
... | ... |
@@ -45,7 +45,7 @@ class Animation(object): |
45 | 45 |
if not self.tick(): |
46 | 46 |
self.stop() |
47 | 47 |
return False |
48 |
- except e: |
|
48 |
+ except AttributeError as e: |
|
49 | 49 |
self.stop() |
50 | 50 |
raise e |
51 | 51 |
return True |
... | ... |
@@ -92,8 +92,8 @@ class MoveToAnimation(LinearAnimation): |
92 | 92 |
def animate(self, t): |
93 | 93 |
sx, sy = self.source_x, self.source_y |
94 | 94 |
tx, ty = self.target_x, self.target_y |
95 |
- self.dot_widget.x = tx * t + sx * (1-t) |
|
96 |
- self.dot_widget.y = ty * t + sy * (1-t) |
|
95 |
+ self.dot_widget.x = tx * t + sx * (1 - t) |
|
96 |
+ self.dot_widget.y = ty * t + sy * (1 - t) |
|
97 | 97 |
self.dot_widget.queue_draw() |
98 | 98 |
|
99 | 99 |
|
... | ... |
@@ -118,6 +118,6 @@ class ZoomToAnimation(MoveToAnimation): |
118 | 118 |
|
119 | 119 |
def animate(self, t): |
120 | 120 |
a, b, c = self.source_zoom, self.extra_zoom, self.target_zoom |
121 |
- self.dot_widget.zoom_ratio = c*t + b*t*(1-t) + a*(1-t) |
|
121 |
+ self.dot_widget.zoom_ratio = c*t + b*t*(1 - t) + a*(1 - t) |
|
122 | 122 |
self.dot_widget.zoom_to_fit_on_resize = False |
123 | 123 |
MoveToAnimation.animate(self, t) |
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,123 @@ |
1 |
+# Copyright 2008-2015 Jose Fonseca |
|
2 |
+# |
|
3 |
+# This program is free software: you can redistribute it and/or modify it |
|
4 |
+# under the terms of the GNU Lesser General Public License as published |
|
5 |
+# by the Free Software Foundation, either version 3 of the License, or |
|
6 |
+# (at your option) any later version. |
|
7 |
+# |
|
8 |
+# This program is distributed in the hope that it will be useful, |
|
9 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 |
+# GNU Lesser General Public License for more details. |
|
12 |
+# |
|
13 |
+# You should have received a copy of the GNU Lesser General Public License |
|
14 |
+# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 |
+# |
|
16 |
+import math |
|
17 |
+import time |
|
18 |
+ |
|
19 |
+import gi |
|
20 |
+gi.require_version('Gtk', '3.0') |
|
21 |
+gi.require_version('PangoCairo', '1.0') |
|
22 |
+ |
|
23 |
+from gi.repository import GLib |
|
24 |
+ |
|
25 |
+ |
|
26 |
+class Animation(object): |
|
27 |
+ |
|
28 |
+ step = 0.03 # seconds |
|
29 |
+ |
|
30 |
+ def __init__(self, dot_widget): |
|
31 |
+ self.dot_widget = dot_widget |
|
32 |
+ self.timeout_id = None |
|
33 |
+ |
|
34 |
+ def start(self): |
|
35 |
+ self.timeout_id = GLib.timeout_add(int(self.step * 1000), self.__real_tick) |
|
36 |
+ |
|
37 |
+ def stop(self): |
|
38 |
+ self.dot_widget.animation = NoAnimation(self.dot_widget) |
|
39 |
+ if self.timeout_id is not None: |
|
40 |
+ GLib.source_remove(self.timeout_id) |
|
41 |
+ self.timeout_id = None |
|
42 |
+ |
|
43 |
+ def __real_tick(self): |
|
44 |
+ try: |
|
45 |
+ if not self.tick(): |
|
46 |
+ self.stop() |
|
47 |
+ return False |
|
48 |
+ except e: |
|
49 |
+ self.stop() |
|
50 |
+ raise e |
|
51 |
+ return True |
|
52 |
+ |
|
53 |
+ def tick(self): |
|
54 |
+ return False |
|
55 |
+ |
|
56 |
+ |
|
57 |
+class NoAnimation(Animation): |
|
58 |
+ |
|
59 |
+ def start(self): |
|
60 |
+ pass |
|
61 |
+ |
|
62 |
+ def stop(self): |
|
63 |
+ pass |
|
64 |
+ |
|
65 |
+ |
|
66 |
+class LinearAnimation(Animation): |
|
67 |
+ |
|
68 |
+ duration = 0.6 |
|
69 |
+ |
|
70 |
+ def start(self): |
|
71 |
+ self.started = time.time() |
|
72 |
+ Animation.start(self) |
|
73 |
+ |
|
74 |
+ def tick(self): |
|
75 |
+ t = (time.time() - self.started) / self.duration |
|
76 |
+ self.animate(max(0, min(t, 1))) |
|
77 |
+ return (t < 1) |
|
78 |
+ |
|
79 |
+ def animate(self, t): |
|
80 |
+ pass |
|
81 |
+ |
|
82 |
+ |
|
83 |
+class MoveToAnimation(LinearAnimation): |
|
84 |
+ |
|
85 |
+ def __init__(self, dot_widget, target_x, target_y): |
|
86 |
+ Animation.__init__(self, dot_widget) |
|
87 |
+ self.source_x = dot_widget.x |
|
88 |
+ self.source_y = dot_widget.y |
|
89 |
+ self.target_x = target_x |
|
90 |
+ self.target_y = target_y |
|
91 |
+ |
|
92 |
+ def animate(self, t): |
|
93 |
+ sx, sy = self.source_x, self.source_y |
|
94 |
+ tx, ty = self.target_x, self.target_y |
|
95 |
+ self.dot_widget.x = tx * t + sx * (1-t) |
|
96 |
+ self.dot_widget.y = ty * t + sy * (1-t) |
|
97 |
+ self.dot_widget.queue_draw() |
|
98 |
+ |
|
99 |
+ |
|
100 |
+class ZoomToAnimation(MoveToAnimation): |
|
101 |
+ |
|
102 |
+ def __init__(self, dot_widget, target_x, target_y): |
|
103 |
+ MoveToAnimation.__init__(self, dot_widget, target_x, target_y) |
|
104 |
+ self.source_zoom = dot_widget.zoom_ratio |
|
105 |
+ self.target_zoom = self.source_zoom |
|
106 |
+ self.extra_zoom = 0 |
|
107 |
+ |
|
108 |
+ middle_zoom = 0.5 * (self.source_zoom + self.target_zoom) |
|
109 |
+ |
|
110 |
+ distance = math.hypot(self.source_x - self.target_x, |
|
111 |
+ self.source_y - self.target_y) |
|
112 |
+ rect = self.dot_widget.get_allocation() |
|
113 |
+ visible = min(rect.width, rect.height) / self.dot_widget.zoom_ratio |
|
114 |
+ visible *= 0.9 |
|
115 |
+ if distance > 0: |
|
116 |
+ desired_middle_zoom = visible / distance |
|
117 |
+ self.extra_zoom = min(0, 4 * (desired_middle_zoom - middle_zoom)) |
|
118 |
+ |
|
119 |
+ def animate(self, t): |
|
120 |
+ a, b, c = self.source_zoom, self.extra_zoom, self.target_zoom |
|
121 |
+ self.dot_widget.zoom_ratio = c*t + b*t*(1-t) + a*(1-t) |
|
122 |
+ self.dot_widget.zoom_to_fit_on_resize = False |
|
123 |
+ MoveToAnimation.animate(self, t) |