sproutcore 1.11.0.rc3 → 1.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +6 -14
  2. data/CHANGELOG +5 -0
  3. data/VERSION.yml +1 -1
  4. data/lib/frameworks/sproutcore/Buildfile +3 -2
  5. data/lib/frameworks/sproutcore/CHANGELOG.md +59 -10
  6. data/lib/frameworks/sproutcore/apps/showcase/resources/main_page.js +1 -0
  7. data/lib/frameworks/sproutcore/apps/showcase/resources/stylesheet.css +9 -4
  8. data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/manipulation.js +1 -1
  9. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/event.js +0 -10
  10. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/locale.js +1 -1
  11. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/root_responder.js +10 -45
  12. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/selection_set.js +3 -3
  13. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/touch.js +76 -0
  14. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/system/{touch.js → touch_test.js} +64 -0
  15. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/pane/append_remove.js +1 -1
  16. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/pane/design_mode_test.js +61 -24
  17. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/createChildViews.js +1 -2
  18. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/destroy.js +0 -3
  19. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/{layoutStyle.js → layout_style_test.js} +4 -4
  20. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layout_test.js +602 -0
  21. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/viewDidResize.js +0 -23
  22. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view.js +18 -17
  23. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/animation.js +5 -5
  24. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/design_mode.js +64 -24
  25. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout.js +904 -871
  26. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout_style.js +1 -3
  27. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/statechart.js +40 -24
  28. data/lib/frameworks/sproutcore/frameworks/datastore/data_sources/fixtures.js +2 -2
  29. data/lib/frameworks/sproutcore/frameworks/datastore/models/record.js +5 -5
  30. data/lib/frameworks/sproutcore/frameworks/datastore/system/nested_store.js +14 -14
  31. data/lib/frameworks/sproutcore/frameworks/datastore/system/query.js +1 -1
  32. data/lib/frameworks/sproutcore/frameworks/datastore/system/record_array.js +2 -2
  33. data/lib/frameworks/sproutcore/frameworks/datastore/system/store.js +36 -33
  34. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/record/writeAttribute.js +1 -1
  35. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/nested_store/autonomous_dataSourceCallbacks.js +6 -6
  36. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/commitChangesFromNestedStore.js +1 -1
  37. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/commitRecord.js +1 -1
  38. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/dataSourceCallbacks.js +7 -7
  39. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/destroyRecord.js +2 -2
  40. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/recordDidChange.js +2 -2
  41. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/retrieveRecord.js +4 -4
  42. data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/system/datetime.js +11 -11
  43. data/lib/frameworks/sproutcore/frameworks/designer/coders/object.js +1 -1
  44. data/lib/frameworks/sproutcore/frameworks/desktop/panes/menu.js +30 -1
  45. data/lib/frameworks/sproutcore/frameworks/desktop/panes/picker.js +12 -1
  46. data/lib/frameworks/sproutcore/frameworks/desktop/views/menu_scroll.js +5 -3
  47. data/lib/frameworks/sproutcore/frameworks/desktop/views/menu_scroller_view.js +0 -36
  48. data/lib/frameworks/sproutcore/frameworks/desktop/views/scroll_view.js +3 -3
  49. data/lib/frameworks/sproutcore/frameworks/desktop/views/segmented.js +1 -1
  50. data/lib/frameworks/sproutcore/frameworks/desktop/views/static_content.js +1 -1
  51. data/lib/frameworks/sproutcore/frameworks/foundation/gestures/pinch_gesture.js +286 -0
  52. data/lib/frameworks/sproutcore/frameworks/foundation/gestures/swipe_gesture.js +449 -0
  53. data/lib/frameworks/sproutcore/frameworks/foundation/gestures/tap_gesture.js +259 -0
  54. data/lib/frameworks/sproutcore/frameworks/foundation/mixins/gesturable.js +218 -30
  55. data/lib/frameworks/sproutcore/frameworks/foundation/system/gesture.js +259 -158
  56. data/lib/frameworks/sproutcore/frameworks/foundation/system/string.js +58 -50
  57. data/lib/frameworks/sproutcore/frameworks/foundation/system/utils/string_measurement.js +1 -1
  58. data/lib/frameworks/sproutcore/frameworks/foundation/tests/gestures/pinch_gesture_test.js +321 -0
  59. data/lib/frameworks/sproutcore/frameworks/foundation/tests/gestures/swipe_gesture_test.js +154 -0
  60. data/lib/frameworks/sproutcore/frameworks/foundation/tests/gestures/tap_gesture_test.js +55 -0
  61. data/lib/frameworks/sproutcore/frameworks/foundation/tests/mixins/gesturable_test.js +233 -0
  62. data/lib/frameworks/sproutcore/frameworks/foundation/tests/mixins/staticLayout.js +2 -2
  63. data/lib/frameworks/sproutcore/frameworks/foundation/tests/system/gesture_test.js +254 -0
  64. data/lib/frameworks/sproutcore/frameworks/foundation/views/container.js +1 -0
  65. data/lib/frameworks/sproutcore/frameworks/foundation/views/text_field.js +1 -1
  66. data/lib/frameworks/sproutcore/frameworks/legacy/object_keys_polyfill.js +51 -0
  67. data/lib/frameworks/sproutcore/frameworks/{core_foundation/system/req_anim_frame.js → legacy/request_animation_frame_polyfill.js} +10 -3
  68. data/lib/frameworks/sproutcore/frameworks/media/views/audio.js +19 -25
  69. data/lib/frameworks/sproutcore/frameworks/media/views/controls.js +7 -7
  70. data/lib/frameworks/sproutcore/frameworks/media/views/mini_controls.js +3 -3
  71. data/lib/frameworks/sproutcore/frameworks/media/views/simple_controls.js +9 -9
  72. data/lib/frameworks/sproutcore/frameworks/runtime/core.js +2 -2
  73. data/lib/frameworks/sproutcore/frameworks/runtime/debug/test_suites/array/insertAt.js +2 -2
  74. data/lib/frameworks/sproutcore/frameworks/runtime/debug/test_suites/array/removeAt.js +1 -1
  75. data/lib/frameworks/sproutcore/frameworks/runtime/ext/array.js +1 -1
  76. data/lib/frameworks/sproutcore/frameworks/runtime/mixins/array.js +2 -2
  77. data/lib/frameworks/sproutcore/frameworks/runtime/mixins/freezable.js +2 -2
  78. data/lib/frameworks/sproutcore/frameworks/runtime/mixins/observable.js +66 -5
  79. data/lib/frameworks/sproutcore/frameworks/runtime/mixins/tree.js +44 -0
  80. data/lib/frameworks/sproutcore/frameworks/runtime/private/observer_queue.js +4 -1
  81. data/lib/frameworks/sproutcore/frameworks/runtime/system/binding.js +0 -25
  82. data/lib/frameworks/sproutcore/frameworks/runtime/system/enumerator.js +1 -1
  83. data/lib/frameworks/sproutcore/frameworks/runtime/system/error.js +67 -15
  84. data/lib/frameworks/sproutcore/frameworks/runtime/system/index_set.js +6 -11
  85. data/lib/frameworks/sproutcore/frameworks/runtime/system/set.js +6 -6
  86. data/lib/frameworks/sproutcore/frameworks/runtime/system/string.js +1 -1
  87. data/lib/frameworks/sproutcore/frameworks/runtime/tests/mixins/observable/{observable.js → observable_test.js} +110 -16
  88. data/lib/frameworks/sproutcore/frameworks/runtime/tests/system/error.js +21 -0
  89. data/lib/frameworks/sproutcore/frameworks/statechart/system/statechart.js +2 -2
  90. data/lib/frameworks/sproutcore/frameworks/template_view/ext/handlebars.js +1 -1
  91. data/lib/frameworks/sproutcore/frameworks/testing/system/plan.js +1 -1
  92. data/lib/sproutcore/render_engines/haml.rb +1 -1
  93. metadata +610 -604
  94. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layout.js +0 -210
  95. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutDidChange.js +0 -275
  96. data/lib/frameworks/sproutcore/frameworks/foundation/gestures/pinch.js +0 -119
  97. data/lib/frameworks/sproutcore/frameworks/foundation/gestures/swipe.js +0 -234
  98. data/lib/frameworks/sproutcore/frameworks/foundation/gestures/tap.js +0 -157
@@ -0,0 +1,259 @@
1
+ // ==========================================================================
2
+ // Project: SproutCore - JavaScript Application Framework
3
+ // Copyright: ©2010 Strobe Inc. All rights reserved.
4
+ // Author: Peter Wagenet
5
+ // License: Licensed under MIT license (see license.js)
6
+ // ==========================================================================
7
+
8
+ sc_require("system/gesture");
9
+
10
+ /**
11
+ ## What is a "tap"?
12
+
13
+ A tap is a touch that starts and ends in a short amount of time without moving along either axis.
14
+ A tap may consist of more than one touch, provided that the touches start and end together. The time
15
+ allowed for touches to start and end together is defined by `touchUnityDelay`.
16
+ Again, to be considered a tap, there should be very little movement of any touches on either axis
17
+ while still touching. The amount of movement allowed is defined by `tapWiggle`.
18
+
19
+ @class
20
+ @extends SC.Gesture
21
+ */
22
+ SC.TapGesture = SC.Gesture.extend(
23
+ /** @scope SC.TapGesture.prototype */{
24
+
25
+ /** @private The time that the first touch started at. */
26
+ _sc_firstTouchAddedAt: null,
27
+
28
+ /** @private The time that the first touch ended at. */
29
+ _sc_firstTouchEndedAt: null,
30
+
31
+ /** @private A flag used to track when the touch was long enough to register tapStart (and tapEnd). */
32
+ _sc_isTapping: false,
33
+
34
+ /** @private The number of touches in the current tap. */
35
+ _sc_numberOfTouches: 0,
36
+
37
+ /** @private A timer started after the first touch starts. */
38
+ _sc_tapStartTimer: null,
39
+
40
+ /**
41
+ @type String
42
+ @default "tap"
43
+ @readOnly
44
+ */
45
+ name: "tap",
46
+
47
+ /**
48
+ The amount of time in milliseconds between when the first touch starts and the last touch ends
49
+ that should be considered a short enough time to constitute a tap.
50
+
51
+ @type Number
52
+ @default 250
53
+ */
54
+ tapLengthDelay: 250,
55
+
56
+ /**
57
+ The amount of time in milliseconds after the first touch starts at which, *if the tap hasn't
58
+ ended in that time*, the `tapStart` event should trigger.
59
+
60
+ Because taps may be very short or because movement of the touch may invalidate a tap gesture
61
+ entirely, you generally won't want to update the state of the view immediately when a touch
62
+ starts.
63
+
64
+ @type Number
65
+ @default 150
66
+ */
67
+ tapStartDelay: 150,
68
+
69
+ /**
70
+ The number of pixels that a touch may move before it will no longer be considered a tap. If any
71
+ of the touches move more than this amount, the gesture will give up.
72
+
73
+ @type Number
74
+ @default 10
75
+ */
76
+ tapWiggle: 10,
77
+
78
+ /**
79
+ The number of milliseconds that touches must start and end together in in order to be considered a
80
+ tap. If the touches start too far apart in time or end too far apart in time based on this
81
+ value, the gesture will give up.
82
+
83
+ @type Number
84
+ @default 75
85
+ */
86
+ touchUnityDelay: 75,
87
+
88
+ /** @private Calculates the distance a touch has moved. */
89
+ _sc_calculateDragDistance: function (touch) {
90
+ return Math.sqrt(Math.pow(touch.pageX - touch.startX, 2) + Math.pow(touch.pageY - touch.startY, 2));
91
+ },
92
+
93
+ /** @private Cleans up the touch session. */
94
+ _sc_cleanUpTouchSession: function (wasCancelled) {
95
+ if (this._sc_isTapping) {
96
+ // Trigger the gesture, 'tapCancelled'.
97
+ if (wasCancelled) {
98
+ this.cancel();
99
+
100
+ // Trigger the gesture, 'tapEnd'.
101
+ } else {
102
+ this.end();
103
+ }
104
+
105
+ this._sc_isTapping = false;
106
+ }
107
+
108
+ // Clean up.
109
+ this._sc_tapStartTimer.invalidate();
110
+ this._sc_numberOfTouches = 0;
111
+ this._sc_tapStartTimer = this._sc_firstTouchAddedAt = this._sc_firstTouchEndedAt = null;
112
+ },
113
+
114
+ /** @private Triggers the tapStart event. Should *not* be reachable unless the tap is still valid. */
115
+ _sc_triggerTapStart: function () {
116
+ // Trigger the gesture, 'tapStart'.
117
+ this.start();
118
+
119
+ this._sc_isTapping = true;
120
+ },
121
+
122
+ /**
123
+ The tap gesture only remains interested in a touch session as long as none of the touches have
124
+ started too long after the first touch (value of `touchUnityDelay`). Once any touch has started
125
+ too late, the tap gesture gives up for the entire touch session and won't attempt to re-engage
126
+ (i.e. even if an extra touch "taps" cleanly in the same touch session, it won't trigger any
127
+ further tap callbacks).
128
+
129
+ @param {SC.Touch} touch The touch to be added to the session.
130
+ @param {Array} touchesInSession The touches already in the session.
131
+ @returns {Boolean} True as long as the new touch doesn't start too late after the first touch.
132
+ @see SC.Gesture#touchAddedToSession
133
+ */
134
+ touchAddedToSession: function (touch, touchesInSession) {
135
+ var stillInterestedInSession,
136
+ delay;
137
+
138
+ // If the new touch came in too late after the first touch was added.
139
+ delay = Date.now() - this._sc_firstTouchAddedAt;
140
+ stillInterestedInSession = delay < this.get('touchUnityDelay');
141
+
142
+ return stillInterestedInSession;
143
+ },
144
+
145
+ /**
146
+ If a touch cancels, the tap doesn't care and remains interested.
147
+
148
+ @param {SC.Touch} touch The touch to be removed from the session.
149
+ @param {Array} touchesInSession The touches in the session.
150
+ @returns {Boolean} True
151
+ @see SC.Gesture#touchCancelledInSession
152
+ */
153
+ touchCancelledInSession: function (touch, touchesInSession) {
154
+ return true;
155
+ },
156
+
157
+ /**
158
+ The tap gesture only remains interested in a touch session as long as none of the touches have
159
+ ended too long after the first touch ends (value of `touchUnityDelay`). Once any touch has ended
160
+ too late, the tap gesture gives up for the entire touch session and won't attempt to re-engage
161
+ (i.e. even if an extra touch "taps" cleanly in the same touch session, it won't trigger any
162
+ further tap callbacks).
163
+
164
+ @param {SC.Touch} touch The touch to be removed from the session.
165
+ @param {Array} touchesInSession The touches in the session.
166
+ @returns {Boolean} True if it is the first touch to end or a subsequent touch that ends not too long after the first touch ended.
167
+ @see SC.Gesture#touchEndedInSession
168
+ */
169
+ touchEndedInSession: function (touch, touchesInSession) {
170
+ var stillInterestedInSession;
171
+
172
+ // Increment the number of touches in the tap.
173
+ this._sc_numberOfTouches += 1;
174
+
175
+ // If it's the first touch to end, remain interested unless tapLengthDelay has passed.
176
+ if (this._sc_firstTouchEndedAt === null) {
177
+ this._sc_firstTouchEndedAt = Date.now();
178
+ stillInterestedInSession = this._sc_firstTouchEndedAt - this._sc_firstTouchAddedAt < this.get('tapLengthDelay');
179
+
180
+ // If the touch ended too late after the first touch ended, give up entirely.
181
+ } else {
182
+ stillInterestedInSession = Date.now() - this._sc_firstTouchEndedAt < this.get('touchUnityDelay');
183
+ }
184
+
185
+ return stillInterestedInSession;
186
+ },
187
+
188
+ /**
189
+ The tap gesture only remains interested in a touch session as long as none of the touches have
190
+ moved too far (value of `tapWiggle`). Once any touch has moved too far, the tap gesture gives
191
+ up for the entire touch session and won't attempt to re-engage (i.e. even if an extra touch
192
+ "taps" cleanly in the same touch session, it won't trigger any further tap callbacks).
193
+
194
+ @param {Array} touchesInSession All touches in the session.
195
+ @returns {Boolean} True as long as none of the touches have moved too far to be a clean tap.
196
+ @see SC.Gesture#touchesMovedInSession
197
+ */
198
+ touchesMovedInSession: function (touchesInSession) {
199
+ var stillInterestedInSession = true;
200
+
201
+ for (var i = 0, len = touchesInSession.length; i < len; i++) {
202
+ var touch = touchesInSession[i],
203
+ movedTooFar = this._sc_calculateDragDistance(touch) > this.get('tapWiggle');
204
+
205
+ // If any touch has gone too far, we don't want to consider any further tap actions for this
206
+ // session. No need to continue.
207
+ if (movedTooFar) {
208
+ stillInterestedInSession = false;
209
+ break;
210
+ }
211
+ }
212
+
213
+ return stillInterestedInSession;
214
+ },
215
+
216
+ /**
217
+ Cleans up all touch session variables.
218
+
219
+ @returns {void}
220
+ @see SC.Gesture#touchSessionCancelled
221
+ */
222
+ touchSessionCancelled: function () {
223
+ // Clean up (will fire tapCancelled if _sc_isTapping is true).
224
+ this._sc_cleanUpTouchSession(true);
225
+ },
226
+
227
+ /**
228
+ Cleans up all touch session variables and triggers the gesture.
229
+
230
+ @returns {void}
231
+ @see SC.Gesture#touchSessionEnded
232
+ */
233
+ touchSessionEnded: function () {
234
+ // Trigger the gesture, 'tap'.
235
+ this.trigger(this._sc_numberOfTouches);
236
+
237
+ // Clean up (will fire tapEnd if _sc_isTapping is true).
238
+ this._sc_cleanUpTouchSession(false);
239
+ },
240
+
241
+ /**
242
+ Registers when the first touch started.
243
+
244
+ @param {SC.Touch} touch The touch that started the session.
245
+ @returns {void}
246
+ @see SC.Gesture#touchSessionStarted
247
+ */
248
+ touchSessionStarted: function (touch) {
249
+ // Initialize.
250
+ this._sc_firstTouchAddedAt = Date.now();
251
+
252
+ this._sc_tapStartTimer = SC.Timer.schedule({
253
+ target: this,
254
+ action: this._sc_triggerTapStart,
255
+ interval: this.get('tapStartDelay')
256
+ });
257
+ }
258
+
259
+ });
@@ -8,8 +8,7 @@
8
8
  /**
9
9
  @namespace
10
10
 
11
- You can mix in SC.Gesturable to your views to add some support for recognizing
12
- gestures.
11
+ You can mix in SC.Gesturable to your views to add some support for recognizing gestures.
13
12
 
14
13
  SproutCore views have built-in touch events. However, sometimes you may want
15
14
  to recognize gestures like tap, pinch, swipe, etc. This becomes tedious if you
@@ -76,9 +75,33 @@
76
75
  })
77
76
 
78
77
  @extends SC.ObjectMixinProtocol
78
+ @extends SC.ResponderProtocol
79
79
  */
80
80
  SC.Gesturable = {
81
81
 
82
+ /** @private An array of all gestures currently interested in the touch session.
83
+
84
+ @type Array
85
+ @default null
86
+ */
87
+ _sc_interestedGestures: null,
88
+
89
+ /** @private An array of the touches that are currently active in a touch session.
90
+
91
+ @type Array
92
+ @default null
93
+ */
94
+ _sc_touchesInSession: null,
95
+
96
+ /**
97
+ Gestures need to understand multiple touches.
98
+
99
+ @type Boolean
100
+ @default true
101
+ @see SC.View#acceptsMultitouch
102
+ */
103
+ acceptsMultitouch: true,
104
+
82
105
  /**
83
106
  @type Array
84
107
  @default ['gestures']
@@ -114,6 +137,59 @@ SC.Gesturable = {
114
137
  */
115
138
  gestures: null,
116
139
 
140
+ /** @private Shared method for finishing a touch.
141
+
142
+ @param {SC.Touch} touch The touch that ended or cancelled.
143
+ @param {Boolean} wasCancelled Whether the touch was cancelled or not (i.e. ended normally).
144
+ */
145
+ _sc_gestureTouchFinish: function (touch, wasCancelled) {
146
+ var touchesInSession = this._sc_touchesInSession,
147
+ touchIndexInSession = touchesInSession.indexOf(touch);
148
+
149
+ // Decrement our list of touches that are being acted upon.
150
+ touchesInSession.replace(touchIndexInSession, 1);
151
+
152
+ var gestures = this._sc_interestedGestures,
153
+ idx,
154
+ gesture;
155
+
156
+ // Loop through the gestures in reverse, as the list may be mutated.
157
+ for (idx = gestures.length - 1; idx >= 0; idx--) {
158
+ var isInterested;
159
+
160
+ gesture = gestures[idx];
161
+
162
+ if (wasCancelled) {
163
+ isInterested = gesture.touchCancelledInSession(touch, touchesInSession);
164
+ } else {
165
+ isInterested = gesture.touchEndedInSession(touch, touchesInSession);
166
+ }
167
+
168
+ // If the gesture is no longer interested in *any* touches for this session, remove it.
169
+ if (!isInterested) {
170
+ // Tell the gesture that the touch session has ended for it.
171
+ gesture.touchSessionCancelled();
172
+
173
+ gestures.replace(idx, 1);
174
+ }
175
+ }
176
+
177
+ // Once there are no more touches in the session, reset the interested gestures.
178
+ if (touchesInSession.length === 0) {
179
+ // Notify all remaining interested gestures that the touch session has finished cleanly.
180
+ var len;
181
+
182
+ for (idx = 0, len = gestures.length; idx < len; idx++) {
183
+ gesture = gestures[idx];
184
+
185
+ gesture.touchSessionEnded();
186
+ }
187
+
188
+ // Clear out the current cache of interested gestures for the session.
189
+ this._sc_interestedGestures.length = 0;
190
+ }
191
+ },
192
+
117
193
  /**
118
194
  When SC.Gesturable initializes, any gestures named on the view are instantiated.
119
195
 
@@ -178,9 +254,13 @@ SC.Gesturable = {
178
254
  at any time. This allows you to avoid passing control until _after_ you
179
255
  have determined your own touchStart, touchesDragged, and touchEnd methods
180
256
  are not going to handle it.
257
+
258
+ @param {SC.Touch} touch The touch that started.
259
+ @returns {Boolean} Whether the touch should be claimed by the view or not.
260
+ @see SC.ResponderProtocol#touchStart
181
261
  */
182
262
  touchStart: function(touch) {
183
- this.gestureTouchStart(touch);
263
+ return this.gestureTouchStart(touch);
184
264
  },
185
265
 
186
266
  /**
@@ -189,6 +269,8 @@ SC.Gesturable = {
189
269
  If you override touchesDragged, you will need to call gestureTouchesDragged
190
270
  (at least for any touches you called gestureTouchStart for in touchStart) to
191
271
  allow the gesture system to update.
272
+
273
+ @see SC.ResponderProtocol#touchesDragged
192
274
  */
193
275
  touchesDragged: function(evt, touches) {
194
276
  this.gestureTouchesDragged(evt, touches);
@@ -198,58 +280,164 @@ SC.Gesturable = {
198
280
  Tells the gesture recognizing code about a touch ending.
199
281
 
200
282
  If you override touchEnd, you will need to call gestureTouchEnd
201
- for any touches you called touchStart for.
283
+ for any touches you called gestureTouchStart for in touchStart (if overridden).
284
+
285
+ @param {SC.Touch} touch The touch that ended.
286
+ @see SC.ResponderProtocol#touchEnd
202
287
  */
203
288
  touchEnd: function(touch) {
204
289
  this.gestureTouchEnd(touch);
205
290
  },
206
291
 
207
292
  /**
208
- Tells the gesture recognizing system about a new touch.
293
+ Tells the gesture recognizing code about a touch cancelling.
209
294
 
210
- This informs all gestures that a new touch, "unassigned" to any gesture,
211
- has been located. Later, each gesture has an opportunity to claim the touch.
295
+ If you override touchCancelled, you will need to call gestureTouchCancelled
296
+ for any touches you called gestureTouchStart for in touchStart (if overridden).
212
297
 
213
- Once they have claimed the touch, further events will go _directly_ to them—
214
- this view will cease receiving the touchesDragged and will not receive a touchEnd.
298
+ @param {SC.Touch} touch The touch that cancelled.
299
+ @see SC.ResponderProtocol#touchCancelled
215
300
  */
216
- gestureTouchStart: function(touch) {
217
- touch.isInteresting = 0;
301
+ touchCancelled: function (touch) {
302
+ this.gestureTouchCancelled(touch);
303
+ },
218
304
 
219
- var gestures = this.get("gestures"), idx, len = gestures.length, g;
220
- for (idx = 0; idx < len; idx++) {
221
- g = gestures[idx];
222
- g.unassignedTouchDidStart(touch);
305
+ /**
306
+ Called by a gesture that has lost interest in the entire touch session, likely due to too much
307
+ time having passed since `gestureTouchStart` or `gestureTouchesDragged` having been called.
308
+
309
+ Simply removes the gesture from the list of interested gestures and calls
310
+ `touchSessionCancelled` on the gesture.
311
+ */
312
+ gestureLostInterest: function (gesture) {
313
+ var gestures = this._sc_interestedGestures,
314
+ gestureIndex = gestures.indexOf(gesture);
315
+
316
+ // Remove the gesture.
317
+ if (gestureIndex >= 0) {
318
+ gesture.touchSessionCancelled();
319
+
320
+ gestures.replace(gestureIndex, 1);
223
321
  }
224
322
  },
225
323
 
226
324
  /**
227
- Tells the gesture recognition system that some touches have moved.
325
+ Tells the gesture recognizing system about a new touch. This notifies all gestures of a new
326
+ touch session starting (if there were no previous touches) or notifies all interested gestures
327
+ that a touch has been added.
228
328
 
229
- This informs all gestures that these touches have changed. All such touches
230
- are "unassigned" because all "assigned" touches already get sent directly
231
- to the gesture.
329
+ As touches are added beyond the first touch, gestures may "lose interest" in the touch session.
330
+ For example, a gesture may explicitly want only a single touch and if a second touch appears,
331
+ the gesture may not want any further updates on this touch session (even if the second touch
332
+ ends again).
333
+
334
+ @param {SC.Touch} touch The touch that started.
335
+ @returns {Boolean} Whether any gesture is interested in the touch or not.
232
336
  */
233
- gestureTouchesDragged: function(evt, touches) {
234
- var gestures = this.get("gestures"), idx, len = gestures.length, g;
235
- for (idx = 0; idx < len; idx++) {
236
- g = gestures[idx];
237
- g.unassignedTouchesDidChange(evt, touches);
337
+ gestureTouchStart: function (touch) {
338
+ var interestedGestures = this._sc_interestedGestures,
339
+ touchesInSession = this._sc_touchesInSession,
340
+ claimedTouch = false,
341
+ idx;
342
+
343
+ // Instantiate once.
344
+ if (touchesInSession === null) {
345
+ touchesInSession = this._sc_touchesInSession = [];
346
+ interestedGestures = this._sc_interestedGestures = [];
347
+ }
348
+
349
+ // When there are no touches in the session, check all gestures.
350
+ if (touchesInSession.length === 0) {
351
+ var gestures = this.get("gestures"),
352
+ len;
353
+
354
+ for (idx = 0, len = gestures.length; idx < len; idx++) {
355
+ var gesture = gestures[idx];
356
+
357
+ gesture.touchSessionStarted(touch);
358
+ interestedGestures.push(gesture);
359
+ }
360
+
361
+ // Keep this touch.
362
+ claimedTouch = true;
363
+
364
+ // Only check gestures that are interested.
365
+ } else {
366
+ // Loop through the gestures in reverse, as the list may be mutated.
367
+ for (idx = interestedGestures.length - 1; idx >= 0; idx--) {
368
+ var interestedGesture = interestedGestures[idx],
369
+ isInterested;
370
+
371
+ // Keep only the gestures still interested in the touch.
372
+ isInterested = interestedGesture.touchAddedToSession(touch, touchesInSession);
373
+
374
+ if (isInterested) {
375
+ // Keep this touch.
376
+ claimedTouch = true;
377
+ } else {
378
+ // Tell the gesture that the touch session has ended for it.
379
+ interestedGesture.touchSessionCancelled();
380
+
381
+ interestedGestures.replace(idx, 1);
382
+ }
383
+ }
384
+ }
385
+
386
+ // If any gesture is interested in the new touch. Add it to the list of touches in the session.
387
+ if (claimedTouch) {
388
+ touchesInSession.push(touch);
238
389
  }
390
+
391
+ return claimedTouch;
239
392
  },
240
393
 
241
394
  /**
242
- Tells the gesture recognition system that a touch have ended.
395
+ Tells the gesture recognition system that touches have moved.
396
+
397
+ @param {SC.Event} evt The touch event.
398
+ @param {Array} touches The touches previously claimed by this view.
399
+ @returns {void}
400
+ */
401
+ gestureTouchesDragged: function (evt, touches) {
402
+ var gestures = this._sc_interestedGestures,
403
+ touchesInSession = this._sc_touchesInSession;
404
+
405
+ // Loop through the gestures in reverse, as the list may be mutated.
406
+ for (var i = gestures.length - 1; i >= 0; i--) {
407
+ var gesture = gestures[i],
408
+ isInterested = gesture.touchesMovedInSession(touchesInSession);
409
+
410
+ // If the gesture is no longer interested in *any* touches for this session, remove it.
411
+ if (!isInterested) {
412
+ // Tell the gesture that the touch session has ended for it.
413
+ gesture.touchSessionCancelled();
414
+
415
+ gestures.replace(i, 1);
416
+
417
+ // TODO: When there are no more interested gestures? Do what with the touches? Anything?
418
+ }
419
+ }
420
+ },
421
+
422
+ /**
423
+ Tells the gesture recognition system that an unassigned touch has ended.
243
424
 
244
425
  This informs all of the gestures that the touch ended. The touch is
245
426
  an unassigned touch as, if it were assigned to a gesture, it would have
246
427
  been sent directly to the gesture, bypassing this view.
247
428
  */
248
429
  gestureTouchEnd: function(touch) {
249
- var gestures = this.get("gestures"), idx, len = gestures.length, g;
250
- for (idx = 0; idx < len; idx++) {
251
- g = gestures[idx];
252
- g.unassignedTouchDidEnd(touch);
253
- }
430
+ this._sc_gestureTouchFinish(touch, false);
431
+ },
432
+
433
+ /**
434
+ Tells the gesture recognition system that an unassigned touch has cancelled.
435
+
436
+ This informs all of the gestures that the touch cancelled. The touch is
437
+ an unassigned touch as, if it were assigned to a gesture, it would have
438
+ been sent directly to the gesture, bypassing this view.
439
+ */
440
+ gestureTouchCancelled: function(touch) {
441
+ this._sc_gestureTouchFinish(touch, true);
254
442
  }
255
443
  };