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