sproutcore 1.11.0.rc3 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
});
|