sproutcore 1.6.0.1-java → 1.7.1.beta-java

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.
Files changed (143) hide show
  1. data/CHANGELOG +21 -0
  2. data/Gemfile +5 -0
  3. data/Rakefile +26 -13
  4. data/VERSION.yml +2 -2
  5. data/lib/Buildfile +43 -4
  6. data/lib/buildtasks/build.rake +10 -0
  7. data/lib/buildtasks/helpers/file_rule.rb +22 -0
  8. data/lib/buildtasks/helpers/file_rule_list.rb +137 -0
  9. data/lib/buildtasks/manifest.rake +133 -122
  10. data/lib/frameworks/sproutcore/CHANGELOG.md +69 -2
  11. data/lib/frameworks/sproutcore/apps/tests/english.lproj/strings.js +1 -0
  12. data/lib/frameworks/sproutcore/frameworks/bootstrap/system/browser.js +28 -22
  13. data/lib/frameworks/sproutcore/frameworks/core_foundation/controllers/array.js +9 -5
  14. data/lib/frameworks/sproutcore/frameworks/core_foundation/controllers/controller.js +1 -1
  15. data/lib/frameworks/sproutcore/frameworks/core_foundation/controls/button.js +18 -13
  16. data/lib/frameworks/sproutcore/frameworks/core_foundation/ext/handlebars/bind.js +5 -3
  17. data/lib/frameworks/sproutcore/frameworks/core_foundation/ext/handlebars/collection.js +2 -0
  18. data/lib/frameworks/sproutcore/frameworks/core_foundation/mixins/action_support.js +80 -0
  19. data/lib/frameworks/sproutcore/frameworks/core_foundation/mixins/template_helpers/text_field_support.js +84 -116
  20. data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/pane.js +8 -5
  21. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/event.js +157 -157
  22. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/platform.js +5 -3
  23. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/root_responder.js +6 -6
  24. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/sparse_array.js +10 -7
  25. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/mixins/action_support.js +106 -0
  26. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/template/collection.js +18 -0
  27. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/template/handlebars.js +71 -1
  28. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/attribute_bindings_test.js +38 -0
  29. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/class_name_bindings_test.js +47 -0
  30. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutChildViews.js +18 -18
  31. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutStyle.js +42 -10
  32. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view.js +158 -1
  33. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/keyboard.js +26 -1
  34. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout_style.js +14 -8
  35. data/lib/frameworks/sproutcore/frameworks/datastore/models/record.js +15 -2
  36. data/lib/frameworks/sproutcore/frameworks/datastore/models/record_attribute.js +108 -108
  37. data/lib/frameworks/sproutcore/frameworks/datastore/system/query.js +1 -1
  38. data/lib/frameworks/sproutcore/frameworks/datastore/system/record_array.js +2 -4
  39. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/record/error_methods.js +2 -2
  40. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/single_attribute.js +26 -0
  41. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/query/builders.js +7 -0
  42. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/record_array/error_methods.js +1 -1
  43. data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/system/datetime.js +4 -1
  44. data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/tests/system/datetime.js +6 -0
  45. data/lib/frameworks/sproutcore/frameworks/desktop/panes/menu.js +26 -5
  46. data/lib/frameworks/sproutcore/frameworks/desktop/panes/picker.js +97 -96
  47. data/lib/frameworks/sproutcore/frameworks/desktop/system/drag.js +4 -3
  48. data/lib/frameworks/sproutcore/frameworks/desktop/tests/panes/menu/ui.js +17 -4
  49. data/lib/frameworks/sproutcore/frameworks/desktop/views/collection.js +7 -7
  50. data/lib/frameworks/sproutcore/frameworks/desktop/views/menu_item.js +7 -5
  51. data/lib/frameworks/sproutcore/frameworks/desktop/views/scroll.js +12 -3
  52. data/lib/frameworks/sproutcore/frameworks/desktop/views/web.js +23 -14
  53. data/lib/frameworks/sproutcore/frameworks/experimental/Buildfile +5 -1
  54. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/render_delegates/menu_scroller.js +28 -0
  55. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/tests/menu/scroll.js +235 -0
  56. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/views/menu/scroll.js +363 -0
  57. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/views/menu/scroller.js +250 -0
  58. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/desktop_scroller.js +92 -0
  59. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/native_scroll.js +25 -0
  60. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/scroll.js +33 -0
  61. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/touch_scroller.js +76 -0
  62. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/integration.js +50 -0
  63. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/methods.js +143 -0
  64. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/ui.js +258 -0
  65. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/core_scroll.js +1164 -0
  66. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/core_scroller.js +332 -0
  67. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/desktop/scroll.js +236 -0
  68. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/desktop/scroller.js +347 -0
  69. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/scroll.js +15 -0
  70. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/scroller.js +10 -0
  71. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/touch/scroll.js +804 -0
  72. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/touch/scroller.js +133 -0
  73. data/lib/frameworks/sproutcore/frameworks/foundation/resources/text_field.css +3 -3
  74. data/lib/frameworks/sproutcore/frameworks/foundation/validators/number.js +3 -1
  75. data/lib/frameworks/sproutcore/frameworks/foundation/views/text_field.js +3 -3
  76. data/lib/frameworks/sproutcore/frameworks/media/views/audio.js +2 -1
  77. data/lib/frameworks/sproutcore/frameworks/media/views/controls.js +2 -1
  78. data/lib/frameworks/sproutcore/frameworks/media/views/media_slider.js +2 -4
  79. data/lib/frameworks/sproutcore/frameworks/media/views/mini_controls.js +2 -4
  80. data/lib/frameworks/sproutcore/frameworks/media/views/simple_controls.js +2 -4
  81. data/lib/frameworks/sproutcore/frameworks/media/views/video.js +2 -2
  82. data/lib/frameworks/sproutcore/frameworks/routing/system/routes.js +29 -3
  83. data/lib/frameworks/sproutcore/frameworks/runtime/core.js +2 -2
  84. data/lib/frameworks/sproutcore/frameworks/runtime/debug/test_suites/array/replace.js +1 -1
  85. data/lib/frameworks/sproutcore/frameworks/runtime/private/property_chain.js +2 -1
  86. data/lib/frameworks/sproutcore/frameworks/runtime/system/binding.js +3 -3
  87. data/lib/frameworks/sproutcore/frameworks/runtime/system/index_set.js +2 -2
  88. data/lib/frameworks/sproutcore/frameworks/runtime/system/object.js +1 -1
  89. data/lib/frameworks/sproutcore/themes/ace/resources/collection/normal/list_item.css +2 -2
  90. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/segmented.css +1 -1
  91. data/lib/gen/app/templates/apps/@target_name@/Buildfile +3 -5
  92. data/lib/gen/app/templates/apps/@target_name@/resources/_theme.css +18 -0
  93. data/lib/gen/project/templates/@filename@/Buildfile +2 -2
  94. data/lib/sproutcore.rb +30 -5
  95. data/lib/sproutcore/builders.rb +1 -0
  96. data/lib/sproutcore/builders/chance_file.rb +9 -16
  97. data/lib/sproutcore/builders/html.rb +2 -1
  98. data/lib/sproutcore/builders/minify.rb +4 -35
  99. data/lib/sproutcore/builders/module.rb +38 -1
  100. data/lib/sproutcore/builders/split.rb +63 -0
  101. data/lib/sproutcore/builders/strings.rb +7 -1
  102. data/lib/sproutcore/helpers.rb +1 -1
  103. data/lib/sproutcore/helpers/css_split.rb +190 -0
  104. data/lib/sproutcore/helpers/entry_sorter.rb +2 -0
  105. data/lib/sproutcore/helpers/minifier.rb +40 -16
  106. data/lib/sproutcore/helpers/static_helper.rb +35 -17
  107. data/lib/sproutcore/models/manifest.rb +26 -0
  108. data/lib/sproutcore/models/target.rb +12 -1
  109. data/lib/sproutcore/rack.rb +1 -0
  110. data/lib/sproutcore/rack/proxy.rb +244 -225
  111. data/lib/sproutcore/rack/restrict_ip.rb +67 -0
  112. data/lib/sproutcore/rack/service.rb +8 -2
  113. data/lib/sproutcore/tools.rb +102 -46
  114. data/lib/sproutcore/tools/build.rb +91 -43
  115. data/lib/sproutcore/tools/gen.rb +2 -3
  116. data/lib/sproutcore/tools/manifest.rb +22 -16
  117. data/lib/sproutcore/tools/server.rb +21 -0
  118. data/spec/buildtasks/helpers/accept_list +22 -0
  119. data/spec/buildtasks/helpers/accept_list.rb +128 -0
  120. data/spec/buildtasks/helpers/list.json +11 -0
  121. data/spec/buildtasks/manifest/prepare_build_tasks/chance_2x_spec.rb +1 -39
  122. data/spec/buildtasks/manifest/prepare_build_tasks/chance_spec.rb +0 -38
  123. data/spec/buildtasks/manifest/prepare_build_tasks/combine_spec.rb +4 -4
  124. data/spec/buildtasks/manifest/prepare_build_tasks/module_spec.rb +2 -2
  125. data/spec/buildtasks/manifest/prepare_build_tasks/packed_2x_indirect_spec.rb +7 -16
  126. data/spec/buildtasks/manifest/prepare_build_tasks/packed_2x_spec.rb +7 -17
  127. data/spec/buildtasks/manifest/prepare_build_tasks/packed_spec.rb +11 -6
  128. data/spec/fixtures/builder_tests/Buildfile +2 -1
  129. data/spec/fixtures/builder_tests/apps/module_test/modules/required_module/core.js +0 -0
  130. data/spec/lib/builders/module_spec.rb +1 -1
  131. data/spec/spec_helper.rb +1 -0
  132. data/sproutcore.gemspec +4 -9
  133. data/vendor/chance/lib/chance.rb +25 -6
  134. data/vendor/chance/lib/chance/factory.rb +45 -0
  135. data/vendor/chance/lib/chance/instance.rb +173 -28
  136. data/vendor/chance/lib/chance/instance/data_url.rb +0 -29
  137. data/vendor/chance/lib/chance/instance/slicing.rb +57 -4
  138. data/vendor/chance/lib/chance/instance/spriting.rb +112 -21
  139. data/vendor/chance/lib/chance/parser.rb +80 -52
  140. data/vendor/sproutcore/SCCompiler.jar +0 -0
  141. data/vendor/sproutcore/lib/args4j-2.0.12.jar +0 -0
  142. data/vendor/sproutcore/lib/yuicompressor-2.4.2.jar +0 -0
  143. metadata +84 -25
@@ -0,0 +1,347 @@
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('views/core_scroller');
9
+
10
+ /** @class
11
+ Implements a custom desktop-like scroller view that handles
12
+ your basic scrollbar events:
13
+
14
+ - Arrow buttons for incremental scrolling in either direction.
15
+ - Clicking in the track to incrementally jump to a location.
16
+ - CTL+Click to jump immediately to a location.
17
+ - A draggable scroll thumb.
18
+
19
+ @extends SC.CoreScrollerView
20
+ @since SproutCore 1.6
21
+ */
22
+ SC.DesktopScrollerView = SC.CoreScrollerView.extend(
23
+ /** @scope SC.DesktopScrollerView.prototype */{
24
+
25
+ /**
26
+ @type String
27
+ @default 'desktopScrollerRenderDelegate'
28
+ */
29
+ renderDelegateName: 'desktopScrollerRenderDelegate',
30
+
31
+ // ..........................................................
32
+ // MOUSE EVENTS
33
+ //
34
+
35
+ /** @private
36
+ Returns the value for a position within the scroller's frame.
37
+ */
38
+ valueForPosition: function (pos) {
39
+ return ((pos - (this.get('capLength') - this.get('capOverlap'))) /
40
+ (this.get('trackLength') - this.get('thumbLength'))) * this.get('maximum');
41
+ },
42
+
43
+ /** @private
44
+ Handles mouse down events and adjusts the value property depending where
45
+ the user clicked.
46
+
47
+ If the control is disabled, we ignore all mouse input.
48
+
49
+ If the user clicks the thumb, we note the position of the mouse event but
50
+ do not take further action until they begin to drag.
51
+
52
+ If the user clicks the track, we adjust the value a page at a time, unless
53
+ alt is pressed, in which case we scroll to that position.
54
+
55
+ If the user clicks the buttons, we adjust the value by a fixed amount, unless
56
+ alt is pressed, in which case we adjust by a page.
57
+
58
+ If the user clicks and holds on either the track or buttons, those actions
59
+ are repeated until they release the mouse button.
60
+
61
+ @param evt {SC.Event} the mousedown event
62
+ */
63
+ mouseDown: function (evt) {
64
+ if (!this.get('isEnabled')) return NO;
65
+
66
+ var target = evt.target,
67
+ thumbPosition = this.get('thumbPosition'),
68
+ value, clickLocation, clickOffset,
69
+ scrollerLength = this.get('scrollerLength');
70
+
71
+ // Determine the subcontrol that was clicked
72
+ if (target.className.indexOf('thumb') >= 0) {
73
+ // Convert the mouseDown coordinates to the view's coordinates
74
+ clickLocation = this.convertFrameFromView({ x: evt.pageX,
75
+ y: evt.pageY });
76
+
77
+ clickLocation.x -= thumbPosition;
78
+ clickLocation.y -= thumbPosition;
79
+
80
+ // Store the starting state so we know how much to adjust the
81
+ // thumb when the user drags
82
+ this._thumbDragging = YES;
83
+ this._thumbOffset = clickLocation;
84
+ this._mouseDownLocation = { x: evt.pageX,
85
+ y: evt.pageY };
86
+ this._thumbPositionAtDragStart = this.get('thumbPosition');
87
+ this._valueAtDragStart = this.get("value");
88
+
89
+ // User clicked the up/left button; decrement the value by a fixed amount or page size
90
+ } else if (target.className.indexOf('button-top') >= 0) {
91
+ this.decrementProperty('value', 30);
92
+ this.makeButtonActive('.button-top');
93
+
94
+ // start a timer that will continue to fire until mouseUp is called
95
+ this.startMouseDownTimer('scrollUp');
96
+ this._isScrollingUp = YES;
97
+
98
+ // User clicked the down/right button; increment the value by a fixed amount
99
+ } else if (target.className.indexOf('button-bottom') >= 0) {
100
+ this.incrementProperty('value', 30);
101
+ this.makeButtonActive('.button-bottom');
102
+
103
+ // start a timer that will continue to fire until mouseUp is called
104
+ this.startMouseDownTimer('scrollDown');
105
+ this._isScrollingDown = YES;
106
+
107
+ // User clicked in the track
108
+ } else {
109
+ var scrollToClick = this.get("shouldScrollToClick"),
110
+ trackLength = this.get('trackLength'),
111
+ thumbLength = this.get('thumbLength'),
112
+ frame = this.convertFrameFromView({ x: evt.pageX, y: evt.pageY }),
113
+ mousePosition;
114
+
115
+ if (evt.altKey) scrollToClick = !scrollToClick;
116
+
117
+ switch (this.get('layoutDirection')) {
118
+ case SC.LAYOUT_VERTICAL:
119
+ this._mouseDownLocation = mousePosition = frame.y;
120
+ break;
121
+ case SC.LAYOUT_HORIZONTAL:
122
+ this._mouseDownLocation = mousePosition = frame.x;
123
+ break;
124
+ }
125
+
126
+ if (scrollToClick) {
127
+ this.set('value', this.valueForPosition(mousePosition - (thumbLength / 2)));
128
+
129
+ // and start a normal mouse down
130
+ thumbPosition = this.get('thumbPosition');
131
+
132
+ this._thumbDragging = YES;
133
+ this._thumbOffset = { x: frame.x - thumbPosition,
134
+ y: frame.y - thumbPosition };
135
+ this._mouseDownLocation = { x: evt.pageX,
136
+ y: evt.pageY };
137
+ this._thumbPositionAtDragStart = thumbPosition;
138
+ this._valueAtDragStart = this.get("value");
139
+
140
+ // Move the thumb up or down a page depending on whether the click
141
+ // was above or below the thumb
142
+ } else if (mousePosition < thumbPosition) {
143
+ this.decrementProperty('value', scrollerLength);
144
+ this.startMouseDownTimer('page');
145
+
146
+ } else {
147
+ this.incrementProperty('value', scrollerLength);
148
+ this.startMouseDownTimer('page');
149
+ }
150
+ }
151
+
152
+ return YES;
153
+ },
154
+
155
+ /** @private
156
+ When the user releases the mouse button, remove any active
157
+ state from the button controls, and cancel any outstanding
158
+ timers.
159
+
160
+ @param evt {SC.Event} the mousedown event
161
+ */
162
+ mouseUp: function (evt) {
163
+ var active = this._scs_buttonActive,
164
+ ret = NO, timer;
165
+
166
+ // If we have an element that was set as active in mouseDown,
167
+ // remove its active state
168
+ if (active) {
169
+ active.removeClass('active');
170
+ ret = YES;
171
+ }
172
+
173
+ // Stop firing repeating events after mouseup
174
+ timer = this._mouseDownTimer;
175
+ if (timer) {
176
+ timer.invalidate();
177
+ this._mouseDownTimer = null;
178
+ }
179
+
180
+ this._thumbDragging = NO;
181
+ this._isScrollingDown = NO;
182
+ this._isScrollingUp = NO;
183
+
184
+ return ret;
185
+ },
186
+
187
+ /** @private
188
+ If the user began the drag on the thumb, we calculate the difference
189
+ between the mouse position at click and where it is now. We then
190
+ offset the thumb by that amount, within the bounds of the track.
191
+
192
+ If the user began scrolling up/down using the buttons, this will track
193
+ what component they are currently over, changing the scroll direction.
194
+
195
+ @param evt {SC.Event} the mousedragged event
196
+ */
197
+ mouseDragged: function (evt) {
198
+ var value, length, delta, thumbPosition,
199
+ target = evt.target,
200
+ thumbPositionAtDragStart = this._thumbPositionAtDragStart,
201
+ isScrollingUp = this._isScrollingUp,
202
+ isScrollingDown = this._isScrollingDown,
203
+ active = this._scs_buttonActive,
204
+ timer;
205
+
206
+ // Only move the thumb if the user clicked on the thumb during mouseDown
207
+ if (this._thumbDragging) {
208
+ switch (this.get('layoutDirection')) {
209
+ case SC.LAYOUT_VERTICAL:
210
+ delta = (evt.pageY - this._mouseDownLocation.y);
211
+ break;
212
+ case SC.LAYOUT_HORIZONTAL:
213
+ delta = (evt.pageX - this._mouseDownLocation.x);
214
+ break;
215
+ }
216
+
217
+ thumbPosition = thumbPositionAtDragStart + delta;
218
+ length = this.get('trackLength') - this.get('thumbLength');
219
+ this.set('value', Math.round( (thumbPosition/length) * this.get('maximum')));
220
+
221
+ } else if (isScrollingUp || isScrollingDown) {
222
+ var nowScrollingUp = NO, nowScrollingDown = NO;
223
+
224
+ var topButtonRect = this.$('.button-top')[0].getBoundingClientRect();
225
+ var bottomButtonRect = this.$('.button-bottom')[0].getBoundingClientRect();
226
+
227
+ switch (this.get('layoutDirection')) {
228
+ case SC.LAYOUT_VERTICAL:
229
+ nowScrollingUp = (evt.clientY < topButtonRect.bottom);
230
+ break;
231
+ case SC.LAYOUT_HORIZONTAL:
232
+ nowScrollingUp = (evt.clientX < topButtonRect.right);
233
+ break;
234
+ }
235
+ nowScrollingDown = !nowScrollingUp;
236
+
237
+ if ((nowScrollingUp || nowScrollingDown) && nowScrollingUp !== isScrollingUp) {
238
+ // If we have an element that was set as active in mouseDown,
239
+ // remove its active state
240
+ if (active) active.removeClass('active');
241
+
242
+ // Stop firing repeating events after mouseup
243
+ this._mouseDownTimerAction = nowScrollingUp ? "scrollUp" : "scrollDown";
244
+
245
+ if (nowScrollingUp) {
246
+ this.makeButtonActive('.button-top');
247
+ } else if (nowScrollingDown) {
248
+ this.makeButtonActive('.button-bottom');
249
+ }
250
+
251
+ this._isScrollingUp = nowScrollingUp;
252
+ this._isScrollingDown = nowScrollingDown;
253
+ }
254
+ }
255
+
256
+ return YES;
257
+ },
258
+
259
+ mouseWheel: function (evt) {
260
+ var el = this.getPath('parentView.containerView.layer'),
261
+ rawEvent = evt.originalEvent;
262
+
263
+ if (el && rawEvent) {
264
+ try {
265
+ if (SC.typeOf(el.fireEvent) === SC.T_FUNCTION) { // IE
266
+ el.fireEvent(rawEvent.type, rawEvent);
267
+ } else { // W3C
268
+ el.dispatchEvent(rawEvent);
269
+ }
270
+ } catch (x) {
271
+ // Can't dispatch the event; give up.
272
+ }
273
+ }
274
+ },
275
+
276
+ /** @private
277
+ Starts a timer that fires after 300ms. This is called when the user
278
+ clicks a button or inside the track to move a page at a time. If they
279
+ continue holding the mouse button down, we want to repeat that action
280
+ after a small delay. This timer will be invalidated in mouseUp.
281
+
282
+ Specify "immediate" as YES if it should not wait.
283
+ */
284
+ startMouseDownTimer: function (action, immediate) {
285
+ this._mouseDownTimerAction = action;
286
+ this._mouseDownTimer = SC.Timer.schedule({
287
+ target: this,
288
+ action: this.mouseDownTimerDidFire,
289
+ interval: immediate ? 0 : 300
290
+ });
291
+ },
292
+
293
+ /** @private
294
+ Called by the mousedown timer. This method determines the initial
295
+ user action and repeats it until the timer is invalidated in mouseUp.
296
+ */
297
+ mouseDownTimerDidFire: function () {
298
+ var scrollerLength = this.get('scrollerLength'),
299
+ mouseLocation = SC.device.get('mouseLocation'),
300
+ thumbPosition = this.get('thumbPosition'),
301
+ thumbLength = this.get('thumbLength'),
302
+ timerInterval = 50;
303
+
304
+ switch (this.get('layoutDirection')) {
305
+ case SC.LAYOUT_VERTICAL:
306
+ mouseLocation = this.convertFrameFromView(mouseLocation).y;
307
+ break;
308
+ case SC.LAYOUT_HORIZONTAL:
309
+ mouseLocation = this.convertFrameFromView(mouseLocation).x;
310
+ break;
311
+ }
312
+
313
+ switch (this._mouseDownTimerAction) {
314
+ case 'scrollDown':
315
+ this.incrementProperty('value', 30);
316
+ break;
317
+ case 'scrollUp':
318
+ this.decrementProperty('value', 30);
319
+ break;
320
+ case 'page':
321
+ timerInterval = 150;
322
+ if (mouseLocation < thumbPosition) {
323
+ this.decrementProperty('value', scrollerLength);
324
+ } else if (mouseLocation > thumbPosition+thumbLength) {
325
+ this.incrementProperty('value', scrollerLength);
326
+ }
327
+ }
328
+
329
+ this._mouseDownTimer = SC.Timer.schedule({
330
+ target: this,
331
+ action: this.mouseDownTimerDidFire,
332
+ interval: timerInterval
333
+ });
334
+ },
335
+
336
+ /** @private
337
+ Given a selector, finds the corresponding DOM element and adds
338
+ the 'active' class name. Also stores the returned element so that
339
+ the 'active' class name can be removed during mouseup.
340
+
341
+ @param {String} the selector to find
342
+ */
343
+ makeButtonActive: function (selector) {
344
+ this._scs_buttonActive = this.$(selector).addClass('active');
345
+ }
346
+
347
+ });
@@ -0,0 +1,15 @@
1
+ // ==========================================================================
2
+ // Project: SproutCore - JavaScript Application Framework
3
+ // Copyright: ©2006-2011 Strobe Inc. and contributors.
4
+ // License: Licensed under MIT license (see license.js)
5
+ // ==========================================================================
6
+
7
+ sc_require('views/desktop/scroll');
8
+ sc_require('views/touch/scroll');
9
+
10
+ SC.ScrollView = SC.platform.touch ? SC.TouchScrollView : SC.DesktopScrollView;
11
+
12
+ // Spoofed browsers should use TouchScrollView.
13
+ if (SC.browser && SC.platform && SC.browser.mobileSafari && !SC.platform.touch) {
14
+ SC.ScrollView = SC.TouchScrollView;
15
+ }
@@ -0,0 +1,10 @@
1
+ // ==========================================================================
2
+ // Project: SproutCore - JavaScript Application Framework
3
+ // Copyright: ©2006-2011 Strobe Inc. and contributors.
4
+ // License: Licensed under MIT license (see license.js)
5
+ // ==========================================================================
6
+
7
+ sc_require('views/desktop/scroller');
8
+
9
+ // Legacy ScrollerView === DesktopScrollerView
10
+ SC.ScrollerView = SC.DesktopScrollerView;
@@ -0,0 +1,804 @@
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('views/core_scroll');
9
+ sc_require('views/touch/scroller');
10
+
11
+ /**
12
+ @static
13
+ @type Number
14
+ @default 0.95
15
+ */
16
+ SC.NORMAL_SCROLL_DECELERATION = 0.95;
17
+
18
+ /**
19
+ @static
20
+ @type Number
21
+ @default 0.85
22
+ */
23
+ SC.FAST_SCROLL_DECELERATION = 0.85;
24
+
25
+
26
+ /** @class
27
+ Implements touch events for a scroll view
28
+
29
+ Since the iPad doesn't allow native one-finger scrolling,
30
+ this has to do all of the work of implementing the solution
31
+ over again.
32
+
33
+ In addition to the one-finger scrolling, this view implements
34
+ edge-resistance.
35
+
36
+ Note that incremental rendering is done after the scrolling
37
+ has completely finished, which makes for a wait-and-see
38
+ experience.
39
+
40
+ @extends SC.CoreScrollerView
41
+ */
42
+ SC.TouchScrollView = SC.CoreScrollView.extend(
43
+ /** @scope SC.TouchScrollView.prototype */{
44
+
45
+ /**
46
+ Use this to overlay the vertical scroller.
47
+
48
+ This ensures that the container frame will not resize to accomodate the
49
+ vertical scroller, hence overlaying the scroller on top of
50
+ the container.
51
+
52
+ @type Boolean
53
+ @default YES
54
+ */
55
+ verticalOverlay: YES,
56
+
57
+ /**
58
+ Use this to overlay the horizontal scroller.
59
+
60
+ This ensures that the container frame will not resize to accomodate the
61
+ horizontal scroller, hence overlaying the scroller on top of
62
+ the container
63
+
64
+ @type Boolean
65
+ @default YES
66
+ */
67
+ horizontalOverlay: YES,
68
+
69
+ /**
70
+ @type SC.CoreScrollerView
71
+ @default SC.TouchScrollerView
72
+ */
73
+ horizontalScrollerView: SC.TouchScrollerView,
74
+
75
+ /**
76
+ @type SC.CoreScrollerView
77
+ @default SC.TouchScrollerView
78
+ */
79
+ verticalScrollerView: SC.TouchScrollerView,
80
+
81
+ // ..........................................................
82
+ // TOUCH SUPPORT
83
+ //
84
+
85
+ /**
86
+ @type Boolean
87
+ @default YES
88
+ @readOnly
89
+ */
90
+ acceptsMultitouch: YES,
91
+
92
+ /**
93
+ The scroll deceleration rate.
94
+
95
+ @type Number
96
+ @default SC.NORMAL_SCROLL_DECELERATION
97
+ */
98
+ decelerationRate: SC.NORMAL_SCROLL_DECELERATION,
99
+
100
+ /**
101
+ If YES, bouncing will always be enabled in the horizontal direction, even if the content
102
+ is smaller or the same size as the view.
103
+
104
+ @type Boolean
105
+ @default NO
106
+ */
107
+ alwaysBounceHorizontal: NO,
108
+
109
+ /**
110
+ If NO, bouncing will not be enabled in the vertical direction when the content is smaller
111
+ or the same size as the scroll view.
112
+
113
+ @type Boolean
114
+ @default YES
115
+ */
116
+ alwaysBounceVertical: YES,
117
+
118
+ /**
119
+ Whether to delay touches from passing through to the content.
120
+
121
+ @type Boolean
122
+ @default YES
123
+ */
124
+ delaysContentTouches: YES,
125
+
126
+ /** @private */
127
+ _applyCSSTransforms: function (layer) {
128
+ var transform = "";
129
+ this.updateScale(this._scale);
130
+ transform += 'translate3d('+ -this._scroll_horizontalScrollOffset +'px, '+ -Math.round(this._scroll_verticalScrollOffset)+'px,0) ';
131
+ transform += this._scale_css;
132
+ if (layer) {
133
+ layer.style.webkitTransform = transform;
134
+ layer.style.webkitTransformOrigin = "top left";
135
+ }
136
+ },
137
+
138
+ /** @private */
139
+ captureTouch: function (touch) {
140
+ return YES;
141
+ },
142
+
143
+ /** @private */
144
+ touchGeneration: 0,
145
+
146
+ /** @private */
147
+ touchStart: function (touch) {
148
+ var generation = ++this.touchGeneration;
149
+ if (!this.tracking && this.get("delaysContentTouches")) {
150
+ this.invokeLater(this.beginTouchesInContent, 150, generation);
151
+ } else if (!this.tracking) {
152
+ // NOTE: We still have to delay because we don't want to call touchStart
153
+ // while touchStart is itself being called...
154
+ this.invokeLater(this.beginTouchesInContent, 1, generation);
155
+ }
156
+ this.beginTouchTracking(touch, YES);
157
+ return YES;
158
+ },
159
+
160
+ /** @private */
161
+ beginTouchesInContent: function (gen) {
162
+ if (gen !== this.touchGeneration) return;
163
+
164
+ var touch = this.touch, itemView;
165
+ if (touch && this.tracking && !this.dragging && !touch.touch.scrollHasEnded) {
166
+ // try to capture the touch
167
+ touch.touch.captureTouch(this, YES);
168
+
169
+ if (!touch.touch.touchResponder) {
170
+ // if it DIDN'T WORK!!!!!
171
+ // then we need to take possession again.
172
+ touch.touch.makeTouchResponder(this);
173
+ // Otherwise, it did work, and if we had a pending scroll end, we must do it now
174
+ } else if (touch.needsScrollEnd) {
175
+ this._touchScrollDidEnd();
176
+ }
177
+ }
178
+ },
179
+
180
+ /** @private
181
+ This will notify anything that's incrementally rendering while
182
+ scrolling, instead of having unrendered views.
183
+ */
184
+ _sctsv_setOffset: function (x, y) {
185
+ if (!SC.none(x)) {
186
+ this._scroll_horizontalScrollOffset = x;
187
+ }
188
+
189
+ if (!SC.none(y)) {
190
+ this._scroll_verticalScrollOffset = y;
191
+ }
192
+ },
193
+
194
+ /** @private
195
+ Initializes the start state of the gesture.
196
+
197
+ We keep information about the initial location of the touch so we can
198
+ disambiguate between a tap and a drag.
199
+
200
+ @param {Event} evt
201
+ */
202
+ beginTouchTracking: function (touch, starting) {
203
+ var avg = touch.averagedTouchesForView(this, starting);
204
+
205
+ var verticalScrollOffset = this._scroll_verticalScrollOffset || 0,
206
+ horizontalScrollOffset = this._scroll_horizontalScrollOffset || 0,
207
+ startClipOffsetX = horizontalScrollOffset,
208
+ startClipOffsetY = verticalScrollOffset,
209
+ needsScrollEnd = NO;
210
+
211
+ this.willScroll(this);
212
+
213
+ if (this.touch && this.touch.timeout) {
214
+ // clear the timeout
215
+ clearTimeout(this.touch.timeout);
216
+ this.touch.timeout = null;
217
+
218
+ // get the scroll offsets
219
+ startClipOffsetX = this.touch.startClipOffset.x;
220
+ startClipOffsetY = this.touch.startClipOffset.y;
221
+ needsScrollEnd = YES;
222
+ }
223
+
224
+ // calculate container+content width/height
225
+ var view = this.get('contentView') ;
226
+ var contentWidth = view ? view.get('frame').width : 0,
227
+ contentHeight = view ? view.get('frame').height : 0;
228
+
229
+ if (view.calculatedWidth && view.calculatedWidth!==0) contentWidth = view.get('calculatedWidth');
230
+ if (view.calculatedHeight && view.calculatedHeight !==0) contentHeight = view.get('calculatedHeight');
231
+
232
+ var containerWidth = this.get('containerView').get('frame').width,
233
+ containerHeight = this.get('containerView').get('frame').height;
234
+
235
+ // calculate position in content
236
+ var globalFrame = this.convertFrameToView(this.get("frame"), null),
237
+ positionInContentX = (horizontalScrollOffset + (avg.x - globalFrame.x)) / this._scale,
238
+ positionInContentY = (verticalScrollOffset + (avg.y - globalFrame.y)) / this._scale;
239
+
240
+ this.touch = {
241
+ startTime: touch.timeStamp,
242
+ notCalculated: YES,
243
+
244
+ enableScrolling: {
245
+ x: contentWidth * this._scale > containerWidth || this.get("alwaysBounceHorizontal"),
246
+ y: contentHeight * this._scale > containerHeight || this.get("alwaysBounceVertical")
247
+ },
248
+ scrolling: { x: NO, y: NO },
249
+
250
+ enableBouncing: SC.platform.bounceOnScroll,
251
+
252
+ // offsets and velocities
253
+ startClipOffset: { x: startClipOffsetX, y: startClipOffsetY },
254
+ lastScrollOffset: { x: horizontalScrollOffset, y: verticalScrollOffset },
255
+ startTouchOffset: { x: avg.x, y: avg.y },
256
+ scrollVelocity: { x: 0, y: 0 },
257
+
258
+ startTouchOffsetInContent: { x: positionInContentX, y: positionInContentY },
259
+
260
+ containerSize: { width: containerWidth, height: containerHeight },
261
+ contentSize: { width: contentWidth, height: contentHeight },
262
+
263
+ startScale: this._scale,
264
+ startDistance: avg.d,
265
+ canScale: this.get("canScale") && SC.platform.pinchToZoom,
266
+ minimumScale: this.get("minimumScale"),
267
+ maximumScale: this.get("maximumScale"),
268
+
269
+ globalFrame: globalFrame,
270
+
271
+ // cache some things
272
+ layer: this.get("contentView").get('layer'),
273
+
274
+ // some constants
275
+ resistanceCoefficient: 0.998,
276
+ resistanceAsymptote: 320,
277
+ decelerationFromEdge: 0.05,
278
+ accelerationToEdge: 0.1,
279
+
280
+ // how much percent of the other drag direction you must drag to start dragging that direction too.
281
+ scrollTolerance: { x: 15, y: 15 },
282
+ scaleTolerance: 5,
283
+ secondaryScrollTolerance: 30,
284
+ scrollLock: 500,
285
+
286
+ decelerationRate: this.get("decelerationRate"),
287
+
288
+ // general status
289
+ lastEventTime: touch.timeStamp,
290
+
291
+ // the touch used
292
+ touch: (starting ? touch : (this.touch ? this.touch.touch : null)),
293
+
294
+ // needsScrollEnd will cause a scrollDidEnd even if this particular touch does not start a scroll.
295
+ // the reason for this is because we don't want to say we've stopped scrolling just because we got
296
+ // another touch, but simultaneously, we still need to send a touch end eventually.
297
+ // there are two cases in which this will be used:
298
+ //
299
+ // 1. If the touch was sent to content touches (in which case we will not be scrolling)
300
+ // 2. If the touch ends before scrolling starts (no scrolling then, either)
301
+ needsScrollEnd: needsScrollEnd
302
+ };
303
+
304
+ if (!this.tracking) {
305
+ this.tracking = YES;
306
+ this.dragging = NO;
307
+ }
308
+ },
309
+
310
+ /** @private */
311
+ _adjustForEdgeResistance: function (offset, minOffset, maxOffset, resistanceCoefficient, asymptote) {
312
+ var distanceFromEdge;
313
+
314
+ // find distance from edge
315
+ if (offset < minOffset) distanceFromEdge = offset - minOffset;
316
+ else if (offset > maxOffset) distanceFromEdge = maxOffset - offset;
317
+ else return offset;
318
+
319
+ // manipulate logarithmically
320
+ distanceFromEdge = Math.pow(resistanceCoefficient, Math.abs(distanceFromEdge)) * asymptote;
321
+
322
+ // adjust mathematically
323
+ if (offset < minOffset) distanceFromEdge = distanceFromEdge - asymptote;
324
+ else distanceFromEdge = -distanceFromEdge + asymptote;
325
+
326
+ // generate final value
327
+ return Math.min(Math.max(minOffset, offset), maxOffset) + distanceFromEdge;
328
+ },
329
+
330
+ /** @private */
331
+ touchesDragged: function (evt, touches) {
332
+ var avg = evt.averagedTouchesForView(this);
333
+ this.updateTouchScroll(avg.x, avg.y, avg.d, evt.timeStamp);
334
+ },
335
+
336
+ /** @private */
337
+ updateTouchScroll: function (touchX, touchY, distance, timeStamp) {
338
+ // get some vars
339
+ var touch = this.touch,
340
+ touchXInFrame = touchX - touch.globalFrame.x,
341
+ touchYInFrame = touchY - touch.globalFrame.y,
342
+ offsetY,
343
+ maxOffsetY,
344
+ offsetX,
345
+ maxOffsetX,
346
+ minOffsetX, minOffsetY;
347
+
348
+ // calculate new position in content
349
+ var positionInContentX = ((this._scroll_horizontalScrollOffset||0) + touchXInFrame) / this._scale,
350
+ positionInContentY = ((this._scroll_verticalScrollOffset||0) + touchYInFrame) / this._scale;
351
+
352
+ // calculate deltas
353
+ var deltaX = positionInContentX - touch.startTouchOffsetInContent.x,
354
+ deltaY = positionInContentY - touch.startTouchOffsetInContent.y;
355
+
356
+ var isDragging = touch.dragging;
357
+ if (!touch.scrolling.x && Math.abs(deltaX) > touch.scrollTolerance.x && touch.enableScrolling.x) {
358
+ // say we are scrolling
359
+ isDragging = YES;
360
+ touch.scrolling.x = YES;
361
+ touch.scrollTolerance.y = touch.secondaryScrollTolerance;
362
+
363
+ // reset position
364
+ touch.startTouchOffset.x = touchX;
365
+ deltaX = 0;
366
+ }
367
+ if (!touch.scrolling.y && Math.abs(deltaY) > touch.scrollTolerance.y && touch.enableScrolling.y) {
368
+ // say we are scrolling
369
+ isDragging = YES;
370
+ touch.scrolling.y = YES;
371
+ touch.scrollTolerance.x = touch.secondaryScrollTolerance;
372
+
373
+ // reset position
374
+ touch.startTouchOffset.y = touchY;
375
+ deltaY = 0;
376
+ }
377
+
378
+ // handle scroll start
379
+ if (isDragging && !touch.dragging) {
380
+ touch.dragging = YES;
381
+ this.dragging = YES;
382
+ this._touchScrollDidStart();
383
+ }
384
+
385
+ // calculate new offset
386
+ if (!touch.scrolling.x && !touch.scrolling.y && !touch.canScale) return;
387
+ if (touch.scrolling.x && !touch.scrolling.y) {
388
+ if (deltaX > touch.scrollLock && !touch.scrolling.y) touch.enableScrolling.y = NO;
389
+ }
390
+ if (touch.scrolling.y && !touch.scrolling.x) {
391
+ if (deltaY > touch.scrollLock && !touch.scrolling.x) touch.enableScrolling.x = NO;
392
+ }
393
+
394
+ // handle scaling through pinch gesture
395
+ if (touch.canScale) {
396
+
397
+ var startDistance = touch.startDistance, dd = distance - startDistance;
398
+ if (Math.abs(dd) > touch.scaleTolerance) {
399
+ touch.scrolling.y = YES; // if you scale, you can scroll.
400
+ touch.scrolling.x = YES;
401
+
402
+ // we want to say something that was the startDistance away from each other should now be
403
+ // distance away. So, if we are twice as far away as we started...
404
+ var scale = touch.startScale * (distance / Math.max(startDistance, 50));
405
+
406
+ var newScale = this._adjustForEdgeResistance(scale, touch.minimumScale, touch.maximumScale, touch.resistanceCoefficient, touch.resistanceAsymptote);
407
+ this.dragging = YES;
408
+ this._scale = newScale;
409
+ var newPositionInContentX = positionInContentX * this._scale,
410
+ newPositionInContentY = positionInContentY * this._scale;
411
+ }
412
+ }
413
+
414
+ // these do exactly what they sound like. So, this comment is just to
415
+ // block off the code a bit
416
+ // In english, these calculate the minimum X/Y offsets
417
+ minOffsetX = this.minimumScrollOffset(touch.contentSize.width * this._scale,
418
+ touch.containerSize.width, this.get("horizontalAlign"));
419
+ minOffsetY = this.minimumScrollOffset(touch.contentSize.height * this._scale,
420
+ touch.containerSize.height, this.get("verticalAlign"));
421
+
422
+ // and now, maximum...
423
+ maxOffsetX = this.maximumScrollOffset(touch.contentSize.width * this._scale,
424
+ touch.containerSize.width, this.get("horizontalAlign"));
425
+ maxOffsetY = this.maximumScrollOffset(touch.contentSize.height * this._scale,
426
+ touch.containerSize.height, this.get("verticalAlign"));
427
+
428
+ // So, the following is the completely written out algebra:
429
+ // (offsetY + touchYInFrame) / this._scale = touch.startTouchOffsetInContent.y
430
+ // offsetY + touchYInFrame = touch.startTouchOffsetInContent.y * this._scale;
431
+ // offsetY = touch.startTouchOffset * this._scale - touchYInFrame
432
+
433
+ // and the result applied:
434
+ offsetX = touch.startTouchOffsetInContent.x * this._scale - touchXInFrame;
435
+ offsetY = touch.startTouchOffsetInContent.y * this._scale - touchYInFrame;
436
+
437
+
438
+ // we need to adjust for edge resistance, or, if bouncing is disabled, just stop flat.
439
+ if (touch.enableBouncing) {
440
+ offsetX = this._adjustForEdgeResistance(offsetX, minOffsetX, maxOffsetX, touch.resistanceCoefficient, touch.resistanceAsymptote);
441
+ offsetY = this._adjustForEdgeResistance(offsetY, minOffsetY, maxOffsetY, touch.resistanceCoefficient, touch.resistanceAsymptote);
442
+ } else {
443
+ offsetX = Math.max(minOffsetX, Math.min(maxOffsetX, offsetX));
444
+ offsetY = Math.max(minOffsetY, Math.min(maxOffsetY, offsetY));
445
+ }
446
+
447
+ // and now, _if_ scrolling is enabled, set the new coordinates
448
+ if (touch.scrolling.x) this._sctsv_setOffset(offsetX, null);
449
+ if (touch.scrolling.y) this._sctsv_setOffset(null, offsetY);
450
+
451
+ // and apply the CSS transforms.
452
+ this._applyCSSTransforms(touch.layer);
453
+ this._touchScrollDidChange();
454
+
455
+
456
+ // prepare for momentum scrolling by calculating the momentum.
457
+ if ((timeStamp - touch.lastEventTime) >= 1 || touch.notCalculated) {
458
+ touch.notCalculated = NO;
459
+ var horizontalOffset = this._scroll_horizontalScrollOffset;
460
+ var verticalOffset = this._scroll_verticalScrollOffset;
461
+
462
+ touch.scrollVelocity.x = (horizontalOffset - touch.lastScrollOffset.x) /
463
+ Math.max(1, timeStamp - touch.lastEventTime); // in px per ms
464
+ touch.scrollVelocity.y = (verticalOffset - touch.lastScrollOffset.y) /
465
+ Math.max(1, timeStamp - touch.lastEventTime); // in px per ms
466
+ touch.lastScrollOffset.x = horizontalOffset;
467
+ touch.lastScrollOffset.y = verticalOffset;
468
+ touch.lastEventTime = timeStamp;
469
+ }
470
+ },
471
+
472
+ /** @private */
473
+ touchEnd: function (touch) {
474
+ var touchStatus = this.touch,
475
+ avg = touch.averagedTouchesForView(this);
476
+
477
+ touch.scrollHasEnded = YES;
478
+ if (avg.touchCount > 0) {
479
+ this.beginTouchTracking(touch, NO);
480
+ } else {
481
+ if (this.dragging) {
482
+ touchStatus.dragging = NO;
483
+
484
+ // reset last event time
485
+ touchStatus.lastEventTime = touch.timeStamp;
486
+
487
+ this.startDecelerationAnimation();
488
+ } else {
489
+ // well. The scrolling stopped. Let us tell everyone if there was a pending one that this non-drag op interrupted.
490
+ if (touchStatus.needsScrollEnd) this._touchScrollDidEnd();
491
+
492
+ // this part looks weird, but it is actually quite simple.
493
+ // First, we send the touch off for capture+starting again, but telling it to return to us
494
+ // if nothing is found or if it is released.
495
+ touch.captureTouch(this, YES);
496
+
497
+ // if we went anywhere, did anything, etc., call end()
498
+ if (touch.touchResponder && touch.touchResponder !== this) {
499
+ touch.end();
500
+ } else if (!touch.touchResponder || touch.touchResponder === this) {
501
+ // if it was released to us or stayed with us the whole time, or is for some
502
+ // wacky reason empty (in which case it is ours still). If so, and there is a next responder,
503
+ // relay to that.
504
+
505
+ if (touch.nextTouchResponder) touch.makeTouchResponder(touch.nextTouchResponder);
506
+ } else {
507
+ // in this case, the view that captured it and changed responder should have handled
508
+ // everything for us.
509
+ }
510
+
511
+ this.touch = null;
512
+ }
513
+
514
+ this.tracking = NO;
515
+ this.dragging = NO;
516
+ }
517
+ },
518
+
519
+ /** @private */
520
+ touchCancelled: function (touch) {
521
+ var touchStatus = this.touch,
522
+ avg = touch.averagedTouchesForView(this);
523
+
524
+ // if we are decelerating, we don't want to stop that. That would be bad. Because there's no point.
525
+ if (!this.touch || !this.touch.timeout) {
526
+ this.beginPropertyChanges();
527
+ this.set("scale", this._scale);
528
+ this.set("verticalScrollOffset", this._scroll_verticalScrollOffset);
529
+ this.set("horizontalScrollOffset", this._scroll_horizontalScrollOffset);
530
+ this.endPropertyChanges();
531
+ this.didScroll(this);
532
+ this.tracking = NO;
533
+
534
+ if (this.dragging) {
535
+ this._touchScrollDidEnd();
536
+ }
537
+
538
+ this.dragging = NO;
539
+ this.touch = null;
540
+ }
541
+ },
542
+
543
+ /** @private */
544
+ startDecelerationAnimation: function (evt) {
545
+ var touch = this.touch;
546
+ touch.decelerationVelocity = {
547
+ x: touch.scrollVelocity.x * 10,
548
+ y: touch.scrollVelocity.y * 10
549
+ };
550
+
551
+ this.decelerateAnimation();
552
+ },
553
+
554
+ /** @private
555
+ Does bounce calculations, adjusting velocity.
556
+
557
+ Bouncing is fun. Functions that handle it should have fun names,
558
+ don'tcha think?
559
+
560
+ P.S.: should this be named "bouncityBounce" instead?
561
+ */
562
+ bouncyBounce: function (velocity, value, minValue, maxValue, de, ac, additionalAcceleration) {
563
+ // we have 4 possible paths. On a higher level, we have two leaf paths that can be applied
564
+ // for either of two super-paths.
565
+ //
566
+ // The first path is if we are decelerating past an edge: in this case, this function must
567
+ // must enhance that deceleration. In this case, our math boils down to taking the amount
568
+ // by which we are past the edge, multiplying it by our deceleration factor, and reducing
569
+ // velocity by that amount.
570
+ //
571
+ // The second path is if we are not decelerating, but are still past the edge. In this case,
572
+ // we must start acceleration back _to_ the edge. The math here takes the distance we are from
573
+ // the edge, multiplies by the acceleration factor, and then performs two additional things:
574
+ // First, it speeds up the acceleration artificially with additionalAcceleration; this will
575
+ // make the stop feel more sudden, as it will still have this additional acceleration when it reaches
576
+ // the edge. Second, it ensures the result does not go past the final value, so we don't end up
577
+ // bouncing back and forth all crazy-like.
578
+ if (value < minValue) {
579
+ if (velocity < 0) velocity = velocity + ((minValue - value) * de);
580
+ else {
581
+ velocity = Math.min((minValue-value) * ac + additionalAcceleration, minValue - value - 0.01);
582
+ }
583
+ } else if (value > maxValue) {
584
+ if (velocity > 0) velocity = velocity - ((value - maxValue) * de);
585
+ else {
586
+ velocity = -Math.min((value - maxValue) * ac + additionalAcceleration, value - maxValue - 0.01);
587
+ }
588
+ }
589
+ return velocity;
590
+ },
591
+
592
+ /** @private */
593
+ decelerateAnimation: function () {
594
+ // get a bunch of properties. They are named well, so not much explanation of what they are...
595
+ // However, note maxOffsetX/Y takes into account the scale;
596
+ // also, newX/Y adds in the current deceleration velocity (the deceleration velocity will
597
+ // be changed later in this function).
598
+ var touch = this.touch,
599
+ scale = this._scale,
600
+ minOffsetX = this.minimumScrollOffset(touch.contentSize.width * this._scale,
601
+ touch.containerSize.width, this.get("horizontalAlign")),
602
+ minOffsetY = this.minimumScrollOffset(touch.contentSize.height * this._scale,
603
+ touch.containerSize.height, this.get("verticalAlign")),
604
+ maxOffsetX = this.maximumScrollOffset(touch.contentSize.width * this._scale,
605
+ touch.containerSize.width, this.get("horizontalAlign")),
606
+ maxOffsetY = this.maximumScrollOffset(touch.contentSize.height * this._scale,
607
+ touch.containerSize.height, this.get("verticalAlign")),
608
+
609
+ now = Date.now(),
610
+ t = Math.max(now - touch.lastEventTime, 1),
611
+
612
+ newX = this._scroll_horizontalScrollOffset + touch.decelerationVelocity.x * (t / 10),
613
+ newY = this._scroll_verticalScrollOffset + touch.decelerationVelocity.y * (t / 10);
614
+
615
+ var de = touch.decelerationFromEdge, ac = touch.accelerationToEdge;
616
+
617
+ // under a few circumstances, we may want to force a valid X/Y position.
618
+ // For instance, if bouncing is disabled, or if position was okay before
619
+ // adjusting scale.
620
+ var forceValidXPosition = !touch.enableBouncing, forceValidYPosition = !touch.enableBouncing;
621
+
622
+ // determine if position was okay before adjusting scale (which we do, in
623
+ // a lovely, animated way, for the scaled out/in too far bounce-back).
624
+ // if the position was okay, then we are going to make sure that we keep the
625
+ // position okay when adjusting the scale.
626
+ //
627
+ // Position OKness, here, referring to if the position is valid (within
628
+ // minimum and maximum scroll offsets)
629
+ if (newX >= minOffsetX && newX <= maxOffsetX) forceValidXPosition = YES;
630
+ if (newY >= minOffsetY && newY <= maxOffsetY) forceValidYPosition = YES;
631
+
632
+ // We are going to change scale in a moment, but the position should stay the
633
+ // same, if possible (unless it would be more jarring, as described above, in
634
+ // the case of starting with a valid position and ending with an invalid one).
635
+ //
636
+ // Because we are changing the scale, we need to make the position scale-neutral.
637
+ // we'll make it non-scale-neutral after applying scale.
638
+ //
639
+ // Question: might it be better to save the center position instead, so scaling
640
+ // bounces back around the center of the screen?
641
+ newX /= this._scale;
642
+ newY /= this._scale;
643
+
644
+ // scale velocity (amount to change) starts out at 0 each time, because
645
+ // it is calculated by how far out of bounds it is, rather than by the
646
+ // previous such velocity.
647
+ var sv = 0;
648
+
649
+ // do said calculation; we'll use the same bouncyBounce method used for everything
650
+ // else, but our adjustor that gives a minimum amount to change by and (which, as we'll
651
+ // discuss, is to make the stop feel slightly more like a stop), we'll leave at 0
652
+ // (scale doesn't really need it as much; if you disagree, at least come up with
653
+ // numbers more appropriate for scale than the ones for X/Y)
654
+ sv = this.bouncyBounce(sv, scale, touch.minimumScale, touch.maximumScale, de, ac, 0);
655
+
656
+ // add the amount to scale. This is linear, rather than multiplicative. If you think
657
+ // it should be multiplicative (or however you say that), come up with a new formula.
658
+ this._scale = scale = scale + sv;
659
+
660
+ // now we can convert newX/Y back to scale-specific coordinates...
661
+ newX *= this._scale;
662
+ newY *= this._scale;
663
+
664
+ // It looks very weird if the content started in-bounds, but the scale animation
665
+ // made it not be in bounds; it causes the position to animate snapping back, and,
666
+ // well, it looks very weird. It is more proper to just make sure it stays in a valid
667
+ // position. So, we'll determine the new maximum/minimum offsets, and then, if it was
668
+ // originally a valid position, we'll adjust the new position to a valid position as well.
669
+
670
+
671
+ // determine new max offset
672
+ minOffsetX = this.minimumScrollOffset(touch.contentSize.width * this._scale,
673
+ touch.containerSize.width, this.get("horizontalAlign"));
674
+ minOffsetY = this.minimumScrollOffset(touch.contentSize.height * this._scale,
675
+ touch.containerSize.height, this.get("verticalAlign"));
676
+ maxOffsetX = this.maximumScrollOffset(touch.contentSize.width * this._scale,
677
+ touch.containerSize.width, this.get("horizontalAlign"));
678
+ maxOffsetY = this.maximumScrollOffset(touch.contentSize.height * this._scale,
679
+ touch.containerSize.height, this.get("verticalAlign"));
680
+
681
+ // see if scaling messed up the X position (but ignore if 'tweren't right to begin with).
682
+ if (forceValidXPosition && (newX < minOffsetX || newX > maxOffsetX)) {
683
+ // Correct the position
684
+ newX = Math.max(minOffsetX, Math.min(newX, maxOffsetX));
685
+
686
+ // also, make the velocity be ZERO; it is obviously not needed...
687
+ touch.decelerationVelocity.x = 0;
688
+ }
689
+
690
+ // now the y
691
+ if (forceValidYPosition && (newY < minOffsetY || newY > maxOffsetY)) {
692
+ // again, correct it...
693
+ newY = Math.max(minOffsetY, Math.min(newY, maxOffsetY));
694
+
695
+ // also, make the velocity be ZERO; it is obviously not needed...
696
+ touch.decelerationVelocity.y = 0;
697
+ }
698
+
699
+
700
+ // now that we are done modifying the position, we may update the actual scroll
701
+ this._sctsv_setOffset(newX, newY);
702
+
703
+ this._applyCSSTransforms(touch.layer); // <- Does what it sounds like.
704
+
705
+ this._touchScrollDidChange();
706
+
707
+ // Now we have to adjust the velocities. The velocities are simple x and y numbers that
708
+ // get added to the scroll X/Y positions each frame.
709
+ // The default decay rate is .950 per frame. To achieve some semblance of accuracy, we
710
+ // make it to the power of the elapsed number of frames. This is not fully accurate,
711
+ // as this is applying the elapsed time between this frame and the previous time to
712
+ // modify the velocity for the next frame. My mind goes blank when I try to figure out
713
+ // a way to fix this (given that we don't want to change the velocity on the first frame),
714
+ // and as it seems to work great as-is, I'm just leaving it.
715
+ var decay = touch.decelerationRate;
716
+ touch.decelerationVelocity.y *= Math.pow(decay, (t / 10));
717
+ touch.decelerationVelocity.x *= Math.pow(decay, (t / 10));
718
+
719
+ // We have a bouncyBounce method that adjusts the velocity for bounce. That is, if it is
720
+ // out of range and still going, it will slow it down. This step is decelerationFromEdge.
721
+ // If it is not moving (or has come to a stop from decelerating), but is still out of range,
722
+ // it will start it moving back into range (accelerationToEdge)
723
+ // we supply de and ac as these properties.
724
+ // The .3 artificially increases the acceleration by .3; this is actually to make the final
725
+ // stop a bit more abrupt.
726
+ touch.decelerationVelocity.x = this.bouncyBounce(touch.decelerationVelocity.x, newX, minOffsetX, maxOffsetX, de, ac, 0.3);
727
+ touch.decelerationVelocity.y = this.bouncyBounce(touch.decelerationVelocity.y, newY, minOffsetY, maxOffsetY, de, ac, 0.3);
728
+
729
+ // if we ain't got no velocity... then we must be finished, as there is no where else to go.
730
+ // to determine our velocity, we take the absolue value, and use that; if it is less than .01, we
731
+ // must be done. Note that we check scale's most recent velocity, calculated above using bouncyBounce,
732
+ // as well.
733
+ var absXVelocity = Math.abs(touch.decelerationVelocity.x);
734
+ var absYVelocity = Math.abs(touch.decelerationVelocity.y);
735
+ if (absYVelocity < 0.05 && absXVelocity < 0.05 && Math.abs(sv) < 0.05) {
736
+ // we can reset the timeout, as it will no longer be required, and we don't want to re-cancel it later.
737
+ touch.timeout = null;
738
+ this.touch = null;
739
+
740
+ // trigger scroll end
741
+ this._touchScrollDidEnd();
742
+
743
+ // set the scale, vertical, and horizontal offsets to what they technically already are,
744
+ // but don't know they are yet. This will finally update things like, say, the clipping frame.
745
+ this.beginPropertyChanges();
746
+ this.set("scale", this._scale);
747
+ this.set("verticalScrollOffset", this._scroll_verticalScrollOffset);
748
+ this.set("horizontalScrollOffset", this._scroll_horizontalScrollOffset);
749
+ this.endPropertyChanges();
750
+ this.didScroll(this);
751
+
752
+ return;
753
+ }
754
+
755
+ // We now set up the next round. We are doing this as raw as we possibly can, not touching the
756
+ // run loop at all. This speeds up performance drastically--keep in mind, we're on comparatively
757
+ // slow devices, here. So, we'll just make a closure, saving "this" into "self" and calling
758
+ // 10ms later (or however long it takes). Note also that we save both the last event time
759
+ // (so we may calculate elapsed time) and the timeout we are creating, so we may cancel it in future.
760
+ var self = this;
761
+ touch.lastEventTime = Date.now();
762
+ this.touch.timeout = setTimeout(function () {
763
+ SC.run(self.decelerateAnimation(), self);
764
+ }, 10);
765
+ },
766
+
767
+ adjustElementScroll: function () {
768
+ var content = this.get('contentView');
769
+
770
+ if (content) {
771
+ this._applyCSSTransforms(content.get('layer'));
772
+ }
773
+ return sc_super();
774
+ },
775
+
776
+ /** @private */
777
+ _touchScrollDidChange: function () {
778
+ var contentView = this.get('contentView'),
779
+ horizontalScrollOffset = this._scroll_horizontalScrollOffset,
780
+ verticalScrollOffset = this._scroll_verticalScrollOffset;
781
+ if (contentView.touchScrollDidChange) {
782
+ contentView.touchScrollDidChange(horizontalScrollOffset, verticalScrollOffset);
783
+ }
784
+
785
+ // tell scrollers
786
+ if (this.verticalScrollerView && this.verticalScrollerView.touchScrollDidChange) {
787
+ this.verticalScrollerView.touchScrollDidChange(verticalScrollOffset);
788
+ }
789
+
790
+ if (this.horizontalScrollerView && this.horizontalScrollerView.touchScrollDidChange) {
791
+ this.horizontalScrollerView.touchScrollDidChange(horizontalScrollOffset);
792
+ }
793
+ }
794
+ });
795
+
796
+ SC.TouchScrollView.prototype.mixin({
797
+
798
+ /** @private */
799
+ _touchScrollDidStart: SC.TouchScrollView.prototype._touchScrollDidChange,
800
+
801
+ /** @private */
802
+ _touchScrollDidEnd: SC.TouchScrollView.prototype._touchScrollDidChange
803
+
804
+ });