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