1 // ========================================================================== 2 // Project: SproutCore - JavaScript Application Framework 3 // Copyright: ©2006-2010 Sprout Systems, Inc. and contributors. 4 // Portions ©2008-2011 Apple Inc. All rights reserved. 5 // License: Licensed under MIT license (see license.js) 6 // ========================================================================== 7 8 /** 9 Tells the SplitThumb to automatically choose which child of the SplitView 10 to move in response to touch or mouse events in an SC.SplitThumb. 11 */ 12 SC.MOVES_AUTOMATIC_CHILD = 'moves-automatic-child'; 13 14 /** 15 Tells the SplitThumb to move the child of the SplitView that is 16 either the SplitThumb or a parent of it. 17 */ 18 SC.MOVES_CHILD = 'moves-child'; 19 20 /** 21 Tells the SplitThumb to move the child of the SplitView that 22 preceeds the child that is either the SplitThumb or a parent of it. 23 */ 24 SC.MOVES_PREVIOUS_CHILD = 'moves-previous-child'; 25 26 /** 27 Tells the SplitThumb to move the child of the SplitVie that 28 comes after the child that is either the SplitThumb or a parent of it. 29 */ 30 SC.MOVES_NEXT_CHILD = 'moves-next-child'; 31 32 /** 33 @namespace 34 A SplitThumb may be placed inside any view in a SplitView, and can even 35 be a direct child of the SplitView. It forwards its events to the SplitView 36 to control the movement of a divider or another child of the SplitView. 37 38 Using a view that mixes in SplitThumb, you can place a handle that moves the 39 divider anywhere inside the SplitView's view tree. 40 41 SplitThumb will automatically choose which divider to move. It's choice will 42 almost always be correct for 2-pane layouts. However, in 3-pane layouts, you 43 may want to adjust the way it chooses. 44 45 You can adjust the behavior by setting the movesSibling property to 46 SC.MOVES_NEXT_CHILD or SC.MOVES_PREVIOUS_CHILD. If your ThumbView is inside 47 the middle pane, for instance, this would tell it whether the ThumbView 48 should move the divider to the left, or the divider to the right. 49 */ 50 SC.SplitThumb = { 51 52 /** 53 * The child which should be moved by any mouse or touch events. Should usually 54 * be a divider view. 55 * 56 * The default implementation calculates the child by first finding 57 * the ancestor of this view that is a direct child of the SplitView, 58 * and then either returning it or one of its immediate siblings, depending 59 * on the value of the movesSibling property. 60 * 61 * @property SC.View 62 */ 63 movesChild: function () { 64 var view = this, child, splitView = this.get('splitView'), 65 sibling = this.get('movesSibling'); 66 while (view && view !== splitView) { 67 child = view; 68 view = view.get('parentView'); 69 } 70 71 if (sibling === SC.MOVES_NEXT_CHILD) return child.nextView; 72 if (sibling === SC.MOVES_PREVIOUS_CHILD) return child.previousView; 73 74 if (sibling === SC.MOVES_AUTOMATIC_CHILD) { 75 if (!child.nextView) return child.previousView; 76 if (!child.previousView) return child.nextView; 77 } 78 79 return child; 80 }.property('splitView', 'movesSibling').cacheable(), 81 82 /** 83 * Decides whether an ancestor of this SplitThumb view or one of its 84 * siblings is to be moved in response to events. 85 * 86 * Usually, you want events to move a divider. If the SplitThumb is inside 87 * a view which is _not_ a divider, you probably want to move one of the 88 * view's siblings-one that _is_ a divider. 89 * 90 * You can tell SC.SplitThumb to: 91 * 92 * - SC.MOVES_AUTOMATIC_CHILD: if the SplitView child is either first or last, 93 * moves the adjacent child (likely a divider). Otherwise moves the child itself. 94 * 95 * - SC.MOVES_CHILD: moves the child itself, not one of its siblings. Divider 96 * views could use this setting. 97 * 98 * - SC.MOVES_PREVIOUS_CHILD: moves the previous child. For instance, in a 99 * two-pane setup, if the SplitThumb is in the rightmost child, this will 100 * move the divider between the two children. 101 * 102 * - SC.MOVES_NEXT_CHILD: moves the next child. 103 * 104 * @type TYPE 105 */ 106 movesSibling: SC.MOVES_AUTOMATIC_CHILD, 107 108 /** 109 * The SplitView that contains the child views to be adjusted. 110 * 111 * This is computed to be the first SplitView found in a search 112 * up the view hierarchy. You can substitute your own SplitView 113 * 114 * @property SC.SplitView 115 */ 116 // splitView: function () { 117 // var view = this; 118 // while (view && !view.isSplitView) view = view.get('parentView'); 119 // return view; 120 // }.property('parentView').cacheable(), 121 122 /** 123 * The layoutDirection of the SplitView. This is observed so that we 124 * can update positioning if the layoutDirection changes but the position 125 * and size properties do not. 126 * 127 * NOTE: duplicated in SplitChild because both this and SplitChild use it. 128 * 129 * @type {LayoutDirection} 130 */ 131 // NOTE: While an edge case, this is implemented because it makes it _much_ 132 // easier to write the sample in the Test Controls app. 133 // splitViewLayoutDirection: null, 134 // splitViewLayoutDirectionBinding: SC.Binding.oneWay('*splitView.layoutDirection'), 135 136 /** 137 * The name of the CSS cursor that should be used for splitting. 138 * The containing SplitView will adopt this cursor if and when this 139 * view is dragged. 140 * 141 * Computed based on the SplitView's layoutDirection. 142 * 143 * @type {String} 144 */ 145 splitCursorStyle: function () { 146 if (this.get('splitViewLayoutDirection') === SC.LAYOUT_HORIZONTAL) { 147 return 'ew-resize'; 148 } else { 149 return 'ns-resize'; 150 } 151 }.property('splitViewLayoutDirection').cacheable(), 152 153 splitCursorStyleDidChange: function () { 154 if (this._isDragging) { 155 this.get('splitView').set('splitChildCursorStyle', this.get('splitCursorStyle')); 156 } 157 158 this.$().css('cursor', this.get('splitCursorStyle')); 159 }.observes('splitCursorStyle'), 160 161 /** @private Include SC.NeedsSplitParent if it hasn't already been included. */ 162 initMixin: function () { 163 if (!this.splitView) { 164 this.mixin(SC.NeedsSplitParent); 165 } 166 }, 167 168 /** 169 * @private 170 * Renders the cursor for the view as defined by this view's splitCursor 171 * property. 172 */ 173 renderMixin: function (context) { 174 context.setStyle('cursor', this.get('splitCursorStyle')); 175 }, 176 177 // 178 // EVENT HANDLING 179 // 180 touchStart: function (touch) { 181 this._isDragging = YES; 182 183 var splitView = this.get('splitView'); 184 splitView.beginLiveResize(); 185 186 this._scst_mouseStartPosition = splitView.get('layoutDirection') === SC.LAYOUT_HORIZONTAL ? 187 touch.pageX : touch.pageY; 188 189 this._scst_childStartPosition = splitView.getPositionForChild(this.get('movesChild')); 190 191 return YES; 192 }, 193 194 touchesDragged: function (evt) { 195 var splitView = this.get('splitView'); 196 197 var mousePosition = splitView.get('layoutDirection') === SC.LAYOUT_HORIZONTAL ? 198 evt.pageX : evt.pageY; 199 200 var diff = mousePosition - this._scst_mouseStartPosition, 201 start = this._scst_childStartPosition; 202 203 splitView.adjustPositionForChild(this.get('movesChild'), start + diff); 204 205 return YES; 206 }, 207 208 touchEnd: function (touch) { 209 this._isDragging = NO; 210 211 var splitView = this.get('splitView'); 212 213 var mousePosition = splitView.get('layoutDirection') === SC.LAYOUT_HORIZONTAL ? 214 touch.pageX : touch.pageY; 215 216 var diff = mousePosition - this._scst_mouseStartPosition, 217 start = this._scst_childStartPosition; 218 219 splitView.adjustPositionForChild(this.get('movesChild'), start + diff); 220 221 splitView.set('splitChildCursorStyle', null); 222 splitView.endLiveResize(); 223 return YES; 224 }, 225 226 mouseDown: function (evt) { 227 var splitView = this.get('splitView'); 228 splitView.set('splitChildCursorStyle', this.get('splitCursorStyle')); 229 230 return this.touchStart(evt); 231 }, 232 233 mouseDragged: function (evt) { 234 return this.touchesDragged(evt); 235 }, 236 237 mouseUp: function (evt) { 238 this.get('splitView').set('splitChildCursorStyle', null); 239 240 return this.touchEnd(evt); 241 } 242 243 }; 244