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.
- checksums.yaml +6 -14
- data/CHANGELOG +5 -0
- data/VERSION.yml +1 -1
- data/lib/frameworks/sproutcore/Buildfile +3 -2
- data/lib/frameworks/sproutcore/CHANGELOG.md +59 -10
- data/lib/frameworks/sproutcore/apps/showcase/resources/main_page.js +1 -0
- data/lib/frameworks/sproutcore/apps/showcase/resources/stylesheet.css +9 -4
- data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/manipulation.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/core_foundation/system/event.js +0 -10
- data/lib/frameworks/sproutcore/frameworks/core_foundation/system/locale.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/core_foundation/system/root_responder.js +10 -45
- data/lib/frameworks/sproutcore/frameworks/core_foundation/system/selection_set.js +3 -3
- data/lib/frameworks/sproutcore/frameworks/core_foundation/system/touch.js +76 -0
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/system/{touch.js → touch_test.js} +64 -0
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/pane/append_remove.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/pane/design_mode_test.js +61 -24
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/createChildViews.js +1 -2
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/destroy.js +0 -3
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/{layoutStyle.js → layout_style_test.js} +4 -4
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layout_test.js +602 -0
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/viewDidResize.js +0 -23
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view.js +18 -17
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/animation.js +5 -5
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/design_mode.js +64 -24
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout.js +904 -871
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout_style.js +1 -3
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/statechart.js +40 -24
- data/lib/frameworks/sproutcore/frameworks/datastore/data_sources/fixtures.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/datastore/models/record.js +5 -5
- data/lib/frameworks/sproutcore/frameworks/datastore/system/nested_store.js +14 -14
- data/lib/frameworks/sproutcore/frameworks/datastore/system/query.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/datastore/system/record_array.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/datastore/system/store.js +36 -33
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/record/writeAttribute.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/nested_store/autonomous_dataSourceCallbacks.js +6 -6
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/commitChangesFromNestedStore.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/commitRecord.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/dataSourceCallbacks.js +7 -7
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/destroyRecord.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/recordDidChange.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/retrieveRecord.js +4 -4
- data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/system/datetime.js +11 -11
- data/lib/frameworks/sproutcore/frameworks/designer/coders/object.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/desktop/panes/menu.js +30 -1
- data/lib/frameworks/sproutcore/frameworks/desktop/panes/picker.js +12 -1
- data/lib/frameworks/sproutcore/frameworks/desktop/views/menu_scroll.js +5 -3
- data/lib/frameworks/sproutcore/frameworks/desktop/views/menu_scroller_view.js +0 -36
- data/lib/frameworks/sproutcore/frameworks/desktop/views/scroll_view.js +3 -3
- data/lib/frameworks/sproutcore/frameworks/desktop/views/segmented.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/desktop/views/static_content.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/foundation/gestures/pinch_gesture.js +286 -0
- data/lib/frameworks/sproutcore/frameworks/foundation/gestures/swipe_gesture.js +449 -0
- data/lib/frameworks/sproutcore/frameworks/foundation/gestures/tap_gesture.js +259 -0
- data/lib/frameworks/sproutcore/frameworks/foundation/mixins/gesturable.js +218 -30
- data/lib/frameworks/sproutcore/frameworks/foundation/system/gesture.js +259 -158
- data/lib/frameworks/sproutcore/frameworks/foundation/system/string.js +58 -50
- data/lib/frameworks/sproutcore/frameworks/foundation/system/utils/string_measurement.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/foundation/tests/gestures/pinch_gesture_test.js +321 -0
- data/lib/frameworks/sproutcore/frameworks/foundation/tests/gestures/swipe_gesture_test.js +154 -0
- data/lib/frameworks/sproutcore/frameworks/foundation/tests/gestures/tap_gesture_test.js +55 -0
- data/lib/frameworks/sproutcore/frameworks/foundation/tests/mixins/gesturable_test.js +233 -0
- data/lib/frameworks/sproutcore/frameworks/foundation/tests/mixins/staticLayout.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/foundation/tests/system/gesture_test.js +254 -0
- data/lib/frameworks/sproutcore/frameworks/foundation/views/container.js +1 -0
- data/lib/frameworks/sproutcore/frameworks/foundation/views/text_field.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/legacy/object_keys_polyfill.js +51 -0
- data/lib/frameworks/sproutcore/frameworks/{core_foundation/system/req_anim_frame.js → legacy/request_animation_frame_polyfill.js} +10 -3
- data/lib/frameworks/sproutcore/frameworks/media/views/audio.js +19 -25
- data/lib/frameworks/sproutcore/frameworks/media/views/controls.js +7 -7
- data/lib/frameworks/sproutcore/frameworks/media/views/mini_controls.js +3 -3
- data/lib/frameworks/sproutcore/frameworks/media/views/simple_controls.js +9 -9
- data/lib/frameworks/sproutcore/frameworks/runtime/core.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/runtime/debug/test_suites/array/insertAt.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/runtime/debug/test_suites/array/removeAt.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/runtime/ext/array.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/runtime/mixins/array.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/runtime/mixins/freezable.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/runtime/mixins/observable.js +66 -5
- data/lib/frameworks/sproutcore/frameworks/runtime/mixins/tree.js +44 -0
- data/lib/frameworks/sproutcore/frameworks/runtime/private/observer_queue.js +4 -1
- data/lib/frameworks/sproutcore/frameworks/runtime/system/binding.js +0 -25
- data/lib/frameworks/sproutcore/frameworks/runtime/system/enumerator.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/runtime/system/error.js +67 -15
- data/lib/frameworks/sproutcore/frameworks/runtime/system/index_set.js +6 -11
- data/lib/frameworks/sproutcore/frameworks/runtime/system/set.js +6 -6
- data/lib/frameworks/sproutcore/frameworks/runtime/system/string.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/runtime/tests/mixins/observable/{observable.js → observable_test.js} +110 -16
- data/lib/frameworks/sproutcore/frameworks/runtime/tests/system/error.js +21 -0
- data/lib/frameworks/sproutcore/frameworks/statechart/system/statechart.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/template_view/ext/handlebars.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/testing/system/plan.js +1 -1
- data/lib/sproutcore/render_engines/haml.rb +1 -1
- metadata +610 -604
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layout.js +0 -210
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutDidChange.js +0 -275
- data/lib/frameworks/sproutcore/frameworks/foundation/gestures/pinch.js +0 -119
- data/lib/frameworks/sproutcore/frameworks/foundation/gestures/swipe.js +0 -234
- data/lib/frameworks/sproutcore/frameworks/foundation/gestures/tap.js +0 -157
@@ -43,7 +43,7 @@ SC.SCROLL = {
|
|
43
43
|
|
44
44
|
@static
|
45
45
|
@type Number
|
46
|
-
@default
|
46
|
+
@default 3
|
47
47
|
*/
|
48
48
|
SCALE_GESTURE_THRESHOLD: 3
|
49
49
|
|
@@ -1705,7 +1705,7 @@ SC.ScrollView = SC.View.extend({
|
|
1705
1705
|
isVisible: !this.get('autohidesHorizontalScroller'),
|
1706
1706
|
layoutDirection: SC.LAYOUT_HORIZONTAL,
|
1707
1707
|
value: this.get('horizontalScrollOffset'),
|
1708
|
-
valueBinding: '.
|
1708
|
+
valueBinding: '.parentView.horizontalScrollOffset', // Bind the value of the scroller to our horizontal offset.
|
1709
1709
|
minimum: this.get('minimumHorizontalScrollOffset'),
|
1710
1710
|
maximum: this.get('maximumHorizontalScrollOffset')
|
1711
1711
|
});
|
@@ -1732,7 +1732,7 @@ SC.ScrollView = SC.View.extend({
|
|
1732
1732
|
isVisible: !this.get('autohidesVerticalScroller'),
|
1733
1733
|
layoutDirection: SC.LAYOUT_VERTICAL,
|
1734
1734
|
value: this.get('verticalScrollOffset'),
|
1735
|
-
valueBinding: '.
|
1735
|
+
valueBinding: '.parentView.verticalScrollOffset', // Bind the value of the scroller to our vertical offset.
|
1736
1736
|
minimum: this.get('minimumVerticalScrollOffset'),
|
1737
1737
|
maximum: this.get('maximumVerticalScrollOffset')
|
1738
1738
|
});
|
@@ -540,7 +540,7 @@ SC.SegmentedView = SC.View.extend(SC.Control,
|
|
540
540
|
Whenever the view resizes, we need to check to see if we're overflowing.
|
541
541
|
*/
|
542
542
|
viewDidResize: function () {
|
543
|
-
this.
|
543
|
+
this._sc_viewFrameDidChange();
|
544
544
|
|
545
545
|
var isHorizontal = this.get('layoutDirection') === SC.LAYOUT_HORIZONTAL,
|
546
546
|
visibleDim = isHorizontal ? this.$().width() : this.$().height();
|
@@ -0,0 +1,286 @@
|
|
1
|
+
// ==========================================================================
|
2
|
+
// Project: SproutCore - JavaScript Application Framework
|
3
|
+
// Copyright: ©2006-2011 Strobe Inc. and contributors.
|
4
|
+
// Portions ©2008-2011 Apple Inc. All rights reserved.
|
5
|
+
// License: Licensed under MIT license (see license.js)
|
6
|
+
// ==========================================================================
|
7
|
+
|
8
|
+
sc_require("system/gesture");
|
9
|
+
|
10
|
+
/*
|
11
|
+
TODO Document this class
|
12
|
+
*/
|
13
|
+
|
14
|
+
/**
|
15
|
+
@class
|
16
|
+
@extends SC.Gesture
|
17
|
+
*/
|
18
|
+
SC.PinchGesture = SC.Gesture.extend(
|
19
|
+
/** @scope SC.PinchGesture.prototype */{
|
20
|
+
|
21
|
+
/** @private Whether we have started pinching or not.
|
22
|
+
|
23
|
+
@type Boolean
|
24
|
+
@default false
|
25
|
+
*/
|
26
|
+
_sc_isPinching: false,
|
27
|
+
|
28
|
+
/** @private The previous distance between touches.
|
29
|
+
|
30
|
+
@type Number
|
31
|
+
@default null
|
32
|
+
*/
|
33
|
+
_sc_pinchAnchorD: null,
|
34
|
+
|
35
|
+
/** @private The initial scale of the view before pinching.
|
36
|
+
|
37
|
+
@type Number
|
38
|
+
@default null
|
39
|
+
*/
|
40
|
+
_sc_pinchAnchorScale: null,
|
41
|
+
|
42
|
+
/**
|
43
|
+
@type String
|
44
|
+
@default "pinch"
|
45
|
+
*/
|
46
|
+
name: "pinch",
|
47
|
+
|
48
|
+
/**
|
49
|
+
The amount of time in milliseconds that touches should stop moving before a `pinchEnd` event
|
50
|
+
will fire. When a pinch gesture begins, the `pinchStart` event is fired and as long as the
|
51
|
+
touches continue to change distance, multiple `pinch` events will fire. If the touches remain
|
52
|
+
active but don't change distance any longer, then after `pinchDelay` milliseconds the `pinchEnd`
|
53
|
+
event will fire.
|
54
|
+
|
55
|
+
@type Number
|
56
|
+
@default 500
|
57
|
+
*/
|
58
|
+
pinchDelay: 500,
|
59
|
+
|
60
|
+
/**
|
61
|
+
The number of pixels that multiple touches need to expand or contract in order to trigger the
|
62
|
+
beginning of a pinch.
|
63
|
+
|
64
|
+
@type Number
|
65
|
+
@default 3
|
66
|
+
*/
|
67
|
+
// pinchStartThreshold: 3,
|
68
|
+
|
69
|
+
/** @private Cleans up the touch session. */
|
70
|
+
_sc_cleanUpTouchSession: function () {
|
71
|
+
// If we were pinching before, end the pinch immediately.
|
72
|
+
if (this._sc_isPinching) {
|
73
|
+
this._sc_pinchingTimer.invalidate();
|
74
|
+
this._sc_pinchingTimer = null;
|
75
|
+
this._sc_lastPinchTime = null;
|
76
|
+
this._sc_isPinching = false;
|
77
|
+
|
78
|
+
// Trigger the gesture, 'pinchEnd'.
|
79
|
+
this.end();
|
80
|
+
}
|
81
|
+
|
82
|
+
// Clean up.
|
83
|
+
this._sc_pinchAnchorD = null;
|
84
|
+
},
|
85
|
+
|
86
|
+
/** @private Shared function for when a touch ends or cancels. */
|
87
|
+
_sc_touchFinishedInSession: function (touch, touchesInSession) {
|
88
|
+
// If there are more than two touches, keep monitoring for pinches by updating _sc_pinchAnchorD.
|
89
|
+
if (touchesInSession.length > 1) {
|
90
|
+
// Get the averaged touches for the the view. Because pinch is always interested in every touch
|
91
|
+
// the touchesInSession will equal the touches for the view.
|
92
|
+
var avgTouch = touch.averagedTouchesForView(this.view);
|
93
|
+
|
94
|
+
this._sc_pinchAnchorD = avgTouch.d;
|
95
|
+
|
96
|
+
// Disregard incoming touches by clearing out _sc_pinchAnchorD and end an active pinch immediately.
|
97
|
+
} else {
|
98
|
+
this._sc_cleanUpTouchSession();
|
99
|
+
}
|
100
|
+
},
|
101
|
+
|
102
|
+
/** @private Triggers pinchEnd and resets _sc_isPinching if enough time has passed. */
|
103
|
+
_sc_triggerPinchEnd: function () {
|
104
|
+
// If a pinch came in since the time the timer was registered, set up a new timer for the
|
105
|
+
// remaining time.
|
106
|
+
if (this._sc_lastPinchTime) {
|
107
|
+
var timePassed = Date.now() - this._sc_lastPinchTime,
|
108
|
+
pinchDelay = this.get('pinchDelay');
|
109
|
+
|
110
|
+
// Prepare to send 'pinchEnd' again.
|
111
|
+
this._sc_pinchingTimer = SC.Timer.schedule({
|
112
|
+
target: this,
|
113
|
+
action: this._sc_triggerPinchEnd,
|
114
|
+
interval: pinchDelay - timePassed // Trigger the timer the amount of time left since the last pinch
|
115
|
+
});
|
116
|
+
|
117
|
+
// Clear out the last pinch time.
|
118
|
+
this._sc_lastPinchTime = null;
|
119
|
+
|
120
|
+
// No additional pinches appeared in the amount of time.
|
121
|
+
} else {
|
122
|
+
// Trigger the gesture, 'pinchEnd'.
|
123
|
+
this.end();
|
124
|
+
|
125
|
+
// Clear out the pinching session.
|
126
|
+
this._sc_isPinching = false;
|
127
|
+
this._sc_pinchingTimer = null;
|
128
|
+
}
|
129
|
+
},
|
130
|
+
|
131
|
+
/**
|
132
|
+
The pinch gesture is always interested in the touch session. When a new touch is added, the
|
133
|
+
distance between all of the touches is registered in order to check for distance changes
|
134
|
+
equating to a pinch gesture.
|
135
|
+
|
136
|
+
@param {SC.Touch} touch The touch to be added to the session.
|
137
|
+
@param {Array} touchesInSession The touches already in the session.
|
138
|
+
@returns {Boolean} True.
|
139
|
+
@see SC.Gesture#touchAddedToSession
|
140
|
+
*/
|
141
|
+
touchAddedToSession: function (touch, touchesInSession) {
|
142
|
+
// Get the averaged touches for the the view. Because pinch is always interested in every touch
|
143
|
+
// the touchesInSession will equal the touches for the view.
|
144
|
+
var avgTouch = touch.averagedTouchesForView(this.view, true);
|
145
|
+
|
146
|
+
this._sc_pinchAnchorD = avgTouch.d;
|
147
|
+
|
148
|
+
return true;
|
149
|
+
},
|
150
|
+
|
151
|
+
/**
|
152
|
+
If a touch cancels, the pinch remains interested (even if there's only one touch left, because a
|
153
|
+
second touch may appear again), but updates its internal variable for tracking for pinch
|
154
|
+
movements.
|
155
|
+
|
156
|
+
@param {SC.Touch} touch The touch to be removed from the session.
|
157
|
+
@param {Array} touchesInSession The touches in the session.
|
158
|
+
@returns {Boolean} True
|
159
|
+
@see SC.Gesture#touchCancelledInSession
|
160
|
+
*/
|
161
|
+
touchCancelledInSession: function (touch, touchesInSession) {
|
162
|
+
this._sc_touchFinishedInSession(touch, touchesInSession);
|
163
|
+
|
164
|
+
return true;
|
165
|
+
},
|
166
|
+
|
167
|
+
/**
|
168
|
+
If a touch ends, the pinch remains interested (even if there's only one touch left, because a
|
169
|
+
second touch may appear again), but updates its internal variable for tracking for pinch
|
170
|
+
movements.
|
171
|
+
|
172
|
+
@param {SC.Touch} touch The touch to be removed from the session.
|
173
|
+
@param {Array} touchesInSession The touches in the session.
|
174
|
+
@returns {Boolean} True
|
175
|
+
@see SC.Gesture#touchEndedInSession
|
176
|
+
*/
|
177
|
+
touchEndedInSession: function (touch, touchesInSession) {
|
178
|
+
this._sc_touchFinishedInSession(touch, touchesInSession);
|
179
|
+
|
180
|
+
return true;
|
181
|
+
},
|
182
|
+
|
183
|
+
/**
|
184
|
+
The pinch is only interested in more than one touch moving. If there are multiple touches
|
185
|
+
moving and the distance between the touches has changed then a `pinchStart` event will fire.
|
186
|
+
If the touches keep expanding or contracting, the `pinch` event will repeatedly fire. Finally,
|
187
|
+
if the touch distance stops changing and enough time passes (value of `pinchDelay`), the
|
188
|
+
`pinchEnd` event will fire.
|
189
|
+
|
190
|
+
Therefore, it's possible for a pinch gesture to start and end more than once in a single touch
|
191
|
+
session. For example, a person may touch two fingers down, expand them to zoom in (`pinchStart`
|
192
|
+
and multiple `pinch` events fire) and then if they stop or move their fingers in one direction
|
193
|
+
in tandem to scroll content (`pinchEnd` event fires after `pinchDelay` exceeded). If the person
|
194
|
+
then starts expanding their fingers again without lifting them, a new set of pinch events will
|
195
|
+
fire.
|
196
|
+
|
197
|
+
@param {Array} touchesInSession All touches in the session.
|
198
|
+
@returns {Boolean} True.
|
199
|
+
@see SC.Gesture#touchesMovedInSession
|
200
|
+
*/
|
201
|
+
touchesMovedInSession: function (touchesInSession) {
|
202
|
+
// console.log('touchesMovedInSession: %@'.fmt(touchesInSession.length));
|
203
|
+
// We should pay attention to the movement.
|
204
|
+
if (touchesInSession.length > 1) {
|
205
|
+
// Get the averaged touches for the the view. Because pinch is always interested in every touch
|
206
|
+
// the touchesInSession will equal the touches for the view.
|
207
|
+
var avgTouch = SC.Touch.averagedTouch(touchesInSession); // touchesInSession[0].averagedTouchesForView(this.view);
|
208
|
+
|
209
|
+
var touchDeltaD = this._sc_pinchAnchorD - avgTouch.d,
|
210
|
+
absDeltaD = Math.abs(touchDeltaD);
|
211
|
+
|
212
|
+
// console.log(' this._sc_pinchAnchorD, %@ - avgTouch.d, %@ = touchDeltaD, %@'.fmt(this._sc_pinchAnchorD, avgTouch.d, touchDeltaD));
|
213
|
+
if (absDeltaD > 0) {
|
214
|
+
// Trigger the gesture, 'pinchStart', once.
|
215
|
+
if (!this._sc_isPinching) {
|
216
|
+
this.start();
|
217
|
+
|
218
|
+
// Prepare to send 'pinchEnd'.
|
219
|
+
this._sc_pinchingTimer = SC.Timer.schedule({
|
220
|
+
target: this,
|
221
|
+
action: this._sc_triggerPinchEnd,
|
222
|
+
interval: this.get('pinchDelay')
|
223
|
+
});
|
224
|
+
|
225
|
+
// Track that we are pinching.
|
226
|
+
this._sc_isPinching = true;
|
227
|
+
|
228
|
+
// Update the last pinch time so that when the timer expires, it doesn't fire pinchEnd.
|
229
|
+
// This is faster than invalidating and creating a new timer each time this method is called.
|
230
|
+
} else {
|
231
|
+
this._sc_lastPinchTime = Date.now();
|
232
|
+
}
|
233
|
+
|
234
|
+
// The percentage difference in touch distance.
|
235
|
+
var scalePercentChange = avgTouch.d / this._sc_pinchAnchorD,
|
236
|
+
scale = this._sc_pinchAnchorScale * scalePercentChange;
|
237
|
+
|
238
|
+
// Trigger the gesture, 'pinch'.
|
239
|
+
this.trigger(scale, touchesInSession.length);
|
240
|
+
|
241
|
+
// Reset the anchor.
|
242
|
+
this._sc_pinchAnchorD = avgTouch.d;
|
243
|
+
this._sc_pinchAnchorScale = scale;
|
244
|
+
}
|
245
|
+
}
|
246
|
+
|
247
|
+
return true;
|
248
|
+
},
|
249
|
+
|
250
|
+
/**
|
251
|
+
Cleans up all touch session variables.
|
252
|
+
|
253
|
+
@returns {void}
|
254
|
+
@see SC.Gesture#touchSessionCancelled
|
255
|
+
*/
|
256
|
+
touchSessionCancelled: function () {
|
257
|
+
// Clean up.
|
258
|
+
this._sc_cleanUpTouchSession();
|
259
|
+
},
|
260
|
+
|
261
|
+
/**
|
262
|
+
Cleans up all touch session variables and triggers the gesture.
|
263
|
+
|
264
|
+
@returns {void}
|
265
|
+
@see SC.Gesture#touchSessionEnded
|
266
|
+
*/
|
267
|
+
touchSessionEnded: function () {
|
268
|
+
// Clean up.
|
269
|
+
this._sc_cleanUpTouchSession();
|
270
|
+
},
|
271
|
+
|
272
|
+
/**
|
273
|
+
Registers the scale of the view when it starts.
|
274
|
+
|
275
|
+
@param {SC.Touch} touch The touch that started the session.
|
276
|
+
@returns {void}
|
277
|
+
@see SC.Gesture#touchSessionStarted
|
278
|
+
*/
|
279
|
+
touchSessionStarted: function (touch) {
|
280
|
+
var viewLayout = this.view.get('layout');
|
281
|
+
|
282
|
+
/*jshint eqnull:true*/
|
283
|
+
this._sc_pinchAnchorScale = viewLayout.scale == null ? 1 : viewLayout.scale;
|
284
|
+
}
|
285
|
+
|
286
|
+
});
|
@@ -0,0 +1,449 @@
|
|
1
|
+
// ==========================================================================
|
2
|
+
// Project: SproutCore - JavaScript Application Framework
|
3
|
+
// Copyright: ©2006-2011 Strobe Inc. and contributors.
|
4
|
+
// Portions ©2008-2011 Apple Inc. All rights reserved.
|
5
|
+
// License: Licensed under MIT license (see license.js)
|
6
|
+
// ==========================================================================
|
7
|
+
|
8
|
+
sc_require("system/gesture");
|
9
|
+
|
10
|
+
|
11
|
+
/**
|
12
|
+
@static
|
13
|
+
@type String
|
14
|
+
@constant
|
15
|
+
*/
|
16
|
+
SC.SWIPE_HORIZONTAL = [0, 180];
|
17
|
+
|
18
|
+
/**
|
19
|
+
@static
|
20
|
+
@type String
|
21
|
+
@constant
|
22
|
+
*/
|
23
|
+
SC.SWIPE_VERTICAL = [90, -90];
|
24
|
+
|
25
|
+
/**
|
26
|
+
@static
|
27
|
+
@type String
|
28
|
+
@constant
|
29
|
+
*/
|
30
|
+
SC.SWIPE_ANY = null;
|
31
|
+
|
32
|
+
/**
|
33
|
+
@static
|
34
|
+
@type String
|
35
|
+
@constant
|
36
|
+
*/
|
37
|
+
SC.SWIPE_LEFT = [180];
|
38
|
+
|
39
|
+
/**
|
40
|
+
@static
|
41
|
+
@type String
|
42
|
+
@constant
|
43
|
+
*/
|
44
|
+
SC.SWIPE_RIGHT = [0];
|
45
|
+
|
46
|
+
/**
|
47
|
+
@static
|
48
|
+
@type String
|
49
|
+
@constant
|
50
|
+
*/
|
51
|
+
SC.SWIPE_UP = [-90];
|
52
|
+
|
53
|
+
/**
|
54
|
+
@static
|
55
|
+
@type String
|
56
|
+
@constant
|
57
|
+
*/
|
58
|
+
SC.SWIPE_DOWN = [90];
|
59
|
+
|
60
|
+
/**
|
61
|
+
## What is a "swipe"?
|
62
|
+
|
63
|
+
A swipe is one or more quick unidirectionally moving touches that end abruptly. By this, it is
|
64
|
+
meant that at some point the touches begin to move quickly, `swipeVelocity` in a single direction,
|
65
|
+
`angles`, and cover a fair amount of distance, `swipeDistance`, before ending.
|
66
|
+
|
67
|
+
The single direction that the touches move in, must follow one of the angles specified in the
|
68
|
+
`angles` array. However, the touches do not need to precisely match an angle and may vary by
|
69
|
+
an amount above or below the angle as defined by the `tolerance` property.
|
70
|
+
|
71
|
+
Because the swipe is the last moment of the touch session, the swipe gesture is always interested
|
72
|
+
in a touch session. As long as the last distance traveled is great enough and at an
|
73
|
+
approved angle, then a swipe will trigger.
|
74
|
+
|
75
|
+
@class
|
76
|
+
@extends SC.Gesture
|
77
|
+
*/
|
78
|
+
SC.SwipeGesture = SC.Gesture.extend(
|
79
|
+
/** @scope SC.SwipeGesture.prototype */ {
|
80
|
+
|
81
|
+
//
|
82
|
+
// - Properties --------------------------------------------------------------------
|
83
|
+
//
|
84
|
+
|
85
|
+
/** @private The last approved angle. Is set as long as a swipe appears valid. */
|
86
|
+
_sc_lastAngle: null,
|
87
|
+
|
88
|
+
/** @private The last computed distance. Is set as long as a swipe appears valid. */
|
89
|
+
_sc_lastDistance: null,
|
90
|
+
|
91
|
+
/** @private The number of touches in the current swipe. */
|
92
|
+
_sc_numberOfTouches: 0,
|
93
|
+
|
94
|
+
/** @private The initial point where a swipe appears to begin. */
|
95
|
+
_sc_swipeAnchorX: null,
|
96
|
+
|
97
|
+
/** @private The initial point where a swipe appears to begin. */
|
98
|
+
_sc_swipeAnchorY: null,
|
99
|
+
|
100
|
+
/** @private The last time a movement in a swipe was recorded. */
|
101
|
+
_sc_swipeLastMovedAt: null,
|
102
|
+
|
103
|
+
/**
|
104
|
+
The angles that the swipe will accept, between 0° and ±180°. The angles start from the right
|
105
|
+
side (0°) and end at the left side (±180°). With the positive angles passing through *down*
|
106
|
+
(+90°) and the negative angles passing through *up* (-90°). The following ASCII art shows the
|
107
|
+
directions of the angles,
|
108
|
+
|
109
|
+
|
110
|
+
-90° (up)
|
111
|
+
|
|
112
|
+
|
|
113
|
+
(left) ± 180° --------- 0° (right)
|
114
|
+
|
|
115
|
+
|
|
116
|
+
(down) +90°
|
117
|
+
|
118
|
+
To make this easier, there are several predefined angles arrays that you can use,
|
119
|
+
|
120
|
+
* SC.SWIPE_HORIZONTAL ([180, 0]), i.e. left or right
|
121
|
+
* SC.SWIPE_VERTICAL ([-90, 90]), i.e. up or down
|
122
|
+
* SC.SWIPE_ANY (null), i.e. 0° to up, down, left or right
|
123
|
+
* SC.SWIPE_LEFT ([180]), i.e. left only
|
124
|
+
* SC.SWIPE_RIGHT ([0]), i.e. right only
|
125
|
+
* SC.SWIPE_UP ([-90]), i.e. up only
|
126
|
+
* SC.SWIPE_DOWN ([90]), down only
|
127
|
+
|
128
|
+
However, you can provide any combination of angles that you want. For example, to support
|
129
|
+
45° angled swipes to the right and straight swipes to the left, we would use,
|
130
|
+
|
131
|
+
angles: [180, -45, 45] // 180° straight left, -45° up & right, 45° down & right
|
132
|
+
|
133
|
+
## How to use the angles.
|
134
|
+
|
135
|
+
When the `swipe` event fires, the angle of the swipe is passed to your view allowing you to
|
136
|
+
recognize which of the supported angles matched the swipe.
|
137
|
+
|
138
|
+
Note, there is one special case, as defined by `SC.SWIPE_ANY`, which is to set angles to `null`
|
139
|
+
in order to support swipes in *any* direction. The code will look for a swipe (unidirectional
|
140
|
+
fast motion) in any direction and pass the observed angle to the `swipe` handler.
|
141
|
+
|
142
|
+
@type Array
|
143
|
+
@default 24
|
144
|
+
*/
|
145
|
+
// This is a computed property in order to provide backwards compatibility for direction.
|
146
|
+
// When direction is removed completely, this can become a simple `SC.SWIPE_HORIZONTAL` value.
|
147
|
+
angles: function (key, value) {
|
148
|
+
var direction = this.get('direction'),
|
149
|
+
ret = SC.SWIPE_HORIZONTAL;
|
150
|
+
|
151
|
+
// Backwards compatibility support
|
152
|
+
if (!SC.none(direction)) {
|
153
|
+
//@if(debug)
|
154
|
+
SC.warn('Developer Warning: The direction property of SC.SwipeGesture has been renamed to angles.');
|
155
|
+
//@endif
|
156
|
+
|
157
|
+
return direction;
|
158
|
+
}
|
159
|
+
|
160
|
+
if (!SC.none(value)) { ret = value; }
|
161
|
+
|
162
|
+
return ret;
|
163
|
+
}.property('direction').cacheable(),
|
164
|
+
|
165
|
+
|
166
|
+
/** @deprecated Version 1.11. Please use the `angles` property instead.
|
167
|
+
@type Array
|
168
|
+
@default SC.SWIPE_HORIZONTAL
|
169
|
+
*/
|
170
|
+
direction: SC.SWIPE_HORIZONTAL,
|
171
|
+
|
172
|
+
/**
|
173
|
+
@type String
|
174
|
+
@default "swipe"
|
175
|
+
@readOnly
|
176
|
+
*/
|
177
|
+
name: "swipe",
|
178
|
+
|
179
|
+
/**
|
180
|
+
The distance in pixels that touches must move in a single direction to be far enough in order to
|
181
|
+
be considered a swipe. If the touches don't move `swipeDistance` amount of pixels, then the
|
182
|
+
gesture will not trigger.
|
183
|
+
|
184
|
+
@type Number
|
185
|
+
@default 40
|
186
|
+
*/
|
187
|
+
swipeDistance: 40,
|
188
|
+
|
189
|
+
/**
|
190
|
+
The velocity in pixels per millisecond that touches must be traveling to begin a swipe motion.
|
191
|
+
If the touches are moving slower than the velocity, the swipe start point won't be set.
|
192
|
+
|
193
|
+
@type Number
|
194
|
+
@default 0.5
|
195
|
+
*/
|
196
|
+
swipeVelocity: 0.5,
|
197
|
+
|
198
|
+
/**
|
199
|
+
Amount of degrees that a touch is allowed to vary off of the target angle(s).
|
200
|
+
|
201
|
+
@type Number
|
202
|
+
@default 15
|
203
|
+
*/
|
204
|
+
tolerance: 15,
|
205
|
+
|
206
|
+
//
|
207
|
+
// - Methods --------------------------------------------------------------------
|
208
|
+
//
|
209
|
+
|
210
|
+
/** @private Cleans up the touch session. */
|
211
|
+
_sc_cleanUpTouchSession: function (wasCancelled) {
|
212
|
+
// Clean up.
|
213
|
+
this._sc_numberOfTouches = 0;
|
214
|
+
this._sc_lastDistance = null;
|
215
|
+
this._sc_swipeStartedAt = null;
|
216
|
+
this._sc_lastAngle = null;
|
217
|
+
this._sc_swipeAnchorX = null;
|
218
|
+
this._sc_swipeAnchorY = null;
|
219
|
+
},
|
220
|
+
|
221
|
+
/** @private Timer used to tell if swipe was too slow. */
|
222
|
+
_sc_swipeTooSlow: function () {
|
223
|
+
// The session took to long to finish from when a swipe appeared to start. Reset.
|
224
|
+
this._sc_cleanUpTouchSession();
|
225
|
+
},
|
226
|
+
|
227
|
+
/**
|
228
|
+
The swipe gesture is always interested in a touch session, because it is only concerned in how
|
229
|
+
the session ends. If it ends with a fast unidirectional sliding movement, then it is a swipe.
|
230
|
+
|
231
|
+
Note, that for multiple touches, touches are expected to start while other touches are already
|
232
|
+
moving. When touches are added we update the swipe start position. This means that inadvertent
|
233
|
+
taps that occur while swiping could break a swipe recognition by making the swipe too short to
|
234
|
+
register.
|
235
|
+
|
236
|
+
@param {SC.Touch} touch The touch to be added to the session.
|
237
|
+
@param {Array} touchesInSession The touches already in the session.
|
238
|
+
@returns {Boolean} True as long as the new touch doesn't start too late after the first touch.
|
239
|
+
@see SC.Gesture#touchAddedToSession
|
240
|
+
*/
|
241
|
+
// TODO: What about first touch starts moving, second touch taps, first touch finishes?
|
242
|
+
// TODO: What about first touch starts tap, second touch starts moving, first touch finishes tap, second touch finishes?
|
243
|
+
touchAddedToSession: function (touch, touchesInSession) {
|
244
|
+
// Get the averaged touches for the the view. Because pinch is always interested in every touch
|
245
|
+
// the touchesInSession will equal the touches for the view.
|
246
|
+
var avgTouch = touch.averagedTouchesForView(this.view, true);
|
247
|
+
|
248
|
+
this._sc_swipeAnchorX = avgTouch.x;
|
249
|
+
this._sc_swipeAnchorY = avgTouch.y;
|
250
|
+
|
251
|
+
return true;
|
252
|
+
},
|
253
|
+
|
254
|
+
/**
|
255
|
+
The swipe gesture is always interested in a touch session, because it is only concerned in how
|
256
|
+
the session ends. If it ends with a fast unidirectional sliding movement, then it is a swipe.
|
257
|
+
|
258
|
+
Note, that a touch may cancel while swiping (went off screen inadvertently). Because of this we
|
259
|
+
don't immediately reduce the number of touches in the swipe, because if the rest of the touches
|
260
|
+
end right away in a swipe, it's best to consider the cancelled touch as part of the group.
|
261
|
+
|
262
|
+
@param {SC.Touch} touch The touch to be removed from the session.
|
263
|
+
@param {Array} touchesInSession The touches in the session.
|
264
|
+
@returns {Boolean} True
|
265
|
+
@see SC.Gesture#touchCancelledInSession
|
266
|
+
*/
|
267
|
+
touchCancelledInSession: function (touch, touchesInSession) {
|
268
|
+
return true;
|
269
|
+
},
|
270
|
+
|
271
|
+
/**
|
272
|
+
The swipe gesture is always interested in a touch session, because it is only concerned in how
|
273
|
+
the session ends. If it ends with a fast unidirectional sliding movement, then it is a swipe.
|
274
|
+
|
275
|
+
Note, that touches are expected to end while swiping. Because of this we don't immediately
|
276
|
+
reduce the number of touches in the swipe, because if the rest of the touches also end right
|
277
|
+
away in a swiping motion, it's best to consider this ended touch as part of the group.
|
278
|
+
|
279
|
+
@param {SC.Touch} touch The touch to be removed from the session.
|
280
|
+
@param {Array} touchesInSession The touches in the session.
|
281
|
+
@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.
|
282
|
+
@see SC.Gesture#touchEndedInSession
|
283
|
+
*/
|
284
|
+
touchEndedInSession: function (touch, touchesInSession) {
|
285
|
+
return true;
|
286
|
+
},
|
287
|
+
|
288
|
+
/** @private Test the given angle against an approved angle. */
|
289
|
+
_sc_testAngle: function (absoluteCurrentAngle, currentIsPositive, approvedAngle, tolerance) {
|
290
|
+
var angleIsPositive = approvedAngle >= 0,
|
291
|
+
absoluteAngle = !angleIsPositive ? Math.abs(approvedAngle) : approvedAngle,
|
292
|
+
upperBound = absoluteAngle + tolerance,
|
293
|
+
lowerBound = absoluteAngle - tolerance,
|
294
|
+
ret = false;
|
295
|
+
|
296
|
+
if (lowerBound <= absoluteCurrentAngle && absoluteCurrentAngle <= upperBound) {
|
297
|
+
// Special case: ex. Don't confuse -45° with 45° or vice versa.
|
298
|
+
var upperBoundIsPositive = upperBound >= 0 && upperBound <= 180,
|
299
|
+
lowerBoundIsPositive = lowerBound >= 0;
|
300
|
+
|
301
|
+
ret = upperBoundIsPositive === lowerBoundIsPositive ? currentIsPositive === angleIsPositive : true;
|
302
|
+
}
|
303
|
+
|
304
|
+
return ret;
|
305
|
+
},
|
306
|
+
|
307
|
+
/**
|
308
|
+
The swipe gesture is always interested in a touch session, because it is only concerned in how
|
309
|
+
the session ends. If it ends with a fast unidirectional sliding movement, then it is a swipe.
|
310
|
+
|
311
|
+
@param {Array} touchesInSession All touches in the session.
|
312
|
+
@returns {Boolean} True as long as none of the touches have moved too far off-axis to be a clean swipe.
|
313
|
+
@see SC.Gesture#touchesMovedInSession
|
314
|
+
*/
|
315
|
+
touchesMovedInSession: function (touchesInSession) {
|
316
|
+
// Get the averaged touches for the the view. Because swipe is always interested in every touch
|
317
|
+
// (or none) the touchesInSession will equal the touches for the view.
|
318
|
+
var angles = this.get('direction'),
|
319
|
+
avgTouch = touchesInSession[0].averagedTouchesForView(this.view),
|
320
|
+
xDiff = avgTouch.x - this._sc_swipeAnchorX,
|
321
|
+
yDiff = avgTouch.y - this._sc_swipeAnchorY,
|
322
|
+
currentAngle = Math.atan2(yDiff, xDiff) * (180 / Math.PI),
|
323
|
+
absoluteCurrentAngle = Math.abs(currentAngle),
|
324
|
+
currentIsPositive = currentAngle >= 0,
|
325
|
+
tolerance = this.get('tolerance'),
|
326
|
+
approvedAngle = null,
|
327
|
+
angle;
|
328
|
+
|
329
|
+
// There is one special case, when angles is null, allow all angles.
|
330
|
+
if (angles === null) {
|
331
|
+
// Use the last angle against itself.
|
332
|
+
angle = this._sc_lastAngle;
|
333
|
+
|
334
|
+
if (angle !== null) {
|
335
|
+
var withinLastAngle = this._sc_testAngle(absoluteCurrentAngle, currentIsPositive, angle, tolerance);
|
336
|
+
|
337
|
+
// If still within the start angle, leave it going.
|
338
|
+
if (withinLastAngle) {
|
339
|
+
approvedAngle = angle;
|
340
|
+
} else {
|
341
|
+
approvedAngle = currentAngle;
|
342
|
+
}
|
343
|
+
} else {
|
344
|
+
approvedAngle = currentAngle;
|
345
|
+
}
|
346
|
+
|
347
|
+
// Check against approved angles.
|
348
|
+
} else {
|
349
|
+
for (var i = 0, len = angles.length; i < len; i++) {
|
350
|
+
angle = angles[i];
|
351
|
+
|
352
|
+
// If the current angle is within the tolerance of the given angle, it's a match.
|
353
|
+
if (this._sc_testAngle(absoluteCurrentAngle, currentIsPositive, angle, tolerance)) {
|
354
|
+
approvedAngle = angle;
|
355
|
+
|
356
|
+
break; // No need to continue.
|
357
|
+
}
|
358
|
+
}
|
359
|
+
}
|
360
|
+
|
361
|
+
// Got angle.
|
362
|
+
if (approvedAngle !== null) {
|
363
|
+
// Same angle. Ensure we're traveling fast enough to keep the angle.
|
364
|
+
if (this._sc_lastAngle === approvedAngle) {
|
365
|
+
// Get distance between the anchor and current average point.
|
366
|
+
var dx = Math.abs(xDiff),
|
367
|
+
dy = Math.abs(yDiff),
|
368
|
+
now = Date.now(),
|
369
|
+
distance,
|
370
|
+
velocity;
|
371
|
+
|
372
|
+
distance = Math.pow(dx * dx + dy * dy, 0.5);
|
373
|
+
velocity = distance / (now - this._sc_swipeStartedAt);
|
374
|
+
|
375
|
+
// If velocity is too slow, lost swipe.
|
376
|
+
var minimumVelocity = this.get('swipeVelocity');
|
377
|
+
if (velocity < minimumVelocity) {
|
378
|
+
this._sc_lastAngle = null;
|
379
|
+
this._sc_swipeAnchorX = avgTouch.x;
|
380
|
+
this._sc_swipeAnchorY = avgTouch.y;
|
381
|
+
this._sc_lastDistance = 0;
|
382
|
+
this._sc_swipeStartedAt = null;
|
383
|
+
} else {
|
384
|
+
// Track how far we've gone in this approved direction.
|
385
|
+
this._sc_lastDistance = distance;
|
386
|
+
this._sc_swipeLastMovedAt = Date.now();
|
387
|
+
}
|
388
|
+
|
389
|
+
// This is the first matched angle or a new direction. Track its values for future comparison.
|
390
|
+
} else {
|
391
|
+
// Track the current approved angle and when we started going on it.
|
392
|
+
this._sc_lastAngle = approvedAngle;
|
393
|
+
this._sc_swipeStartedAt = Date.now();
|
394
|
+
|
395
|
+
// Use the current number of touches as the number in the session. Some may get cancelled.
|
396
|
+
this._sc_numberOfTouches = touchesInSession.length;
|
397
|
+
}
|
398
|
+
|
399
|
+
// No angle or lost the angle.
|
400
|
+
} else {
|
401
|
+
this._sc_lastAngle = null;
|
402
|
+
this._sc_swipeAnchorX = avgTouch.x;
|
403
|
+
this._sc_swipeAnchorY = avgTouch.y;
|
404
|
+
this._sc_lastDistance = 0;
|
405
|
+
this._sc_swipeStartedAt = null;
|
406
|
+
}
|
407
|
+
|
408
|
+
return true;
|
409
|
+
},
|
410
|
+
|
411
|
+
/**
|
412
|
+
Cleans up all touch session variables.
|
413
|
+
|
414
|
+
@returns {void}
|
415
|
+
@see SC.Gesture#touchSessionCancelled
|
416
|
+
*/
|
417
|
+
touchSessionCancelled: function () {
|
418
|
+
// Clean up.
|
419
|
+
this._sc_cleanUpTouchSession(true);
|
420
|
+
},
|
421
|
+
|
422
|
+
/**
|
423
|
+
Cleans up all touch session variables and triggers the gesture.
|
424
|
+
|
425
|
+
@returns {void}
|
426
|
+
@see SC.Gesture#touchSessionEnded
|
427
|
+
*/
|
428
|
+
touchSessionEnded: function () {
|
429
|
+
// Watch out for touches that move far and fast, but then hesitate too long before ending.
|
430
|
+
var notTooLongSinceLastMove = (Date.now() - this._sc_swipeLastMovedAt) < 200;
|
431
|
+
|
432
|
+
// If an approved angle remained set, the distance was far enough and it wasn't too long since
|
433
|
+
// the last movement, trigger the gesture, 'swipe'.
|
434
|
+
if (this._sc_lastAngle !== null &&
|
435
|
+
this._sc_lastDistance > this.get('swipeDistance') &&
|
436
|
+
notTooLongSinceLastMove) {
|
437
|
+
this.trigger(this._sc_lastAngle, this._sc_numberOfTouches);
|
438
|
+
}
|
439
|
+
|
440
|
+
// Clean up (will fire tapEnd if _sc_isTapping is true).
|
441
|
+
this._sc_cleanUpTouchSession(false);
|
442
|
+
},
|
443
|
+
|
444
|
+
touchSessionStarted: function (touch) {
|
445
|
+
this._sc_swipeAnchorX = touch.pageX;
|
446
|
+
this._sc_swipeAnchorY = touch.pageY;
|
447
|
+
}
|
448
|
+
|
449
|
+
});
|