rasputin 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,2 +1,1629 @@
1
1
 
2
+ (function(exports) {
3
+ // Vector and Matrix mathematics modules for JavaScript
4
+ // Copyright (c) 2007 James Coglan
5
+ //
6
+ // Permission is hereby granted, free of charge, to any person obtaining
7
+ // a copy of this software and associated documentation files (the "Software"),
8
+ // to deal in the Software without restriction, including without limitation
9
+ // the rights to use, copy, modify, merge, publish, distribute, sublicense,
10
+ // and/or sell copies of the Software, and to permit persons to whom the
11
+ // Software is furnished to do so, subject to the following conditions:
12
+ //
13
+ // The above copyright notice and this permission notice shall be included
14
+ // in all copies or substantial portions of the Software.
15
+ //
16
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17
+ // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19
+ // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22
+ // DEALINGS IN THE SOFTWARE.
2
23
 
24
+ var Sylvester = {
25
+ version: '0.1.3',
26
+ precision: 1e-6
27
+ };
28
+
29
+ function Matrix() {}
30
+ Matrix.prototype = {
31
+
32
+ // Returns element (i,j) of the matrix
33
+ e: function(i,j) {
34
+ if (i < 1 || i > this.elements.length || j < 1 || j > this.elements[0].length) { return null; }
35
+ return this.elements[i-1][j-1];
36
+ },
37
+
38
+ // Maps the matrix to another matrix (of the same dimensions) according to the given function
39
+ map: function(fn) {
40
+ var els = [], ni = this.elements.length, ki = ni, i, nj, kj = this.elements[0].length, j;
41
+ do { i = ki - ni;
42
+ nj = kj;
43
+ els[i] = [];
44
+ do { j = kj - nj;
45
+ els[i][j] = fn(this.elements[i][j], i + 1, j + 1);
46
+ } while (--nj);
47
+ } while (--ni);
48
+ return Matrix.create(els);
49
+ },
50
+
51
+ // Returns the result of multiplying the matrix from the right by the argument.
52
+ // If the argument is a scalar then just multiply all the elements. If the argument is
53
+ // a vector, a vector is returned, which saves you having to remember calling
54
+ // col(1) on the result.
55
+ multiply: function(matrix) {
56
+ if (!matrix.elements) {
57
+ return this.map(function(x) { return x * matrix; });
58
+ }
59
+ var returnVector = matrix.modulus ? true : false;
60
+ var M = matrix.elements || matrix;
61
+ if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
62
+ if (!this.canMultiplyFromLeft(M)) { return null; }
63
+ var ni = this.elements.length, ki = ni, i, nj, kj = M[0].length, j;
64
+ var cols = this.elements[0].length, elements = [], sum, nc, c;
65
+ do { i = ki - ni;
66
+ elements[i] = [];
67
+ nj = kj;
68
+ do { j = kj - nj;
69
+ sum = 0;
70
+ nc = cols;
71
+ do { c = cols - nc;
72
+ sum += this.elements[i][c] * M[c][j];
73
+ } while (--nc);
74
+ elements[i][j] = sum;
75
+ } while (--nj);
76
+ } while (--ni);
77
+ var M = Matrix.create(elements);
78
+ return returnVector ? M.col(1) : M;
79
+ },
80
+
81
+ x: function(matrix) { return this.multiply(matrix); },
82
+
83
+ // Returns true iff the matrix can multiply the argument from the left
84
+ canMultiplyFromLeft: function(matrix) {
85
+ var M = matrix.elements || matrix;
86
+ if (typeof(M[0][0]) == 'undefined') { M = Matrix.create(M).elements; }
87
+ // this.columns should equal matrix.rows
88
+ return (this.elements[0].length == M.length);
89
+ },
90
+
91
+ // Set the matrix's elements from an array. If the argument passed
92
+ // is a vector, the resulting matrix will be a single column.
93
+ setElements: function(els) {
94
+ var i, elements = els.elements || els;
95
+ if (typeof(elements[0][0]) != 'undefined') {
96
+ var ni = elements.length, ki = ni, nj, kj, j;
97
+ this.elements = [];
98
+ do { i = ki - ni;
99
+ nj = elements[i].length; kj = nj;
100
+ this.elements[i] = [];
101
+ do { j = kj - nj;
102
+ this.elements[i][j] = elements[i][j];
103
+ } while (--nj);
104
+ } while(--ni);
105
+ return this;
106
+ }
107
+ var n = elements.length, k = n;
108
+ this.elements = [];
109
+ do { i = k - n;
110
+ this.elements.push([elements[i]]);
111
+ } while (--n);
112
+ return this;
113
+ }
114
+ };
115
+
116
+ // Constructor function
117
+ Matrix.create = function(elements) {
118
+ var M = new Matrix();
119
+ return M.setElements(elements);
120
+ };
121
+
122
+ // Utility functions
123
+ $M = Matrix.create;
124
+
125
+ })({});
126
+
127
+
128
+ (function(exports) {
129
+ // ==========================================================================
130
+ // Project: AcceleratedEffects
131
+ // Copyright: ©2011 Majd Taby
132
+ // License: Licensed under MIT license (see license.js)
133
+ // ==========================================================================
134
+
135
+ (function($) {
136
+ if ( !$.cssHooks ) {
137
+ throw("jQuery 1.4.3+ is needed for this plugin to work");
138
+ return;
139
+ }
140
+
141
+ function styleSupport( prop ) {
142
+ var vendorProp, supportedProp,
143
+
144
+ // capitalize first character of the prop to test vendor prefix
145
+ capProp = prop.charAt(0).toUpperCase() + prop.slice(1),
146
+ prefixes = [ "Moz", "Webkit", "O", "ms" ],
147
+ div = document.createElement( "div" );
148
+
149
+ if ( prop in div.style ) {
150
+
151
+ // browser supports standard CSS property name
152
+ supportedProp = prop;
153
+ } else {
154
+
155
+ // otherwise test support for vendor-prefixed property names
156
+ for ( var i = 0; i < prefixes.length; i++ ) {
157
+ vendorProp = prefixes[i] + capProp;
158
+ if ( vendorProp in div.style ) {
159
+ supportedProp = vendorProp;
160
+ break;
161
+ }
162
+ }
163
+ }
164
+
165
+ // avoid memory leak in IE
166
+ div = null;
167
+
168
+ // add property to $.support so it can be accessed elsewhere
169
+ $.support[ prop ] = supportedProp;
170
+
171
+ return supportedProp;
172
+ }
173
+
174
+ var transformProperty = styleSupport('transform');
175
+ console.log(transformProperty);
176
+
177
+ var properties = {
178
+ rotateX: {
179
+ defaultValue: 0
180
+ },
181
+ rotateY: {
182
+ defaultValue: 0
183
+ },
184
+ rotateZ: {
185
+ defaultValue: 0
186
+ },
187
+ translateX: {
188
+ defaultValue: 0
189
+ },
190
+ translateY: {
191
+ defaultValue: 0
192
+ },
193
+ translateZ: {
194
+ defaultValue: 0
195
+ },
196
+ scale: {
197
+ defaultValue: 1
198
+ }
199
+ };
200
+
201
+ var RotationXMatrix = function(a) {
202
+ return $M([
203
+ [1,0,0,0],
204
+ [0,Math.cos(a), Math.sin(-a), 0],
205
+ [0,Math.sin(a), Math.cos( a), 0],
206
+ [0,0,0,1]
207
+ ]);
208
+ };
209
+
210
+ var RotationYMatrix = function(b) {
211
+ return $M([
212
+ [Math.cos( b), 0, Math.sin(b),0],
213
+ [0,1,0,0],
214
+ [Math.sin(-b), 0, Math.cos(b), 0],
215
+ [0,0,0,1]
216
+ ]);
217
+ };
218
+
219
+ var RotationZMatrix = function(c) {
220
+ return $M([
221
+ [Math.cos(c), Math.sin(-c), 0, 0],
222
+ [Math.sin(c), Math.cos( c), 0, 0],
223
+ [0,0,1,0],
224
+ [0,0,0,1]
225
+ ]);
226
+ };
227
+
228
+ var TranslationMatrix = function(tx,ty,tz) {
229
+ return $M([
230
+ [1,0,0,0],
231
+ [0,1,0,0],
232
+ [0,0,1,0],
233
+ [tx,ty,tz,1]
234
+ ]);
235
+ };
236
+
237
+ var ScaleMatrix = function(s) {
238
+ return $M([
239
+ [s,0,0,0],
240
+ [0,s,0,0],
241
+ [0,0,s,0],
242
+ [0,0,0,1]
243
+ ]);
244
+ };
245
+
246
+ var applyMatrix = function(elem) {
247
+ var transforms = $(elem).data('transforms');
248
+
249
+ var rotX = transforms.rotateX || properties.rotateX.defaultValue,
250
+ rotY = transforms.rotateY || properties.rotateY.defaultValue,
251
+ rotZ = transforms.rotateZ || properties.rotateZ.defaultValue,
252
+ scale = transforms.scale || properties.scale.defaultValue,
253
+ translateX = transforms.translateX || properties.translateX.defaultValue,
254
+ translateY = transforms.translateY || properties.translateY.defaultValue,
255
+ translateZ = transforms.translateZ || properties.translateZ.defaultValue;
256
+
257
+ var tM = RotationXMatrix(rotX)
258
+ .x(RotationYMatrix(rotY))
259
+ .x(RotationZMatrix(rotZ))
260
+ .x(ScaleMatrix(scale))
261
+ .x(TranslationMatrix(translateX,translateY,translateZ));
262
+
263
+ s = "matrix3d(";
264
+ s += tM.e(1,1).toFixed(10) + "," + tM.e(1,2).toFixed(10) + "," + tM.e(1,3).toFixed(10) + "," + tM.e(1,4).toFixed(10) + ",";
265
+ s += tM.e(2,1).toFixed(10) + "," + tM.e(2,2).toFixed(10) + "," + tM.e(2,3).toFixed(10) + "," + tM.e(2,4).toFixed(10) + ",";
266
+ s += tM.e(3,1).toFixed(10) + "," + tM.e(3,2).toFixed(10) + "," + tM.e(3,3).toFixed(10) + "," + tM.e(3,4).toFixed(10) + ",";
267
+ s += tM.e(4,1).toFixed(10) + "," + tM.e(4,2).toFixed(10) + "," + tM.e(4,3).toFixed(10) + "," + tM.e(4,4).toFixed(10);
268
+ s += ")";
269
+
270
+ elem.style[transformProperty] = s;
271
+ }
272
+
273
+ var hookFor = function(name) {
274
+
275
+ $.fx.step[name] = function(fx){
276
+ $.cssHooks[name].set( fx.elem, fx.now + fx.unit );
277
+ };
278
+
279
+ return {
280
+ get: function( elem, computed, extra ) {
281
+ var transforms = $(elem).data('transforms');
282
+ if (transforms === undefined) {
283
+ transforms = {};
284
+ $(elem).data('transforms',transforms);
285
+ }
286
+
287
+ return transforms[name] || properties[name].defaultValue;
288
+ },
289
+ set: function( elem, value) {
290
+ var transforms = $(elem).data('transforms');
291
+ if (transforms === undefined) transforms = {};
292
+ var propInfo = properties[name];
293
+
294
+ if (typeof propInfo.apply === 'function') {
295
+ transforms[name] = propInfo.apply(transforms[name] || propInfo.defaultValue, value);
296
+ } else {
297
+ transforms[name] = value
298
+ }
299
+
300
+ $(elem).data('transforms',transforms);
301
+ applyMatrix(elem);
302
+ }
303
+ }
304
+ }
305
+
306
+ if (transformProperty) {
307
+ for (var name in properties) {
308
+ $.cssHooks[name] = hookFor(name);
309
+ $.cssNumber[name] = true;
310
+ }
311
+ }
312
+
313
+ })(jQuery);
314
+
315
+ })({});
316
+
317
+
318
+ (function(exports) {
319
+ // ==========================================================================
320
+ // Project: AcceleratedEffects
321
+ // Copyright: ©2011 Majd Taby
322
+ // License: Licensed under MIT license (see license.js)
323
+ // ==========================================================================
324
+
325
+ })({});
326
+
327
+ (function(exports) {
328
+ // ==========================================================================
329
+ // Project: SproutCore Runtime
330
+ // Copyright: ©2011 Strobe Inc. and contributors.
331
+ // License: Licensed under MIT license (see license.js)
332
+ // ==========================================================================
333
+
334
+ var get = SC.get;
335
+ var set = SC.set;
336
+
337
+ /**
338
+ @class
339
+
340
+ Registry of known gestures in the system. This is a singleton class, and is
341
+ used by SC.View to analyze instances of SC.View for gesture support.
342
+
343
+ You will not use this class yourself. Rather, gesture recognizers will call
344
+ SC.Gestures.register(name, recognizer) when they want to make the system aware
345
+ of them.
346
+
347
+ @private
348
+ @extends SC.Object
349
+ */
350
+ SC.Gestures = SC.Object.create(
351
+ /** @scope SC.Gestures.prototype */{
352
+
353
+ _registeredGestures: null,
354
+
355
+ init: function() {
356
+ this._registeredGestures = {};
357
+
358
+ return this._super();
359
+ },
360
+
361
+ /**
362
+ Registers a gesture recognizer to the system. The gesture recognizer is
363
+ identified by the name parameter, which must be globally unique.
364
+ */
365
+ register: function(name, /** SC.Gesture */recognizer) {
366
+ var registeredGestures = this._registeredGestures;
367
+
368
+ if (registeredGestures[name] !== undefined) {
369
+ throw new SC.Error(name+" already exists as a registered gesture recognizers. Gesture recognizers must have globally unique names.");
370
+ }
371
+
372
+ registeredGestures[name] = recognizer;
373
+ },
374
+
375
+ unregister: function(name) {
376
+ var registeredGestures = this._registeredGestures;
377
+
378
+ if (registeredGestures[name] !== undefined) {
379
+ registeredGestures[name] = undefined;
380
+ }
381
+ },
382
+
383
+ /**
384
+ Registers a gesture recognizer to the system. The gesture recognizer is
385
+ identified by the name parameter, which must be unique across the system.
386
+ */
387
+ knownGestures: function() {
388
+ var registeredGestures = this._registeredGestures;
389
+
390
+ return (registeredGestures)? registeredGestures : {};
391
+ }
392
+
393
+ });
394
+
395
+
396
+ })({});
397
+
398
+
399
+ (function(exports) {
400
+ // ==========================================================================
401
+ // Project: SproutCore Runtime
402
+ // Copyright: ©2011 Strobe Inc. and contributors.
403
+ // License: Licensed under MIT license (see license.js)
404
+ // ==========================================================================
405
+
406
+ var get = SC.get;
407
+ var set = SC.set;
408
+
409
+ /**
410
+ @class
411
+
412
+ Manages multiplegesture recognizers that are associated with a view.
413
+ This class is instantiated automatically by SC.View and you wouldn't
414
+ interact with it yourself.
415
+
416
+ SC.GestureManager mainly acts as a composite for the multiple gesture
417
+ recognizers associated with a view. Whenever it gets a touch event, it
418
+ relays it to the gestures. The other main resposibility of
419
+ SC.GestureManager is to handle re-dispatching of events to the view.
420
+
421
+ @extends SC.Object
422
+ */
423
+ SC.GestureManager = SC.Object.extend({
424
+
425
+ /**
426
+ An array containing all the gesture recognizers associated with a
427
+ view. This is set automatically by SC.View.
428
+
429
+ @default null
430
+ @type Array
431
+ */
432
+ gestures: null,
433
+
434
+ /**
435
+ Internal hash used to keep a list of the events that need to be
436
+ re-dispatched to the views. It's used so we don't re-dispatch
437
+ the same event multiple times to the same view.
438
+
439
+ @default null
440
+ @type Array
441
+ */
442
+ _redispatchQueue: null,
443
+
444
+ _redispatchToNearestParentViewWaitingForTouches: function(evt, view) {
445
+ var foundManager = null,
446
+ successful = false;
447
+ var view = get(view, 'parentView');
448
+
449
+ while(view) {
450
+ var manager = get(view, 'eventManager');
451
+
452
+ if (manager !== undefined && manager !== null) {
453
+ var gestures = get(manager, 'gestures');
454
+
455
+ for (var i=0, l=gestures.length; i<l; i++) {
456
+ if (get(gestures[i], 'state') === SC.Gesture.WAITING_FOR_TOUCHES) {
457
+ foundManager = manager;
458
+ }
459
+ }
460
+
461
+ if (foundManager) {
462
+ successful = true;
463
+ foundManager.touchStart(evt, view);
464
+ break;
465
+ }
466
+ }
467
+
468
+ view = get(view, 'parentView');
469
+ }
470
+
471
+ return successful;
472
+ },
473
+
474
+ /**
475
+ Relays touchStart events to all the gesture recognizers to the
476
+ specified view
477
+
478
+ @return Boolen
479
+ */
480
+ touchStart: function(evt, view) {
481
+ if (this._redispatchToNearestParentViewWaitingForTouches(evt, view)) {
482
+ return;
483
+ }
484
+
485
+ return this._invokeEvent('touchStart',evt, view);
486
+ },
487
+
488
+ /**
489
+ Relays touchMove events to all the gesture recognizers to the
490
+ specified view
491
+
492
+ @return Boolen
493
+ */
494
+ touchMove: function(evt, view) {
495
+ return this._invokeEvent('touchMove',evt, view);
496
+ },
497
+
498
+ /**
499
+ Relays touchEnd events to all the gesture recognizers to the
500
+ specified view
501
+
502
+ @return Boolen
503
+ */
504
+ touchEnd: function(evt, view) {
505
+ return this._invokeEvent('touchEnd',evt, view);
506
+ },
507
+
508
+ /**
509
+ Relays touchCancel events to all the gesture recognizers to the
510
+ specified view
511
+
512
+ @return Boolen
513
+ */
514
+ touchCancel: function(evt, view) {
515
+ return this._invokeEvent('touchCancel',evt, view);
516
+ },
517
+
518
+ /**
519
+ Relays an event to the gesture recognizers. Used internally
520
+ by the touch event listeners.
521
+
522
+ @private
523
+ @return Boolean
524
+ */
525
+ _invokeEvent: function(eventName, eventObject, view) {
526
+ var gestures = get(this, 'gestures'),
527
+ gesture, result = true;
528
+
529
+ this._redispatchQueue = {};
530
+
531
+ for (var i=0, l=gestures.length; i < l; i++) {
532
+ gesture = gestures[i];
533
+ handler = gesture[eventName];
534
+
535
+ if (SC.typeOf(handler) === 'function') {
536
+ result = handler.call(gesture, eventObject, view, this);
537
+ }
538
+ };
539
+
540
+ this._flushReDispatchQueue();
541
+
542
+ return result;
543
+ },
544
+
545
+ /**
546
+ Similar to _invokeEvent, but instead of invoking the event
547
+ to the gesture recognizers, it re-dispatches the event to the
548
+ view. This method is used by the gesture recognizers when they
549
+ want to let the view respond to the original events.
550
+ */
551
+ redispatchEventToView: function(view, eventName, eventObject) {
552
+ var queue = this._redispatchQueue;
553
+
554
+ if (queue[eventName] === undefined) {
555
+ queue[eventName] = [];
556
+ }
557
+ else {
558
+ var views = queue[eventName];
559
+
560
+ for (var i=0, l=views.length; i<l; i++) {
561
+ if (view === views[i].view) {
562
+ return;
563
+ }
564
+ }
565
+ }
566
+
567
+ var originalEvent = null;
568
+ if (eventObject && eventObject.originalEvent) originalEvent = eventObject.originalEvent;
569
+
570
+ queue[eventName].push({
571
+ view: view,
572
+ originalEvent: originalEvent
573
+ });
574
+ },
575
+
576
+ /**
577
+ This method is used internally by _invokeEvent. It re-dispatches
578
+ events to the view if the gestures decided they want to.
579
+ */
580
+ _flushReDispatchQueue: function() {
581
+ var queue = this._redispatchQueue;
582
+
583
+ for (var eventName in queue) {
584
+ var views = queue[eventName];
585
+
586
+ for (var i=0, l=views.length; i<l; i++) {
587
+ var view = views[i].view;
588
+ var event = jQuery.Event(eventName);
589
+
590
+ event.originalEvent = views[i].originalEvent;
591
+
592
+ // Trigger event so it bubbles up the hierarchy
593
+ view.$().trigger(event, this);
594
+ }
595
+ }
596
+ }
597
+
598
+ });
599
+
600
+ })({});
601
+
602
+
603
+ (function(exports) {
604
+ // ==========================================================================
605
+ // Project: SproutCore Touch
606
+ // Copyright: ©2011 Strobe Inc. and contributors.
607
+ // License: Licensed under MIT license (see license.js)
608
+ // ==========================================================================
609
+
610
+ var get = SC.get;
611
+ var set = SC.set;
612
+
613
+ /**
614
+ @class
615
+ @private
616
+
617
+ Used to manage and maintain a list of active touches related to a gesture
618
+ recognizer.
619
+ */
620
+ SC.TouchList = SC.Object.extend({
621
+ touches: null,
622
+
623
+ timestamp: null,
624
+
625
+ init: function() {
626
+ this._super();
627
+
628
+ set(this, 'touches', []);
629
+ },
630
+
631
+ addTouch: function(touch) {
632
+ var touches = get(this, 'touches');
633
+ touches.push(touch);
634
+ this.notifyPropertyChange('touches');
635
+ },
636
+
637
+ updateTouch: function(touch) {
638
+ var touches = get(this, 'touches');
639
+
640
+ for (var i=0, l=touches.length; i<l; i++) {
641
+ var _t = touches[i];
642
+
643
+ if (_t.identifier === touch.identifier) {
644
+ touches[i] = touch;
645
+ this.notifyPropertyChange('touches');
646
+ break;
647
+ }
648
+ }
649
+ },
650
+
651
+ removeTouch: function(touch) {
652
+ var touches = get(this, 'touches');
653
+
654
+ for (var i=0, l=touches.length; i<l; i++) {
655
+ var _t = touches[i];
656
+
657
+ if (_t.identifier === touch.identifier) {
658
+ touches.splice(i,1);
659
+ this.notifyPropertyChange('touches');
660
+ break;
661
+ }
662
+ }
663
+ },
664
+
665
+ removeAllTouches: function() {
666
+ set(this, 'touches', []);
667
+ },
668
+
669
+ touchWithId: function(id) {
670
+ var ret = null,
671
+ touches = get(this, 'touches');
672
+
673
+ for (var i=0, l=touches.length; i<l; i++) {
674
+ var _t = touches[i];
675
+
676
+ if (_t.identifier === id) {
677
+ ret = _t;
678
+ break;
679
+ }
680
+ }
681
+
682
+ return ret;
683
+ },
684
+
685
+ length: function() {
686
+ var touches = get(this, 'touches');
687
+ return touches.length;
688
+ }.property('touches').cacheable()
689
+ });
690
+
691
+ })({});
692
+
693
+
694
+ (function(exports) {
695
+ // ==========================================================================
696
+ // Project: SproutCore Runtime
697
+ // Copyright: ©2011 Strobe Inc. and contributors.
698
+ // License: Licensed under MIT license (see license.js)
699
+ // ==========================================================================
700
+
701
+
702
+
703
+ var get = SC.get;
704
+ var set = SC.set;
705
+
706
+ var sigFigs = 100;
707
+
708
+ /**
709
+ @class
710
+
711
+ Base class for all gesture recognizers. Handles low-level touch and state
712
+ management, and provides some utility methods and some required methods all
713
+ gesture recognizers are expected to implement.
714
+
715
+ Overview
716
+ =========
717
+
718
+ Gestures coalesce multiple touch events to a single higher-level gesture
719
+ event. For example, a tap gesture recognizer takes information about a
720
+ touchstart event, a few touchmove events, and a touchend event and uses
721
+ some heuristics to decide whether or not that sequence of events qualifies
722
+ as a tap event. If it does, then it will notify the view of the higher-level
723
+ tap events.
724
+
725
+ Gesture events follow the format:
726
+
727
+ * [GESTURE_NAME]Start - Sent when a gesture has gathered enough information
728
+ to begin tracking the gesture
729
+
730
+ * [GESTURE_NAME]Change - Sent when a gesture has already started and has
731
+ received touchmove events that cause its state to change
732
+
733
+ * [GESTURE_NAME]End - Sent when a touchend event is received and the gesture
734
+ recognizer decides that the gesture is finished.
735
+
736
+ * [GESTURE_NAME]Cancel - Sent when a touchcancel event is received.
737
+
738
+ There are two types of gesturess: Discrete and Continuous gestures. In contrast
739
+ to continuous gestures, discrete gestures don't have any change events. Rather,
740
+ the start and end events are the only one that gets sent.
741
+
742
+ Usage
743
+ =======
744
+
745
+ While you wouldn't use SC.Gesture directly, all its subclasses have the same
746
+ API. For example, to implement pinch on a view, you implement pinchChange and
747
+ optionally pinchStart, pinchEnd and pinchCancel.
748
+
749
+ var myView = SC.View.create({
750
+ pinchStart: function(recognizer) {
751
+ this.$().css('background','red');
752
+ },
753
+
754
+ pinchChange: function(recognizer) {
755
+ var scale = recognizer.get('scale');
756
+ this.$().css('-webkit-transform','scale3d('+scale+','+scale+',1)');
757
+ },
758
+
759
+ pinchEnd: function(recognizer) {
760
+ this.$().css('background','blue');
761
+ },
762
+
763
+ pinchCancel: function(recognizer) {
764
+ this.$().css('background','blue');
765
+ }
766
+ });
767
+
768
+ pinchStart(), pinchEnd() and pinchCancel() will only get called once per
769
+ gesture, but pinchChange() will get called repeatedly called every time
770
+ one of the touches moves.
771
+
772
+ Creating Custom Gesture Recognizers
773
+ ======
774
+
775
+ SC.Gesture also defines an API which its subclasses can implement to build
776
+ custom gestures. The methods are:
777
+
778
+ * **didBecomePossible** - Called when a gesture enters a possible state. This
779
+ means the gesture recognizer has accepted enough touches to match
780
+ the number of required touches. You would usually initialize your state
781
+ in this callback.
782
+
783
+ * **eventWasRejected** - Called if a view returns false from a gesture event.
784
+ This callback allows you to reset internal state if the user rejects
785
+ an event.
786
+
787
+ * **shouldBegin** - Allows a gesture to block itself from entering a began state.
788
+ This callback will continuously be called as touches move until it begins.
789
+
790
+ * **shouldEnd** - Allows a gesture to block itself from entering an ended state.
791
+ This callback gets called whenever a tracked touch gets a touchEnd event.
792
+
793
+ * **didBegin** - Called when the gesture enters a began state. Called before the
794
+ view receives the Start event.
795
+
796
+ * **didChange** - Called when the gesture enters a began state, and when one of the
797
+ touches moves. Called before the view receives the Change event.
798
+
799
+ * **didEnd** - Called when the gesture enters an ended state. Called before the
800
+ view receives the End event.
801
+
802
+ * **didCancel** - Called when the gesture enters a cancelled state. Called before the
803
+ view receives the Cancel event.
804
+
805
+ In all the callbacks, you can use the `touches` protected property to access the
806
+ touches hash. The touches hash is keyed on the identifiers of the touches, and the
807
+ values are the jQuery.Event objects.
808
+
809
+ You can also use the numberOfActiveTouches property to inspect how many touches
810
+ are active, this is mostly useful in shouldBegin since every other callback can
811
+ assume that there are as many active touches as specified in the
812
+ numberOfRequiredTouches property.
813
+
814
+ Discrete vs Continuous Gestures
815
+ =======
816
+
817
+ There are two main classes of gesture recognizers: Discrete and Continuous
818
+ gestures. Discrete gestures do not get Change events sent, since they represent
819
+ a single, instantaneous event, rather than a continuous motion. If you are
820
+ implementing your own discrete gesture recognizer, you must set the
821
+ isDiscreteGesture property to yes, and SC.Gesture will adapt its behavior.
822
+
823
+ Discrete gestures use the shouldEnd callback to either accept or decline the gesture
824
+ event. If it is delined, then the gesture will enter a Cancelled state and trigger
825
+ the Cancel event on the view.
826
+
827
+ @extends SC.Object
828
+ */
829
+
830
+ SC.Gesture = SC.Object.extend(
831
+ /** @scope SC.Gesture.prototype */{
832
+
833
+ /**
834
+ The current state of the gesture recognizer. This value can be any one
835
+ of the states defined at the end of this file.
836
+
837
+ @type Number
838
+ */
839
+ state: null,
840
+
841
+ /**
842
+ A string of the gesture recognizer's name. This value is set automatically
843
+ but SC.Gestures when a gesture is registered.
844
+
845
+ @type String
846
+ */
847
+ name: null,
848
+
849
+ /**
850
+ Specifies whether a gesture is discrete or continuous.
851
+
852
+ @type Boolean
853
+ @default false
854
+ */
855
+ gestureIsDiscrete: false,
856
+
857
+ /**
858
+ You can use the `touches` protected property to access the touches hash. The touches
859
+ hash is keyed on the identifiers of the touches, and the values are the jQuery.Event
860
+ objects.
861
+
862
+ @private
863
+ @type Hash
864
+ */
865
+ touches: null,
866
+
867
+ /**
868
+ You can also use the numberOfActiveTouches property to inspect how many touches
869
+ are active, this is mostly useful in shouldBegin since every other callback can
870
+ assume that there are as many active touches as specified in the
871
+ numberOfRequiredTouches property.
872
+
873
+ @private
874
+ @type Number
875
+ */
876
+ numberOfActiveTouches: 0,
877
+
878
+ /**
879
+ Used to specify the number of touches required for the gesture to enter a possible
880
+ state
881
+
882
+ @private
883
+ @type Number
884
+ */
885
+ numberOfRequiredTouches: 1,
886
+
887
+ init: function() {
888
+ this._super();
889
+ this.touches = SC.TouchList.create();
890
+ },
891
+
892
+ //..............................................
893
+ // Gesture Callbacks
894
+
895
+ /** @private */
896
+ didBecomePossible: function() { },
897
+
898
+ /** @private */
899
+ shouldBegin: function() {
900
+ return true;
901
+ },
902
+
903
+ /** @private */
904
+ didBegin: function() { },
905
+
906
+ /** @private */
907
+ didChange: function() { },
908
+
909
+ /** @private */
910
+ eventWasRejected: function() { },
911
+
912
+ /** @private */
913
+ shouldEnd: function() {
914
+ return true;
915
+ },
916
+
917
+ /** @private */
918
+ didEnd: function() { },
919
+
920
+ /** @private */
921
+ didCancel: function() { },
922
+
923
+ //..............................................
924
+ // Utilities
925
+
926
+ /** @private */
927
+ attemptGestureEventDelivery: function(evt, view, eventName) {
928
+ if (this.notifyViewOfGestureEvent(view, eventName) === false) {
929
+ this.eventWasRejected();
930
+ } else {
931
+ evt.preventDefault();
932
+ }
933
+ },
934
+
935
+ /**
936
+ Given two Touch objects, this method returns the distance between them.
937
+
938
+ @return Number
939
+ */
940
+ distance: function(touches) {
941
+
942
+ if (touches.length < 2) {
943
+ return 0;
944
+ }
945
+
946
+ var first = touches[0];
947
+ var second = touches[1];
948
+
949
+ var x = first.pageX;
950
+ var y = first.pageY;
951
+ var x0 = second.pageX;
952
+ var y0 = second.pageY;
953
+
954
+ return Math.sqrt((x -= x0) * x + (y -= y0) * y);
955
+ },
956
+
957
+ /**
958
+ Given two Touch objects, this method returns the midpoint between them.
959
+
960
+ @return Number
961
+ */
962
+ centerPointForTouches: function(touches) {
963
+ var sumX = 0,
964
+ sumY = 0;
965
+
966
+ for (var i=0, l=touches.length; i<l; i++) {
967
+ var touch = touches[i];
968
+ sumX += touch.pageX;
969
+ sumY += touch.pageY;
970
+ }
971
+
972
+ var location = {
973
+ x: sumX / touches.length,
974
+ y: sumY / touches.length
975
+ };
976
+
977
+ return location;
978
+ },
979
+
980
+ /** @private */
981
+ _objectValues: function(object) {
982
+ var ret = [];
983
+
984
+ for (var item in object ) {
985
+ if (object.hasOwnProperty(item)) {
986
+ ret.push(object[item]);
987
+ }
988
+ }
989
+
990
+ return ret;
991
+ },
992
+
993
+ /**
994
+ Allows the gesture to notify the view it's associated with of a gesture
995
+ event.
996
+
997
+ @private
998
+ */
999
+ notifyViewOfGestureEvent: function(view, eventName, data) {
1000
+ var handler = view[eventName];
1001
+ var result = true;
1002
+
1003
+ if (SC.typeOf(handler) === 'function') {
1004
+ result = handler.call(view, this, data);
1005
+ }
1006
+
1007
+ return result;
1008
+ },
1009
+
1010
+ toString: function() {
1011
+ return SC.Gesture+'<'+SC.guidFor(this)+'>';
1012
+ },
1013
+
1014
+ /** @private */
1015
+ _resetState: function() {
1016
+ this.touches.removeAllTouches();
1017
+ },
1018
+
1019
+ //..............................................
1020
+ // Touch event handlers
1021
+
1022
+ /** @private */
1023
+ touchStart: function(evt, view, manager) {
1024
+ var targetTouches = evt.originalEvent.targetTouches;
1025
+ var _touches = this.touches;
1026
+ var state = get(this, 'state');
1027
+
1028
+ set(_touches, 'timestamp', Date.now());
1029
+
1030
+ //Collect touches by their identifiers
1031
+ for (var i=0, l=targetTouches.length; i<l; i++) {
1032
+ var touch = targetTouches[i];
1033
+
1034
+ if(_touches.touchWithId(touch.identifier) === null && _touches.get('length') < get(this, 'numberOfRequiredTouches')) {
1035
+ _touches.addTouch(touch);
1036
+ }
1037
+ }
1038
+
1039
+ if (_touches.get('length') < get(this, 'numberOfRequiredTouches')) {
1040
+ set(this ,'state', SC.Gesture.WAITING_FOR_TOUCHES);
1041
+
1042
+ } else {
1043
+ // Discrete gestures may skip the possible step if they're ready to begin
1044
+ if (get(this, 'gestureIsDiscrete') && this.shouldBegin()) {
1045
+ set(this, 'state', SC.Gesture.BEGAN);
1046
+ this.didBegin();
1047
+ this.attemptGestureEventDelivery(evt, view, get(this, 'name')+'Start');
1048
+ } else {
1049
+ set(this, 'state', SC.Gesture.POSSIBLE);
1050
+ this.didBecomePossible();
1051
+ }
1052
+ }
1053
+
1054
+ manager.redispatchEventToView(view,'touchstart', evt);
1055
+ },
1056
+
1057
+ /** @private */
1058
+ touchMove: function(evt, view, manager) {
1059
+ var state = get(this, 'state');
1060
+
1061
+ if (state === SC.Gesture.WAITING_FOR_TOUCHES || state === SC.Gesture.ENDED || state === SC.Gesture.CANCELLED) {
1062
+ // Nothing to do here
1063
+ manager.redispatchEventToView(view,'touchmove', evt);
1064
+ return;
1065
+ }
1066
+
1067
+ var changedTouches = evt.originalEvent.changedTouches;
1068
+ var _touches = this.touches;
1069
+
1070
+ set(_touches, 'timestamp', Date.now());
1071
+
1072
+ // Update touches hash
1073
+ for (var i=0, l=changedTouches.length; i<l; i++) {
1074
+ var touch = changedTouches[i];
1075
+ _touches.updateTouch(touch);
1076
+ }
1077
+
1078
+ if (state === SC.Gesture.POSSIBLE) {
1079
+ if (this.shouldBegin()) {
1080
+ set(this, 'state', SC.Gesture.BEGAN);
1081
+ this.didBegin();
1082
+
1083
+ // Give the gesture a chance to update its state so the view can get
1084
+ // updated information in the Start event
1085
+ this.didChange();
1086
+
1087
+ this.attemptGestureEventDelivery(evt, view, get(this, 'name')+'Start');
1088
+ }
1089
+
1090
+ // Discrete gestures don't fire changed events
1091
+ } else if ((state === SC.Gesture.BEGAN || state === SC.Gesture.CHANGED) && !get(this, 'gestureIsDiscrete')) {
1092
+ set(this, 'state', SC.Gesture.CHANGED);
1093
+ this.didChange();
1094
+
1095
+ this.attemptGestureEventDelivery(evt, view, get(this, 'name')+'Change');
1096
+
1097
+ } else {
1098
+ manager.redispatchEventToView(view,'touchmove', evt);
1099
+ }
1100
+ },
1101
+
1102
+ /** @private */
1103
+ touchEnd: function(evt, view, manager) {
1104
+ // Discrete gestures need to cancel if they shouldn't end successfully
1105
+ if (get(this, 'gestureIsDiscrete')) {
1106
+
1107
+ // Discrete gestures use shouldEnd to either accept or decline the gesture.
1108
+ if (this.state === SC.Gesture.BEGAN && this.shouldEnd()) {
1109
+ set(this, 'state', SC.Gesture.ENDED);
1110
+ this.didEnd();
1111
+ this.attemptGestureEventDelivery(evt, view, get(this, 'name')+'End');
1112
+ } else {
1113
+ set(this, 'state', SC.Gesture.CANCELLED);
1114
+ this.didCancel();
1115
+ this.attemptGestureEventDelivery(evt, view, get(this, 'name')+'Cancel');
1116
+ }
1117
+ }
1118
+ else {
1119
+ if (this.state !== SC.Gesture.ENDED && this.shouldEnd()) {
1120
+ set(this, 'state', SC.Gesture.ENDED);
1121
+ this.didEnd();
1122
+
1123
+ this.attemptGestureEventDelivery(evt, view, get(this, 'name')+'End');
1124
+ }
1125
+
1126
+ manager.redispatchEventToView(view,'touchend', evt);
1127
+ }
1128
+
1129
+ this._resetState();
1130
+ },
1131
+
1132
+ /** @private */
1133
+ touchCancel: function(evt, view, manager) {
1134
+ if (this.state !== SC.Gesture.CANCELLED) {
1135
+ this._resetState();
1136
+ set(this, 'state', SC.Gesture.CANCELLED);
1137
+ this.notifyViewOfGestureEvent(view,get(this, 'name')+'Cancel');
1138
+ } else {
1139
+ manager.redispatchEventToView(view,'touchcancel', evt);
1140
+ }
1141
+ }
1142
+ });
1143
+
1144
+ SC.Gesture.WAITING_FOR_TOUCHES = 0;
1145
+ SC.Gesture.POSSIBLE = 1;
1146
+ SC.Gesture.BEGAN = 2;
1147
+ SC.Gesture.CHANGED = 3;
1148
+ SC.Gesture.ENDED = 4;
1149
+ SC.Gesture.CANCELLED = 4;
1150
+
1151
+ })({});
1152
+
1153
+
1154
+ (function(exports) {
1155
+ // ==========================================================================
1156
+ // Project: SproutCore Runtime
1157
+ // Copyright: ©2011 Strobe Inc. and contributors.
1158
+ // License: Licensed under MIT license (see license.js)
1159
+ // ==========================================================================
1160
+
1161
+ var get = SC.get;
1162
+ var set = SC.set;
1163
+
1164
+ var sigFigs = 100;
1165
+
1166
+ /**
1167
+ @class
1168
+
1169
+ Recognizes a multi-touch pinch gesture. Pinch gestures require a specified number
1170
+ of fingers to move and will record and update the scale.
1171
+
1172
+ For pinchChange events, the pinch gesture recognizer includes a scale property
1173
+ which can be applied as a CSS transform directly.
1174
+
1175
+ var myview = SC.View.create({
1176
+ elementId: 'gestureTest',
1177
+ pinchChange: function(recognizer) {
1178
+ var scale = recognizer.get('scale');
1179
+ this.$().css('-webkit-transform','scale3d('+scale+','+scale+',1)');
1180
+ }
1181
+ })
1182
+
1183
+ You can specify how many touches the gesture requires to start using the numberOfRequiredTouches
1184
+ property, which you can set in the pinchOptions hash:
1185
+
1186
+ var myview = SC.View.create({
1187
+ pinchOptions: {
1188
+ numberOfRequiredTouches: 3
1189
+ }
1190
+ ...
1191
+ })
1192
+
1193
+
1194
+ @extends SC.Gesture
1195
+ */
1196
+ SC.PinchGestureRecognizer = SC.Gesture.extend({
1197
+
1198
+ /**
1199
+ The scale value which represents the current amount of scaling that has been applied
1200
+ to the view. You would normally apply this value directly to your element as a 3D
1201
+ scale.
1202
+
1203
+ @type Number
1204
+ */
1205
+ scale: 1,
1206
+
1207
+ numberOfRequiredTouches: 2,
1208
+
1209
+ //..................................................
1210
+ // Private Methods and Properties
1211
+
1212
+ /**
1213
+ Track starting distance between touches per gesture.
1214
+
1215
+ @private
1216
+ @type Number
1217
+ */
1218
+ _startingDistanceBetweenTouches: null,
1219
+
1220
+ /**
1221
+ Used for measuring velocity
1222
+
1223
+ @private
1224
+ @type Number
1225
+ */
1226
+ _previousTimestamp: null,
1227
+
1228
+ /**
1229
+ Used for measuring velocity and scale
1230
+
1231
+ @private
1232
+ @type Number
1233
+ */
1234
+ _previousDistance: 0,
1235
+
1236
+ /**
1237
+ The pixel distance that the fingers need to get closer/farther away by before
1238
+ this gesture is recognized.
1239
+
1240
+ @private
1241
+ @type Number
1242
+ */
1243
+ _deltaThreshold: 5,
1244
+
1245
+ /**
1246
+ Used for rejected events
1247
+
1248
+ @private
1249
+ @type Number
1250
+ */
1251
+ _previousScale: 1,
1252
+
1253
+ /**
1254
+ @private
1255
+ */
1256
+ didBecomePossible: function() {
1257
+ this._startingDistanceBetweenTouches = this.distance(get(this.touches,'touches'));
1258
+ this._previousDistance = this._startingDistanceBetweenTouches;
1259
+ this._previousTimestamp = get(this.touches,'timestamp');
1260
+ },
1261
+
1262
+ shouldBegin: function() {
1263
+ var currentDistanceBetweenTouches = this.distance(get(this.touches,'touches'));
1264
+
1265
+ return Math.abs(currentDistanceBetweenTouches - this._startingDistanceBetweenTouches) >= this._deltaThreshold;
1266
+ },
1267
+
1268
+ didChange: function() {
1269
+ var scale = this._previousScale = get(this, 'scale');
1270
+ var timeDifference = this.touches.timestamp - this._previousTimestamp;
1271
+ var currentDistanceBetweenTouches = this.distance(get(this.touches,'touches'));
1272
+ var distanceDifference = (currentDistanceBetweenTouches - this._previousDistance);
1273
+
1274
+ set(this, 'velocity', distanceDifference / timeDifference);
1275
+ set(this, 'scale', currentDistanceBetweenTouches / this._previousDistance);
1276
+
1277
+ this._previousTimestamp = get(this.touches,'timestamp');
1278
+ this._previousDistance = currentDistanceBetweenTouches;
1279
+ },
1280
+
1281
+ eventWasRejected: function() {
1282
+ set(this, 'scale', this._previousScale);
1283
+ }
1284
+ });
1285
+
1286
+ SC.Gestures.register('pinch', SC.PinchGestureRecognizer);
1287
+
1288
+ })({});
1289
+
1290
+
1291
+ (function(exports) {
1292
+ // ==========================================================================
1293
+ // Project: SproutCore Runtime
1294
+ // Copyright: ©2011 Strobe Inc. and contributors.
1295
+ // License: Licensed under MIT license (see license.js)
1296
+ // ==========================================================================
1297
+
1298
+ var get = SC.get;
1299
+ var set = SC.set;
1300
+ var x = 0;
1301
+
1302
+ /**
1303
+ @class
1304
+
1305
+ Recognizes a multi-touch pan gesture. Pan gestures require a specified number
1306
+ of fingers to move and will record and update the center point between the
1307
+ touches.
1308
+
1309
+ For panChange events, the pan gesture recognizer includes a translation property
1310
+ which can be applied as a CSS transform directly. Translation values are hashes
1311
+ which contain an x and a y value.
1312
+
1313
+ var myview = SC.View.create({
1314
+ elementId: 'gestureTest',
1315
+ panChange: function(recognizer) {
1316
+ var translation = recognizer.get('translation');
1317
+ this.$().css('-webkit-transform','translate3d('+translate.x+'px,'+translate.y+'px,0)');
1318
+ }
1319
+ })
1320
+
1321
+ You can specify how many touches the gesture requires to start using the numberOfRequiredTouches
1322
+ property, which you can set in the panOptions hash:
1323
+
1324
+ var myview = SC.View.create({
1325
+ panOptions: {
1326
+ numberOfRequiredTouches: 3
1327
+ }
1328
+ ...
1329
+ })
1330
+
1331
+ @extends SC.Gesture
1332
+ */
1333
+ SC.PanGestureRecognizer = SC.Gesture.extend({
1334
+
1335
+ /**
1336
+ The translation value which represents the current amount of movement that has been applied
1337
+ to the view. You would normally apply this value directly to your element as a 3D
1338
+ transform.
1339
+
1340
+ @type Location
1341
+ */
1342
+ translation: null,
1343
+
1344
+ //..................................................
1345
+ // Private Methods and Properties
1346
+
1347
+ /**
1348
+ Used to measure offsets
1349
+
1350
+ @private
1351
+ @type Number
1352
+ */
1353
+ _previousLocation: null,
1354
+
1355
+ /**
1356
+ Used for rejected events
1357
+
1358
+ @private
1359
+ @type Hash
1360
+ */
1361
+ _previousTranslation: null,
1362
+
1363
+ /**
1364
+ The pixel distance that the fingers need to move before this gesture is recognized.
1365
+
1366
+ @private
1367
+ @type Number
1368
+ */
1369
+ _translationThreshold: 5,
1370
+
1371
+ init: function() {
1372
+ this._super();
1373
+ set(this, 'translation', {x:0,y:0});
1374
+ },
1375
+
1376
+ didBecomePossible: function() {
1377
+ this._previousLocation = this.centerPointForTouches(get(this.touches,'touches'));
1378
+ },
1379
+
1380
+ shouldBegin: function() {
1381
+ var previousLocation = this._previousLocation;
1382
+ var currentLocation = this.centerPointForTouches(get(this.touches,'touches'));
1383
+
1384
+ var x = previousLocation.x;
1385
+ var y = previousLocation.y;
1386
+ var x0 = currentLocation.x;
1387
+ var y0 = currentLocation.y;
1388
+
1389
+ var distance = Math.sqrt((x -= x0) * x + (y -= y0) * y);
1390
+ return distance >= this._translationThreshold;
1391
+ },
1392
+
1393
+ didChange: function() {
1394
+ var previousLocation = this._previousLocation;
1395
+ var currentLocation = this.centerPointForTouches(get(this.touches,'touches'));
1396
+ var translation = {x:currentLocation.x, y:currentLocation.y};
1397
+
1398
+ translation.x = currentLocation.x - previousLocation.x;
1399
+ translation.y = currentLocation.y - previousLocation.y;
1400
+
1401
+ this._previousTranslation = get(this, 'translation');
1402
+ set(this, 'translation', translation);
1403
+ this._previousLocation = currentLocation;
1404
+ },
1405
+
1406
+ eventWasRejected: function() {
1407
+ set(this, 'translation', this._previousTranslation);
1408
+ }
1409
+ });
1410
+
1411
+ SC.Gestures.register('pan', SC.PanGestureRecognizer);
1412
+
1413
+ })({});
1414
+
1415
+
1416
+ (function(exports) {
1417
+ // ==========================================================================
1418
+ // Project: SproutCore Runtime
1419
+ // Copyright: ©2011 Strobe Inc. and contributors.
1420
+ // License: Licensed under MIT license (see license.js)
1421
+ // ==========================================================================
1422
+
1423
+ var get = SC.get;
1424
+ var set = SC.set;
1425
+
1426
+ /**
1427
+ @class
1428
+
1429
+ Recognizes a multi-touch tap gesture. Tap gestures allow for a certain amount
1430
+ of wiggle-room between a start and end of a touch. Taps are discrete gestures
1431
+ so only tapStart() and tapEnd() will get fired on a view.
1432
+
1433
+ var myview = SC.View.create({
1434
+ elementId: 'gestureTest',
1435
+ tapStart: function(recognizer) {
1436
+ $('#gestureTest').css('background','green');
1437
+ },
1438
+
1439
+ tapEnd: function(recognizer) {
1440
+ $('#gestureTest').css('background','yellow');
1441
+ }
1442
+ })
1443
+
1444
+ You can specify how many touches the gesture requires to start using the numberOfRequiredTouches
1445
+ property, which you can set in the panOptions hash:
1446
+
1447
+ var myview = SC.View.create({
1448
+ panOptions: {
1449
+ numberOfRequiredTouches: 3
1450
+ }
1451
+ ...
1452
+ })
1453
+
1454
+ And you can also specify the number of taps required for the gesture to fire using the numberOfTaps
1455
+ property.
1456
+
1457
+ @extends SC.Gesture
1458
+ */
1459
+ SC.TapGestureRecognizer = SC.Gesture.extend({
1460
+
1461
+ /**
1462
+ The translation value which represents the current amount of movement that has been applied
1463
+ to the view. You would normally apply this value directly to your element as a 3D
1464
+ transform.
1465
+
1466
+ @type Location
1467
+ */
1468
+ numberOfTaps: 1,
1469
+
1470
+ //..................................................
1471
+ // Private Methods and Properties
1472
+
1473
+ /** @private */
1474
+ MULTITAP_DELAY: 150,
1475
+
1476
+ /** @private */
1477
+ gestureIsDiscrete: true,
1478
+
1479
+ /** @private */
1480
+ _initialLocation: null,
1481
+
1482
+ /** @private */
1483
+ _waitingInterval: null,
1484
+
1485
+ /** @private */
1486
+ _waitingForMoreTouches: false,
1487
+
1488
+ /** @private */
1489
+ _moveThreshold: 10,
1490
+
1491
+ shouldBegin: function() {
1492
+ return get(this.touches,'length') === get(this, 'numberOfRequiredTouches');
1493
+ },
1494
+
1495
+ didBegin: function() {
1496
+ this._initialLocation = this.centerPointForTouches(get(this.touches,'touches'));
1497
+
1498
+ if (get(this.touches,'length') < get(this, 'numberOfTaps')) {
1499
+ this._waitingForMoreTouches = true;
1500
+ this._waitingInterval = window.setInterval(this._intervalFired,this.MULTITAP_DELAY);
1501
+ }
1502
+ },
1503
+
1504
+ shouldEnd: function() {
1505
+ var currentLocation = this.centerPointForTouches(get(this.touches,'touches'));
1506
+
1507
+ var x = this._initialLocation.x;
1508
+ var y = this._initialLocation.y;
1509
+ var x0 = currentLocation.x;
1510
+ var y0 = currentLocation.y;
1511
+
1512
+ var distance = Math.sqrt((x -= x0) * x + (y -= y0) * y);
1513
+
1514
+ return (Math.abs(distance) < this._moveThreshold) && !this._waitingForMoreTouches;
1515
+ },
1516
+
1517
+ didEnd: function() {
1518
+ this._initialLocation = null;
1519
+ },
1520
+
1521
+ didCancel: function() {
1522
+ this._initialLocation = null;
1523
+ },
1524
+
1525
+ _intervalFired: function() {
1526
+ window.clearInterval(this._waitingInterval);
1527
+ _waitingForMoreTouches = false;
1528
+ }
1529
+ });
1530
+
1531
+ SC.Gestures.register('tap', SC.TapGestureRecognizer);
1532
+
1533
+ })({});
1534
+
1535
+
1536
+ (function(exports) {
1537
+
1538
+
1539
+
1540
+ })({});
1541
+
1542
+
1543
+ (function(exports) {
1544
+ // ==========================================================================
1545
+ // Project: SproutCore Runtime
1546
+ // Copyright: ©2011 Strobe Inc. and contributors.
1547
+ // License: Licensed under MIT license (see license.js)
1548
+ // ==========================================================================
1549
+
1550
+ var get = SC.get;
1551
+ var set = SC.set;
1552
+
1553
+ /**
1554
+ @class
1555
+
1556
+ Extends SC.View by making the init method gesture-aware.
1557
+
1558
+ @extends SC.Object
1559
+ */
1560
+ SC.View.reopen(
1561
+ /** @scope SC.View.prototype */{
1562
+
1563
+ /**
1564
+ The SC.GestureManager instance which will manager the gestures of the view.
1565
+ This object is automatically created and set at init-time.
1566
+
1567
+ @default null
1568
+ @type Array
1569
+ */
1570
+ eventManager: null,
1571
+
1572
+ /**
1573
+ Inspects the properties on the view instance and create gestures if they're
1574
+ used.
1575
+ */
1576
+ init: function() {
1577
+ this._super();
1578
+
1579
+ var knownGestures = SC.Gestures.knownGestures();
1580
+ var eventManager = get(this, 'eventManager');
1581
+
1582
+ if (knownGestures && !eventManager) {
1583
+ var gestures = [];
1584
+
1585
+ for (var gesture in knownGestures) {
1586
+ if (this[gesture+'Start'] || this[gesture+'Change'] || this[gesture+'End']) {
1587
+
1588
+ var optionsHash;
1589
+ if (this[gesture+'Options'] !== undefined && typeof this[gesture+'Options'] === 'object') {
1590
+ optionsHash = this[gesture+'Options'];
1591
+ } else {
1592
+ optionsHash = {};
1593
+ }
1594
+
1595
+ optionsHash.name = gesture;
1596
+ optionsHash.view = this;
1597
+
1598
+ gestures.push(knownGestures[gesture].create(optionsHash));
1599
+ }
1600
+ }
1601
+
1602
+ var manager = SC.GestureManager.create({
1603
+ gestures: gestures
1604
+ });
1605
+
1606
+ set(this, 'eventManager', manager);
1607
+
1608
+ }
1609
+ }
1610
+
1611
+ });
1612
+
1613
+
1614
+ })({});
1615
+
1616
+
1617
+ (function(exports) {
1618
+
1619
+
1620
+
1621
+
1622
+ })({});
1623
+
1624
+
1625
+ (function(exports) {
1626
+
1627
+
1628
+
1629
+ })({});