sproutcore 1.10.3.1 → 1.11.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (380) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG +4 -8
  3. data/VERSION.yml +2 -2
  4. data/lib/frameworks/sproutcore/Buildfile +5 -4
  5. data/lib/frameworks/sproutcore/CHANGELOG.md +274 -40
  6. data/lib/frameworks/sproutcore/CONTRIBUTORS.md +133 -0
  7. data/lib/frameworks/sproutcore/README.md +31 -144
  8. data/lib/frameworks/sproutcore/apps/showcase/controllers/source_tree_controller.js +9 -4
  9. data/lib/frameworks/sproutcore/apps/showcase/resources/stylesheet.css +5 -0
  10. data/lib/frameworks/sproutcore/apps/showcase/system/views_item_content.js +1 -1
  11. data/lib/frameworks/sproutcore/apps/showcase/views/split_views.js +15 -2
  12. data/lib/frameworks/sproutcore/apps/showcase/views/stacked_views.js +1 -1
  13. data/lib/frameworks/sproutcore/apps/tests/english.lproj/main_page.js +11 -1
  14. data/lib/frameworks/sproutcore/frameworks/ajax/mixins/websocket_delegate.js +90 -0
  15. data/lib/frameworks/sproutcore/frameworks/ajax/system/request.js +81 -5
  16. data/lib/frameworks/sproutcore/frameworks/ajax/system/response.js +23 -4
  17. data/lib/frameworks/sproutcore/frameworks/ajax/system/websocket.js +475 -0
  18. data/lib/frameworks/sproutcore/frameworks/ajax/tests/system/request.js +149 -26
  19. data/lib/frameworks/sproutcore/frameworks/ajax/tests/system/websocket.js +197 -0
  20. data/lib/frameworks/sproutcore/frameworks/ajax/tests/system/xhr_response_test.js +65 -0
  21. data/lib/frameworks/sproutcore/frameworks/bootstrap/system/loader.js +4 -0
  22. data/lib/frameworks/sproutcore/frameworks/core_foundation/child_view_layouts/horizontal_stack_layout.js +232 -52
  23. data/lib/frameworks/sproutcore/frameworks/core_foundation/child_view_layouts/vertical_stack_layout.js +235 -49
  24. data/lib/frameworks/sproutcore/frameworks/core_foundation/controllers/array.js +23 -13
  25. data/lib/frameworks/sproutcore/frameworks/core_foundation/controllers/object.js +3 -1
  26. data/lib/frameworks/sproutcore/frameworks/core_foundation/core.js +81 -1
  27. data/lib/frameworks/sproutcore/frameworks/core_foundation/english.lproj/ordinal.js +17 -0
  28. data/lib/frameworks/sproutcore/frameworks/core_foundation/ext/string.js +7 -0
  29. data/lib/frameworks/sproutcore/frameworks/{desktop/tests/views/disclosure/methods.js → core_foundation/french.lproj/ordinal.js} +7 -4
  30. data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/layout.js +2 -6
  31. data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/main.js +1 -1
  32. data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/pane.js +104 -69
  33. data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/pane_statechart.js +6 -1
  34. data/lib/frameworks/sproutcore/frameworks/core_foundation/protocols/child_view_layout_protocol.js +59 -0
  35. data/lib/frameworks/sproutcore/frameworks/core_foundation/protocols/view_transition_protocol.js +18 -1
  36. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/application.js +192 -0
  37. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/bezier_curves.js +52 -0
  38. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/color.js +384 -64
  39. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/core_query.js +6 -14
  40. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/device.js +21 -35
  41. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/event.js +72 -36
  42. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/locale.js +90 -34
  43. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/platform.js +55 -7
  44. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/render_context.js +20 -15
  45. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/req_anim_frame.js +9 -10
  46. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/root_responder.js +763 -542
  47. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/selection_set.js +4 -3
  48. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/sparse_array.js +1 -7
  49. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/string.js +14 -0
  50. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/touch.js +538 -0
  51. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/utils/rect.js +56 -1
  52. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/controllers/array/array_case.js +99 -4
  53. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/controllers/object/single_case.js +25 -19
  54. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/core_tests.js +75 -0
  55. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/ext/number_test.js +81 -0
  56. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/mixins/action_support.js +4 -4
  57. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/mixins/responder_context.js +4 -4
  58. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/mixins/string.js +19 -1
  59. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/system/color.js +36 -20
  60. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/system/root_responder/design_modes_test.js +83 -0
  61. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/system/root_responder/makeMainPane.js +7 -3
  62. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/system/root_responder/mouse_events.js +338 -0
  63. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/system/root_responder/root_responder.js +14 -89
  64. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/system/root_responder/touch.js +106 -0
  65. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/system/sparse_array.js +2 -2
  66. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/system/touch.js +136 -0
  67. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/system/utils/rect.js +42 -1
  68. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/pane/append_remove.js +11 -0
  69. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/pane/child_view.js +5 -5
  70. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/pane/design_mode_test.js +457 -0
  71. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/pane/sendEvent.js +36 -10
  72. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/background_color.js +44 -0
  73. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/border_frame_test.js +51 -24
  74. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/childViewLayout_test.js +176 -1
  75. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/clippingFrame.js +46 -16
  76. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/convertFrames.js +69 -15
  77. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/didAppendToDocument.js +2 -2
  78. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layout.js +7 -1
  79. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutDidChange.js +30 -10
  80. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutStyle.js +376 -71
  81. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/static_layout.js +0 -10
  82. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/viewDidResize.js +117 -34
  83. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/view_states_test.js +52 -2
  84. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view.js +656 -42
  85. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/animation.js +159 -38
  86. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/cursor.js +0 -7
  87. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/design_mode.js +206 -0
  88. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/enabled.js +0 -28
  89. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/keyboard.js +21 -6
  90. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout.js +372 -450
  91. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout_style.js +28 -13
  92. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/manipulation.js +22 -51
  93. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/statechart.js +59 -30
  94. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/theming.js +0 -29
  95. data/lib/frameworks/sproutcore/frameworks/datastore/mixins/relationship_support.js +22 -10
  96. data/lib/frameworks/sproutcore/frameworks/datastore/models/children_attribute.js +42 -36
  97. data/lib/frameworks/sproutcore/frameworks/datastore/models/many_attribute.js +54 -3
  98. data/lib/frameworks/sproutcore/frameworks/datastore/models/record.js +178 -59
  99. data/lib/frameworks/sproutcore/frameworks/datastore/models/record_attribute.js +2 -2
  100. data/lib/frameworks/sproutcore/frameworks/datastore/system/child_array.js +206 -132
  101. data/lib/frameworks/sproutcore/frameworks/datastore/system/many_array.js +214 -118
  102. data/lib/frameworks/sproutcore/frameworks/datastore/system/nested_store.js +96 -13
  103. data/lib/frameworks/sproutcore/frameworks/datastore/system/query.js +14 -4
  104. data/lib/frameworks/sproutcore/frameworks/datastore/system/record_array.js +82 -42
  105. data/lib/frameworks/sproutcore/frameworks/datastore/system/store.js +272 -177
  106. data/lib/frameworks/sproutcore/frameworks/datastore/tests/integration/store_interaction_test.js +54 -0
  107. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/datetime_recordattribute.js +24 -16
  108. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/many_attribute.js +6 -3
  109. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/nested_records/data_store.js +267 -35
  110. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/nested_records/nested_record.js +57 -46
  111. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/nested_records/nested_record_array.js +150 -53
  112. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/nested_records/nested_record_array_complex.js +57 -17
  113. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/nested_records/nested_record_complex.js +13 -9
  114. data/lib/frameworks/sproutcore/frameworks/{experimental/frameworks/polymorphism → datastore}/tests/models/polymorphism/many.js +2 -2
  115. data/lib/frameworks/sproutcore/frameworks/{experimental/frameworks/polymorphism → datastore}/tests/models/polymorphism/simple.js +0 -0
  116. data/lib/frameworks/sproutcore/frameworks/{experimental/frameworks/polymorphism → datastore}/tests/models/polymorphism/single.js +12 -2
  117. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/record/writeAttribute.js +20 -15
  118. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/record_attribute.js +9 -2
  119. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/many_array/core_methods.js +80 -14
  120. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/nested_store/autonomous_dataSourceCallbacks.js +280 -0
  121. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/nested_store/autonomous_pushChanges.js +232 -0
  122. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/nested_store/chain.js +31 -5
  123. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/query/parse.js +16 -2
  124. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/core_methods.js +60 -40
  125. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/materializeRecord.js +78 -0
  126. data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/system/datetime.js +13 -1
  127. data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/tests/system/datetime.js +20 -0
  128. data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/localized/{resources → english.lproj}/strings.js +0 -0
  129. data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/localized/french.lproj/strings.js +45 -0
  130. data/lib/frameworks/sproutcore/frameworks/designer/designers/object_designer.js +7 -3
  131. data/lib/frameworks/sproutcore/frameworks/desktop/mixins/collection_row_delegate.js +125 -44
  132. data/lib/frameworks/sproutcore/frameworks/desktop/panes/alert.js +139 -48
  133. data/lib/frameworks/sproutcore/frameworks/desktop/panes/draggable.js +202 -0
  134. data/lib/frameworks/sproutcore/frameworks/desktop/panes/menu.js +59 -56
  135. data/lib/frameworks/sproutcore/frameworks/desktop/panes/palette.js +13 -49
  136. data/lib/frameworks/sproutcore/frameworks/desktop/panes/picker.js +466 -305
  137. data/lib/frameworks/sproutcore/frameworks/desktop/protocols/drag_source.js +49 -12
  138. data/lib/frameworks/sproutcore/frameworks/desktop/render_delegates/slider.js +79 -21
  139. data/lib/frameworks/sproutcore/frameworks/desktop/render_delegates/split.js +12 -2
  140. data/lib/frameworks/sproutcore/frameworks/desktop/resources/menu_item_view.css +8 -0
  141. data/lib/frameworks/sproutcore/frameworks/desktop/resources/overlay-scroller.css +187 -0
  142. data/lib/frameworks/sproutcore/frameworks/desktop/system/drag.js +94 -30
  143. data/lib/frameworks/sproutcore/frameworks/desktop/tests/panes/alert/ui.js +163 -3
  144. data/lib/frameworks/sproutcore/frameworks/desktop/tests/panes/menu/methods.js +97 -78
  145. data/lib/frameworks/sproutcore/frameworks/desktop/tests/panes/menu/ui.js +61 -1
  146. data/lib/frameworks/sproutcore/frameworks/desktop/tests/panes/panel/methods.js +7 -3
  147. data/lib/frameworks/sproutcore/frameworks/desktop/tests/panes/panel/ui.js +47 -22
  148. data/lib/frameworks/sproutcore/frameworks/desktop/tests/panes/picker/methods.js +66 -9
  149. data/lib/frameworks/sproutcore/frameworks/desktop/tests/panes/picker/ui.js +21 -11
  150. data/lib/frameworks/sproutcore/frameworks/desktop/tests/panes/sheet/ui.js +12 -18
  151. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/button/methods.js +17 -14
  152. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/button/ui.js +2 -1
  153. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/checkbox/methods.js +9 -6
  154. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/collection/collection_fast_path.js +54 -21
  155. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/collection/content.js +52 -20
  156. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/collection/itemViewForContentIndex.js +94 -4
  157. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/collection/keyboard.js +177 -0
  158. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/collection/layerIdFor.js +13 -1
  159. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/collection/length.js +9 -9
  160. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/collection/mouse.js +18 -0
  161. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/date_field/methods.js +104 -0
  162. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/disclosure/ui.js +48 -49
  163. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/grid/drag_and_drop.js +22 -18
  164. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/grid/methods.js +17 -5
  165. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/link_view_test.js +136 -0
  166. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/list/contentIndexesInRect.js +77 -0
  167. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/list/drag_and_drop.js +53 -16
  168. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/list/layoutForContentIndex.js +41 -0
  169. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/list/rowDelegate.js +25 -25
  170. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/list/rowOffsetForContentIndex.js +102 -27
  171. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/list/{rowHeightForContentIndex.js → rowSizeForContentIndex.js} +7 -6
  172. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/list/ui_outline.js +2 -0
  173. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/list/ui_row_heights.js +70 -75
  174. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/list/ui_simple.js +29 -30
  175. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/list_item.js +57 -0
  176. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/menu_scroll_view/menu_scroll_view_test.js +206 -0
  177. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/progress/ui.js +15 -0
  178. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/radio/methods.js +15 -7
  179. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/scroll/integration.js +16 -11
  180. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/scroll/methods.js +164 -12
  181. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/scroll/scale.js +387 -0
  182. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/scroll/touch.js +549 -0
  183. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/scroll/ui.js +214 -45
  184. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/scroller.js +5 -5
  185. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/segmented/methods.js +73 -22
  186. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/segmented/ui.js +88 -3
  187. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/select/methods.js +8 -0
  188. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/slider/methods.js +16 -1
  189. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/slider/ui.js +54 -0
  190. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/split/dividers.js +21 -2
  191. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/static_content.js +31 -25
  192. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/tab/methods.js +109 -29
  193. data/lib/frameworks/sproutcore/frameworks/desktop/views/button.js +10 -1
  194. data/lib/frameworks/sproutcore/frameworks/desktop/views/checkbox.js +3 -0
  195. data/lib/frameworks/sproutcore/frameworks/desktop/views/collection.js +779 -603
  196. data/lib/frameworks/sproutcore/frameworks/desktop/views/date_field.js +106 -7
  197. data/lib/frameworks/sproutcore/frameworks/desktop/views/link_view.js +406 -0
  198. data/lib/frameworks/sproutcore/frameworks/desktop/views/list.js +437 -245
  199. data/lib/frameworks/sproutcore/frameworks/desktop/views/list_item.js +13 -0
  200. data/lib/frameworks/sproutcore/frameworks/desktop/views/menu_item.js +124 -62
  201. data/lib/frameworks/sproutcore/frameworks/desktop/views/menu_scroll.js +176 -597
  202. data/lib/frameworks/sproutcore/frameworks/desktop/views/menu_scroller_view.js +206 -0
  203. data/lib/frameworks/sproutcore/frameworks/desktop/views/popup_button.js +3 -0
  204. data/lib/frameworks/sproutcore/frameworks/desktop/views/progress.js +5 -4
  205. data/lib/frameworks/sproutcore/frameworks/desktop/views/radio.js +3 -0
  206. data/lib/frameworks/sproutcore/frameworks/desktop/views/scene.js +56 -158
  207. data/lib/frameworks/sproutcore/frameworks/desktop/views/scroll_view.js +2560 -0
  208. data/lib/frameworks/sproutcore/frameworks/desktop/views/scroller.js +458 -242
  209. data/lib/frameworks/sproutcore/frameworks/desktop/views/segmented.js +117 -54
  210. data/lib/frameworks/sproutcore/frameworks/desktop/views/select.js +18 -12
  211. data/lib/frameworks/sproutcore/frameworks/desktop/views/slider.js +162 -34
  212. data/lib/frameworks/sproutcore/frameworks/desktop/views/split.js +30 -15
  213. data/lib/frameworks/sproutcore/frameworks/desktop/views/split_divider.js +33 -7
  214. data/lib/frameworks/sproutcore/frameworks/desktop/views/static_content.js +22 -2
  215. data/lib/frameworks/sproutcore/frameworks/desktop/views/tab.js +47 -22
  216. data/lib/frameworks/sproutcore/frameworks/experimental/Buildfile +0 -6
  217. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/forms/views/form.js +2 -1
  218. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/forms/views/form_row.js +21 -21
  219. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/select_view/ext/menu.js +14 -3
  220. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/select_view/mixins/select_view_menu.js +24 -10
  221. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/select_view/tests/ext/menu_resizing.js +1 -1
  222. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/select_view/tests/mixins/select_view_menu/bindings.js +7 -4
  223. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/select_view/tests/mixins/select_view_menu/check_selected.js +7 -9
  224. data/lib/frameworks/sproutcore/frameworks/{desktop/tests/panes/select_button/methods.js → experimental/frameworks/select_view/tests/views/select/method.js} +54 -76
  225. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/select_view/tests/views/select/selected_item.js +35 -0
  226. data/lib/frameworks/sproutcore/frameworks/{desktop/tests/panes/select_button → experimental/frameworks/select_view/tests/views/select}/ui.js +107 -36
  227. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/select_view/views/select.js +225 -66
  228. data/lib/frameworks/sproutcore/frameworks/foundation/controllers/tree.js +39 -38
  229. data/lib/frameworks/sproutcore/frameworks/foundation/core.js +5 -18
  230. data/lib/frameworks/sproutcore/frameworks/foundation/debug/control_test_pane.js +12 -0
  231. data/lib/frameworks/sproutcore/frameworks/foundation/english.lproj/inflections.js +84 -0
  232. data/lib/frameworks/sproutcore/frameworks/foundation/french.lproj/inflections.js +41 -0
  233. data/lib/frameworks/sproutcore/frameworks/foundation/mixins/auto_mixin.js +1 -0
  234. data/lib/frameworks/sproutcore/frameworks/foundation/mixins/auto_resize.js +7 -0
  235. data/lib/frameworks/sproutcore/frameworks/foundation/mixins/content_display.js +3 -4
  236. data/lib/frameworks/sproutcore/frameworks/foundation/mixins/flowed_layout.js +6 -2
  237. data/lib/frameworks/sproutcore/frameworks/foundation/private/tree_item_observer.js +408 -239
  238. data/lib/frameworks/sproutcore/frameworks/foundation/render_delegates/canvas_image.js +1 -1
  239. data/lib/frameworks/sproutcore/frameworks/foundation/resources/text_field.css +2 -1
  240. data/lib/frameworks/sproutcore/frameworks/foundation/spanish.lproj/inflections.js +38 -0
  241. data/lib/frameworks/sproutcore/frameworks/foundation/system/benchmark.js +104 -76
  242. data/lib/frameworks/sproutcore/frameworks/foundation/system/string.js +20 -94
  243. data/lib/frameworks/sproutcore/frameworks/foundation/system/text_selection.js +33 -22
  244. data/lib/frameworks/sproutcore/frameworks/foundation/system/undo_manager.js +475 -0
  245. data/lib/frameworks/sproutcore/frameworks/foundation/tests/mixins/auto_resize_test.js +163 -1
  246. data/lib/frameworks/sproutcore/frameworks/foundation/tests/mixins/flowed_layout/tests.js +41 -0
  247. data/lib/frameworks/sproutcore/frameworks/foundation/tests/mixins/staticLayout.js +2 -5
  248. data/lib/frameworks/sproutcore/frameworks/foundation/tests/private/tree_item_observer/methods.js +268 -0
  249. data/lib/frameworks/sproutcore/frameworks/foundation/tests/system/undo_manager.js +231 -0
  250. data/lib/frameworks/sproutcore/frameworks/foundation/tests/views/container/ui.js +16 -0
  251. data/lib/frameworks/sproutcore/frameworks/foundation/tests/views/image/ui.js +27 -0
  252. data/lib/frameworks/sproutcore/frameworks/foundation/tests/views/text_field/methods.js +24 -0
  253. data/lib/frameworks/sproutcore/frameworks/foundation/tests/views/text_field/ui.js +135 -6
  254. data/lib/frameworks/sproutcore/frameworks/foundation/transitions/fade_transition.js +6 -0
  255. data/lib/frameworks/sproutcore/frameworks/foundation/transitions/pop_transition.js +7 -0
  256. data/lib/frameworks/sproutcore/frameworks/foundation/transitions/scale_transition.js +6 -0
  257. data/lib/frameworks/sproutcore/frameworks/foundation/transitions/slide_transition.js +4 -0
  258. data/lib/frameworks/sproutcore/frameworks/foundation/transitions/swap_dissolve_transition.js +3 -1
  259. data/lib/frameworks/sproutcore/frameworks/foundation/validators/credit_card.js +21 -21
  260. data/lib/frameworks/sproutcore/frameworks/foundation/views/container.js +65 -15
  261. data/lib/frameworks/sproutcore/frameworks/foundation/views/image.js +4 -1
  262. data/lib/frameworks/sproutcore/frameworks/foundation/views/label.js +1 -1
  263. data/lib/frameworks/sproutcore/frameworks/foundation/views/text_field.js +193 -213
  264. data/lib/frameworks/sproutcore/frameworks/jquery/{jquery-1.8.3-patched.js → jquery-1.11.1.js} +7507 -6684
  265. data/lib/frameworks/sproutcore/frameworks/routing/system/routes.js +28 -11
  266. data/lib/frameworks/sproutcore/frameworks/routing/tests/system/routes.js +26 -0
  267. data/lib/frameworks/sproutcore/frameworks/runtime/core.js +54 -25
  268. data/lib/frameworks/sproutcore/frameworks/runtime/ext/array.js +0 -6
  269. data/lib/frameworks/sproutcore/frameworks/runtime/ext/number.js +36 -0
  270. data/lib/frameworks/sproutcore/frameworks/runtime/ext/window.js +25 -0
  271. data/lib/frameworks/sproutcore/frameworks/runtime/mixins/array.js +3 -3
  272. data/lib/frameworks/sproutcore/frameworks/runtime/mixins/enumerable.js +1 -1
  273. data/lib/frameworks/sproutcore/frameworks/runtime/mixins/observable.js +156 -66
  274. data/lib/frameworks/sproutcore/frameworks/runtime/private/observer_set.js +2 -2
  275. data/lib/frameworks/sproutcore/frameworks/runtime/system/binding.js +150 -65
  276. data/lib/frameworks/sproutcore/frameworks/runtime/system/index_set.js +57 -11
  277. data/lib/frameworks/sproutcore/frameworks/runtime/system/object.js +68 -49
  278. data/lib/frameworks/sproutcore/frameworks/runtime/system/run_loop.js +14 -6
  279. data/lib/frameworks/sproutcore/frameworks/runtime/system/string.js +23 -23
  280. data/lib/frameworks/sproutcore/frameworks/runtime/tests/ext/number_test.js +44 -0
  281. data/lib/frameworks/sproutcore/frameworks/runtime/tests/mixins/array.js +0 -10
  282. data/lib/frameworks/sproutcore/frameworks/runtime/tests/mixins/enumerable/enumerable.js +340 -285
  283. data/lib/frameworks/sproutcore/frameworks/runtime/tests/system/binding.js +104 -3
  284. data/lib/frameworks/sproutcore/frameworks/runtime/tests/system/observer_set.js +14 -1
  285. data/lib/frameworks/sproutcore/frameworks/runtime/tests/system/string.js +15 -2
  286. data/lib/frameworks/sproutcore/frameworks/statechart/system/state.js +21 -18
  287. data/lib/frameworks/sproutcore/frameworks/statechart/system/statechart.js +52 -19
  288. data/lib/frameworks/sproutcore/frameworks/statechart/tests/event_handling/responder/pane.js +27 -24
  289. data/lib/frameworks/sproutcore/frameworks/template_view/controls/button.js +30 -0
  290. data/lib/frameworks/sproutcore/frameworks/template_view/ext/handlebars/bind.js +1 -1
  291. data/lib/frameworks/sproutcore/frameworks/template_view/ext/handlebars/collection.js +2 -0
  292. data/lib/frameworks/sproutcore/frameworks/template_view/ext/handlebars/view.js +1 -0
  293. data/lib/frameworks/sproutcore/frameworks/template_view/tests/mixins/template_helpers/checkbox_support.js +2 -2
  294. data/lib/frameworks/sproutcore/frameworks/template_view/tests/views/template/handlebars.js +4 -2
  295. data/lib/frameworks/sproutcore/frameworks/template_view/views/bindable_span.js +1 -1
  296. data/lib/frameworks/sproutcore/frameworks/template_view/views/template_collection.js +16 -14
  297. data/lib/frameworks/sproutcore/frameworks/testing/core.js +5 -3
  298. data/lib/frameworks/sproutcore/frameworks/testing/system/plan.js +13 -0
  299. data/lib/frameworks/sproutcore/lib/index.rhtml +2 -2
  300. data/lib/frameworks/sproutcore/phantomjs/test_runner.js +28 -7
  301. data/lib/frameworks/sproutcore/scripts/run_sc_server_master.sh +1 -1
  302. data/lib/frameworks/sproutcore/themes/ace/resources/_variables.css +2 -0
  303. data/lib/frameworks/sproutcore/themes/ace/resources/disclosure/ace/disclosure.css +1 -0
  304. data/lib/frameworks/sproutcore/themes/ace/resources/picker/popover/popover.css +3 -4
  305. data/lib/frameworks/sproutcore/themes/ace/resources/scroller/horizontal/horizontal.css +15 -15
  306. data/lib/frameworks/sproutcore/themes/ace/resources/scroller/horizontal/horizontal_overlay.css +74 -0
  307. data/lib/frameworks/sproutcore/themes/ace/resources/scroller/vertical/vertical.css +11 -13
  308. data/lib/frameworks/sproutcore/themes/ace/resources/scroller/vertical/vertical_overlay.css +74 -0
  309. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/jumbo/knob-active.png +0 -0
  310. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/jumbo/knob-active@2x.png +0 -0
  311. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/jumbo/knob.png +0 -0
  312. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/jumbo/knob@2x.png +0 -0
  313. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/{22px → jumbo}/slider.css +9 -4
  314. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/jumbo/track.png +0 -0
  315. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/jumbo/track@2x.png +0 -0
  316. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/regular/knob-active.png +0 -0
  317. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/regular/knob-active@2x.png +0 -0
  318. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/regular/knob.png +0 -0
  319. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/regular/knob@2x.png +0 -0
  320. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/regular/slider.css +32 -0
  321. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/regular/track.png +0 -0
  322. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/regular/track@2x.png +0 -0
  323. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/slider.css +13 -0
  324. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/small/knob-active.png +0 -0
  325. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/small/knob-active@2x.png +0 -0
  326. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/small/knob.png +0 -0
  327. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/small/knob@2x.png +0 -0
  328. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/small/slider.css +32 -0
  329. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/small/track.png +0 -0
  330. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/small/track@2x.png +0 -0
  331. data/lib/frameworks/sproutcore/themes/ace/resources/split/split.css +2 -3
  332. data/lib/sproutcore/builders/chance_file.rb +3 -3
  333. data/lib/sproutcore/helpers/minifier.rb +1 -0
  334. data/vendor/chance/lib/chance/instance.rb +34 -34
  335. data/vendor/chance/lib/chance/instance/spriting.rb +21 -16
  336. metadata +81 -58
  337. data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/visibility.js +0 -17
  338. data/lib/frameworks/sproutcore/frameworks/desktop/mixins/collection_fast_path.js +0 -710
  339. data/lib/frameworks/sproutcore/frameworks/desktop/mixins/scrollable.js +0 -267
  340. data/lib/frameworks/sproutcore/frameworks/desktop/resources/touch-scroller.css +0 -196
  341. data/lib/frameworks/sproutcore/frameworks/desktop/system/undo_manager.js +0 -224
  342. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/select_field/methods.js +0 -163
  343. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/select_field/ui.js +0 -177
  344. data/lib/frameworks/sproutcore/frameworks/desktop/views/scroll.js +0 -2053
  345. data/lib/frameworks/sproutcore/frameworks/desktop/views/select_button.js +0 -1024
  346. data/lib/frameworks/sproutcore/frameworks/desktop/views/select_field.js +0 -404
  347. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/render_delegates/menu_scroller.js +0 -28
  348. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/tests/menu/scroll.js +0 -235
  349. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/views/menu/scroll.js +0 -363
  350. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/views/menu/scroller.js +0 -250
  351. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/polymorphism/README.md +0 -47
  352. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/polymorphism/models/record.js +0 -134
  353. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/desktop_scroller.js +0 -92
  354. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/native_scroll.js +0 -25
  355. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/scroll.js +0 -33
  356. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/touch_scroller.js +0 -76
  357. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/integration.js +0 -25
  358. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/methods.js +0 -143
  359. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/ui.js +0 -256
  360. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/core_scroll.js +0 -1164
  361. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/core_scroller.js +0 -332
  362. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/desktop/scroll.js +0 -236
  363. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/desktop/scroller.js +0 -347
  364. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/scroll.js +0 -15
  365. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/scroller.js +0 -10
  366. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/touch/scroll.js +0 -804
  367. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/touch/scroller.js +0 -133
  368. data/lib/frameworks/sproutcore/frameworks/foundation/tasks/preload_bundle.js +0 -41
  369. data/lib/frameworks/sproutcore/themes/ace/resources/scroller/horizontal/horizontal_touch.css +0 -91
  370. data/lib/frameworks/sproutcore/themes/ace/resources/scroller/vertical/vertical_touch.css +0 -92
  371. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/14px/knob.png +0 -0
  372. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/14px/knob_active.png +0 -0
  373. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/14px/slider.css +0 -27
  374. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/16px/knob.png +0 -0
  375. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/16px/knob_active.png +0 -0
  376. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/16px/slider.css +0 -27
  377. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/22px/knob.png +0 -0
  378. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/22px/knob_active.png +0 -0
  379. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/22px/track.png +0 -0
  380. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/track.png +0 -0
@@ -0,0 +1,2560 @@
1
+ // ==========================================================================
2
+ // Project: SproutCore - JavaScript Application Framework
3
+ // Copyright: ©2014 7x7 Software Inc. All rights reserved.
4
+ // Portions ©2008-2011 Apple Inc. All rights reserved.
5
+ // License: Licensed under MIT license (see license.js)
6
+ // ==========================================================================
7
+ sc_require('views/scroller');
8
+
9
+
10
+ SC.SCROLL = {
11
+
12
+ /**
13
+ The rate of deceleration in pixels per square millisecond after scrolling from a drag gesture.
14
+
15
+ @static
16
+ @type Number
17
+ @default 3.0
18
+ */
19
+ DRAG_SCROLL_DECELERATION: 3.0,
20
+
21
+ /**
22
+ The number of pixels a gesture needs to move before it should be considered a scroll gesture.
23
+
24
+ @static
25
+ @type Number
26
+ @default 5
27
+ */
28
+ SCROLL_GESTURE_THRESHOLD: 5,
29
+
30
+ /**
31
+ The number of pixels a gesture needs to move in only a single direction, before it should be
32
+ considered as a locked scrolling direction (i.e. no gestures in the other direction will scroll
33
+ in that direction).
34
+
35
+ @static
36
+ @type Number
37
+ @default 50
38
+ */
39
+ SCROLL_LOCK_GESTURE_THRESHOLD: 50,
40
+
41
+ /**
42
+ The number of pixels a gesture needs to expand or contract before it should be considered a scale gesture.
43
+
44
+ @static
45
+ @type Number
46
+ @default 5
47
+ */
48
+ SCALE_GESTURE_THRESHOLD: 3
49
+
50
+ };
51
+
52
+
53
+ /** @class
54
+ Implements a complete scroll view. SproutCore implements its own JS-based scrolling in order
55
+ to unify scrolling behavior across platforms, and to enable progressive rendering (via the
56
+ clipping frame) during scroll on all devices.
57
+
58
+ Important Properties
59
+ -----
60
+
61
+ ScrollView positions its contentView according to three properties: `verticalScrollOffset`,
62
+ `horizontalScrollOffset`, and `scale`. These properties are bindable and observable, but you
63
+ should not override them.
64
+
65
+ Gutter vs. Overlaid Scrollers
66
+ -----
67
+
68
+ Scroll views use swappable scroll-bar views with various behavior (see `verticalScrollerView`
69
+ and `horizontalScrollerView`). `SC.ScrollerView` is a gutter-based scroller which persists and
70
+ takes up fourteen pixels. (By default, no scroller is shown for content that is too small to
71
+ scroll; see `autohidesHorizontalScroller` and `autohidesVerticalScroller`.) `SC.OverlayScrollerView`
72
+ is a gutterless view which fades when not scrolling. If you would like your view to always have
73
+ OS X-style fading overlaid scrollers, you can use the following:
74
+
75
+ SC.ScrollView.extend({
76
+ horizontalOverlay: true,
77
+ verticalOverlay: true
78
+ });
79
+
80
+ @extends SC.View
81
+ @since SproutCore 1.0
82
+ */
83
+ SC.ScrollView = SC.View.extend({
84
+ /** @scope SC.ScrollView.prototype */
85
+
86
+ // ---------------------------------------------------------------------------------------------
87
+ // Properties
88
+ //
89
+
90
+ /** @private Flag used to determine whether to animate the adjustment. */
91
+ _sc_animationDuration: null,
92
+
93
+ /** @private The animation timing to use. */
94
+ _sc_animationTiming: null,
95
+
96
+ /** @private The cached height of the container. */
97
+ _sc_containerHeight: 0,
98
+
99
+ /** @private The cached offset of the container. */
100
+ _sc_containerOffset: null,
101
+
102
+ /** @private The cached width of the container. */
103
+ _sc_containerWidth: 0,
104
+
105
+ /** @private The cached height of the content. */
106
+ _sc_contentHeight: 0,
107
+
108
+ /** @private Flag used to react accordingly when the content's height changes. */
109
+ _sc_contentHeightDidChange: false,
110
+
111
+ /** @private The cached scale of the content. */
112
+ _sc_contentScale: 1,
113
+
114
+ /** @private Flag used to react accordingly when the content's scale changes. */
115
+ _sc_contentScaleDidChange: false,
116
+
117
+ /** @private The cached width of the content. */
118
+ _sc_contentWidth: 0,
119
+
120
+ /** @private Flag used to react accordingly when the content's width changes. */
121
+ _sc_contentWidthDidChange: false,
122
+
123
+ /** @private The anchor horizontal offset of the touch gesture. */
124
+ _sc_gestureAnchorHOffset: null,
125
+
126
+ /** @private The anchor position of the initial touch gesture. */
127
+ _sc_gestureAnchorTotalD: null,
128
+
129
+ /** @private The anchor position of the initial touch gesture. */
130
+ _sc_gestureAnchorTotalX: null,
131
+
132
+ /** @private The anchor position of the initial touch gesture. */
133
+ _sc_gestureAnchorTotalY: null,
134
+
135
+ /** @private The anchor vertical offset of the touch gesture. */
136
+ _sc_gestureAnchorVOffset: null,
137
+
138
+ /** @private The anchor position of the last touch gesture. */
139
+ _sc_gestureAnchorX: null,
140
+
141
+ /** @private The anchor position of the last touch gesture. */
142
+ _sc_gestureAnchorY: null,
143
+
144
+ /** @private The anchor distance from center of the last touch gesture. */
145
+ _sc_gestureAnchorD: null,
146
+
147
+ /** @private The original scale before a touch gesture. */
148
+ _sc_gestureAnchorScale: null,
149
+
150
+ /** @private The timer used to fade out this scroller. */
151
+ _sc_horizontalFadeOutTimer: null,
152
+
153
+ /** @private The actual horizontal scroll offset. */
154
+ _sc_horizontalScrollOffset: null,
155
+
156
+ /** @private The percentage offset scrolled horizontally. Used to maintain the horizontal position when the content size changes. */
157
+ _sc_horizontalPct: null,
158
+
159
+ /** @private Flag is true when scaling. Used in capturing touches. */
160
+ _sc_isTouchScaling: false,
161
+
162
+ /** @private Flag is true when scrolling horizontally. Used in capturing touches. */
163
+ _sc_isTouchScrollingH: false,
164
+
165
+ /** @private Flag is true when scrolling is locked to horizontal. */
166
+ _sc_isTouchScrollingHOnly: false,
167
+
168
+ /** @private Flag is true when scrolling vertically. Used in capturing touches. */
169
+ _sc_isTouchScrollingV: false,
170
+
171
+ /** @private Flag is true when scrolling is locked to vertical. */
172
+ _sc_isTouchScrollingVOnly: false,
173
+
174
+ /** @private The minimum delay before applying a fade transition. */
175
+ _sc_minimumFadeOutDelay: function () {
176
+ // The fade out delay is never less than 100ms (so that the current run loop can complete) and is never less than the fade in duration (so that it can fade fully in).
177
+ return Math.max(Math.max(this.get('fadeOutDelay') || 0, 0.1), this.get('fadeInDuration') || 0) * 1000;
178
+ }.property('fadeOutDelay').cacheable(),
179
+
180
+ /** @private The amount of slip while over dragging (drag past bounds). 1.0 or 100% would slip completely, and 0.0 or 0% would not slip at all. */
181
+ _sc_overDragSlip: 0.5,
182
+
183
+ /** @private Timer used to pass a touch through to its content if we don't start scrolling in that time. */
184
+ _sc_passTouchToContentTimer: null,
185
+
186
+ /** @private The actual scale. */
187
+ _sc_scale: null,
188
+
189
+ /** @private Flag used to indicate when we should resize the content width manually. */
190
+ _sc_shouldResizeContentWidth: false,
191
+
192
+ /** @private Flag used to indicate when we should resize the content height manually. */
193
+ _sc_shouldResizeContentHeight: false,
194
+
195
+ /** @private The offset center x of a multi-touch gesture. */
196
+ _sc_touchCenterX: null,
197
+
198
+ /** @private The offset center y of a multi-touch gesture. */
199
+ _sc_touchCenterY: null,
200
+
201
+ /** @private The timer used to fade out this scroller. */
202
+ _sc_verticalFadeOutTimer: null,
203
+
204
+ /** @private The actual vertical scroll offset. */
205
+ _sc_verticalScrollOffset: null,
206
+
207
+ /** @private The percentage offset scrolled vertically. Used to maintain the vertical position when the content size changes. */
208
+ _sc_verticalPct: null,
209
+
210
+ /** @see SC.View.prototype.acceptsMultitouch
211
+
212
+ @type Boolean
213
+ @default true
214
+ */
215
+ acceptsMultitouch: true,
216
+
217
+ /** @private Animation curves. Kept private b/c it will likely become a computed property. */
218
+ animationCurveDecelerate: SC.easingCurve(0.35,0.34,0.84,1), // 'cubic-bezier(.35,.34,.84,1)', // http://cubic-bezier.com
219
+
220
+ /** @private Animation curves. Kept private b/c it will likely become a computed property. */
221
+ animationCurveReverse: SC.easingCurve(0.45,-0.47,0.73,1.3), // 'cubic-bezier(0.45,-0.47,0.73,1.3)',
222
+
223
+ /** @private Animation curves. Kept private b/c it will likely become a computed property. */
224
+ animationCurveSnap: SC.easingCurve(0.28,0.36,0.52,1), // 'cubic-bezier(.28,.36,.52,1)',
225
+
226
+ /**
227
+ If true, the horizontal scroller will automatically hide if the contentView is smaller than the
228
+ visible area. The `hasHorizontalScroller` property must be set to true in order for this property
229
+ to have any effect.
230
+
231
+ @type Boolean
232
+ @default true
233
+ */
234
+ autohidesHorizontalScroller: true,
235
+
236
+ /**
237
+ If true, the vertical scroller will automatically hide if the contentView is smaller than the
238
+ visible area. The `hasVerticalScroller` property must be set to true in order for this property
239
+ to have any effect.
240
+
241
+ @type Boolean
242
+ @default true
243
+ */
244
+ autohidesVerticalScroller: true,
245
+
246
+ /**
247
+ Determines whether scaling is allowed.
248
+
249
+ @type Boolean
250
+ @default false
251
+ */
252
+ canScale: false,
253
+
254
+ /**
255
+ Returns true if the view has both a horizontal scroller and the scroller is visible.
256
+
257
+ @field
258
+ @type Boolean
259
+ @readonly
260
+ */
261
+ canScrollHorizontal: function () {
262
+ return !!(this.get('hasHorizontalScroller') && // This property isn't bindable.
263
+ this.get('horizontalScrollerView') && // This property isn't bindable.
264
+ this.get('isHorizontalScrollerVisible'));
265
+ }.property('isHorizontalScrollerVisible').cacheable(),
266
+
267
+ /**
268
+ Returns true if the view has both a vertical scroller and the scroller is visible.
269
+
270
+ @field
271
+ @type Boolean
272
+ @readonly
273
+ */
274
+ canScrollVertical: function () {
275
+ return !!(this.get('hasVerticalScroller') && // This property isn't bindable.
276
+ this.get('verticalScrollerView') && // This property isn't bindable.
277
+ this.get('isVerticalScrollerVisible'));
278
+ }.property('isVerticalScrollerVisible').cacheable(),
279
+
280
+ /**
281
+ @type Array
282
+ @default ['sc-scroll-view']
283
+ @see SC.View#classNames
284
+ */
285
+ classNames: ['sc-scroll-view'],
286
+
287
+ /**
288
+ The container view that will wrap your content view. You can replace this property with your own
289
+ custom class if you prefer.
290
+
291
+ @type SC.ContainerView
292
+ @default SC.ContainerView
293
+ */
294
+ containerView: SC.ContainerView,
295
+
296
+ /**
297
+ The content view you want the scroll view to manage.
298
+
299
+ @type SC.View
300
+ @default null
301
+ */
302
+ contentView: null,
303
+
304
+ /**
305
+ The scroll deceleration rate.
306
+
307
+ @type Number
308
+ @default SC.SCROLL.DRAG_SCROLL_DECELERATION
309
+ */
310
+ decelerationRate: SC.SCROLL.DRAG_SCROLL_DECELERATION,
311
+
312
+ /** @private
313
+ Whether to delay touches from passing through to the content. By default, if the touch moves enough to
314
+ trigger a scroll within 150ms, this view will retain control of the touch, and content views will not
315
+ have a chance to handle it. This is generally the behavior you want.
316
+
317
+ If you set this to NO, the touch will not trigger a scroll until you pass control back to this view via
318
+ `touch.restoreLastTouchResponder`, for example when the touch has dragged by a certain amount. You should
319
+ use this option only if you know what you're doing.
320
+
321
+ @type Boolean
322
+ @default true
323
+ */
324
+ delaysContentTouches: true,
325
+
326
+ /**
327
+ Determines how long (in seconds) scrollbars wait before fading out.
328
+
329
+ @property Number
330
+ @default 0.4
331
+ */
332
+ fadeOutDelay: 0.4,
333
+
334
+ /**
335
+ True if the view should maintain a horizontal scroller. This property must be set when the
336
+ view is created.
337
+
338
+ @type Boolean
339
+ @default true
340
+ */
341
+ hasHorizontalScroller: true,
342
+
343
+ /**
344
+ True if the view should maintain a vertical scroller. This property must be set when the
345
+ view is created.
346
+
347
+ @type Boolean
348
+ @default true
349
+ */
350
+ hasVerticalScroller: true,
351
+
352
+ /**
353
+ The horizontal alignment for non-filling content inside of the ScrollView. Possible values:
354
+
355
+ - SC.ALIGN_LEFT
356
+ - SC.ALIGN_RIGHT
357
+ - SC.ALIGN_CENTER
358
+
359
+ @type String
360
+ @default SC.ALIGN_CENTER
361
+ */
362
+ horizontalAlign: SC.ALIGN_CENTER,
363
+
364
+ /**
365
+ Determines whether the horizontal scroller should fade out while in overlay mode. Has no effect
366
+ if `horizontalOverlay` is set to false.
367
+
368
+ @property Boolean
369
+ @default true
370
+ */
371
+ horizontalFade: true,
372
+
373
+ /**
374
+ Amount to scroll one horizontal line.
375
+
376
+ Used by the default implementation of scrollLeftLine() and
377
+ scrollRightLine().
378
+
379
+ @type Number
380
+ @default 20
381
+ */
382
+ horizontalLineScroll: 20,
383
+
384
+ /**
385
+ Use this to overlay the horizontal scroller.
386
+
387
+ This ensures that the content container will not resize to accommodate the horizontal scroller,
388
+ hence overlaying the scroller on top of the container.
389
+
390
+ @field
391
+ @type Boolean
392
+ @default true
393
+ */
394
+ horizontalOverlay: false,
395
+
396
+ /**
397
+ Amount to scroll one horizontal page.
398
+
399
+ Used by the default implementation of scrollLeftPage() and scrollRightPage().
400
+
401
+ @field
402
+ @type Number
403
+ @default value of frame.width
404
+ @observes frame
405
+ */
406
+ horizontalPageScroll: function () {
407
+ return this.get('frame').width;
408
+ }.property('frame'),
409
+
410
+ /**
411
+ The current horizontal scroll offset. Changing this value will update both the position of the
412
+ contentView and the horizontal scroller, if there is one.
413
+
414
+ @field
415
+ @type Number
416
+ @default 0
417
+ */
418
+ horizontalScrollOffset: function (key, value) {
419
+ var containerWidth = this._sc_containerWidth,
420
+ contentWidth = this._sc_contentWidth,
421
+ min = this.get('minimumHorizontalScrollOffset'),
422
+ max = this.get('maximumHorizontalScrollOffset');
423
+
424
+ /* jshint eqnull:true */
425
+ if (value != null) {
426
+ // When touch scrolling, we allow scroll to pass the limits by a small amount.
427
+ if (!this._sc_isTouchScrollingH) {
428
+ // Constrain to the set limits.
429
+ value = Math.max(min, Math.min(max, value));
430
+ }
431
+
432
+ // Record the relative percentage offset for maintaining position while scaling.
433
+ if (contentWidth > 0) {
434
+ this._sc_horizontalPct = (value + (containerWidth / 2)) / contentWidth;
435
+ }
436
+
437
+ // Use the cached value.
438
+ } else {
439
+ value = this._sc_horizontalScrollOffset;
440
+
441
+ // Default value.
442
+ if (value == null) {
443
+ var horizontalAlign = this.get('initialHorizontalAlign');
444
+
445
+ value = this._sc_alignedHorizontalOffset(horizontalAlign, containerWidth, contentWidth);
446
+ }
447
+ }
448
+
449
+ // Update the actual value.
450
+ this._sc_horizontalScrollOffset = value;
451
+
452
+ return value;
453
+ }.property().cacheable(),
454
+
455
+ /**
456
+ Use to control the positioning of the horizontal scroller. If you do not set 'horizontalOverlay' to
457
+ true, then the content view will be automatically sized to meet the left edge of the vertical
458
+ scroller, wherever it may be.
459
+
460
+ This allows you to easily, for example, have “one pixel higher and one pixel lower” scroll bars
461
+ that blend into their parent views.
462
+
463
+ If you do set 'horizontalOverlay' to true, then the scroller view will “float on top” of the content view.
464
+
465
+ Example: { left: -1, bottom: 0, right: -1 }
466
+
467
+ @type Object
468
+ @default null
469
+ */
470
+ horizontalScrollerLayout: null,
471
+
472
+ /**
473
+ The horizontal scroller view class. This will be replaced with a view instance when the
474
+ ScrollView is created unless `hasHorizontalScroller` is false.
475
+
476
+ If `horizontalOverlay` is `true`, the default view used will be an SC.OverlayScrollerView,
477
+ otherwise SC.ScrollerView will be used.
478
+
479
+ @type SC.View
480
+ @default SC.ScrollerView | SC.OverlayScrollerView
481
+ */
482
+ horizontalScrollerView: null,
483
+
484
+ /**
485
+ Your content view's initial horizontal alignment, if wider than the container. This allows you to e.g.
486
+ center the content view when zoomed out, but begin with it zoomed in and left-aligned. If not specified,
487
+ defaults to value of `horizontalAlign`. May be:
488
+
489
+ - SC.ALIGN_LEFT
490
+ - SC.ALIGN_RIGHT
491
+ - SC.ALIGN_CENTER
492
+
493
+ @type String
494
+ @default SC.ALIGN_LEFT
495
+ */
496
+ initialHorizontalAlign: SC.outlet('horizontalAlign'),
497
+
498
+ /**
499
+ Your content view's initial vertical alignment, if taller than the container. This allows you to e.g.
500
+ center the content view when zoomed out, but begin with it zoomed in and top-aligned. If not specified,
501
+ defaults to the value of `verticalAlign`. May be:
502
+
503
+ - SC.ALIGN_TOP
504
+ - SC.ALIGN_BOTTOM
505
+ - SC.ALIGN_MIDDLE
506
+
507
+ @type String
508
+ @default SC.ALIGN_TOP
509
+ */
510
+ initialVerticalAlign: SC.outlet('verticalAlign'),
511
+
512
+ /**
513
+ True if the horizontal scroller should be visible. You can change this property value anytime to
514
+ show or hide the horizontal scroller. If you do not want to use a horizontal scroller at all, you
515
+ should instead set `hasHorizontalScroller` to false to avoid creating a scroller view in the first
516
+ place.
517
+
518
+ @type Boolean
519
+ @default true
520
+ */
521
+ isHorizontalScrollerVisible: true,
522
+
523
+ /**
524
+ Walk like a duck.
525
+
526
+ @type Boolean
527
+ @default true
528
+ @readOnly
529
+ */
530
+ isScrollable: true,
531
+
532
+ /**
533
+ True if the vertical scroller should be visible. You can change this property value anytime to
534
+ show or hide the vertical scroller. If you do not want to use a vertical scroller at all, you
535
+ should instead set `hasVerticalScroller` to false to avoid creating a scroller view in the first
536
+ place.
537
+
538
+ @type Boolean
539
+ @default true
540
+ */
541
+ isVerticalScrollerVisible: true,
542
+
543
+ /**
544
+ The maximum horizontal scroll offset allowed given the current contentView size and the size of
545
+ the scroll view. If horizontal scrolling is disabled, this will always return 0.
546
+
547
+ @field
548
+ @type Number
549
+ @default 0
550
+ */
551
+ maximumHorizontalScrollOffset: function () {
552
+ return Math.max(this._sc_contentWidth - this._sc_containerWidth, 0);
553
+ }.property('_sc_containerWidth', '_sc_contentWidth').cacheable(),
554
+
555
+ /**
556
+ The maximum scale.
557
+
558
+ @type Number
559
+ @default 2.0
560
+ */
561
+ maximumScale: 2.0,
562
+
563
+ /**
564
+ The maximum vertical scroll offset allowed given the current contentView size and the size of
565
+ the scroll view. If vertical scrolling is disabled, this will always return 0 (or whatever
566
+ alignment dictates).
567
+
568
+ @field
569
+ @type Number
570
+ @default 0
571
+ */
572
+ maximumVerticalScrollOffset: function () {
573
+ return Math.max(this._sc_contentHeight - this._sc_containerHeight, 0);
574
+ }.property('_sc_containerHeight', '_sc_contentHeight').cacheable(),
575
+
576
+ /**
577
+ The minimum horizontal scroll offset allowed given the current contentView size and the size of
578
+ the scroll view. If horizontal scrolling is disabled, this will always return 0 (or whatever alignment dictates).
579
+
580
+ @field
581
+ @type Number
582
+ @default 0
583
+ */
584
+ minimumHorizontalScrollOffset: function () {
585
+ return Math.min(this._sc_contentWidth - this._sc_containerWidth, 0);
586
+ }.property('_sc_containerWidth', '_sc_contentWidth').cacheable(),
587
+
588
+ /**
589
+ The minimum scale.
590
+
591
+ @type Number
592
+ @default 0.25
593
+ */
594
+ minimumScale: 0.25,
595
+
596
+ /**
597
+ The minimum vertical scroll offset allowed given the current contentView size and the size of
598
+ the scroll view. If vertical scrolling is disabled, this will always return 0 (or whatever alignment dictates).
599
+
600
+ @field
601
+ @type Number
602
+ @default 0
603
+ */
604
+ minimumVerticalScrollOffset: function () {
605
+ return Math.min(this._sc_contentHeight - this._sc_containerHeight, 0);
606
+ }.property('_sc_containerHeight', '_sc_contentHeight').cacheable(),
607
+
608
+ /**
609
+ The current scale. Setting this will adjust the scale of the contentView.
610
+
611
+ If the contentView implements the SC.Scalable protocol, it will instead pass the scale to the contentView's
612
+ `applyScale` method instead.
613
+
614
+ Note that on platforms that allow bounce, setting scale outside of the minimum/maximumScale bounds will
615
+ result in a bounce. It is up to the developer to alert this view when the action is over and it should
616
+ bounce back.
617
+
618
+ @field
619
+ @type Number
620
+ @default 1.0
621
+ */
622
+ scale: function (key, value) {
623
+ /* jshint eqnull:true */
624
+ if (value != null) {
625
+ if (!this.get('canScale')) {
626
+ value = 1;
627
+ } else {
628
+ var min = this.get('minimumScale'),
629
+ max = this.get('maximumScale');
630
+
631
+ // When touch scaling, we allow scaling to pass the limits by a small amount.
632
+ if (this._sc_isTouchScaling) {
633
+ min = min - (min * 0.1);
634
+ max = max + (max * 0.1);
635
+ if ((value < min || value > max)) {
636
+ value = Math.min(Math.max(min, value), max);
637
+ }
638
+
639
+ // Constrain to the set limits.
640
+ } else {
641
+ if ((value < min || value > max)) {
642
+ value = Math.min(Math.max(min, value), max);
643
+ }
644
+ }
645
+ }
646
+ } else {
647
+ value = this._sc_scale;
648
+
649
+ // Default value.
650
+ if (value == null) {
651
+ value = 1;
652
+ }
653
+ }
654
+
655
+ // Update the actual value.
656
+ this._sc_scale = value;
657
+
658
+ return value;
659
+ }.property('canScale', 'minimumScale', 'maximumScale').cacheable(),
660
+
661
+ /**
662
+ This determines how much a gesture must pinch or spread apart (in pixels) before it is registered as a scale action.
663
+
664
+ You can change this value for all instances of SC.ScrollView in your application by overriding
665
+ `SC.SCROLL.SCALE_GESTURE_THRESHOLD` at launch time.
666
+
667
+ @type Number
668
+ @default SC.SCROLL.SCALE_GESTURE_THRESHOLD
669
+ */
670
+ scaleGestureThreshold: SC.SCROLL.SCALE_GESTURE_THRESHOLD,
671
+
672
+ /**
673
+ This determines how far (in pixels) a gesture must move before it is registered as a scroll.
674
+
675
+ You can change this value for all instances of SC.ScrollView in your application by overriding
676
+ `SC.SCROLL.SCROLL_THRESHOLD` at launch time.
677
+
678
+ @type Number
679
+ @default SC.SCROLL.SCROLL_GESTURE_THRESHOLD
680
+ */
681
+ scrollGestureThreshold: SC.SCROLL.SCROLL_GESTURE_THRESHOLD,
682
+
683
+ /**
684
+ Once a vertical or horizontal scroll has been triggered, this determines how far (in pixels) the gesture
685
+ must move on the other axis to trigger a two-axis scroll. If your scroll view's content is omnidirectional
686
+ (e.g. a map) you should set this value to 0.
687
+
688
+ You can change this value for all instances of SC.ScrollView in your application by overriding
689
+ `SC.SCROLL.SCROLL_LOCK_GESTURE_THRESHOLD` at launch time.
690
+
691
+ @type Number
692
+ @default SC.SCROLL.SCROLL_LOCK_GESTURE_THRESHOLD
693
+ */
694
+ scrollLockGestureThreshold: SC.SCROLL.SCROLL_LOCK_GESTURE_THRESHOLD,
695
+
696
+ /** @private
697
+ Once a vertical or horizontal scroll has been triggered, this determines how far (in pixels) the gesture
698
+ must move on the other axis to trigger a two-axis scroll. If your scroll view's content is omnidirectional
699
+ (e.g. a map) you should set this value to 0.
700
+
701
+ You can change this value for all instances of SC.ScrollView in your application by overriding
702
+ `SC.SCROLL.TOUCH.DEFAULT_SECONDARY_SCROLL_THRESHOLD` at launch time.
703
+
704
+ @type Number
705
+ @default SC.SCROLL.TOUCH.DEFAULT_SECONDARY_SCROLL_THRESHOLD
706
+ */
707
+ // scrollSecondaryGestureThreshold: SC.SCROLL.TOUCH.DEFAULT_SECONDARY_SCROLL_THRESHOLD,
708
+
709
+ /**
710
+ The vertical alignment for non-filling content inside of the ScrollView. Possible values:
711
+
712
+ - SC.ALIGN_TOP
713
+ - SC.ALIGN_BOTTOM
714
+ - SC.ALIGN_MIDDLE
715
+
716
+ @type String
717
+ @default SC.ALIGN_TOP
718
+ */
719
+ verticalAlign: SC.ALIGN_TOP,
720
+
721
+ /**
722
+ Determines whether the vertical scroller should fade out while in overlay mode. Has no effect if
723
+ `verticalOverlay` is set to false.
724
+
725
+ @property Boolean
726
+ @default true
727
+ */
728
+ verticalFade: true,
729
+
730
+ /**
731
+ Amount to scroll one vertical line.
732
+
733
+ Used by the default implementation of scrollDownLine() and scrollUpLine().
734
+
735
+ @type Number
736
+ @default 20
737
+ */
738
+ verticalLineScroll: 20,
739
+
740
+ /**
741
+ Use this to overlay the vertical scroller.
742
+
743
+ This ensures that the content container will not resize to accommodate the vertical scroller,
744
+ hence overlaying the scroller on top of the container.
745
+
746
+ @field
747
+ @type Boolean
748
+ @default true
749
+ */
750
+ verticalOverlay: false,
751
+
752
+ /**
753
+ Amount to scroll one vertical page.
754
+
755
+ Used by the default implementation of scrollUpPage() and scrollDownPage().
756
+
757
+ @field
758
+ @type Number
759
+ @default value of frame.height
760
+ @observes frame
761
+ */
762
+ verticalPageScroll: function () {
763
+ return this.get('frame').height;
764
+ }.property('frame'),
765
+
766
+ /**
767
+ The current vertical scroll offset. Changing this value will update both the position of the
768
+ contentView and the vertical scroller, if there is one.
769
+
770
+ @field
771
+ @type Number
772
+ @default 0
773
+ */
774
+ verticalScrollOffset: function (key, value) {
775
+ var containerHeight = this._sc_containerHeight,
776
+ contentHeight = this._sc_contentHeight,
777
+ min = this.get('minimumVerticalScrollOffset'),
778
+ max = this.get('maximumVerticalScrollOffset');
779
+
780
+ /* jshint eqnull:true */
781
+ if (value != null) {
782
+
783
+ // When touch scrolling, we allow scroll to pass the limits by a small amount.
784
+ if (!this._sc_isTouchScrollingV) {
785
+ // Constrain to the set limits.
786
+ value = Math.max(min, Math.min(max, value));
787
+ }
788
+
789
+ // Record the relative percentage offset for maintaining position while scaling.
790
+ if (contentHeight > 0) {
791
+ this._sc_verticalPct = (value + (containerHeight / 2)) / contentHeight;
792
+ }
793
+
794
+ // Use the cached value.
795
+ } else {
796
+ value = this._sc_verticalScrollOffset;
797
+
798
+ // Default value.
799
+ if (value == null) {
800
+ var verticalAlign = this.get('initialVerticalAlign');
801
+
802
+ value = this._sc_alignedVerticalOffset(verticalAlign, containerHeight, contentHeight);
803
+ }
804
+ }
805
+
806
+ // Update the actual value.
807
+ this._sc_verticalScrollOffset = value;
808
+
809
+ return value;
810
+ }.property().cacheable(),
811
+
812
+ /**
813
+ Use to control the positioning of the vertical scroller. If you do not set 'verticalOverlay' to
814
+ true, then the content view will be automatically sized to meet the left edge of the vertical
815
+ scroller, wherever it may be.
816
+
817
+ This allows you to easily, for example, have “one pixel higher and one pixel lower” scroll bars
818
+ that blend into their parent views.
819
+
820
+ If you do set 'verticalOverlay' to true, then the scroller view will “float on top” of the content view.
821
+
822
+ Example: { top: -1, bottom: -1, right: 0 }
823
+
824
+ @type Object
825
+ @default null
826
+ */
827
+ verticalScrollerLayout: null,
828
+
829
+ /**
830
+ The vertical scroller view class. This will be replaced with a view instance when the
831
+ ScrollView is created unless `hasVerticalScroller` is false.
832
+
833
+ If `verticalOverlay` is `true`, the default view used will be an SC.OverlayScrollerView,
834
+ otherwise SC.ScrollerView will be used.
835
+
836
+ @type SC.View
837
+ @default SC.ScrollerView | SC.OverlayScrollerView
838
+ */
839
+ verticalScrollerView: null,
840
+
841
+ // ---------------------------------------------------------------------------------------------
842
+ // Methods
843
+ //
844
+
845
+ /** @private Aligns the content horizontally. */
846
+ _sc_alignedHorizontalOffset: function (horizontalAlign, containerWidth, contentWidth) {
847
+ switch (horizontalAlign) {
848
+ case SC.ALIGN_RIGHT:
849
+ return 0 - (containerWidth - contentWidth);
850
+ case SC.ALIGN_CENTER:
851
+ return 0 - ((containerWidth - contentWidth) / 2);
852
+ default: // SC.ALIGN_LEFT
853
+ return 0;
854
+ }
855
+ },
856
+
857
+ /** @private Aligns the content vertically. */
858
+ _sc_alignedVerticalOffset: function (verticalAlign, containerHeight, contentHeight) {
859
+ switch (verticalAlign) {
860
+ case SC.ALIGN_BOTTOM:
861
+ return 0 - (containerHeight - contentHeight);
862
+ case SC.ALIGN_MIDDLE:
863
+ return 0 - ((containerHeight - contentHeight) / 2);
864
+ default: // SC.ALIGN_TOP
865
+ return 0;
866
+ }
867
+ },
868
+
869
+ /** @private Manually animates the content view. */
870
+ _sc_animateContentView: function (contentAdjustMap) {
871
+ var easingCurve = this._sc_animationTiming,
872
+ totalDuration = this._sc_animationDuration * 1000,
873
+ start = new Date(),
874
+ contentView = this.get('contentView'),
875
+ contentViewLayout = contentView.get('layout'),
876
+ leftStart = contentViewLayout.left,
877
+ leftDelta = contentAdjustMap.left - leftStart,
878
+ scaleStart = contentViewLayout.scale,
879
+ scaleDelta = contentAdjustMap.scale - scaleStart,
880
+ topStart = contentViewLayout.top,
881
+ topDelta = contentAdjustMap.top - topStart,
882
+ self = this;
883
+
884
+ function animationFrame() {
885
+ if (self._sc_isAnimating) {
886
+ var duration = new Date() - start,
887
+ percent = Math.min(duration / totalDuration, 1); // Capped at 100%
888
+
889
+ SC.run(function () {
890
+ var currentLeft = leftStart + leftDelta * easingCurve.value(percent),
891
+ currentScale = scaleStart + scaleDelta * easingCurve.value(percent),
892
+ currentTop = topStart + topDelta * easingCurve.value(percent);
893
+
894
+ contentAdjustMap.left = currentLeft;
895
+ contentAdjustMap.top = currentTop;
896
+ contentAdjustMap.scale = currentScale;
897
+
898
+ contentView.adjust(contentAdjustMap);
899
+ });
900
+
901
+ // Keep animating as long as we haven't hit 100%.
902
+ if (percent < 1) {
903
+ window.requestAnimationFrame(animationFrame);
904
+ } else {
905
+ // Clear out the animation flags.
906
+ self._sc_isAnimating = false;
907
+ self._sc_animationDuration = null;
908
+ self._sc_animationTiming = null;
909
+ }
910
+ }
911
+ }
912
+
913
+ // Start the animation.
914
+ self._sc_isAnimating = true;
915
+ animationFrame();
916
+ },
917
+
918
+ /* @private Cancels any content view animation if it exists. */
919
+ _sc_cancelAnimation: function () {
920
+ if (this._sc_isAnimating) {
921
+ var contentView = this.get('contentView');
922
+
923
+ // UNUSED. Animate using SC.View.prototype.animate. Cancelling the animation in place proved problematic.
924
+ // if (contentView.get('viewState') === SC.CoreView.ATTACHED_SHOWN_ANIMATING) {
925
+ // // Stop the animation in place.
926
+ // contentView.cancelAnimation(SC.LayoutState.CURRENT);
927
+
928
+ var curLayout = contentView.get('layout');
929
+
930
+ // Update offsets to match actual placement.
931
+ this.set('horizontalScrollOffset', -curLayout.left);
932
+ this.set('verticalScrollOffset', -curLayout.top);
933
+ this.set('scale', curLayout.scale);
934
+
935
+ // Clear out the animation flags.
936
+ this._sc_isAnimating = false;
937
+ this._sc_animationDuration = null;
938
+ this._sc_animationTiming = null;
939
+ }
940
+
941
+ },
942
+
943
+ /** @private Reposition our content view if necessary according to aligment. */
944
+ _sc_containerViewFrameDidChange: function () {
945
+ // Run the content view size change code (updates min & max offsets, sets content alignment if necessary, shows scrollers if necessary)
946
+ var containerFrame = this.getPath('containerView.frame'),
947
+ contentView = this.get('contentView');
948
+
949
+ // Cache the current height and width of the container view, so we can only watch for size changes.
950
+ this.set('_sc_containerHeight', containerFrame.height);
951
+ this.set('_sc_containerWidth', containerFrame.width);
952
+
953
+ if (contentView) {
954
+ var didAdjust = false;
955
+
956
+ if (this._sc_shouldResizeContentHeight) {
957
+ contentView.adjust('height', containerFrame.height);
958
+ didAdjust = true;
959
+ }
960
+
961
+ if (this._sc_shouldResizeContentWidth) {
962
+ contentView.adjust('width', containerFrame.width);
963
+ didAdjust = true;
964
+ }
965
+
966
+ // Update the scrollers regardless.
967
+ if (!didAdjust) { this._sc_contentViewSizeDidChange(); }
968
+ }
969
+
970
+ },
971
+
972
+ /** @private Whenever the contentView of the container changes, set up new observers and clean up old observers. */
973
+ _sc_contentViewDidChange: function () {
974
+ var newView = this.get('contentView'), // Our content view.
975
+ containerView = this.get('containerView'),
976
+ frameChangeFunc = this._sc_contentViewFrameDidChange;
977
+
978
+ // Clean up observers on the previous content view.
979
+ this._sc_removeContentViewObservers();
980
+
981
+ // Reset caches.
982
+ this._sc_shouldResizeContentWidth = false;
983
+ this._sc_shouldResizeContentHeight = false;
984
+ this._sc_contentHeight = 0;
985
+ this._sc_contentWidth = 0;
986
+ this._sc_contentScale = 1;
987
+
988
+ // Assign the content view to our container view. This ensures that it is instantiated.
989
+ containerView.set('contentView', newView);
990
+ newView = this.contentView = containerView.get('contentView'); // Actual content view.
991
+
992
+ if (newView) {
993
+ /* jshint eqnull:true */
994
+
995
+ // Be wary of content views that replace their layers.
996
+ // newView.addObserver('layer', this, layerChangeFunc);
997
+
998
+ if (!newView.useStaticLayout) {
999
+ // Ensure that scale transforms occur from the top-left corner (per our math).
1000
+ newView.adjust({
1001
+ transformOriginX: 0,
1002
+ transformOriginY: 0
1003
+ });
1004
+
1005
+ // When a view wants an accelerated layer and isn't a fixed size, we convert it to a fixed
1006
+ // size and resize it when our container resizes.
1007
+ if (!newView.get('isFixedSize')) {
1008
+ var contentViewLayout = newView.get('layout');
1009
+
1010
+ // Fix the width.
1011
+ if (contentViewLayout.width == null) {
1012
+ this._sc_shouldResizeContentWidth = true; // Flag to indicate that when the container's width changes, we should update the content's width.
1013
+
1014
+ newView.adjust({
1015
+ right: null,
1016
+ width: this._sc_containerWidth
1017
+ });
1018
+ }
1019
+
1020
+ // Fix the height.
1021
+ if (contentViewLayout.height == null) {
1022
+ this._sc_shouldResizeContentHeight = true; // Flag to indicate that when the container's height changes, we should update the content's height.
1023
+
1024
+ newView.adjust({
1025
+ bottom: null,
1026
+ height: this._sc_containerHeight
1027
+ });
1028
+ }
1029
+ }
1030
+ }
1031
+
1032
+ // TODO: Can we remove this if a calculated property exists?
1033
+ newView.addObserver('frame', this, frameChangeFunc);
1034
+
1035
+ // Initialize once.
1036
+ this._sc_contentViewFrameDidChange();
1037
+ }
1038
+
1039
+ // Cache the current content view so that we can properly clean up when it changes.
1040
+ this._sc_contentView = newView;
1041
+ },
1042
+
1043
+ /** @private */
1044
+ // _sc_contentViewLayerDidChange: function () {
1045
+ // ???
1046
+ // },
1047
+
1048
+ /** @private Check frame changes for size changes. */
1049
+ _sc_contentViewFrameDidChange: function () {
1050
+ var lastHeight = this._sc_contentHeight,
1051
+ lastScale = this._sc_contentScale,
1052
+ lastWidth = this._sc_contentWidth,
1053
+ newFrame = this.getPath('contentView.borderFrame');
1054
+
1055
+ if (newFrame) {
1056
+ // Determine whether the scale has changed.
1057
+ if (lastScale !== newFrame.scale) {
1058
+ this._sc_contentScaleDidChange = true;
1059
+ this.set('_sc_contentScale', newFrame.scale);
1060
+ }
1061
+
1062
+ if (lastWidth !== newFrame.width) {
1063
+ this._sc_contentWidthDidChange = true;
1064
+ this.set('_sc_contentWidth', newFrame.width);
1065
+ }
1066
+
1067
+ if (lastHeight !== newFrame.height) {
1068
+ this._sc_contentHeightDidChange = true;
1069
+ this.set('_sc_contentHeight', newFrame.height);
1070
+ }
1071
+
1072
+ // If any of the size values changed, update.
1073
+ if (this._sc_contentScaleDidChange || this._sc_contentWidthDidChange || this._sc_contentHeightDidChange) {
1074
+ // Filter the observer input.
1075
+ this.invokeOnce(this._sc_contentViewSizeDidChange);
1076
+ }
1077
+ }
1078
+ },
1079
+
1080
+ /** @private When the content view's size changes, we need to update our scroll offset properties. */
1081
+ _sc_contentViewSizeDidChange: function () {
1082
+ var maximumHorizontalScrollOffset = this.get('maximumHorizontalScrollOffset'),
1083
+ maximumVerticalScrollOffset = this.get('maximumVerticalScrollOffset'),
1084
+ containerHeight, containerWidth,
1085
+ contentHeight, contentWidth;
1086
+
1087
+ containerHeight = this._sc_containerHeight;
1088
+ containerWidth = this._sc_containerWidth;
1089
+ contentHeight = this._sc_contentHeight;
1090
+ contentWidth = this._sc_contentWidth;
1091
+
1092
+ var value;
1093
+ if (contentWidth) {
1094
+ if (maximumHorizontalScrollOffset === 0) {
1095
+ // Align horizontally.
1096
+ value = this._sc_alignedHorizontalOffset(this.get('horizontalAlign'), containerWidth, contentWidth);
1097
+ this.set('horizontalScrollOffset', value); // Note: Trigger for _sc_scrollOffsetHorizontalDidChange
1098
+
1099
+ } else {
1100
+ /* jshint eqnull:true */
1101
+ // If the horizontal position has never been set, use the initial alignment.
1102
+ if (this._sc_horizontalPct == null) {
1103
+ this._sc_horizontalScrollOffset = null;
1104
+ this.notifyPropertyChange('horizontalScrollOffset');
1105
+
1106
+ // If the scale of the content view changes, we want to maintain relative position so that zooming remains centered.
1107
+ } else if (this._sc_contentScaleDidChange) {
1108
+ if (this._sc_touchCenterX != null) {
1109
+ value = (this._sc_horizontalPct * contentWidth) - this._sc_touchCenterX;
1110
+ } else {
1111
+ value = (this._sc_horizontalPct * contentWidth) - (containerWidth / 2);
1112
+ }
1113
+ this.set('horizontalScrollOffset', value); // Note: Trigger for _sc_scrollOffsetHorizontalDidChange
1114
+
1115
+ // Live scale gesture. Update the anchor so that the scroll deltas are calculated correctly.
1116
+ if (this._sc_gestureAnchorHOffset != null) {
1117
+ this._sc_gestureAnchorHOffset = value;
1118
+ }
1119
+ }
1120
+ }
1121
+ }
1122
+
1123
+ if (contentHeight) {
1124
+ if (maximumVerticalScrollOffset === 0) {
1125
+ // Align vertically.
1126
+ value = this._sc_alignedVerticalOffset(this.get('verticalAlign'), containerHeight, contentHeight);
1127
+ this.set('verticalScrollOffset', value); // Note: Trigger for _sc_scrollOffsetHorizontalDidChange
1128
+
1129
+ } else {
1130
+ /* jshint eqnull:true */
1131
+ // If the vertical position has never been set, use the initial alignment.
1132
+ if (this._sc_verticalPct == null) {
1133
+ this._sc_verticalScrollOffset = null;
1134
+ this.notifyPropertyChange('verticalScrollOffset');
1135
+
1136
+ // If the scale of the content view changes, we want to maintain relative position so that zooming remains centered.
1137
+ } else if (this._sc_contentScaleDidChange) {
1138
+ if (this._sc_touchCenterY != null) {
1139
+ value = (this._sc_verticalPct * contentHeight) - this._sc_touchCenterY;
1140
+ } else {
1141
+ value = (this._sc_verticalPct * contentHeight) - (containerHeight / 2);
1142
+ }
1143
+ this.set('verticalScrollOffset', value); // Note: Trigger for _sc_scrollOffsetVerticalDidChange
1144
+
1145
+ // Live scale gesture. Update the anchor so that the scroll deltas are calculated correctly.
1146
+ if (this._sc_gestureAnchorVOffset != null) {
1147
+ this._sc_gestureAnchorVOffset = value;
1148
+ }
1149
+ }
1150
+ }
1151
+ }
1152
+
1153
+ // Reset our flags.
1154
+ this._sc_contentScaleDidChange = false;
1155
+ this._sc_contentHeightDidChange = false;
1156
+ this._sc_contentWidthDidChange = false;
1157
+
1158
+ // TODO: Updating scrollers may change the container size, which will cause this to run again. Can we bring
1159
+ // this into a single call?
1160
+ this._sc_updateScrollers();
1161
+ },
1162
+
1163
+ /** @private Fade in the horizontal scroller. Each scroller fades in/out independently. */
1164
+ _sc_fadeInHorizontalScroller: function () {
1165
+ var canScrollHorizontal = this.get('canScrollHorizontal'),
1166
+ horizontalScroller = this.get('horizontalScrollerView'),
1167
+ delay;
1168
+
1169
+ if (canScrollHorizontal && horizontalScroller && horizontalScroller.get('fadeIn')) {
1170
+ if (this._sc_horizontalFadeOutTimer) {
1171
+ // Reschedule the current timer (avoid creating a new instance).
1172
+ this._sc_horizontalFadeOutTimer.startTime = null;
1173
+ this._sc_horizontalFadeOutTimer.schedule();
1174
+ } else {
1175
+ // Fade in.
1176
+ horizontalScroller.fadeIn();
1177
+
1178
+ // Wait the minimum time before fading out again.
1179
+ delay = this.get('_sc_minimumFadeOutDelay');
1180
+ this._sc_horizontalFadeOutTimer = this.invokeLater(this._sc_fadeOutHorizontalScroller, delay);
1181
+ }
1182
+ }
1183
+ },
1184
+
1185
+ /** @private Fade in the vertical scroller. Each scroller fades in/out independently. */
1186
+ _sc_fadeInVerticalScroller: function () {
1187
+ var canScrollVertical = this.get('canScrollVertical'),
1188
+ verticalScroller = this.get('verticalScrollerView'),
1189
+ delay;
1190
+
1191
+ if (canScrollVertical && verticalScroller && verticalScroller.get('fadeIn')) {
1192
+ if (this._sc_verticalFadeOutTimer) {
1193
+ // Reschedule the current timer (avoid creating a new instance).
1194
+ this._sc_verticalFadeOutTimer.startTime = null;
1195
+ this._sc_verticalFadeOutTimer.schedule();
1196
+
1197
+ } else {
1198
+ // Fade in.
1199
+ verticalScroller.fadeIn();
1200
+
1201
+ // Wait the minimum time before fading out again.
1202
+ delay = this.get('_sc_minimumFadeOutDelay');
1203
+ this._sc_verticalFadeOutTimer = this.invokeLater(this._sc_fadeOutVerticalScroller, delay);
1204
+ }
1205
+ }
1206
+ },
1207
+
1208
+ /** @private Fade out the horizontal scroller. */
1209
+ _sc_fadeOutHorizontalScroller: function () {
1210
+ var horizontalScroller = this.get('horizontalScrollerView');
1211
+
1212
+ if (horizontalScroller && horizontalScroller.get('fadeOut')) {
1213
+ // Fade out.
1214
+ horizontalScroller.fadeOut();
1215
+ }
1216
+
1217
+ this._sc_horizontalFadeOutTimer = null;
1218
+ },
1219
+
1220
+ /** @private Fade out the vertical scroller. */
1221
+ _sc_fadeOutVerticalScroller: function () {
1222
+ var verticalScroller = this.get('verticalScrollerView');
1223
+
1224
+ if (verticalScroller && verticalScroller.get('fadeOut')) {
1225
+ // Fade out.
1226
+ verticalScroller.fadeOut();
1227
+ }
1228
+
1229
+ this._sc_verticalFadeOutTimer = null;
1230
+ },
1231
+
1232
+ /** @private Adjust the content alignment horizontally on change. */
1233
+ _sc_horizontalAlignDidChange: function () {
1234
+ var maximumHorizontalScrollOffset = this.get('maximumHorizontalScrollOffset');
1235
+
1236
+ // Align horizontally (Unless content width is zero).
1237
+ if (maximumHorizontalScrollOffset === 0 && this._sc_contentWidth) {
1238
+ var horizontalAlign = this.get('horizontalAlign'),
1239
+ value;
1240
+
1241
+ value = this._sc_alignedHorizontalOffset(horizontalAlign, this._sc_containerWidth, this._sc_contentWidth);
1242
+ this.set('horizontalScrollOffset', value);
1243
+ }
1244
+ },
1245
+
1246
+ /** @private
1247
+ Calculates the maximum offset given content and container sizes, and the
1248
+ alignment.
1249
+ */
1250
+ _sc_maximumScrollOffset: function (contentSize, containerSize, align, canScroll) {
1251
+ // If we can't scroll, we pretend our content size is no larger than the container.
1252
+ if (canScroll === NO) contentSize = Math.min(contentSize, containerSize);
1253
+
1254
+ // if our content size is larger than or the same size as the container, it's quite
1255
+ // simple to calculate the answer. Otherwise, we need to do some fancy-pants
1256
+ // alignment logic (read: simple math)
1257
+ if (contentSize >= containerSize) return contentSize - containerSize;
1258
+
1259
+ // alignment, yeah
1260
+ if (align === SC.ALIGN_LEFT || align === SC.ALIGN_TOP) {
1261
+ // if we left-align something, and it is smaller than the view, does that not mean
1262
+ // that it's maximum (and minimum) offset is 0, because it should be positioned at 0?
1263
+ return 0;
1264
+ } else if (align === SC.ALIGN_MIDDLE || align === SC.ALIGN_CENTER) {
1265
+ // middle align means the difference divided by two, because we want equal parts on each side.
1266
+ return 0 - Math.round((containerSize - contentSize) / 2);
1267
+ } else {
1268
+ // right align means the entire difference, because we want all that space on the left
1269
+ return 0 - (containerSize - contentSize);
1270
+ }
1271
+ },
1272
+
1273
+ /** @private
1274
+ Calculates the minimum offset given content and container sizes, and the
1275
+ alignment.
1276
+ */
1277
+ _sc_minimumScrollOffset: function (contentSize, containerSize, align, canScroll) {
1278
+ // If we can't scroll, we pretend our content size is no larger than the container.
1279
+ if (canScroll === NO) contentSize = Math.min(contentSize, containerSize);
1280
+
1281
+ // if the content is larger than the container, we have no need to change the minimum
1282
+ // away from the natural 0 position.
1283
+ if (contentSize > containerSize) return 0;
1284
+
1285
+ // alignment, yeah
1286
+ if (align === SC.ALIGN_LEFT || align === SC.ALIGN_TOP) {
1287
+ // if we left-align something, and it is smaller than the view, does that not mean
1288
+ // that it's maximum (and minimum) offset is 0, because it should be positioned at 0?
1289
+ return 0;
1290
+ } else if (align === SC.ALIGN_MIDDLE || align === SC.ALIGN_CENTER) {
1291
+ // middle align means the difference divided by two, because we want equal parts on each side.
1292
+ return 0 - Math.round((containerSize - contentSize) / 2);
1293
+ } else {
1294
+ // right align means the entire difference, because we want all that space on the left
1295
+ return 0 - (containerSize - contentSize);
1296
+ }
1297
+ },
1298
+
1299
+ /** @private Registers/deregisters view with SC.Drag for autoscrolling. */
1300
+ _sc_registerAutoscroll: function () {
1301
+ if (this.get('isVisibleInWindow') && this.get('isEnabledInPane')) {
1302
+ SC.Drag.addScrollableView(this);
1303
+ } else {
1304
+ SC.Drag.removeScrollableView(this);
1305
+ }
1306
+ },
1307
+
1308
+ /** @private Reposition the content view to match the current scroll offsets and scale. */
1309
+ _sc_repositionContentView: function () {
1310
+ var contentView = this.get('contentView');
1311
+
1312
+ if (contentView) {
1313
+ this.invokeOnce(this._sc_repositionContentViewUnfiltered);
1314
+ }
1315
+ },
1316
+
1317
+ /** @private Reposition the content view to match the current scroll offsets and scale. */
1318
+ _sc_repositionContentViewUnfiltered: function () {
1319
+ var containerView = this.get('containerView'),
1320
+ contentView = this.get('contentView'),
1321
+ horizontalScrollOffset = this.get('horizontalScrollOffset'),
1322
+ verticalScrollOffset = this.get('verticalScrollOffset'),
1323
+ scale = this.get('scale');
1324
+
1325
+ // If the content is statically laid out, use margins in the container layer to move it.
1326
+ // TODO: Remove static layout support.
1327
+ if (contentView.useStaticLayout) {
1328
+ var containerViewLayer = containerView.get('layer');
1329
+
1330
+ // Set the margins on the layer.
1331
+ containerViewLayer.style.marginLeft = -horizontalScrollOffset + 'px';
1332
+ containerViewLayer.style.marginTop = -verticalScrollOffset + 'px';
1333
+
1334
+ // Otherwise call adjust on the content.
1335
+ } else {
1336
+ // Constrain the offsets to full (actual) pixels to prevent blurry text et cetera. Note that this assumes
1337
+ // that the scroll view itself is living at a scale of 1, and may give blurry subpixel results if scaled.
1338
+ var horizontalAlign = this.get('horizontalAlign'),
1339
+ verticalAlign = this.get('verticalAlign'),
1340
+ left, top;
1341
+
1342
+ left = -horizontalScrollOffset;
1343
+
1344
+ // Round according to the alignment to avoid jitter at the edges. For example, we don't want 0.55 rounding up to 1 when left aligned. This also prevents implied percentage values (i.e. 0.0 > value > 1.0 == %)!
1345
+ switch (horizontalAlign) {
1346
+ case SC.ALIGN_CENTER:
1347
+ left = Math.round(left);
1348
+ break;
1349
+ case SC.ALIGN_RIGHT:
1350
+ left = Math.ceil(left);
1351
+ break;
1352
+ default: // SC.ALIGN_LEFT
1353
+ left = Math.floor(left);
1354
+ }
1355
+
1356
+ top = -verticalScrollOffset;
1357
+
1358
+ switch (verticalAlign) {
1359
+ case SC.ALIGN_MIDDLE:
1360
+ top = Math.round(top);
1361
+ break;
1362
+ case SC.ALIGN_BOTTOM:
1363
+ top = Math.ceil(top);
1364
+ break;
1365
+ default: // SC.ALIGN_TOP
1366
+ top = Math.floor(top);
1367
+ }
1368
+
1369
+ // Cancel any active animation in place.
1370
+ // this._sc_cancelAnimation();
1371
+
1372
+ var contentAdjustMap = SC.ScrollView._SC_CONTENT_ADJUST_MAP; // Shared object used to avoid continually initializing/destroying objects.
1373
+
1374
+ // Create the content adjust map once. Note: This is a shared object, all properties must be overwritten each time.
1375
+ if (!contentAdjustMap) { contentAdjustMap = SC.ScrollView._SC_CONTENT_ADJUST_MAP = {}; }
1376
+
1377
+ contentAdjustMap.left = left;
1378
+ contentAdjustMap.top = top;
1379
+ contentAdjustMap.scale = scale;
1380
+
1381
+ if (this._sc_animationDuration) {
1382
+ // UNUSED. Animate using SC.View.prototype.animate. Cancelling the animation in place proved problematic.
1383
+ // contentView.animate({ left: left, top: top, scale: scale }, {
1384
+ // duration: this._sc_animationDuration,
1385
+ // timing: this._sc_animationTiming
1386
+ // });
1387
+
1388
+ // // Run the animation immediately (don't wait for next Run Loop).
1389
+ // // Note: The next run loop will be queued none-the-less, so we may want to avoid that entirely in the future.
1390
+ // contentView._animate();
1391
+ this._sc_animateContentView(contentAdjustMap);
1392
+
1393
+ } else {
1394
+ contentView.adjust(contentAdjustMap);
1395
+ }
1396
+ }
1397
+ },
1398
+
1399
+ /** @private Re-position the scrollers and content depending on the need to scroll or not. */
1400
+ _sc_repositionScrollers: function () {
1401
+ this.invokeOnce(this._sc_repositionScrollersUnfiltered);
1402
+ },
1403
+
1404
+ /** @private Re-position the scrollers and content depending on the need to scroll or not. */
1405
+ _sc_repositionScrollersUnfiltered: function () {
1406
+ var hasHorizontalScroller = this.get('hasHorizontalScroller'),
1407
+ hasVerticalScroller = this.get('hasVerticalScroller'),
1408
+ canScrollHorizontal = this.get('canScrollHorizontal'),
1409
+ canScrollVertical = this.get('canScrollVertical'),
1410
+ containerLayoutMap = SC.ScrollView._SC_CONTAINER_LAYOUT_MAP, // Shared object used to avoid continually initializing/destroying objects.
1411
+ horizontalScrollerView = this.get('horizontalScrollerView'),
1412
+ horizontalScrollerHeight = horizontalScrollerView && canScrollHorizontal ? horizontalScrollerView.get('scrollbarThickness') : 0,
1413
+ verticalScrollerView = this.get('verticalScrollerView'),
1414
+ verticalScrollerWidth = verticalScrollerView && canScrollVertical ? verticalScrollerView.get('scrollbarThickness') : 0,
1415
+ layout; // The new layout to be applied to each scroller.
1416
+
1417
+ // Create the container layout map once. Note: This is a shared object, all properties must be overwritten each time.
1418
+ if (!containerLayoutMap) { containerLayoutMap = SC.ScrollView._SC_CONTAINER_LAYOUT_MAP = {}; }
1419
+
1420
+ // Set the standard.
1421
+ containerLayoutMap.bottom = 0;
1422
+ containerLayoutMap.right = 0;
1423
+
1424
+ // Adjust the horizontal scroller.
1425
+ if (hasHorizontalScroller) {
1426
+ var horizontalOverlay = this.get('horizontalOverlay'),
1427
+ horizontalScrollerLayout = this.get('horizontalScrollerLayout');
1428
+
1429
+ // Adjust the scroller view accordingly. Allow for a custom default scroller layout to be set.
1430
+ if (horizontalScrollerLayout) {
1431
+ layout = {
1432
+ left: horizontalScrollerLayout.left,
1433
+ bottom: horizontalScrollerLayout.bottom,
1434
+ right: horizontalScrollerLayout.right + verticalScrollerWidth,
1435
+ height: horizontalScrollerHeight
1436
+ };
1437
+ } else {
1438
+ layout = {
1439
+ left: 0,
1440
+ bottom: 0,
1441
+ right: verticalScrollerWidth,
1442
+ height: horizontalScrollerHeight
1443
+ };
1444
+ }
1445
+ horizontalScrollerView.set('layout', layout);
1446
+
1447
+ // Adjust the content view accordingly.
1448
+ if (canScrollHorizontal && !horizontalOverlay) {
1449
+ containerLayoutMap.bottom = horizontalScrollerHeight;
1450
+ }
1451
+
1452
+ // Set the visibility of the scroller immediately.
1453
+ horizontalScrollerView.set('isVisible', canScrollHorizontal);
1454
+ this._sc_fadeInHorizontalScroller();
1455
+ }
1456
+
1457
+ // Adjust the vertical scroller.
1458
+ if (hasVerticalScroller) {
1459
+ var verticalOverlay = this.get('verticalOverlay'),
1460
+ verticalScrollerLayout = this.get('verticalScrollerLayout');
1461
+
1462
+ // Adjust the scroller view accordingly. Allow for a custom default scroller layout to be set.
1463
+ if (verticalScrollerLayout) {
1464
+ layout = {
1465
+ top: verticalScrollerLayout.top,
1466
+ right: verticalScrollerLayout.right,
1467
+ bottom: verticalScrollerLayout.bottom + horizontalScrollerHeight,
1468
+ width: verticalScrollerWidth
1469
+ };
1470
+ } else {
1471
+ layout = {
1472
+ top: 0,
1473
+ right: 0,
1474
+ bottom: horizontalScrollerHeight,
1475
+ width: verticalScrollerWidth
1476
+ };
1477
+ }
1478
+ verticalScrollerView.set('layout', layout);
1479
+
1480
+ // Prepare to adjust the content view accordingly.
1481
+ if (canScrollVertical && !verticalOverlay) {
1482
+ containerLayoutMap.right = verticalScrollerWidth;
1483
+ }
1484
+
1485
+ // Set the visibility of the scroller immediately.
1486
+ verticalScrollerView.set('isVisible', canScrollVertical);
1487
+ this._sc_fadeInVerticalScroller();
1488
+ }
1489
+
1490
+ // Adjust the container view accordingly (i.e. to make space for scrollers or take space back).
1491
+ var containerView = this.get('containerView');
1492
+ containerView.adjust(containerLayoutMap);
1493
+ },
1494
+
1495
+ /** @private Clean up observers on the content view. */
1496
+ _sc_removeContentViewObservers: function () {
1497
+ var oldView = this._sc_contentView,
1498
+ frameChangeFunc = this._sc_contentViewFrameDidChange;
1499
+ // layerChangeFunc = this._sc_contentViewLayerDidChange;
1500
+
1501
+ if (oldView) {
1502
+ oldView.removeObserver('frame', this, frameChangeFunc);
1503
+ // oldView.removeObserver('layer', this, layerChangeFunc);
1504
+
1505
+ this._sc_shouldResizeContentWidth = false;
1506
+ this._sc_shouldResizeContentHeight = false;
1507
+ }
1508
+ },
1509
+
1510
+ /** @private Whenever the scale changes, update the scrollers and adjust the location of the content view. */
1511
+ _sc_scaleDidChange: function () {
1512
+ var contentView = this.get('contentView'),
1513
+ scale = this.get('scale');
1514
+
1515
+ // If the content is statically laid out, use margins in the container layer to move it.
1516
+ // TODO: Remove static layout support.
1517
+ if (contentView) {
1518
+ if (contentView.useStaticLayout) {
1519
+ //@if(debug)
1520
+ // If the scale is not 1 then assume the developer is trying to scale static content.
1521
+ if (scale !== 1) {
1522
+ SC.warn("Developer Warning: SC.ScrollView's `scale` feature does not support statically laid out content views.");
1523
+ }
1524
+ //@endif
1525
+
1526
+ // Reposition the content view to apply the scale.
1527
+ } else {
1528
+ // Apply this change.
1529
+ this._sc_repositionContentView();
1530
+ }
1531
+ }
1532
+ },
1533
+
1534
+ /** @private Whenever the scroll offsets change, update the scrollers and adjust the location of the content view. */
1535
+ _sc_scrollOffsetHorizontalDidChange: function () {
1536
+ this._sc_repositionContentView();
1537
+ this.invokeLast(this._sc_fadeInHorizontalScroller);
1538
+ },
1539
+
1540
+ /** @private Whenever the scroll offsets change, update the scrollers and adjust the location of the content view. */
1541
+ _sc_scrollOffsetVerticalDidChange: function () {
1542
+ this._sc_repositionContentView();
1543
+ this.invokeLast(this._sc_fadeInVerticalScroller);
1544
+ },
1545
+
1546
+ /** @private Update the scrollers. */
1547
+ _sc_updateScrollers: function () {
1548
+ var horizontalScrollerView = this.get('horizontalScrollerView'),
1549
+ verticalScrollerView = this.get('verticalScrollerView'),
1550
+ minimumHorizontalScrollOffset = this.get('minimumHorizontalScrollOffset'),
1551
+ minimumVerticalScrollOffset = this.get('minimumVerticalScrollOffset'),
1552
+ maximumHorizontalScrollOffset = this.get('maximumHorizontalScrollOffset'),
1553
+ maximumVerticalScrollOffset = this.get('maximumVerticalScrollOffset'),
1554
+ containerHeight, containerWidth,
1555
+ contentHeight, contentWidth;
1556
+
1557
+ containerHeight = this._sc_containerHeight;
1558
+ containerWidth = this._sc_containerWidth;
1559
+ contentHeight = this._sc_contentHeight;
1560
+ contentWidth = this._sc_contentWidth;
1561
+
1562
+ // Update the minimum and maximum scrollable distance on the scrollers as well as their visibility.
1563
+ var proportion;
1564
+ if (horizontalScrollerView) {
1565
+ horizontalScrollerView.set('minimum', minimumHorizontalScrollOffset);
1566
+ horizontalScrollerView.set('maximum', maximumHorizontalScrollOffset);
1567
+
1568
+ if (this.get('autohidesHorizontalScroller')) {
1569
+ this.setIfChanged('isHorizontalScrollerVisible', contentWidth > containerWidth);
1570
+ }
1571
+
1572
+ // Constrain the proportion to 100%.
1573
+ proportion = Math.min(containerWidth / contentWidth, 1.0);
1574
+ horizontalScrollerView.setIfChanged('proportion', proportion);
1575
+ }
1576
+
1577
+ if (verticalScrollerView) {
1578
+ verticalScrollerView.set('minimum', minimumVerticalScrollOffset);
1579
+ verticalScrollerView.set('maximum', maximumVerticalScrollOffset);
1580
+
1581
+ if (this.get('autohidesVerticalScroller')) {
1582
+ this.setIfChanged('isVerticalScrollerVisible', contentHeight > containerHeight);
1583
+ }
1584
+
1585
+ // Constrain the proportion to 100%.
1586
+ proportion = Math.min(containerHeight / contentHeight, 1.0);
1587
+ verticalScrollerView.setIfChanged('proportion', proportion);
1588
+ }
1589
+ },
1590
+
1591
+ /** @private Adjust the content alignment vertically on change. */
1592
+ _sc_verticalAlignDidChange: function () {
1593
+ var maximumVerticalScrollOffset = this.get('maximumVerticalScrollOffset');
1594
+
1595
+ // Align vertically (Unless content height is zero).
1596
+ if (maximumVerticalScrollOffset === 0 && this._sc_contentHeight) {
1597
+ var verticalAlign = this.get('verticalAlign'),
1598
+ value;
1599
+
1600
+ value = this._sc_alignedVerticalOffset(verticalAlign, this._sc_containerHeight, this._sc_contentHeight);
1601
+ this.set('verticalScrollOffset', value);
1602
+ }
1603
+ },
1604
+
1605
+ /** @private Instantiate the container view and the scrollers as needed. */
1606
+ createChildViews: function () {
1607
+ var childViews = [];
1608
+
1609
+ // Set up the container view.
1610
+ var containerView = this.get('containerView');
1611
+
1612
+ //@if(debug)
1613
+ // Provide some debug-mode only developer support to prevent problems.
1614
+ if (!containerView) {
1615
+ throw new Error("Developer Error: SC.ScrollView must have a containerView class set before it is instantiated.");
1616
+ }
1617
+ //@endif
1618
+
1619
+ containerView = this.containerView = this.createChildView(containerView, {
1620
+ contentView: this.contentView // Initialize the view with the currently set container view.
1621
+ });
1622
+ this.contentView = containerView.get('contentView'); // Replace our content view with the instantiated version.
1623
+ childViews.push(containerView);
1624
+
1625
+ // Set up the scrollers.
1626
+ var scrollerView;
1627
+
1628
+ // Create a horizontal scroller view if needed.
1629
+ if (!this.get('hasHorizontalScroller')) {
1630
+ // Remove the class entirely.
1631
+ this.horizontalScrollerView = null;
1632
+ } else {
1633
+ scrollerView = this.get('horizontalScrollerView');
1634
+
1635
+ // Use a default scroller view.
1636
+ /* jshint eqnull:true */
1637
+ if (scrollerView == null) {
1638
+ scrollerView = this.get('horizontalOverlay') ? SC.OverlayScrollerView : SC.ScrollerView;
1639
+ }
1640
+
1641
+ // Replace the class property with an instance.
1642
+ scrollerView = this.horizontalScrollerView = this.createChildView(scrollerView, {
1643
+ isVisible: !this.get('autohidesHorizontalScroller'),
1644
+ layoutDirection: SC.LAYOUT_HORIZONTAL,
1645
+ value: this.get('horizontalScrollOffset'),
1646
+ valueBinding: '.owner.horizontalScrollOffset', // Bind the value of the scroller to our horizontal offset.
1647
+ minimum: this.get('minimumHorizontalScrollOffset'),
1648
+ maximum: this.get('maximumHorizontalScrollOffset')
1649
+ });
1650
+
1651
+ // Add the scroller view to the child views array.
1652
+ childViews.push(scrollerView);
1653
+ }
1654
+
1655
+ // Create a vertical scroller view if needed.
1656
+ if (!this.get('hasVerticalScroller')) {
1657
+ // Remove the class entirely.
1658
+ this.verticalScrollerView = null;
1659
+ } else {
1660
+ scrollerView = this.get('verticalScrollerView');
1661
+
1662
+ // Use a default scroller view.
1663
+ /* jshint eqnull:true */
1664
+ if (scrollerView == null) {
1665
+ scrollerView = this.get('verticalOverlay') ? SC.OverlayScrollerView : SC.ScrollerView;
1666
+ }
1667
+
1668
+ // Replace the class property with an instance.
1669
+ scrollerView = this.verticalScrollerView = this.createChildView(scrollerView, {
1670
+ isVisible: !this.get('autohidesVerticalScroller'),
1671
+ layoutDirection: SC.LAYOUT_VERTICAL,
1672
+ value: this.get('verticalScrollOffset'),
1673
+ valueBinding: '.owner.verticalScrollOffset', // Bind the value of the scroller to our vertical offset.
1674
+ minimum: this.get('minimumVerticalScrollOffset'),
1675
+ maximum: this.get('maximumVerticalScrollOffset')
1676
+ });
1677
+
1678
+ // Add the scroller view to the child views array.
1679
+ childViews.push(scrollerView);
1680
+ }
1681
+
1682
+ // Set the childViews array.
1683
+ this.childViews = childViews;
1684
+ },
1685
+
1686
+ /** @private */
1687
+ destroy: function() {
1688
+ // Clean up.
1689
+ this._sc_removeContentViewObservers();
1690
+ this.removeObserver('contentView', this, this._sc_contentViewDidChange);
1691
+
1692
+ this.removeObserver('horizontalAlign', this, this._sc_horizontalAlignDidChange);
1693
+ this.removeObserver('verticalAlign', this, this._sc_verticalAlignDidChange);
1694
+
1695
+ sc_super();
1696
+ },
1697
+
1698
+ /** @private SC.View */
1699
+ didCreateLayer: function () {
1700
+ // Observe the scroll offsets for changes and initialize once.
1701
+ this.addObserver('horizontalScrollOffset', this, this._sc_scrollOffsetHorizontalDidChange);
1702
+ this.addObserver('verticalScrollOffset', this, this._sc_scrollOffsetVerticalDidChange);
1703
+ this._sc_scrollOffsetHorizontalDidChange();
1704
+ this._sc_scrollOffsetVerticalDidChange();
1705
+
1706
+ // Observe the scroller visibility properties for changes and initialize once.
1707
+ this.addObserver('isHorizontalScrollerVisible', this, this._sc_repositionScrollers);
1708
+ this.addObserver('isVerticalScrollerVisible', this, this._sc_repositionScrollers);
1709
+ this._sc_repositionScrollers();
1710
+
1711
+ // Observe the scale for changes and initialize once.
1712
+ this.addObserver('scale', this, this._sc_scaleDidChange);
1713
+ this._sc_scaleDidChange();
1714
+
1715
+ // Observe our container view frame for changes and initialize once.
1716
+ var containerView = this.get('containerView');
1717
+ containerView.addObserver('frame', this, this._sc_containerViewFrameDidChange);
1718
+ this._sc_containerViewFrameDidChange();
1719
+
1720
+ // Observe for changes in enablement and visibility for registering with SC.Drag auto-scrolling and initialize once.
1721
+ this.addObserver('isVisibleInWindow', this, this._sc_registerAutoscroll);
1722
+ this.addObserver('isEnabledInPane', this, this._sc_registerAutoscroll);
1723
+ this._sc_registerAutoscroll();
1724
+ },
1725
+
1726
+ /** SC.Object.prototype */
1727
+ init: function () {
1728
+ sc_super();
1729
+
1730
+ // Observe the content view for changes and initialize once.
1731
+ this.addObserver('contentView', this, this._sc_contentViewDidChange);
1732
+ this._sc_contentViewDidChange();
1733
+
1734
+ // Observe the alignment properties for changes. No need to initialize, the default alignment property
1735
+ // will be used.
1736
+ this.addObserver('horizontalAlign', this, this._sc_horizontalAlignDidChange);
1737
+ this.addObserver('verticalAlign', this, this._sc_verticalAlignDidChange);
1738
+ },
1739
+
1740
+ /**
1741
+ Scrolls the receiver in the horizontal and vertical directions by the amount specified, if
1742
+ allowed. The actual scroll amount will be constrained by the current scroll minimums and
1743
+ maximums. (If you wish to scroll outside of those bounds, you should call `scrollTo` directly.)
1744
+
1745
+ If you only want to scroll in one direction, pass null or 0 for the other direction.
1746
+
1747
+ @param {Number} x change in the x direction (or hash)
1748
+ @param {Number} y change in the y direction
1749
+ @returns {SC.ScrollView} receiver
1750
+ */
1751
+ scrollBy: function (x, y) {
1752
+ // Normalize (deprecated).
1753
+ if (y === undefined && SC.typeOf(x) === SC.T_HASH) {
1754
+ //@if(debug)
1755
+ // Add some developer support. It's faster to pass the arguments individually so that we don't need to do this normalization and the
1756
+ // developer isn't creating an extra Object needlessly.
1757
+ SC.warn("Developer Warning: Passing an object to SC.ScrollView.scrollBy is deprecated. Please pass both the x and y arguments.");
1758
+ //@endif
1759
+
1760
+ y = x.y;
1761
+ x = x.x;
1762
+ }
1763
+
1764
+ // If null, undefined, or 0, pass null; otherwise just add current offset.
1765
+ x = (x) ? this.get('horizontalScrollOffset') + x : null;
1766
+ y = (y) ? this.get('verticalScrollOffset') + y : null;
1767
+
1768
+ // Constrain within min and max. (Calls to scrollBy are generally convenience calls that should not have to
1769
+ // worry about exceeding bounds and making the followup call. Features that want to allow overscroll should call
1770
+ // scrollTo directly.)
1771
+ if (x !== null) {
1772
+ x = Math.min(Math.max(this.get('minimumHorizontalScrollOffset'), x), this.get('maximumHorizontalScrollOffset'));
1773
+ }
1774
+
1775
+ if (y !== null) {
1776
+ y = Math.min(Math.max(this.get('minimumVerticalScrollOffset'), y), this.get('maximumVerticalScrollOffset'));
1777
+ }
1778
+
1779
+ return this.scrollTo(x, y);
1780
+ },
1781
+
1782
+ /**
1783
+ Scrolls the receiver down one or more lines if allowed. If number of
1784
+ lines is not specified, scrolls one line.
1785
+
1786
+ @param {Number} lines number of lines
1787
+ @returns {SC.ScrollView} receiver
1788
+ */
1789
+ scrollDownLine: function (lines) {
1790
+ if (lines === undefined) lines = 1;
1791
+ return this.scrollBy(null, this.get('verticalLineScroll') * lines);
1792
+ },
1793
+
1794
+ /**
1795
+ Scrolls the receiver down one or more page if allowed. If number of
1796
+ pages is not specified, scrolls one page. The page size is determined by
1797
+ the verticalPageScroll value. By default this is the size of the current
1798
+ scrollable area.
1799
+
1800
+ @param {Number} pages number of lines
1801
+ @returns {SC.ScrollView} receiver
1802
+ */
1803
+ scrollDownPage: function (pages) {
1804
+ if (pages === undefined) pages = 1;
1805
+ return this.scrollBy(null, this.get('verticalPageScroll') * pages);
1806
+ },
1807
+
1808
+ /**
1809
+ Scrolls the receiver left one or more lines if allowed. If number of
1810
+ lines is not specified, scrolls one line.
1811
+
1812
+ @param {Number} lines number of lines
1813
+ @returns {SC.ScrollView} receiver
1814
+ */
1815
+ scrollLeftLine: function (lines) {
1816
+ if (lines === undefined) lines = 1;
1817
+ return this.scrollTo(0 - this.get('horizontalLineScroll') * lines, null);
1818
+ },
1819
+
1820
+ /**
1821
+ Scrolls the receiver left one or more page if allowed. If number of
1822
+ pages is not specified, scrolls one page. The page size is determined by
1823
+ the verticalPageScroll value. By default this is the size of the current
1824
+ scrollable area.
1825
+
1826
+ @param {Number} pages number of lines
1827
+ @returns {SC.ScrollView} receiver
1828
+ */
1829
+ scrollLeftPage: function (pages) {
1830
+ if (pages === undefined) pages = 1;
1831
+ return this.scrollBy(0 - (this.get('horizontalPageScroll') * pages), null);
1832
+ },
1833
+
1834
+ /**
1835
+ Scrolls the receiver right one or more lines if allowed. If number of
1836
+ lines is not specified, scrolls one line.
1837
+
1838
+ @param {Number} lines number of lines
1839
+ @returns {SC.ScrollView} receiver
1840
+ */
1841
+ scrollRightLine: function (lines) {
1842
+ if (lines === undefined) lines = 1;
1843
+ return this.scrollTo(this.get('horizontalLineScroll') * lines, null);
1844
+ },
1845
+
1846
+ /**
1847
+ Scrolls the receiver right one or more page if allowed. If number of
1848
+ pages is not specified, scrolls one page. The page size is determined by
1849
+ the verticalPageScroll value. By default this is the size of the current
1850
+ scrollable area.
1851
+
1852
+ @param {Number} pages number of lines
1853
+ @returns {SC.ScrollView} receiver
1854
+ */
1855
+ scrollRightPage: function (pages) {
1856
+ if (pages === undefined) pages = 1;
1857
+ return this.scrollBy(this.get('horizontalPageScroll') * pages, null);
1858
+ },
1859
+
1860
+ /**
1861
+ Scrolls to the specified x,y coordinates. This should be the offset into the contentView that
1862
+ you want to appear at the top-left corner of the scroll view.
1863
+
1864
+ This method will contain the actual scroll based on whether the view can scroll in the named
1865
+ direction and the maximum distance it can scroll.
1866
+
1867
+ If you only want to scroll in one direction, pass null for the other direction.
1868
+
1869
+ @param {Number} x the x scroll location
1870
+ @param {Number} y the y scroll location
1871
+ @returns {SC.ScrollView} receiver
1872
+ */
1873
+ scrollTo: function (x, y) {
1874
+ // Normalize (deprecated).
1875
+ if (y === undefined && SC.typeOf(x) === SC.T_HASH) {
1876
+ //@if(debug)
1877
+ // Add some developer support. It's faster to pass the arguments individually so that we don't need to do this normalization and the
1878
+ // developer isn't creating an extra Object needlessly.
1879
+ SC.warn("Developer Warning: Passing an object to SC.ScrollView.scrollTo is deprecated. Please pass both the x and y arguments.");
1880
+ //@endif
1881
+
1882
+ y = x.y;
1883
+ x = x.x;
1884
+ }
1885
+
1886
+ if (!SC.none(x)) {
1887
+ this.set('horizontalScrollOffset', x);
1888
+ }
1889
+
1890
+ if (!SC.none(y)) {
1891
+ this.set('verticalScrollOffset', y);
1892
+ }
1893
+
1894
+ return this;
1895
+ },
1896
+
1897
+ /**
1898
+ Scroll to the supplied rectangle.
1899
+
1900
+ If the rectangle is bigger than the viewport, the top-left
1901
+ will be preferred.
1902
+
1903
+ (Note that if your content is scaled, the rectangle must be
1904
+ relative to the contentView's scale, not the ScrollView's.)
1905
+
1906
+ @param {Rect} rect Rectangle to which to scroll.
1907
+ @returns {Boolean} YES if scroll position was changed.
1908
+ */
1909
+ scrollToRect: function (rect) {
1910
+ // find current visible frame.
1911
+ var vo = SC.cloneRect(this.get('containerView').get('frame')),
1912
+ origX = this.get('horizontalScrollOffset'),
1913
+ origY = this.get('verticalScrollOffset'),
1914
+ scale = this.get('scale');
1915
+
1916
+ vo.x = origX;
1917
+ vo.y = origY;
1918
+
1919
+ // Scale rect.
1920
+ if (scale !== 1) {
1921
+ rect = SC.cloneRect(rect);
1922
+ rect.x *= scale;
1923
+ rect.y *= scale;
1924
+ rect.height *= scale;
1925
+ rect.width *= scale;
1926
+ }
1927
+
1928
+ // if bottom edge is not visible, shift origin
1929
+ vo.y += Math.max(0, SC.maxY(rect) - SC.maxY(vo));
1930
+ vo.x += Math.max(0, SC.maxX(rect) - SC.maxX(vo));
1931
+
1932
+ // if top edge is not visible, shift origin
1933
+ vo.y -= Math.max(0, SC.minY(vo) - SC.minY(rect));
1934
+ vo.x -= Math.max(0, SC.minX(vo) - SC.minX(rect));
1935
+
1936
+ // scroll to that origin.
1937
+ if ((origX !== vo.x) || (origY !== vo.y)) {
1938
+ this.scrollTo(vo.x, vo.y);
1939
+ return YES;
1940
+ } else {
1941
+ return NO;
1942
+ }
1943
+ },
1944
+
1945
+ /**
1946
+ Scrolls the receiver up one or more lines if allowed. If number of
1947
+ lines is not specified, scrolls one line.
1948
+
1949
+ @param {Number} lines number of lines
1950
+ @returns {SC.ScrollView} receiver
1951
+ */
1952
+ scrollUpLine: function (lines) {
1953
+ if (lines === undefined) lines = 1;
1954
+ return this.scrollBy(null, 0 - this.get('verticalLineScroll') * lines);
1955
+ },
1956
+
1957
+ /**
1958
+ Scrolls the receiver up one or more page if allowed. If number of
1959
+ pages is not specified, scrolls one page. The page size is determined by
1960
+ the verticalPageScroll value. By default this is the size of the current
1961
+ scrollable area.
1962
+
1963
+ @param {Number} pages number of lines
1964
+ @returns {SC.ScrollView} receiver
1965
+ */
1966
+ scrollUpPage: function (pages) {
1967
+ if (pages === undefined) pages = 1;
1968
+ return this.scrollBy(null, 0 - (this.get('verticalPageScroll') * pages));
1969
+ },
1970
+
1971
+ /**
1972
+ Scroll the view to make the view's frame visible. For this to make sense,
1973
+ the view should be a subview of the contentView. Otherwise the results
1974
+ will be undefined.
1975
+
1976
+ @param {SC.View} view view to scroll or null to scroll receiver visible
1977
+ @returns {Boolean} YES if scroll position was changed
1978
+ */
1979
+ scrollToVisible: function (view) {
1980
+
1981
+ // if no view is passed, do default
1982
+ if (arguments.length === 0) return sc_super();
1983
+
1984
+ var contentView = this.get('contentView');
1985
+ if (!contentView) return NO; // nothing to do if no contentView.
1986
+
1987
+ // get the frame for the view - should work even for views with static
1988
+ // layout, assuming it has been added to the screen.
1989
+ var viewFrame = view.get('borderFrame');
1990
+ if (!viewFrame) return NO; // nothing to do
1991
+
1992
+ // convert view's frame to an offset from the contentView origin. This
1993
+ // will become the new scroll offset after some adjustment.
1994
+ viewFrame = contentView.convertFrameFromView(viewFrame, view.get('parentView'));
1995
+
1996
+ return this.scrollToRect(viewFrame);
1997
+ },
1998
+
1999
+ /** @private SC.View */
2000
+ willDestroyLayer: function () {
2001
+ // Clean up.
2002
+ this.removeObserver('horizontalScrollOffset', this, this._sc_scrollOffsetHorizontalDidChange);
2003
+ this.removeObserver('verticalScrollOffset', this, this._sc_scrollOffsetVerticalDidChange);
2004
+ this.removeObserver('isHorizontalScrollerVisible', this, this._sc_repositionScrollers);
2005
+ this.removeObserver('isVerticalScrollerVisible', this, this._sc_repositionScrollers);
2006
+
2007
+ this.removeObserver('scale', this, this._sc_scaleDidChange);
2008
+
2009
+ var containerView = this.get('containerView');
2010
+ containerView.removeObserver('frame', this, this._sc_containerViewFrameDidChange);
2011
+
2012
+ // Be sure to remove this view as a scrollable view for SC.Drag.
2013
+ this.removeObserver('isVisibleInWindow', this, this._sc_registerAutoscroll);
2014
+ this.removeObserver('isEnabledInPane', this, this._sc_registerAutoscroll);
2015
+ SC.Drag.removeScrollableView(this);
2016
+ },
2017
+
2018
+ // ---------------------------------------------------------------------------------------------
2019
+ // Interaction
2020
+ //
2021
+
2022
+ /** @private
2023
+ This method gives our descendent views a chance to capture the touch via captureTouch, and subsequently to handle the
2024
+ touch, via touchStart. If no view elects to do so, control is returned to the scroll view for standard scrolling.
2025
+ */
2026
+ _sc_beginTouchesInContent: function (touch) {
2027
+ // Clean up.
2028
+ this._sc_passTouchToContentTimer = null;
2029
+
2030
+ // If the touch is not a scroll or scale, see if any of our descendent views want to handle the touch. If not,
2031
+ // we keep our existing respondership and all is well.
2032
+ if (!touch.captureTouch(this, true)) {
2033
+ touch.makeTouchResponder(touch.targetView, true, this);
2034
+ }
2035
+ },
2036
+
2037
+ /** @private */
2038
+ _sc_touchEnded: function (touch, wasCancelled) {
2039
+ // When the last touch ends, we stop touch scrolling.
2040
+ var hasTouch = this.get('hasTouch');
2041
+ if (hasTouch) {
2042
+ // Update the average distance to center of the touch to include the new touch. This is used to recognize pinch/zoom movement of the touch.
2043
+ var avgTouch = touch.averagedTouchesForView(this);
2044
+
2045
+ this._sc_gestureAnchorD = this._sc_gestureAnchorTotalD = avgTouch.d;
2046
+ this._sc_gestureAnchorX = this._sc_gestureAnchorTotalX = avgTouch.x;
2047
+ this._sc_gestureAnchorY = this._sc_gestureAnchorTotalY = avgTouch.y;
2048
+
2049
+ if (this._sc_containerOffset) {
2050
+ this._sc_touchCenterX = avgTouch.x - this._sc_containerOffset.x;
2051
+ this._sc_touchCenterY = avgTouch.y - this._sc_containerOffset.y;
2052
+ }
2053
+
2054
+ } else {
2055
+
2056
+ // If we were scrolling, continue scrolling at present velocity with deceleration.
2057
+ if (this._sc_isTouchScrollingV || this._sc_isTouchScrollingH || this._sc_isTouchScaling) {
2058
+ var decelerationRate = this.get('decelerationRate'),
2059
+ containerHeight = this._sc_containerHeight,
2060
+ containerWidth = this._sc_containerWidth,
2061
+ durationH = 0,
2062
+ durationV = 0,
2063
+ c2x, c2y;
2064
+
2065
+ if (this._sc_isTouchScrollingH) {
2066
+ var horizontalScrollOffset = this.get('horizontalScrollOffset'),
2067
+ maximumHorizontalScrollOffset = this.get('maximumHorizontalScrollOffset'),
2068
+ minimumHorizontalScrollOffset = this.get('minimumHorizontalScrollOffset'),
2069
+ horizontalVelocity = this._sc_touchVelocityH;
2070
+
2071
+ // Past the maximum.
2072
+ if (horizontalScrollOffset > maximumHorizontalScrollOffset) {
2073
+ this.set('horizontalScrollOffset', maximumHorizontalScrollOffset);
2074
+
2075
+ // Moving away from maximum. Change direction.
2076
+ if (horizontalVelocity < 0.2) {
2077
+ this._sc_animationTiming = this.get('animationCurveReverse');
2078
+
2079
+ // Stopped or moving back towards maximum. Maintain direction, snap at the end.
2080
+ } else {
2081
+ this._sc_animationTiming = this.get('animationCurveSnap');
2082
+ }
2083
+
2084
+ // 0.8 seconds for a full screen animation (most will be 50% or less of screen)
2085
+ durationH = 0.8 * (horizontalScrollOffset - maximumHorizontalScrollOffset) / containerWidth;
2086
+
2087
+ // Bounce back from min.
2088
+ } else if (horizontalScrollOffset < minimumHorizontalScrollOffset) {
2089
+ this.set('horizontalScrollOffset', minimumHorizontalScrollOffset);
2090
+
2091
+ // Moving away from minimum. Change direction.
2092
+ if (horizontalVelocity > 0.2) {
2093
+ this._sc_animationTiming = this.get('animationCurveReverse');
2094
+
2095
+ // Stopped or moving back towards minimum. Maintain direction, snap at the end.
2096
+ } else {
2097
+ this._sc_animationTiming = this.get('animationCurveSnap');
2098
+ }
2099
+
2100
+ // 0.8 seconds for a full screen animation (most will be 50% or less of screen)
2101
+ durationH = 0.8 * (minimumHorizontalScrollOffset - horizontalScrollOffset) / containerWidth;
2102
+
2103
+ // Slide.
2104
+ } else {
2105
+ // Set the final position we should slide to as we decelerate based on last velocity.
2106
+ horizontalScrollOffset -= (Math.abs(horizontalVelocity) * horizontalVelocity * 1000) / (2 * decelerationRate);
2107
+
2108
+ // Constrain within bounds.
2109
+ if (horizontalScrollOffset > maximumHorizontalScrollOffset) {
2110
+ // Generate an animation curve that bounces past the end point.
2111
+ c2x = (horizontalScrollOffset - maximumHorizontalScrollOffset) / containerWidth;
2112
+ c2y = 2 * c2x;
2113
+ this._sc_animationTiming = SC.easingCurve(0.0,0.5,c2x.toFixed(1),c2y.toFixed(1)); // 'cubic-bezier(0.0,0.5,%@,%@)'.fmt(c2x.toFixed(1), c2y.toFixed(1));
2114
+
2115
+ horizontalScrollOffset = maximumHorizontalScrollOffset;
2116
+
2117
+ } else if (horizontalScrollOffset < minimumHorizontalScrollOffset) {
2118
+ // Generate an animation curve that bounces past the end point.
2119
+ c2x = (minimumHorizontalScrollOffset - horizontalScrollOffset) / containerWidth;
2120
+ c2y = 2 * c2x;
2121
+ this._sc_animationTiming = SC.easingCurve(0.0,0.5,c2x.toFixed(1),c2y.toFixed(1)); // 'cubic-bezier(0.0,0.5,%@,%@)'.fmt(c2x.toFixed(1), c2y.toFixed(1));
2122
+
2123
+ horizontalScrollOffset = minimumHorizontalScrollOffset;
2124
+
2125
+ } else {
2126
+ this._sc_animationTiming = this.get('animationCurveDecelerate');
2127
+ }
2128
+
2129
+ this.set('horizontalScrollOffset', horizontalScrollOffset);
2130
+
2131
+ durationH = Math.abs(horizontalVelocity / decelerationRate);
2132
+ }
2133
+ }
2134
+
2135
+ if (this._sc_isTouchScrollingV) {
2136
+ var verticalScrollOffset = this.get('verticalScrollOffset'),
2137
+ maximumVerticalScrollOffset = this.get('maximumVerticalScrollOffset'),
2138
+ minimumVerticalScrollOffset = this.get('minimumVerticalScrollOffset'),
2139
+ verticalVelocity = this._sc_touchVelocityV;
2140
+
2141
+ // Past the maximum.
2142
+ if (verticalScrollOffset > maximumVerticalScrollOffset) {
2143
+ this.set('verticalScrollOffset', maximumVerticalScrollOffset);
2144
+
2145
+ // Moving away from maximum. Change direction.
2146
+ if (verticalVelocity < 0.2) {
2147
+ this._sc_animationTiming = this.get('animationCurveReverse');
2148
+
2149
+ // Stopped or moving back towards maximum. Maintain direction, snap at the end.
2150
+ } else {
2151
+ this._sc_animationTiming = this.get('animationCurveSnap');
2152
+ }
2153
+
2154
+ // 0.8 seconds for a full screen animation (most will be 50% or less of screen)
2155
+ durationV = 0.8 * (verticalScrollOffset - maximumVerticalScrollOffset) / containerHeight;
2156
+
2157
+ // Bounce back from min.
2158
+ } else if (verticalScrollOffset < minimumVerticalScrollOffset) {
2159
+ this.set('verticalScrollOffset', minimumVerticalScrollOffset);
2160
+
2161
+ // Moving away from minimum. Change direction.
2162
+ if (verticalVelocity > 0.2) {
2163
+ this._sc_animationTiming = this.get('animationCurveReverse');
2164
+
2165
+ // Stopped or moving back towards minimum. Maintain direction, snap at the end.
2166
+ } else {
2167
+ this._sc_animationTiming = this.get('animationCurveSnap');
2168
+ }
2169
+
2170
+ // 0.8 seconds for a full screen animation (most will be 50% or less of screen)
2171
+ durationV = 0.8 * (minimumVerticalScrollOffset - verticalScrollOffset) / containerHeight;
2172
+
2173
+ // Slide.
2174
+ } else {
2175
+ // Set the final position we should slide to as we decelerate based on last velocity.
2176
+ verticalScrollOffset -= (Math.abs(verticalVelocity) * verticalVelocity * 1000) / (2 * decelerationRate);
2177
+
2178
+ // Constrain within bounds.
2179
+ if (verticalScrollOffset > maximumVerticalScrollOffset) {
2180
+ // Generate an animation curve that bounces past the end point.
2181
+ c2x = (verticalScrollOffset - maximumVerticalScrollOffset) / containerHeight;
2182
+ c2y = 2 * c2x;
2183
+ this._sc_animationTiming = SC.easingCurve(0.0, 0.5,c2x.toFixed(1), c2y.toFixed(1)); 'cubic-bezier(0.0,0.5,%@,%@)'.fmt(c2x.toFixed(1), c2y.toFixed(1));
2184
+
2185
+ verticalScrollOffset = maximumVerticalScrollOffset;
2186
+
2187
+ } else if (verticalScrollOffset < minimumVerticalScrollOffset) {
2188
+ // Generate an animation curve that bounces past the end point.
2189
+ c2x = (minimumVerticalScrollOffset - verticalScrollOffset) / containerHeight;
2190
+ c2y = 2 * c2x;
2191
+ this._sc_animationTiming = SC.easingCurve(0.0, 0.5,c2x.toFixed(1), c2y.toFixed(1)); 'cubic-bezier(0.0,0.5,%@,%@)'.fmt(c2x.toFixed(1), c2y.toFixed(1));
2192
+
2193
+ verticalScrollOffset = minimumVerticalScrollOffset;
2194
+
2195
+ } else {
2196
+ this._sc_animationTiming = this.get('animationCurveDecelerate');
2197
+ }
2198
+
2199
+ this.set('verticalScrollOffset', verticalScrollOffset);
2200
+
2201
+ durationV = Math.abs(verticalVelocity / decelerationRate);
2202
+ }
2203
+ }
2204
+
2205
+ var scale = this.get('scale'),
2206
+ maximumScale = this.get('maximumScale'),
2207
+ minimumScale = this.get('minimumScale'),
2208
+ durationS = 0;
2209
+
2210
+ // Bounce back from max.
2211
+ if (scale > maximumScale) {
2212
+ this.set('scale', maximumScale);
2213
+ durationS = 0.25;
2214
+
2215
+ // Bounce back from min.
2216
+ } else if (scale < minimumScale) {
2217
+ this.set('scale', minimumScale);
2218
+ durationS = 0.25;
2219
+
2220
+ // Slide.
2221
+ } else {
2222
+
2223
+ }
2224
+
2225
+ // Determine how long the deceleration should take (we can't animate left/top separately, so use the largest duration for both).
2226
+ // This variable also acts as a flag so that when the content view is repositioned, it will be animated.
2227
+ this._sc_animationDuration = Math.max(Math.max(durationH, durationV), durationS);
2228
+
2229
+ // Clear up all caches from touchesDragged.
2230
+ this._sc_touchVelocityH = null;
2231
+ this._sc_touchVelocityV = null;
2232
+
2233
+ // Pass the initial touch on to the content view if it hasn't tried yet (i.e. a tap) and the touch wasn't cancelled.
2234
+ } else if (this._sc_passTouchToContentTimer) {
2235
+ // Clean up.
2236
+ this._sc_passTouchToContentTimer.invalidate();
2237
+ this._sc_passTouchToContentTimer = null;
2238
+
2239
+ if (!wasCancelled) {
2240
+ // If the content has handled the touch, then immediately end it.
2241
+ if (touch.makeTouchResponder(touch.targetView, true, this)) {
2242
+ touch.end();
2243
+ }
2244
+ }
2245
+ }
2246
+
2247
+ // Clean up all caches from touchStart & touchesDragged
2248
+ this._sc_gestureAnchorX = this._sc_gestureAnchorY = this._sc_gestureAnchorD = null;
2249
+ this._sc_gestureAnchorTotalX = this._sc_gestureAnchorTotalY = this._sc_gestureAnchorTotalD = null;
2250
+ this._sc_gestureAnchorScale = null;
2251
+ this._sc_gestureAnchorHOffset = null;
2252
+ this._sc_gestureAnchorVOffset = null;
2253
+ this._sc_containerOffset = null;
2254
+ this._sc_touchCenterX = null;
2255
+ this._sc_touchCenterY = null;
2256
+ }
2257
+
2258
+ // Force recalculation of scrolling and scaling.
2259
+ this._sc_isTouchScrollingH = false;
2260
+ this._sc_isTouchScrollingHOnly = false;
2261
+ this._sc_isTouchScrollingV = false;
2262
+ this._sc_isTouchScrollingVOnly = false;
2263
+ this._sc_isTouchScaling = false;
2264
+
2265
+ // TODO: What happens when isEnabledInPane goes false while interacting? Statechart would help solve this.
2266
+ return true;
2267
+ },
2268
+
2269
+ /** @private @see SC.RootResponder.prototype.captureTouch */
2270
+ captureTouch: function (touch) {
2271
+ // Capture the touch and begin determination of actual scroll or not.
2272
+ if (this.get('delaysContentTouches')) {
2273
+ return true;
2274
+
2275
+ // Otherwise, suggest ourselves as a reasonable fallback responder. If none of our children capture
2276
+ // the touch or handle touchStart, we'll get another crack at it in touchStart.
2277
+ } else {
2278
+ touch.stackCandidateTouchResponder(this);
2279
+
2280
+ return false;
2281
+ }
2282
+ },
2283
+
2284
+ /** @private */
2285
+ mouseWheel: function (evt) {
2286
+ var handled = false,
2287
+ contentView = this.get('contentView');
2288
+
2289
+ // Ignore it if not enabled.
2290
+ if (contentView && this.get('isEnabledInPane')) {
2291
+
2292
+ var horizontalScrollOffset = this.get('horizontalScrollOffset'),
2293
+ minimumHorizontalScrollOffset = this.get('minimumHorizontalScrollOffset'),
2294
+ minimumVerticalScrollOffset = this.get('minimumVerticalScrollOffset'),
2295
+ maximumHorizontalScrollOffset = this.get('maximumHorizontalScrollOffset'),
2296
+ maximumVerticalScrollOffset = this.get('maximumVerticalScrollOffset'),
2297
+ verticalScrollOffset = this.get('verticalScrollOffset'),
2298
+ wheelDeltaX = evt.wheelDeltaX,
2299
+ wheelDeltaY = evt.wheelDeltaY;
2300
+
2301
+ // If we can't scroll in one direction, limit that direction.
2302
+ if (!this.get('canScrollHorizontal')) { // Don't allow inverted scrolling for now.
2303
+ wheelDeltaX = 0;
2304
+ }
2305
+
2306
+ if (!this.get('canScrollVertical')) { // Don't allow inverted scrolling for now.
2307
+ wheelDeltaY = 0;
2308
+ }
2309
+
2310
+ // Only attempt to scroll if we are allowed to scroll in the direction and have room to scroll
2311
+ // in the direction. Otherwise, ignore the event so that an outer ScrollView may capture it.
2312
+ handled = ((wheelDeltaX < 0 && horizontalScrollOffset > minimumHorizontalScrollOffset) ||
2313
+ (wheelDeltaX > 0 && horizontalScrollOffset < maximumHorizontalScrollOffset)) ||
2314
+ ((wheelDeltaY < 0 && verticalScrollOffset > minimumVerticalScrollOffset) ||
2315
+ (wheelDeltaY > 0 && verticalScrollOffset < maximumVerticalScrollOffset));
2316
+
2317
+ if (handled) {
2318
+ this.scrollBy(wheelDeltaX, wheelDeltaY);
2319
+ }
2320
+ }
2321
+
2322
+ return handled;
2323
+ },
2324
+
2325
+ /** @private */
2326
+ touchesDragged: function (evt, touchesForView) {
2327
+ var avgTouch = evt.averagedTouchesForView(this),
2328
+ canScale = this.get('canScale'),
2329
+ canScrollHorizontal = this.get('canScrollHorizontal'),
2330
+ canScrollVertical = this.get('canScrollVertical'),
2331
+ scrollThreshold = this.get('scrollGestureThreshold'),
2332
+ scaleThreshold = this.get('scaleGestureThreshold'),
2333
+ scrollLockThreshold = this.get('scrollLockGestureThreshold'),
2334
+ horizontalScrollOffset,
2335
+ verticalScrollOffset;
2336
+
2337
+
2338
+ // Determine if we've moved enough to claim horizontal or vertical scrolling.
2339
+ if (!(this._sc_isTouchScrollingH && this._sc_isTouchScrollingV) &&
2340
+ !this._sc_isTouchScrollingHOnly && !this._sc_isTouchScrollingVOnly) {
2341
+
2342
+ if (canScrollHorizontal) {
2343
+ var totalAbsDeltaX = Math.abs(this._sc_gestureAnchorTotalX - avgTouch.x);
2344
+
2345
+ if (!this._sc_isTouchScrollingH) {
2346
+ this._sc_isTouchScrollingH = totalAbsDeltaX >= scrollThreshold;
2347
+
2348
+ // Determine if we've moved enough to lock scrolling to only this direction.
2349
+ } else {
2350
+ this._sc_isTouchScrollingHOnly = totalAbsDeltaX >= scrollLockThreshold;
2351
+ }
2352
+ }
2353
+
2354
+ if (canScrollVertical) {
2355
+ var totalAbsDeltaY = Math.abs(this._sc_gestureAnchorTotalY - avgTouch.y);
2356
+
2357
+ if (!this._sc_isTouchScrollingV) {
2358
+ this._sc_isTouchScrollingV = totalAbsDeltaY >= scrollThreshold;
2359
+
2360
+ // Determine if we've moved enough to lock scrolling to only this direction.
2361
+ } else {
2362
+ this._sc_isTouchScrollingVOnly = totalAbsDeltaY >= scrollLockThreshold;
2363
+ }
2364
+ }
2365
+ }
2366
+
2367
+ var touchDeltaX = this._sc_gestureAnchorX - avgTouch.x,
2368
+ absDeltaX = Math.abs(touchDeltaX);
2369
+
2370
+ // Adjust scroll.
2371
+ if (canScrollHorizontal && absDeltaX >= 1 && !this._sc_isTouchScrollingVOnly) {
2372
+ // Record the last velocity.
2373
+ this._sc_touchVelocityH = avgTouch.velocityX;
2374
+
2375
+ var minimumHorizontalScrollOffset = this.get('minimumHorizontalScrollOffset'),
2376
+ maximumHorizontalScrollOffset = this.get('maximumHorizontalScrollOffset');
2377
+
2378
+ horizontalScrollOffset = this._sc_gestureAnchorHOffset + touchDeltaX;
2379
+
2380
+ // Reset the anchor. Note: Do this before degrading the offset.
2381
+ this._sc_gestureAnchorX = avgTouch.x;
2382
+ this._sc_gestureAnchorHOffset = horizontalScrollOffset;
2383
+
2384
+ // Degrade the offset as we pass maximum.
2385
+ if (horizontalScrollOffset > maximumHorizontalScrollOffset) {
2386
+ horizontalScrollOffset = horizontalScrollOffset - this._sc_overDragSlip * (horizontalScrollOffset - maximumHorizontalScrollOffset);
2387
+
2388
+ // Degrade the offset as we pass minimum.
2389
+ } else if (horizontalScrollOffset < minimumHorizontalScrollOffset) {
2390
+ horizontalScrollOffset = horizontalScrollOffset + this._sc_overDragSlip * (minimumHorizontalScrollOffset - horizontalScrollOffset);
2391
+ }
2392
+
2393
+ // Update the scroll offset.
2394
+ this.set('horizontalScrollOffset', horizontalScrollOffset);
2395
+ }
2396
+
2397
+ var touchDeltaY = this._sc_gestureAnchorY - avgTouch.y,
2398
+ absDeltaY = Math.abs(touchDeltaY);
2399
+
2400
+ if (canScrollVertical && absDeltaY > 0 && !this._sc_isTouchScrollingHOnly) {
2401
+ // Record the last velocity.
2402
+ this._sc_touchVelocityV = avgTouch.velocityY;
2403
+
2404
+ var minimumVerticalScrollOffset = this.get('minimumVerticalScrollOffset'),
2405
+ maximumVerticalScrollOffset = this.get('maximumVerticalScrollOffset');
2406
+
2407
+ verticalScrollOffset = this._sc_gestureAnchorVOffset + touchDeltaY;
2408
+
2409
+ // Reset the anchor. Note: Do this before degrading the offset.
2410
+ this._sc_gestureAnchorY = avgTouch.y;
2411
+ this._sc_gestureAnchorVOffset = verticalScrollOffset;
2412
+
2413
+ // Degrade the offset as we pass maximum.
2414
+ if (verticalScrollOffset > maximumVerticalScrollOffset) {
2415
+ verticalScrollOffset = verticalScrollOffset - this._sc_overDragSlip * (verticalScrollOffset - maximumVerticalScrollOffset);
2416
+
2417
+ // Degrade the offset as we pass minimum.
2418
+ } else if (verticalScrollOffset < minimumVerticalScrollOffset) {
2419
+ verticalScrollOffset = verticalScrollOffset + this._sc_overDragSlip * (minimumVerticalScrollOffset - verticalScrollOffset);
2420
+ }
2421
+
2422
+ // Update the scroll offset.
2423
+ this.set('verticalScrollOffset', verticalScrollOffset);
2424
+ }
2425
+
2426
+ // Adjust scale.
2427
+ if (canScale) {
2428
+
2429
+ // Determine if we've moved enough to claim scaling.
2430
+ if (!this._sc_isTouchScaling) {
2431
+ var totalAbsDeltaD = Math.abs(this._sc_gestureAnchorTotalD - avgTouch.d);
2432
+ this._sc_isTouchScaling = !!avgTouch.d && totalAbsDeltaD > scaleThreshold;
2433
+ }
2434
+
2435
+ var touchDeltaD = this._sc_gestureAnchorD - avgTouch.d,
2436
+ absDeltaD = Math.abs(touchDeltaD);
2437
+ if (absDeltaD > 0) {
2438
+ // The percentage difference in touch distance.
2439
+ var scalePercentChange = avgTouch.d / this._sc_gestureAnchorD,
2440
+ scale = this._sc_gestureAnchorScale * scalePercentChange;
2441
+
2442
+ // Adjust the center of the zoom to the center of the gesture.
2443
+ horizontalScrollOffset = this._sc_horizontalScrollOffset;
2444
+ verticalScrollOffset = this._sc_verticalScrollOffset;
2445
+
2446
+ // Cache the current offset of the container view in the document. Calculated each time touch scaling begins.
2447
+ if (!this._sc_containerOffset) {
2448
+ var el = this.getPath('containerView.layer');
2449
+
2450
+ this._sc_containerOffset = SC.offset(el);
2451
+ this._sc_touchCenterX = avgTouch.x - this._sc_containerOffset.x;
2452
+ this._sc_touchCenterY = avgTouch.y - this._sc_containerOffset.y;
2453
+ }
2454
+
2455
+ // Compute the relative center of the scale gesture.
2456
+ this._sc_horizontalPct = (horizontalScrollOffset + this._sc_touchCenterX) / this._sc_contentWidth;
2457
+ this._sc_verticalPct = (verticalScrollOffset + this._sc_touchCenterY) / this._sc_contentHeight;
2458
+
2459
+ this.set('scale', scale);
2460
+
2461
+ // Reset the anchor.
2462
+ this._sc_gestureAnchorD = avgTouch.d;
2463
+ this._sc_gestureAnchorScale = scale;
2464
+ }
2465
+ }
2466
+
2467
+ // No longer pass the initial touch on to the content view if it was still about to.
2468
+ if (this._sc_passTouchToContentTimer && (this._sc_isTouchScrollingV || this._sc_isTouchScrollingH || this._sc_isTouchScaling)) {
2469
+ this._sc_passTouchToContentTimer.invalidate();
2470
+ this._sc_passTouchToContentTimer = null;
2471
+ }
2472
+
2473
+ // Note: If the content view has already accepted the initial touch, it will be sent a touchCancelled event.
2474
+ },
2475
+
2476
+ /** @private
2477
+ If we're in hand-holding mode and our content claims the touch, we will receive a touchCancelled
2478
+ event at its completion. We still need to do most of our touch-ending wrap up, for example to finish
2479
+ bouncing back from a previous gesture.
2480
+ */
2481
+ touchCancelled: function (touch) {
2482
+ return this._sc_touchEnded(touch, true);
2483
+ },
2484
+
2485
+ /** @private
2486
+ If we are the touch's responder at its completion, we'll get a touchEnd event. If this is the
2487
+ gesture's last touch, we wrap up in spectacular fashion.
2488
+ */
2489
+ touchEnd: function (touch) {
2490
+ return this._sc_touchEnded(touch, false);
2491
+ },
2492
+
2493
+ // /** @private */
2494
+ touchStart: function (touch) {
2495
+ var handled = false,
2496
+ contentView = this.get('contentView');
2497
+
2498
+ if (contentView && this.get('isEnabledInPane')) {
2499
+ var hasTouch = this.get('hasTouch');
2500
+
2501
+ // Additional touches can be used for pinching gestures.
2502
+ if (hasTouch) {
2503
+
2504
+ // If a new touch has appeared, force scrolling to recalculate.
2505
+ this._sc_isTouchScrollingV = this._sc_isTouchScrollingH = false;
2506
+ this._sc_isTouchScrollingHOnly = this._sc_isTouchScrollingHOnly = false;
2507
+
2508
+ // No longer pass the initial touch on to the content view if it was still about to.
2509
+ if (this._sc_passTouchToContentTimer) {
2510
+ this._sc_passTouchToContentTimer.invalidate();
2511
+ this._sc_passTouchToContentTimer = null;
2512
+ }
2513
+
2514
+ // The first touch is used to set up initial state.
2515
+ } else {
2516
+ // Cancel any active animation in place.
2517
+ this._sc_cancelAnimation();
2518
+
2519
+ // If we have captured the touch and are not yet scrolling, we may want to delay a moment to test for
2520
+ // scrolling and if not scrolling, we will pass the touch through to the content.
2521
+ // If configured to do so, delay 150ms to verify that the user is not scrolling before passing touches through to the content.
2522
+ if (this.get('delaysContentTouches')) {
2523
+ this._sc_passTouchToContentTimer = this.invokeLater(this._sc_beginTouchesInContent, 150, touch);
2524
+ } // Else do nothing.
2525
+ }
2526
+
2527
+ // Update the average distance to center of the touch, which is used to recognize pinch/zoom movement of the touch.
2528
+ var avgTouch = touch.averagedTouchesForView(this, true);
2529
+
2530
+ /* A note on these variables:
2531
+
2532
+ _sc_gestureAnchorX: the last x position (so that we don't update horizontal scroll if the change since last is 0)
2533
+ _sc_gestureAnchorTotalX: the initial x position (so that we can determine whether to take total control of touches and possibly lock the position)
2534
+ */
2535
+ this._sc_gestureAnchorX = this._sc_gestureAnchorTotalX = avgTouch.x;
2536
+ this._sc_gestureAnchorY = this._sc_gestureAnchorTotalY = avgTouch.y;
2537
+ this._sc_gestureAnchorD = this._sc_gestureAnchorTotalD = avgTouch.d;
2538
+ this._sc_gestureAnchorScale = this.get('scale');
2539
+ this._sc_gestureAnchorHOffset = this.get('horizontalScrollOffset');
2540
+ this._sc_gestureAnchorVOffset = this.get('verticalScrollOffset');
2541
+
2542
+ handled = true;
2543
+ }
2544
+
2545
+ return handled;
2546
+ }
2547
+
2548
+ });
2549
+
2550
+
2551
+ SC.ScrollView.mixin(
2552
+ /** @scope SC.ScrollView */ {
2553
+
2554
+ /** @private Shared object used to avoid continually initializing/destroying objects. */
2555
+ _SC_CONTAINER_LAYOUT_MAP: null,
2556
+
2557
+ /** @private Shared object used to avoid continually initializing/destroying objects. */
2558
+ _SC_CONTENT_ADJUST_MAP: null
2559
+
2560
+ });