]> git.proxmox.com Git - sencha-touch.git/blob - src/src/AnimationQueue.js
import Sencha Touch 2.4.2 source
[sencha-touch.git] / src / src / AnimationQueue.js
1 (function() {
2 var lastTime = 0,
3 vendors = ['ms', 'moz', 'webkit', 'o'],
4 ln = vendors.length,
5 i, vendor;
6
7 for (i = 0; i < ln && !window.requestAnimationFrame; ++i) {
8 vendor = vendors[i];
9 if (window[vendor + 'RequestAnimationFrame']) {
10 window.requestAnimationFrame = window[vendor + 'RequestAnimationFrame'];
11 window.cancelAnimationFrame = window[vendor + 'CancelAnimationFrame'] || window[vendor + 'CancelRequestAnimationFrame'];
12 }
13 }
14
15 if (!window.Ext) {
16 window.Ext = {};
17 }
18 Ext.performance = {};
19
20 if (window.performance && window.performance.now) {
21 Ext.performance.now = function() {
22 return window.performance.now();
23 }
24 }
25 else {
26 Ext.performance.now = function() {
27 return Date.now();
28 }
29 }
30
31 if (!window.requestAnimationFrame) {
32 window.requestAnimationFrame = function(callback) {
33 var currTime = Ext.performance.now(),
34 timeToCall = Math.max(0, 16 - (currTime - lastTime)),
35 id = window.setTimeout(function() {
36 callback(currTime + timeToCall);
37 }, timeToCall);
38 lastTime = currTime + timeToCall;
39 return id;
40 };
41 }
42 else {
43 Ext.trueRequestAnimationFrames = true;
44 }
45
46 if (!window.cancelAnimationFrame) {
47 window.cancelAnimationFrame = function(id) {
48 clearTimeout(id);
49 };
50 }
51 }());
52
53 (function(global) {
54
55 /**
56 * @private
57 */
58 Ext.define('Ext.AnimationQueue', {
59 singleton: true,
60
61 constructor: function() {
62 var bind = Ext.Function.bind;
63
64 this.queue = [];
65 this.taskQueue = [];
66 this.runningQueue = [];
67 this.idleQueue = [];
68 this.isRunning = false;
69 this.isIdle = true;
70
71 this.run = bind(this.run, this);
72 this.whenIdle = bind(this.whenIdle, this);
73 this.processIdleQueueItem = bind(this.processIdleQueueItem, this);
74 this.processTaskQueueItem = bind(this.processTaskQueueItem, this);
75
76
77 // iOS has a nasty bug which causes pending requestAnimationFrame to not release
78 // the callback when the WebView is switched back and forth from / to being background process
79 // We use a watchdog timer to workaround this, and restore the pending state correctly if this happens
80 // This timer has to be set as an interval from the very beginning and we have to keep it running for
81 // as long as the app lives, setting it later doesn't seem to work
82 if (Ext.os.is.iOS) {
83 setInterval(this.watch, 500);
84 }
85 },
86
87 /**
88 *
89 * @param {Function} fn
90 * @param {Object} [scope]
91 * @param {Object} [args]
92 */
93 start: function(fn, scope, args) {
94 this.queue.push(arguments);
95
96 if (!this.isRunning) {
97 if (this.hasOwnProperty('idleTimer')) {
98 clearTimeout(this.idleTimer);
99 delete this.idleTimer;
100 }
101
102 if (this.hasOwnProperty('idleQueueTimer')) {
103 clearTimeout(this.idleQueueTimer);
104 delete this.idleQueueTimer;
105 }
106
107 this.isIdle = false;
108 this.isRunning = true;
109 //<debug>
110 this.startCountTime = Ext.performance.now();
111 this.count = 0;
112 //</debug>
113 this.doStart();
114 }
115 },
116
117 watch: function() {
118 if (this.isRunning && Date.now() - this.lastRunTime >= 500) {
119 this.run();
120 }
121 },
122
123 run: function() {
124 if (!this.isRunning) {
125 return;
126 }
127
128 var queue = this.runningQueue,
129 i, ln;
130
131 this.lastRunTime = Date.now();
132 this.frameStartTime = Ext.performance.now();
133
134 queue.push.apply(queue, this.queue);
135
136 for (i = 0, ln = queue.length; i < ln; i++) {
137 this.invoke(queue[i]);
138 }
139
140 queue.length = 0;
141
142 //<debug>
143 var now = this.frameStartTime,
144 startCountTime = this.startCountTime,
145 elapse = now - startCountTime,
146 count = ++this.count;
147
148 if (elapse >= 200) {
149 this.onFpsChanged(count * 1000 / elapse, count, elapse);
150 this.startCountTime = now;
151 this.count = 0;
152 }
153 //</debug>
154
155 this.doIterate();
156 },
157
158 //<debug>
159 onFpsChanged: Ext.emptyFn,
160
161 onStop: Ext.emptyFn,
162 //</debug>
163
164 doStart: function() {
165 this.animationFrameId = requestAnimationFrame(this.run);
166 this.lastRunTime = Date.now();
167 },
168
169 doIterate: function() {
170 this.animationFrameId = requestAnimationFrame(this.run);
171 },
172
173 doStop: function() {
174 cancelAnimationFrame(this.animationFrameId);
175 },
176
177 /**
178 *
179 * @param {Function} fn
180 * @param {Object} [scope]
181 * @param {Object} [args]
182 */
183 stop: function(fn, scope, args) {
184 if (!this.isRunning) {
185 return;
186 }
187
188 var queue = this.queue,
189 ln = queue.length,
190 i, item;
191
192 for (i = 0; i < ln; i++) {
193 item = queue[i];
194 if (item[0] === fn && item[1] === scope && item[2] === args) {
195 queue.splice(i, 1);
196 i--;
197 ln--;
198 }
199 }
200
201 if (ln === 0) {
202 this.doStop();
203 //<debug>
204 this.onStop();
205 //</debug>
206 this.isRunning = false;
207
208 this.idleTimer = setTimeout(this.whenIdle, 100);
209 }
210 },
211
212 onIdle: function(fn, scope, args) {
213 var listeners = this.idleQueue,
214 i, ln, listener;
215
216 for (i = 0, ln = listeners.length; i < ln; i++) {
217 listener = listeners[i];
218 if (fn === listener[0] && scope === listener[1] && args === listener[2]) {
219 return;
220 }
221 }
222
223 listeners.push(arguments);
224
225 if (this.isIdle) {
226 this.processIdleQueue();
227 }
228 },
229
230 unIdle: function(fn, scope, args) {
231 var listeners = this.idleQueue,
232 i, ln, listener;
233
234 for (i = 0, ln = listeners.length; i < ln; i++) {
235 listener = listeners[i];
236 if (fn === listener[0] && scope === listener[1] && args === listener[2]) {
237 listeners.splice(i, 1);
238 return true;
239 }
240 }
241
242 return false;
243 },
244
245 queueTask: function(fn, scope, args) {
246 this.taskQueue.push(arguments);
247 this.processTaskQueue();
248 },
249
250 dequeueTask: function(fn, scope, args) {
251 var listeners = this.taskQueue,
252 i, ln, listener;
253
254 for (i = 0, ln = listeners.length; i < ln; i++) {
255 listener = listeners[i];
256 if (fn === listener[0] && scope === listener[1] && args === listener[2]) {
257 listeners.splice(i, 1);
258 i--;
259 ln--;
260 }
261 }
262 },
263
264 invoke: function(listener) {
265 var fn = listener[0],
266 scope = listener[1],
267 args = listener[2];
268
269 fn = (typeof fn == 'string' ? scope[fn] : fn);
270
271 if (Ext.isArray(args)) {
272 fn.apply(scope, args);
273 }
274 else {
275 fn.call(scope, args);
276 }
277 },
278
279 whenIdle: function() {
280 this.isIdle = true;
281 this.processIdleQueue();
282 },
283
284 processIdleQueue: function() {
285 if (!this.hasOwnProperty('idleQueueTimer')) {
286 this.idleQueueTimer = setTimeout(this.processIdleQueueItem, 1);
287 }
288 },
289
290 processIdleQueueItem: function() {
291 delete this.idleQueueTimer;
292
293 if (!this.isIdle) {
294 return;
295 }
296
297 var listeners = this.idleQueue,
298 listener;
299
300 if (listeners.length > 0) {
301 listener = listeners.shift();
302 this.invoke(listener);
303 this.processIdleQueue();
304 }
305 },
306
307 processTaskQueue: function() {
308 if (!this.hasOwnProperty('taskQueueTimer')) {
309 this.taskQueueTimer = setTimeout(this.processTaskQueueItem, 15);
310 }
311 },
312
313 processTaskQueueItem: function() {
314 delete this.taskQueueTimer;
315
316 var listeners = this.taskQueue,
317 listener;
318
319 if (listeners.length > 0) {
320 listener = listeners.shift();
321 this.invoke(listener);
322 this.processTaskQueue();
323 }
324 },
325
326 showFps: function() {
327 if (!Ext.trueRequestAnimationFrames) {
328 alert("This browser does not support requestAnimationFrame. The FPS listed will not be accurate");
329 }
330 Ext.onReady(function() {
331 Ext.Viewport.add([{
332 xtype: 'component',
333 bottom: 50,
334 left: 0,
335 width: 50,
336 height: 20,
337 html: 'Average',
338 style: 'background-color: black; color: white; text-align: center; line-height: 20px; font-size: 8px;'
339 },
340 {
341 id: '__averageFps',
342 xtype: 'component',
343 bottom: 0,
344 left: 0,
345 width: 50,
346 height: 50,
347 html: '0',
348 style: 'background-color: red; color: white; text-align: center; line-height: 50px;'
349 },
350 {
351 xtype: 'component',
352 bottom: 50,
353 left: 50,
354 width: 50,
355 height: 20,
356 html: 'Min (Last 1k)',
357 style: 'background-color: black; color: white; text-align: center; line-height: 20px; font-size: 8px;'
358 },
359 {
360 id: '__minFps',
361 xtype: 'component',
362 bottom: 0,
363 left: 50,
364 width: 50,
365 height: 50,
366 html: '0',
367 style: 'background-color: orange; color: white; text-align: center; line-height: 50px;'
368 },
369 {
370 xtype: 'component',
371 bottom: 50,
372 left: 100,
373 width: 50,
374 height: 20,
375 html: 'Max (Last 1k)',
376 style: 'background-color: black; color: white; text-align: center; line-height: 20px; font-size: 8px;'
377 },
378 {
379 id: '__maxFps',
380 xtype: 'component',
381 bottom: 0,
382 left: 100,
383 width: 50,
384 height: 50,
385 html: '0',
386 style: 'background-color: yellow; color: black; text-align: center; line-height: 50px;'
387 },
388 {
389 xtype: 'component',
390 bottom: 50,
391 left: 150,
392 width: 50,
393 height: 20,
394 html: 'Current',
395 style: 'background-color: black; color: white; text-align: center; line-height: 20px; font-size: 8px;'
396 },
397 {
398 id: '__currentFps',
399 xtype: 'component',
400 bottom: 0,
401 left: 150,
402 width: 50,
403 height: 50,
404 html: '0',
405 style: 'background-color: green; color: white; text-align: center; line-height: 50px;'
406 }
407 ]);
408 Ext.AnimationQueue.resetFps();
409 });
410
411 },
412
413 resetFps: function() {
414 var currentFps = Ext.getCmp('__currentFps'),
415 averageFps = Ext.getCmp('__averageFps'),
416 minFps = Ext.getCmp('__minFps'),
417 maxFps = Ext.getCmp('__maxFps'),
418 min = 1000,
419 max = 0,
420 count = 0,
421 sum = 0;
422
423 Ext.AnimationQueue.onFpsChanged = function(fps) {
424 count++;
425
426 if (!(count % 10)) {
427 min = 1000;
428 max = 0;
429 }
430
431 sum += fps;
432 min = Math.min(min, fps);
433 max = Math.max(max, fps);
434 currentFps.setHtml(Math.round(fps));
435 averageFps.setHtml(Math.round(sum / count));
436 minFps.setHtml(Math.round(min));
437 maxFps.setHtml(Math.round(max));
438 };
439 }
440 }, function() {
441 /*
442 Global FPS indicator. Add ?showfps to use in any application. Note that this REQUIRES true requestAnimationFrame
443 to be accurate.
444 */
445 //<debug>
446 var paramsString = window.location.search.substr(1),
447 paramsArray = paramsString.split("&");
448
449 if (paramsArray.indexOf("showfps") !== -1) {
450 Ext.AnimationQueue.showFps();
451 }
452 //</debug>
453
454 });
455
456 })(this);