1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 /**
  9   @namespace
 10 
 11   Provides drag functionality to a pane. If you need to disable dragging at certain times set the
 12   property isAnchored to YES and the pane will no longer move.
 13 
 14   See SC.PalettePane, a simple panel pane with SC.DraggablePaneSupport mixed in.
 15 */
 16 SC.DraggablePaneSupport = /** @scope SC.DraggablePaneSupport.prototype */{
 17 
 18   /**
 19     Walk like a duck.
 20 
 21     @type Boolean
 22   */
 23   isDraggablePane: YES,
 24 
 25   /**
 26    @type Boolean
 27    @default NO
 28   */
 29   isAnchored: NO,
 30 
 31   /** @private */
 32   _drag_cachedMouseX: null,
 33 
 34   /** @private */
 35   _drag_cachedMouseY: null,
 36 
 37   /**
 38    To provide drag functionality enhance mouseDown, mouseDragged, touchStart, and touchesDragged if they exist.
 39    */
 40   initMixin: function() {
 41     if (this.mouseDown) {
 42       this.mouseDown = SC._enhance(this.mouseDown, function(original, evt) {
 43         var ret = this._drag_mouseDown(evt);
 44         return original(evt) || ret;
 45       });
 46     } else {
 47       this.mouseDown = this._drag_mouseDown;
 48     }
 49 
 50     if (this.mouseDragged) {
 51       this.mouseDragged = SC._enhance(this.mouseDragged, function(original, evt) {
 52         var ret = this._drag_mouseDragged(evt);
 53         return original(evt) || ret;
 54       });
 55     } else {
 56       this.mouseDragged = this._drag_mouseDragged;
 57     }
 58 
 59     if (this.touchStart) {
 60       this.touchStart = SC._enhance(this.touchStart, function(original, evt) {
 61         var ret = this._drag_touchStart(evt);
 62         return original(evt) || ret;
 63       });
 64     } else {
 65       this.touchStart = this._drag_touchStart;
 66     }
 67 
 68     if (this.touchesDragged) {
 69       this.touchesDragged = SC._enhance(this.touchesDragged, function(original, evt) {
 70         var ret = this._drag_touchesDragged(evt);
 71         return original(evt) || ret;
 72       });
 73     } else {
 74       this.touchesDragged = this._drag_touchesDragged;
 75     }
 76   },
 77 
 78   /**
 79     Returns true if the target view of the event exists and has a truthy isTextSelectable
 80 
 81     @param evt The event
 82     @return {Boolean}
 83   */
 84   _drag_targetHasSelectableText: function(evt) {
 85     var targetView = SC.RootResponder.responder.targetViewForEvent(evt);
 86 
 87     return !!(targetView && targetView.isTextSelectable);
 88   },
 89 
 90   /**
 91     Returns true if we should handle a drag.
 92 
 93     @param evt The event
 94     @return {Boolean}
 95   */
 96   _drag_shouldHandleDrag: function(evt) {
 97     return !this.get('isAnchored') && !this._drag_targetHasSelectableText(evt);
 98   },
 99 
100   /**
101     The drag code will modify the existing layout by the difference between each drag event so for the first one store
102     the original mouse down position.
103 
104     @param evt The mouseDown event
105     @return {Boolean} YES
106   */
107   _drag_mouseDown: function(evt) {
108     this._drag_cachedMouseX = evt.pageX;
109     this._drag_cachedMouseY = evt.pageY;
110     return this._drag_shouldHandleDrag(evt);
111   },
112 
113   /**
114     Modify the current layout by the movement since the last drag event.
115 
116     @param evt The mouseDrag event
117     @return {Boolean} YES if we moved the view, NO if we didn't due to isAnchored being YES
118   */
119   _drag_mouseDragged: function(evt) {
120     var xOffset = this._drag_cachedMouseX - evt.pageX,
121         yOffset = this._drag_cachedMouseY - evt.pageY,
122         frame = this.get('frame'),
123         // NOTE: wFrame will be incorrect if this pane is not attached to document.body (e.g. via appendTo).
124         wFrame = SC.RootResponder.responder.computeWindowSize(),
125         oldLayout = SC.clone(this.get('layout')),
126         layout = {},
127         isPercent = function(num) {
128           return (num < 1 && num > 0);
129         };
130 
131     //Update the cached coordinates so we can track the change between each drag event
132     this._drag_cachedMouseX = evt.pageX;
133     this._drag_cachedMouseY = evt.pageY;
134 
135     if (!this._drag_shouldHandleDrag(evt)) {
136       return NO;
137     }
138 
139     // If a layout property is in the layout no matter what other layout properties are used we need to modify it the
140     // same way. For the 4 offsets we check if they've been specified as percentages and if so convert them to regular
141     // offsets based on our current frame and the current window. For simplicity's sake, it is assumed that the frame
142     // frame coordinates are in the browser window's coordinates (see above note on wFrame).
143 
144     if (oldLayout.hasOwnProperty('left')) {
145       if (isPercent(oldLayout.left)) {
146         oldLayout.left = frame.x;
147       }
148 
149       layout.left = oldLayout.left - xOffset;
150     }
151 
152     if (oldLayout.hasOwnProperty('right')) {
153       if (isPercent(oldLayout.right)) {
154         oldLayout.right = wFrame.width - (frame.x + frame.width);
155       }
156 
157       layout.right = oldLayout.right + xOffset;
158     }
159 
160     if (oldLayout.hasOwnProperty('centerX')) {
161       layout.centerX = oldLayout.centerX - xOffset;
162     }
163 
164     if (oldLayout.hasOwnProperty('top')) {
165       if (isPercent(oldLayout.top)) {
166         oldLayout.top = frame.y;
167       }
168 
169       layout.top = oldLayout.top - yOffset;
170     }
171 
172     if (oldLayout.hasOwnProperty('bottom')) {
173       if (isPercent(oldLayout.bottom)) {
174         oldLayout.bottom = wFrame.height - (frame.y + frame.height);
175       }
176 
177       layout.bottom = oldLayout.bottom + yOffset;
178     }
179 
180     if (oldLayout.hasOwnProperty('centerY')) {
181       layout.centerY = oldLayout.centerY - yOffset;
182     }
183 
184     this.adjust(layout);
185 
186     return YES;
187   },
188 
189   /**
190     Forward to our mouseDown handler.
191   */
192   _drag_touchStart: function(evt) {
193     return this._drag_mouseDown(evt);
194   },
195 
196   /**
197     Forward to our mouseDragged handler.
198   */
199   _drag_touchesDragged: function(evt) {
200     return this._drag_mouseDragged(evt);
201   }
202 };
203