sproutcore 1.6.0.1-java → 1.7.1.beta-java

Sign up to get free protection for your applications and to get access to all the features.
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
+ });